Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
219 lines (162 sloc) 5.26 KB

Imagine an application with an "orders" resource, OrdersResource, that represents the collection of orders in the application, and an "order" resource, OrderResource, that represents a single order object.

This is how the /orders and /orders/:id routes are mapped to their respective resource classes.

App = Webmachine::Application.new do |app|
  app.routes do
    add ["orders"], OrdersResource
    add ["orders", :id], OrderResource
  end
end

GET

  • Override resource_exists?, content_types_provided, allowed_methods, and implement the method to render the resource.

Curious as to which order the callbacks will be invoked in? Read why it doesn't have to matter.

class OrderResource < Webmachine::Resource
  def allowed_methods
    ["GET"]
  end

  def content_types_provided
    [["application/json", :to_json]]
  end

  def resource_exists?
    order
  end

  def to_json
    order.to_json
  end

  private

  def order
    @order ||= Order.new(params)
  end

  def id
    request.path_info[:id]
  end
end

POST to create a new resource in a collection

  • Override post_is_create? to return true
  • Override create_path to return the relative path to the new resource. Note that create_path will be called before the content type handler (eg. from_json) is called, which means that you need to know the ID before the object has been inserted into the database. This might seem a hassle, but it stops you from exposing your database column IDs to the world, which is a naughty and lazy habit we've all picked up from Rails.
  • The response Content-Type and status will be set for you.
class OrdersResource < Webmachine::Resource

  def allowed_methods
    ["POST"]
  end

  def content_types_accepted
    [["application/json", :from_json]]
  end

  def post_is_create?
    true
  end

  def create_path
    "/orders/#{next_id}"
  end

  private

  def from_json
    response.body = new_order.save(next_id).to_json
  end

  def next_id
    @id ||= Order.next_id
  end

  def new_order
    @new_order ||= Order.new(params)
  end

  def params
    JSON.parse(request.body.to_s)
  end
end

POST to perform a task

  • Override allowed_methods and process_post. Put all the code to be executed in process_post.
  • process_post must return true, or the HTTP response code
  • Response headers like Content-Type will need to be set manually.
class DispatchOrderResource < Webmachine::Resource

  def allowed_methods
    ["POST"]
  end

  def resource_exists?
    @order = Order.find(id)
  end

  def process_post
    @order.dispatch
    response.headers['Content-Type'] = 'text/plain'
    response.body = "Successfully dispatched order #{id}"
    true
  end

  private

  def id
    request.path_info[:id]
  end
end

PUT

  • Override resource_exists?, content_types_accepted, allowed_methods, and implement the method to create/replace the resource.
class OrderResource < Webmachine::Resource

  def allowed_methods
    ["PUT"]
  end

  def content_types_accepted
    [["application/json", :from_json]]
  end

  # Note that returning falsey will NOT result in a 404 for PUT requests.
  # See note below.
  def resource_exists?
    order
  end

  def from_json
    # Remember PUT should replace the entire resource, not merge the attributes! That's what PATCH is for.
    # It's also why you should not expose your database IDs as your API IDs.
    order.destroy if order
    new_order = Order.new(params)
    new_order.save(id)
    response.body = new_order.to_json
  end

  private

  def order
    @order ||= Order.find(id)
  end

  def params
    JSON.parse(request.body.to_s)
  end

  def id
    request.path_info[:id]
  end
end

If you wish to disallow PUT to a non-existent resource, read more here.

PATCH

DELETE

  • Override resource_exists? and delete_resource
  • delete_resource must return true
  • See callbacks.rb for documentation on asynchronous deletes.
class OrderResource < Webmachine::Resource

  def allowed_methods
    ["DELETE"]
  end

  def resource_exists?
    order
  end

  def delete_resource
    order.destroy
    true
  end

  private

  def order
    @order ||= Order.find(id)
  end

  def id
    request.path_info[:id]
  end

end

Thanks to oestrich for putting together the original example. You can see the full source code here.

What order are the callbacks invoked in?

This question is actually irrelevant if you write your code in a "stateless" way using lazy initialization as the examples do above. As much as possible, think about exposing "facts" about your resource, not writing procedural code that needs to be called in a certain order. See How it works for more information on how the Webmachine state machine works.

You can’t perform that action at this time.