Salesforce Miami User Group Event - 1st Quarter 2024
Making Rails Really restful
1. Making Rails really
Restful
Improving the world of Resources on Rails with Restfulie
Fabio Akita - akitaonrails.com - @akitaonrails
Wednesday, June 9, 2010
24. class PeopleController < ApplicationController
# POST /people/1
# POST /people/1.xml
def create form_for(@person) do |f|
end f.text_field :name
end
# GET /people
# GET /people.xml
def show
end
# PUT /people/1
# PUT /people/1.xml
def update
end
# DELETE /people/1
# DELETE /people/1.xml
def destroy
end
end
Wednesday, June 9, 2010
25. class PeopleController < ApplicationController
# POST /people/1
# POST /people/1.xml
def create
end
# GET /people
# GET /people.xml
def show link_to 'Show', person
end
# PUT /people/1
# PUT /people/1.xml
def update
end
# DELETE /people/1
# DELETE /people/1.xml
def destroy
end
end
Wednesday, June 9, 2010
26. class PeopleController < ApplicationController
# POST /people/1
# POST /people/1.xml
def create
end
# GET /people
# GET /people.xml
def show
end
# PUT /people/1
# PUT /people/1.xml
def update form_for(@person) do |f|
end f.text_field :name
end
# DELETE /people/1
# DELETE /people/1.xml
def destroy
end
end
Wednesday, June 9, 2010
27. class PeopleController < ApplicationController
# POST /people/1
# POST /people/1.xml
def create
end
# GET /people
# GET /people.xml
def show
end
# PUT /people/1
# PUT /people/1.xml
def update
end
# DELETE /people/1
# DELETE /people/1.xml
def destroy link_to 'Destroy',
end person, :method => :delete
end
Wednesday, June 9, 2010
29. Answering to mime types
One controller for many clients
Wednesday, June 9, 2010
30. Answering to mime types
One controller for many clients
One action returning different representations
Wednesday, June 9, 2010
31. Answering to mime types
One controller for many clients
One action returning different representations
“Content Negotiation"
Wednesday, June 9, 2010
32. class PeopleController < ApplicationController
def index
@people = Person.all
respond_to do |format|
format.html # index.html.erb
format.js # index.js.erb
format.atom # index.atom.builder
format.xml { render :xml => @people }
end
end
end
Wednesday, June 9, 2010
33. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def index
@people = Person.all
respond_with(@people)
end
end
Wednesday, June 9, 2010
34. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def index
@people = Person.all
respond_with(@people)
end
end
Wednesday, June 9, 2010
35. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def index
@people = Person.all
respond_with(@people)
end
end
GET /people GET /people.xml
=> returns HTML => returns XML
Accept: text/javascript Accept: text/xml
GET /people GET /people
=> returns JS => returns XML
Wednesday, June 9, 2010
36. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def index
@people = Person.all
respond_with(@people)
end
end
GET /people GET /people.xml
=> returns HTML => returns XML
Accept: text/javascript Accept: text/xml
GET /people GET /people
=> returns JS => returns XML
Wednesday, June 9, 2010
37. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def index
@people = Person.all
respond_with(@people)
end
end
GET /people GET /people.xml
=> returns HTML => returns XML
Accept: text/javascript Accept: text/xml
GET /people GET /people
=> returns JS => returns XML
Wednesday, June 9, 2010
38. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def index
@people = Person.all
respond_with(@people)
end
end
GET /people GET /people.xml
=> returns HTML => returns XML
Accept: text/javascript Accept: text/xml
GET /people GET /people
=> returns JS => returns XML
Wednesday, June 9, 2010
40. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
Wednesday, June 9, 2010
41. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
Wednesday, June 9, 2010
42. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
GET /people/1
Wednesday, June 9, 2010
43. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
GET /people/1
=> returns:
HTTP/1.1 200 OK
Etag: "fd19b85b6ba49b5778de34310d141319"
Last-Modified: Fri, 21 May 2010 05:31:11 GMT
Content-Type: text/html; charset=utf-8
Cache-Control: public
...
Wednesday, June 9, 2010
44. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
GET /people/1
=> returns:
HTTP/1.1 200 OK
Etag: "fd19b85b6ba49b5778de34310d141319"
Last-Modified: Fri, 21 May 2010 05:31:11 GMT
Content-Type: text/html; charset=utf-8
Cache-Control: public
...
Wednesday, June 9, 2010
45. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
If-None-Match: "fd19b85b6ba49b5778de34310d141319"
GET /people/1
Wednesday, June 9, 2010
46. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
If-None-Match: "fd19b85b6ba49b5778de34310d141319"
GET /people/1
Wednesday, June 9, 2010
47. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
If-None-Match: "fd19b85b6ba49b5778de34310d141319"
GET /people/1
=> returns:
HTTP/1.1 304 Not Modified
Etag: "fd19b85b6ba49b5778de34310d141319"
Last-Modified: Fri, 21 May 2010 05:31:11 GMT
Cache-Control: public
...
Wednesday, June 9, 2010
48. class PeopleController < ApplicationController
respond_to :html, :js, :xml
def show
@person = Person.find(params[:id])
if stale?(:etag => @person,
:last_modified => @person.created_at.utc,
:public => true)
respond_with @person
end
end
end
If-None-Match: "fd19b85b6ba49b5778de34310d141319"
GET /people/1
=> returns:
HTTP/1.1 304 Not Modified
Etag: "fd19b85b6ba49b5778de34310d141319"
Last-Modified: Fri, 21 May 2010 05:31:11 GMT
Cache-Control: public
...
Wednesday, June 9, 2010
98. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
Wednesday, June 9, 2010
99. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
Wednesday, June 9, 2010
100. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Wednesday, June 9, 2010
101. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Then approve the app
Wednesday, June 9, 2010
102. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Then approve the app
When there is an app being evaluated
Wednesday, June 9, 2010
103. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Then approve the app
When there is an app being evaluated
And the app doesn't pass the criteria
Wednesday, June 9, 2010
104. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Then approve the app
When there is an app being evaluated
And the app doesn't pass the criteria
Then decline the app
Wednesday, June 9, 2010
131. No Mechanization?
Hyperlinks
No Semantics
Human Heuristics
Wednesday, June 9, 2010
132. require 'rubygems'
require 'active_resource'
class App < ActiveResource::Base
self.site = "http://localhost:3000/"
end
apps = App.find(:all, :params => { :state => "new" })
Wednesday, June 9, 2010
133. require 'rubygems'
require 'active_resource'
class App < ActiveResource::Base
self.site = "http://localhost:3000/"
end
apps = App.find(:all, :params => { :state => "new" })
GET /apps?state=new
Wednesday, June 9, 2010
134. require 'rubygems'
require 'active_resource'
class App < ActiveResource::Base
self.site = "http://localhost:3000/"
end
apps = App.find(:all, :params => { :state => "new" })
GET /apps?state=new
Wednesday, June 9, 2010
135. require 'rubygems'
require 'active_resource'
class App < ActiveResource::Base
self.site = "http://localhost:3000/"
end
apps = App.find(:all, :params => { :state => "new" })
GET /apps?state=new
Wednesday, June 9, 2010
136. require 'rubygems'
require 'active_resource'
class App < ActiveResource::Base
self.site = "http://localhost:3000/"
end
apps = App.find(:all, :params => { :state => "new" })
GET /apps?state=new
Wednesday, June 9, 2010
137. ActionController::Routing::Routes.draw do |map|
map.resources :apps,
:member => {
:evaluate => :post,
:approve => :post,
:decline => :post
}
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
138. app = apps.first
app.post(:evaluate) if app.state == "new"
if apps.first.description =~ /Flash/
app.post(:decline)
elsif apps.first.description =~ /sex/
app.post(:decline)
else
app.post(:approve)
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
139. app = apps.first
app.post(:evaluate) if app.state == "new"
if apps.first.description =~ /Flash/
app.post(:decline)
elsif apps.first.description =~ /sex/
app.post(:decline)
else
app.post(:approve)
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
140. app = apps.first
app.post(:evaluate) if app.state == "new"
if apps.first.description =~ /Flash/
app.post(:decline)
elsif apps.first.description =~ /sex/
app.post(:decline)
else
app.post(:approve)
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
141. app = apps.first
app.post(:evaluate) if app.state == "new"
if apps.first.description =~ /Flash/
app.post(:decline)
elsif apps.first.description =~ /sex/
app.post(:decline)
else
app.post(:approve)
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
142. app = apps.first
app.post(:evaluate) if app.state == "new"
if apps.first.description =~ /Flash/
app.post(:decline)
elsif apps.first.description =~ /sex/
app.post(:decline)
else
app.post(:approve)
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
143. app = apps.first
app.post(:evaluate) if app.state == "new"
if apps.first.description =~ /Flash/
app.post(:decline)
elsif apps.first.description =~ /sex/
app.post(:decline)
else
app.post(:approve)
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
144. app = apps.first
app.post(:evaluate) if app.state == "new"
if apps.first.description =~ /Flash/
app.post(:decline)
elsif apps.first.description =~ /sex/
app.post(:decline)
else
app.post(:approve)
end
POST /apps/1/evaluate
POST /apps/1/decline
POST /apps/1/approve
Wednesday, June 9, 2010
145. <?xml version="1.0" encoding="UTF-8"?>
<app>
<id type="integer">1</id>
<title>Facebook</title>
<updated_at type="datetime">2010-06-02T14:16:45Z</updated_at>
<state>new</state>
<description>Facebook for iPhone ... on the go.</description>
<price type="float">0.0</price>
</app>
application/xml
Wednesday, June 9, 2010
149. Why not ActiveResource?
No Hyperlinks
No Semantics
Doesn’t respect HTTP (etag, last modi ed)
Wednesday, June 9, 2010
150. Why not ActiveResource?
No Hyperlinks
No Semantics
Doesn’t respect HTTP (etag, last modi ed)
Ruby on Rails only Conventions
Wednesday, June 9, 2010
151. Why not other web clients?
HTTParty, RestClient, Typhoeus, Curb
Wednesday, June 9, 2010
152. Why not other web clients?
HTTParty, RestClient, Typhoeus, Curb
Most are not really “REST”
Wednesday, June 9, 2010
153. Why not other web clients?
HTTParty, RestClient, Typhoeus, Curb
Most are not really “REST”
Just URI consumers
Wednesday, June 9, 2010
154. Why not other web clients?
HTTParty, RestClient, Typhoeus, Curb
Most are not really “REST”
Just URI consumers
Wrappers for low level HTTP connections
Wednesday, June 9, 2010
155. One goal is to avoid
tight coupling
Wednesday, June 9, 2010
186. Mechanizable Web
Hyperlinks
Link Relations
Wednesday, June 9, 2010
187. Mechanizable Web
Hyperlinks
Link Relations
Media Types
Wednesday, June 9, 2010
188. Mechanizable Web
Hyperlinks
Link Relations
Media Types
Domain Application Protocol (DAP)
Wednesday, June 9, 2010
189. Mechanizable Web
Hyperlinks
Link Relations
Media Types
Domain Application Protocol (DAP)
HATEOAS
Wednesday, June 9, 2010
190. Mechanizable Web
Hyperlinks
Link Relations
Media Types
Domain Application Protocol (DAP)
HATEOAS
Hypermedia As The Engine of Application State
Wednesday, June 9, 2010
192. Protocol
HTTP idioms Media Types
Entry-point URIs
Contract
Protocols
Wednesday, June 9, 2010
193. Media Type
Formats Link Relations
Processing Models
Schema
Protocol
HTTP idioms Media Types
Entry-point URIs
Contract
Protocols
Wednesday, June 9, 2010
195. Improving Restfulie
Single interface for representations
Wednesday, June 9, 2010
196. Improving Restfulie
Single interface for representations
Smart defaults for serialization
Wednesday, June 9, 2010
197. Improving Restfulie
Single interface for representations
Smart defaults for serialization
XML serialization compatible with Rails
Wednesday, June 9, 2010
198. Improving Restfulie
Single interface for representations
Smart defaults for serialization
XML serialization compatible with Rails
Rails 3 compatibility
Wednesday, June 9, 2010
202. Evaluation Workflow
When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Then approve the app
When there is an app being evaluated
And the app doesn't pass the criteria
Then decline the app
Wednesday, June 9, 2010
203. require 'restfulie'
class EvaluationProcess < Restfulie::Client::Mikyung::RestProcessModel
at "http://localhost:3000/apps/list_new"
current_dir File.dirname(__FILE__)
follow true
def initialize(*black_list)
@black_list = black_list
end
def completed?(resource)
resource.entries.size == 0
end
def self.run
goal = EvaluationProcess.new("Flash", "sex")
result = Restfulie::Mikyung.new.achieve(goal).run
puts result.response.body
end
end
evaluation_process.rb
Wednesday, June 9, 2010
204. require 'restfulie'
class EvaluationProcess < Restfulie::Client::Mikyung::RestProcessModel
at "http://localhost:3000/apps/list_new"
current_dir File.dirname(__FILE__)
follow true
def initialize(*black_list)
@black_list = black_list
end
def completed?(resource)
resource.entries.size == 0
end
def self.run
goal = EvaluationProcess.new("Flash", "sex")
result = Restfulie::Mikyung.new.achieve(goal).run
puts result.response.body
end
end
evaluation_process.rb
Wednesday, June 9, 2010
205. require 'restfulie'
class EvaluationProcess < Restfulie::Client::Mikyung::RestProcessModel
at "http://localhost:3000/apps/list_new"
current_dir File.dirname(__FILE__)
follow true
def initialize(*black_list)
@black_list = black_list
end
def completed?(resource)
resource.entries.size == 0
end
def self.run
goal = EvaluationProcess.new("Flash", "sex")
result = Restfulie::Mikyung.new.achieve(goal).run
puts result.response.body
end
end
evaluation_process.rb
Wednesday, June 9, 2010
206. require 'restfulie'
class EvaluationProcess < Restfulie::Client::Mikyung::RestProcessModel
at "http://localhost:3000/apps/list_new"
current_dir File.dirname(__FILE__)
follow true
def initialize(*black_list)
@black_list = black_list
end
def completed?(resource)
resource.entries.size == 0
end
def self.run
goal = EvaluationProcess.new("Flash", "sex")
result = Restfulie::Mikyung.new.achieve(goal).run
puts result.response.body
end
end
evaluation_process.rb
Wednesday, June 9, 2010
207. def is_valid?(app)
result = true
@black_list.each do |word|
if app.description =~ /#{word}/
result = false
end
end
result
end
Then "move forward to evaluate it" do |resource|
@app = @app.links.evaluate.follow.post!("")
resource
end
When "the app passes the criteria" do |resource|
is_valid?(@app)
end
steps/evaluation_process.rb
Wednesday, June 9, 2010
208. def is_valid?(app)
result = true
@black_list.each do |word|
if app.description =~ /#{word}/
result = false
end
end
result
end
Then "move forward to evaluate it" do |resource|
@app = @app.links.evaluate.follow.post!("")
resource
end
When "the app passes the criteria" do |resource|
is_valid?(@app)
end
steps/evaluation_process.rb
Wednesday, June 9, 2010
209. When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Then approve the app
When there is an app being evaluated
And the app doesnt pass the criteria
Then decline the app
scenarios/evaluation_process.scenario
Wednesday, June 9, 2010
210. When there is a new app
Then move forward to evaluate it
When there is an app being evaluated
And the app passes the criteria
Then approve the app
When there is an app being evaluated
And the app doesnt pass the criteria
Then decline the app
This is Ruby Code!
scenarios/evaluation_process.scenario
Wednesday, June 9, 2010
211. EvaluationProcess.run
http://bit.ly/railsconf2010-restfulie
Wednesday, June 9, 2010