Skip to content

Latest commit

 

History

History
219 lines (163 loc) · 5.26 KB

File metadata and controls

219 lines (163 loc) · 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.