Every repository with this icon (
Every repository with this icon (
| name | age | message | |
|---|---|---|---|
| |
.gitignore | Mon Nov 23 10:57:20 -0800 2009 | |
| |
README.textile | Thu Nov 26 10:03:23 -0800 2009 | |
| |
Rakefile | Tue Nov 24 18:34:33 -0800 2009 | |
| |
init.rb | Wed Oct 28 23:04:28 -0700 2009 | |
| |
lib/ | Thu Nov 26 09:41:52 -0800 2009 | |
| |
spec/ | Thu Nov 26 10:03:23 -0800 2009 |
Quit pretending
CRUD through HTTP is a good step forward to using resources and becoming RESTful, another step further into it is to make use of hypermedia based services and this gem allows you to do it really fast.
You can read the article on using the web for real which gives an introduction to hypermedia/resources/services.
Why would I use restfulie?
1. Easy —> writing hypermedia aware resource based clients
2. Easy —> hypermedia aware resource based services
3. Small → it’s not a bloated solution with a huge list of APIs
4. HATEOAS —> clients you are unaware of will not bother if you change your URIs
5. HATEOAS —> services that you consume will not affect your software whenever they change part of their flow or URIs
Could you compare it with Spring or JAX-RS based APIs?
Restfulie is the first API which tries to somehow implement Jim Webber and Ian Robinson opinion on how RESTFul systems use hypermedia
as the way to lead your client’s path through a business process.
Therefore Restfulie is unique both in its feature set when compared to both Spring and JAX-RS based implementations, and its implementation: looking for simple code and favoring conventions over manual configurations.
Short examples
Restfulie: client-side
Example on accessing a resource and its services through the restfulie API:
order = Order.from_web resource_uri
puts "Order price is #{order.price}"
order.pay payment # sends a post request to pay this order
order.cancel # sends a delete request
Restfulie: server-side
This is a simple example how to make your state changes available to your resource consumers:
class Order < ActiveRecord::Base
acts_as_restfulie
def following_transitions
transitions = []
transitions << [:show, {}]
transitions << [:destroy, {}] if can_cancel?
transitions << [:pay, {:id => id}] if can_pay?
transitions
end
end
You might want to create a migration with a string field named status for your resource:
scripts/generate migration add_status_to_order
Content:
class AddStatusToOrder < ActiveRecord::Migration
def self.up
add_column :orders, :status, :string
Order.all.each do |order|
order.status = "unpaid"
order.save
end
end
def self.down
remove_column :orders, :status
end
end
Or simply define a status reader and writer on your own.
Restfulie server-side: state machine
For those willing to implement a more complex or advanced state machine, you can use the dsl-like api:
class Order < ActiveRecord::Base
acts_as_restfulie
state :unpaid, :allow => [:latest, :pay, :cancel]
state :cancelled, :allow => :latest
transition :latest, {:action => :show}
transition :cancel, {:action => :destroy}, :cancelled
transition :pay, {}, :preparing
end
Installing
Just add in your environment.rb the following line:
config.gem "restfulie", :source => "http://gemcutter.org"
And then execute:
rake gems:install
or, if you prefer to install it as a plugin:
script/plugin install git://github.com/caelum/restfulie.git
Typical hypermedia aware example
Trying to follow the definition of a RESTful application supporting resources with hypermedia content, a resource would be:
<order> <product>basic rails course</product> <product>RESTful training</product> <atom:link rel="refresh" href="http://www.caelum.com.br/orders/1" xmlns:atom="http://www.w3.org/2005/Atom"/> <atom:link rel="update" href="http://www.caelum.com.br/orders/1" xmlns:atom="http://www.w3.org/2005/Atom"/> <atom:link rel="pay" href="http://www.caelum.com.br/orders/1/pay" xmlns:atom="http://www.w3.org/2005/Atom"/> <atom:link rel="destroy" href="http://www.caelum.com.br/orders/1" xmlns:atom="http://www.w3.org/2005/Atom"/> </order>
Client Usage
Create your class and invoke the uses_restfulie method:
class Order < ActiveRecord::Base uses_restfulie end
One should first acquire the representation from the server through your common GET request and process it through the usual from_* methods:
xml = Net::HTTP.get(URI.parse(‘http://www.caelum.com.br/orders/1’))
order = Order.from_xml(xml)
or use the restfulie from_web:
order = Order.from_web ‘http://www.caelum.com.br/orders/1’
And now you can invoke all those actions in order to change your resource’s state:
order.refresh order.update order.destroy order.pay
Note that:
- refresh is get
- update is put (and you have to put everything back)
- destroy is delete
- pay (unknown methods) is post
Resource format support
Restfulie currently supports full xml+atom, partial xml+rel and will soon expand its support to json+links.
Help
If you are looking for or want to help, let us know at the mailing list:
http://groups.google.com/group/restfulie
Client-side configuration: how to customize your request
HTTP verbs
By default, restfulie uses the following table:
- destroy, cancel and delete send a DELETE request
- update sends a PUT request
- refresh, reload, show, latest sends a GET request
- other methods sends a POST request
If you want to use a custom http verb in order to send your request, you can do it by setting the optional string ‘method’:
order.update(:method=>"post")
Request parameters
If you want to send extra parameters, you can do it through the data parameter:
order.pay(:data => {:payment => my_payment})
The parameters will be serialized either to xml or json according to which format was used to deserialize the order at first place.
Executing another GET request
If your method executes another GET request, it will automatically deserialize its result as:
order = Order.from_web order_uri payment = order.check_payment_info
If you want to parse the response yourself, instead of receiving just the final deserialized object, you can do it by passing a body to your method
order = Order.from_web order_uri successful = order.check_payment_info do |response| return response.code==200 end
Server-side configuration
There are two different approaches that can be combined to create a full hypermedia aware resource based service, including awareness of its states and transitions.
Simple usage: following transitions
The most easy way to use restfulie is to write the following_transitions method.
There are three easy steps to make it work:
1. Create your model (i.e. Order) with an status field
script/generate scaffold Order status:string location:string
rake db:create
rake db:migrate
Note that with this usage the status field is optional (from 0.3.0 onwards).
2. Add the acts_as_restfulie invocation and following_transitions method returning an array of possible transitions:
acts_as_restfulie
def following_transitions
transitions = []
transitions << [:show, {}]
transitions
end
3. Update your show method within the OrdersController to show the hypermedia content:
def show
@order = Order.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @order.to_xml(:controller=>self) }
end
end
You are ready to go, create a new order and save it into the database:
order = Order.new
order.location = "take away"
order.status = "unpaid"
order.save
puts "Order #{order.id} saved"
Start up the server:
script/server
And now access your server at http://localhost:3000/orders/1.xml
<?xml version="1.0" encoding="UTF-8"?> <order> <created-at>2009-11-23T00:15:15Z</created-at> <id>1</id> <location>take away</location> <status>unpaid</status> <updated-at>2009-11-23T00:15:15Z</updated-at> <atom:link rel="show" xmlns:atom="http://www.w3.org/2005/Atom" href="http://localhost:3000/orders/3"/> </order>
Customizing the rel name
You can also override the action used, but still keep the rel
def following_transitions
transitions = []
transitions << [:cancel, { :action => :destroy }]
transitions
end
Which will generate an hyperlink as
<atom:link rel="cancel" rel="http://yourserver/orders/15" />
Example
A full example showing all capabilities of this method follows:
def following_transitions
transitions = []
transitions << [:show, {}]
transitions << [:destroy, {}] if can_cancel?
transitions << [:pay, {:id => id}] if can_pay?
transitions << [:show, {:controller => :payments, :payment_id => payment.id }] if paid?
transitions
end
Advanced usage: Defining the state machine and its transitions
The second way of defining your available transitions is to explicitely define the states and transitions.
By using this approach, one has to define a new column named status in a database migration file.
The first step involves defining all your states, each one with its own name and possible transitions, as:
state :state_name, :allow => [ :first_transition_name, :second_transition_name]
The following example shows all possible states for an order:
class Order < ActiveRecord::Base acts_as_restfulie state :unpaid, :allow => [:latest, :pay, :cancel] state :cancelled, :allow => :latest state :received, :allow => [:latest, :check_payment_info] state :preparing, :allow => [:latest, :check_payment_info] state :ready, :allow => [:latest, :receive, :check_payment_info] end
Now its time to define which controller and action each transition invokes, in a much similar way to
the transition definitions in the following_transitions method:
class Order < ActiveRecord::Base end
Once a transition has been given a name, its name can be used in the following_transitions method also.
The next example does not configure the transition because it was already defined, only adding it to the
list of available transition whenever the can_pay? method returns true:
class Order < ActiveRecord::Base
acts_as_restfulie
transition :pay, {:action => pay_this_order, :controller => :payments}, :preparing
def following_transitions
transitions = []
transitions << :pay if can_pay?
transitions
end
end
Note that whenever one defines a transition, there is a third – optional – argument, this is the
transition’s target’s state. Whenever the method order.pay method is invoked in the server, it will
automatically change the order’s status to preparing.
You can download the server side example to see the complete code.
The last usage of the transition definition involves passing a block which receives the element in which
the transition URI’s is required. The block should return all the necessary information for retrieving the URI, now having access to your element’s instance variables:
class Order < ActiveRecord::Base
transition :check_payment_info do |order|
{:controller => :payments, :action => :show, :order_id => order.id, :payment_id => order.payments[0].id, :rel => "check_payment_info"}
end
end
Accessing all possible transitions
One can access all possible transitions for an object by invoking its available_transitions method:
transitions = order.available_transitions
Checking the possibility of following transitions
By following the advanced usage, one receives also all can_ method. i.e.:
order.status = :unpaid puts(order.can_pay?) # will print true order.status = :paid puts(order.can_pay?) # will print false
You can use the can_xxx methods in your controllers to check if your current resource’s state can be changed:
def pay
@order = Order.find(params[:id])
raise "impossible to pay due to this order status #{order.status}" if !@order.can_pay?
# payment code
end
Using xml+rel links instead of atom links
Atom is everywhere and can be consumed by a number of existing tools but if your system wants to supply its
services through commons rel+link xml as
<order> <product>basic rails course</product> <product>RESTful training</product> <refresh>http://www.caelum.com.br/orders/1</refresh> <update>http://www.caelum.com.br/orders/1</update> <pay>http://www.caelum.com.br/orders/1/pay</pay> <destroy>http://www.caelum.com.br/orders/1</destroy> </order>
You can do it by passing the use_name_based_link argument:
order.to_xml(:controller => my_controller, :use_name_based_link => true)
Team
Restfulie was created and is maintained within Caelum by
Projetct Founder
- Guilherme Silveira – twitter:http://www.twitter.com/guilhermecaelum http://guilhermesilveira.wordpress.com
Active Commiters
Contributors
- Diego Carrion
- Leandro Silva
- Gavin-John Noonan
Try it online
We have a live example of a server implementation using a resource+hypermedia course ordering system available.
Follow the steps below to try out the system:
- Access the server system
- Create a couple of trainings
- Create an order
- Access the order listing and retrieve its xml link
And now you can try the restfulie client api through a simple and generic resource+hypermedia client application:
- Access the client system
- Enter your order uri
- Check your order information which was retrieved and all available actions
Now you can either:
- latest – refresh your order information order.latest
- cancel – cancel your order (dead end!) order.destroy
- pay – pay for your order, and don’t forget to send your (fake) credit card information order.pay(payment)
- check_payment_info – after paying you can check the payment information stored at the server order.check_payment_info
In order to pay do not forget to send the parameter payment with a value as
<payment> <amount>15</amount> <cardholder_name>Guilherme Silveira</cardholder_name> <card_number>123456789012</card_number> <expiry_month>12</expiry_month> <expiry_year>12</expiry_year> </payment>
Sources
You can see an application’s source code here, both client and server side were implemented using restfulie:
More tutorials
There is a portuguese tutorial on the server-side support, more on restfulie – portuguese and a blog post on the entire ecosystem in english
What’s new
next release
- API change: you need to invoke “acts_as_restfulie” to your models
- implemented support to can_*** methods
- post data through http POST body
- no need for the status field if you use the following_transition approach on the server side
- bug fixed when status was nil
- lots of internal refactoring
- lots of new documentation
0.2
- support to state machine configuration
- resources must contain a status field
0.1
- first release
Coming soon
- support lastModified
- support eTag
- remove client dependency from ActiveRecord
- remove server side dependency from ActiveRecord (its ok to use anything else)
- transitions << [:show] should work
- Generate and link to rubydoc
- change parameter order for transition: last one is HASH, first is name, second (if existing) is target state
- rel prepend suffix as http://iansrobinson.com/resources/link-relations/preceding
- automatically generate uri for this rel with its transition description
- release 0.4
- controller filtering and methods
- english tutorial and screencast
- allows pure String/byte array client side post and server side retrieval
- integration tests on orderserver/client api
- controller method should check if its an restfulie resource
- full support to extended json
- pure href definition of link
- post entry point support
- Set the correct media type instead of application/xml
- rails 3 easier support (no controller argument!)
- allow servers to define transitions by accessing other systems
- allow servers to define a state method instead of internal variable
- when receiving a 201 + content, it should believe the content
- when receiving a 201 without any content, it should allow to redirect or not
- client side should allow withTimeStamp, withETag, withAuth
- is there is an etag, use it by default (maybe NOT use it by default)… modified since and so on (header tags)
- server side maybe allow hypermedia controls or not
- server and client side 303 support on alternate uri (or content-location)
- Use If-Unmodified-Since on a timestamp to
* Or use If-Match and an ETag
* You’ll get a 412 Prec if you lost the race
License
/*
- Copyright © 2009 Caelum – www.caelum.com.br/opensource
- All rights reserved.
* - Licensed under the Apache License, Version 2.0 (the “License”);
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
* - http://www.apache.org/licenses/LICENSE-2.0
* - Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an “AS IS” BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
*/







