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
- 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
- Override
post_is_create?
to return true - Override
create_path
to return the relative path to the new resource. Note thatcreate_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
- Override
allowed_methods
,process_post
, andcontent_types_provided
(if the response has a content type). - Rather than providing a method handler in the
content_type_provided
mappings, put all the code to be executed inprocess_post
. process_post
must return true, or the HTTP response code.
class DispatchOrderResource < Webmachine::Resource
def content_types_provided
[["application/json"]]
end
def allowed_methods
["POST"]
end
def resource_exists?
@order = Order.find(id)
end
def process_post
@order.dispatch(params['some_param'])
response.body = { message: "Successfully dispatched order #{id}" }.to_json
true
end
private
def id
request.path_info[:id]
end
def params
JSON.parse(request.body.to_s)
end
end
- 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.
- Webmachine does not currently support PATCH requests. See #109 for more information and https://github.com/bethesque/pact_broker/blob/2918814e70bbda14df68598a6a41502a5eac4308/lib/pact_broker/api/resources/pacticipant.rb for a dirty hack to make it work if you need to.
- Override
resource_exists?
anddelete_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.
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.