Setting up rom-http relation for REST CRUD

192 Views Asked by At

I'm trying to set up a rom-http relation for basic REST CRUD, but I find the documentation to be pretty scarce for a beginner, and a little too complex when digging in. What I've tried so far is this:

rom = ROM.container(:http, uri: 'http://localhost:8000', handlers: :json) do |conf|
  conf.relation(:users) do
    schema(:users) do
    end
  end
end

This queries the URI http://localhost:8000/users, but how do I configure prefixes, parameters and related resources?

What I'd like to accomplish is being able to consume a URI such as http://localhost:8000/users/1/posts?start=0&size=10 where we have

  • a global prefix (api)
  • a version prefix (v1, could be part of the global prefix)
  • a parent resource (users/1)
  • a child resource (posts)
  • query parameters (bonus points if they can be chained like .offset(0).limit(10))

Is this possible with the current implementation? The documentation could use a deeper example, without forcing newcomers to dig into the architecture - which is without doubt brilliant, but complex for someone coming from the ease of use (and the pitfalls) of ActiveRecord. :-)

1

There are 1 best solutions below

1
On

Sorry that nobody has replied to this yet, the current built-in json handler is a bit broken at the moment, it builds the uri manually when it should just use it from the dataset, you can achieve what you want with something like the following:

NOTE: I only called .dataset.uri to show an example of the URI that would be queried, as I don't have a compatible API running locally.

NOTE: For anything beyond playing around with the library, you'll probably want to use a custom adapter anyway.

require 'bundler/inline'

gemfile(true) do
  gem 'rom'
  gem 'rom-http'
end

class MyJSONRequest
  def self.call(dataset)
    uri = dataset.uri
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true if uri.scheme.eql?('https')

    request_class = Net::HTTP.const_get(ROM::Inflector.classify(dataset.request_method))

    request = request_class.new(uri.request_uri)

    dataset.headers.each_with_object(request) do |(header, value), request|
      request[header.to_s] = value
    end

    http.request(request)
  end
end

class MyJSONResponse
  # Handle JSON responses
  #
  # @param [Net::HTTP::Response] response
  # @param [Dataset] dataset
  #
  # @return [Array<Hash>]
  #
  # @api public
  def self.call(response, dataset)
    Array([JSON.parse(response.body, symbolize_names: true)]).flatten(1)
  end
end

ROM::HTTP::Handlers.register(
  :my_json,
  request: MyJSONRequest,
  response: MyJSONResponse
)

rom = ROM.container(:http, uri: 'http://localhost:8000/api', handlers: :my_json) do |conf|
  conf.relation(:users) do
    schema('v1/users') do
      attribute :id, ROM::Types::Integer.meta(
        primary_key: true
      )
      attribute :name, ROM::Types::String
    end

    def by_id(id)
      append_path(id)
    end

    def offset(offset)
      add_params(start: offset)
    end

    def limit(limit)
      add_params(size: limit)
    end
  end

  conf.relation(:posts) do
    schema('v1/posts') do
      attribute :id, ROM::Types::Integer.meta(
        primary_key: true
      )
      attribute :name, ROM::Types::String
    end

    def by_user(user_id)
      with_options(
        base_path: 'v1/users',
        path: "#{user_id}/posts"
      )
    end
  end
end

users = rom.relations[:users]
posts = rom.relations[:posts]

users.offset(0).limit(10).dataset.uri
# => #<URI::HTTP http://localhost:8000/api/v1/users?start=0&size=10>

posts.by_user(1).dataset.uri
# => #<URI::HTTP http://localhost:8000/api/v1/users/1/posts>

Also, for nested resources, ROM can query those automatically, check the (out-dated) example sections below to see how that works.