Download - Advanced Restful Rails - Europe
Advanced RESTful RailsBen Scofield 4 September 2008
RailsConf Europe
1
What is REST?
2
Resources
hey-helen - flickr3
Representations
stevedave - flickr4
Tools
5
Singletonsahmedrabea - flickr
6
ActionController::Routing::Routes.draw do |map| map.resource :session map.resource :profile map.resource :password map.resource :searchend
Singletons7
Namespacingmarxpix - flickr
8
ActionController::Routing::Routes.draw do |map| map.namespace :admin do |admin| admin.resources :books admin.resources :users admin.resources :invitations endend
Namespacing9
Nestingangelrays - flickr
10
ActionController::Routing::Routes.draw do |map| map.resources :users, :has_many => [:widgets],
:has_one => [:profile]end
Nesting
class WidgetsController < ActionController::Base def show @user = User.find(params[:user_id]) @widget = @user.widgets.find(params[:id]) endend
11
Polymorphism12
ActionController::Routing::Routes.draw do |map| map.resources :users, :has_many => [:entries, :readings] map.resources :books, :has_many => [:entries, :readings]end
class ReadingsController < ApplicationController def index @parent = User.find_by_id(params[:user_id]) || Book.find_by_id(params[:book_id]) @readings = @parent.readings endend
Polymorphism13
Custom Routeshi-phi - flickr
14
ActionController::Routing::Routes.draw do |map| map.with_options :controller => 'searches', :action => 'show' do | s.user_search 'search/users', :scope => 'User' s.book_search 'search/books', :scope => 'Book' s.entry_search 'search/notes', :scope => 'Entry' end map.user_following_users 'users/:user_id/following/users', :controller => 'followings', :action => 'index', :interest => 'u map.user_following_books 'users/:user_id/following/books', :controller => 'followings', :action => 'index', :interest => 'b map.timeline '/timeline', :controller => 'entries', :action => 'i
map.faq '/faq', :controller => 'pages', :action => 'show', :pa map.terms '/terms', :controller => 'pages', :action => 'show', :pa
# ...end
Custom Routes15
Pluginsimjustincognito - flickr
16
resource_this
class UsersController < ActionController::Base resource_thisend
17
resource_controller
class UsersController < ResourceController::Baseend
18
resources_controller
class UsersController < ApplicationController resources_controller_for :usersend
19
class UsersController < ApplicationController make_resourceful do actions :all endend
make_resourceful20
Modeling
21
tiptoe - flickr
22
Domain
ejpphoto - flickr
23
Modeled
kerim - flickr24
Identifyresources
25
Selectmethods to expose
26
Tips
27
When in Doubtdanielygo - flickr
28
Respect the Middleman29
Resources != Modelswilliamhook - flickr
30
Examples
31
Password Resetschromewavesdotorg - flickr
32
Password Resets
ActionController::Routing::Routes.draw do |map| map.resource :password map.resources :users, :has_one => [:password]end
33
class PasswordsController < ApplicationController before_filter :validate_user, :only => :edit def new; end def create @user = User.find_by_email(params[:email]) if @user param = rand(1000000).to_s.ljust(4, '0') code = Digest::SHA1.hexdigest(param + @user.created_at.to_s) PasswordMailer.deliver_reset_request(@user, param, code) else flash.now[:errors] = "We couldn't find an account with that email - please try again" render :action => 'new' end end
def edit self.current_user = @user end private def validate_user @user = User.find(params[:user_id]) code = Digest::SHA1.hexdigest(params[:p] + @user.created_at.to_s) raise ActiveRecord::RecordNotFound unless code == params[:code] rescue ActiveRecord::RecordNotFound redirect_to new_password_path endend
Password Resets34
Homepageseandreilinger - flickr
35
class HomepagesController < ApplicationController # homepage def show; endend
ActionController::Routing::Routes.draw do |map| map.resource :homepage map.root :homepageend
Homepage36
class ContentsController < ApplicationController # static content def show # code to find a template named according to params[:page] endend
ActionController::Routing::Routes.draw do |map| map.resources :contents map.root :controller => 'contents', :action => 'show', :page => 'homepage'end
Homepage37
class AdsController < ApplicationController # ad index - the million dollar homepage def index; endend
ActionController::Routing::Routes.draw do |map| map.resources :ads map.root :adsend
Homepage38
Dashboardhel2005 - flickr
39
class DashboardsController < ApplicationController before_filter :require_login
# dashboard def show; endend
ActionController::Routing::Routes.draw do |map| map.resource :dashboardend
Dashboard40
class InstrumentsController < ApplicationController before_filter :require_login # dashboard def index @instruments = current_user.instruments endend
ActionController::Routing::Routes.draw do |map| map.resources :instrumentsend
Dashboard41
Previewashoe - flickr
42
class PreviewsController < ApplicationController def create @post = Post.find(params[:post_id]) @post.attributes = params[:post] render :template => 'posts/show' endend
ActionController::Routing::Routes.draw do |map| map.resources :posts, :has_one => [:preview]end
Preview43
class PreviewsController < ApplicationController def create @post = Post.new(params[:post]) render :template => 'posts/show' endend
ActionController::Routing::Routes.draw do |map| map.resources :posts map.resource :previewend
Preview44
Searchseandreilinger - flickr
45
class PostsController < ApplicationController def index if params[:query].blank? @posts = Post.find(:all) else @posts = Post.find_for_query(params[:query]) end endend
ActionController::Routing::Routes.draw do |map| map.resources :postsend
Search46
class SearchesController < ApplicationController def show @results = Searcher.find(params[:query]) endend
ActionController::Routing::Routes.draw do |map| map.resource :searchend
Search47
Wizardsdunechaser - flickr
48
49
/galleries/new50
/restaurants/:id/photos/new51
/restaurants/:id/photos/edit52
ActionController::Routing::Routes.draw do |map| map.resources :galleries map.resources :galleries map.resources :restaurants, :has_many => [:photos] map.with_options :controller => 'photos' do |p| p.edit_restaurant_photos 'restaurants/:restaurant_id/photos/edit', :action => 'edit' p.update_restaurant_photos 'restaurants/:restaurant_id/photos/update', :action => 'update', :conditions => {:method => :put} endend
Wizards53
Bulk Actionzoomar - flickr
54
ActionController::Routing::Routes.draw do |map| map.resources :users map.users 'users', :controller => 'users', :action => 'destroy', :conditions => {:method => :delete}end
class UsersController < ApplicationController def destroy ids = params[:ids] || [params[:id]] @users = User.find( :all, :conditions => ['id IN (?)', ids] ).map(&:destroy) end redirect_to users_path endend
Bulk Action55
Transactionseb78 - flickr
56
ActionController::Routing::Routes.draw do |map| map.resources :transactions, :has_many => [:accounts]end
class TransactionsController < ActionController::Base def create BankTransaction.create(params[:transaction]) end def update # commit end def destroy # rollback endend
Transactions57
class AccountsController < ActionController::Base def update @transaction = BankTransaction.find(params[:transaction_id]) @account = Account.find(params[:id]) if @account.update_attributes(params[:account]) render :text => "Update succeeded" else render :text => "Update failed", :status => 500 end endend
Transactions58
Administration
this slide left intentionally blank
59
ActionController::Routing::Routes.draw do |map| map.namespace :admin do |admin| admin.resources :invitations admin.resources :emails admin.resources :users admin.resources :features endend
Administration60
Separate Collection/Single
ActionController::Routing::Routes.draw do |map| map.resource :record_list map.resources :recordsend
class RecordListsController < ApplicationController def show; end
# ...end
class RecordsController < ApplicationController def show; end
# ...end
ActiveResource
61
Server
class MoviesController < ApplicationController def index @movies = Movie.find(:all)
respond_to do |format| format.html # index.html.erb format.xml { render :xml => @movies } end end
def show @movie = Movie.find(params[:id])
respond_to do |format| format.html # show.html.erb format.xml { render :xml => @movie } end endend
62
class Movie < ActiveResource::Base self.site = 'http://localhost:3000'end
Client Model63
class MoviesController < ApplicationController def index @movies = Movie.find(:all) end
def show @movie = Movie.find(params[:id]) endend
Client Controller64
Separate Collection/Single
ActionController::Routing::Routes.draw do |map| map.resource :record_list map.resources :recordsend
class RecordListsController < ApplicationController def show; end
# ...end
class RecordsController < ApplicationController def show; end
# ...end
Web Services
65
Text
Inventory
Staff Directory
HR
etc.
RESTful APISearch Application
66
Text
Inventory
Staff Directory
HR
etc.
RESTful API
RESTful API
RESTful API
RESTful API
Search Application
67
Stumbling Blocks
68
<a href="/records/1" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete</a>
<%= link_to 'Delete', record, :method => 'delete', :confirm => 'Are you sure?' %>
Accessibility69
ActionController::Routing::Routes.draw do |map| map.users 'users', :controller => 'users', :action => 'index' map.users 'users', :controller => 'users', :action => 'create'end
Hand-Written Routes70
Default Routing
ActionController::Routing::Routes.draw do |map| map.resources :users
# Install the default route as the lowest priority. map.connect ':controller/:action/:id'end
71
Unused Actions
new_user_reading GET /users/:user_id/readings/new formatted_new_user_reading GET /users/:user_id/readings/new.:format edit_user_reading GET /users/:user_id/readings/:id/edit user_reading GET /users/:user_id/readings/:id formatted_user_reading GET /users/:user_id/readings/:id.:format PUT /users/:user_id/readings/:id PUT /users/:user_id/readings/:id.:format DELETE /users/:user_id/readings/:id DELETE /users/:user_id/readings/:id.:format user_followers GET /users/:user_id/followers formatted_user_followers GET /users/:user_id/followers.:format POST /users/:user_id/followers POST /users/:user_id/followers.:format new_user_follower GET /users/:user_id/followers/new formatted_new_user_follower GET /users/:user_id/followers/new.:format edit_user_follower GET /users/:user_id/followers/:id/edit user_follower GET /users/:user_id/followers/:id formatted_user_follower GET /users/:user_id/followers/:id.:format PUT /users/:user_id/followers/:id PUT /users/:user_id/followers/:id.:format DELETE /users/:user_id/followers/:id DELETE /users/:user_id/followers/:id.:format
... approximately 280 lines deleted ...
72
Collectionswooandy - flickr
73
Mixed Collection/Single
class RecordsController < ApplicationController def index; end
def show; end
# ...end
ActionController::Routing::Routes.draw do |map| map.resources :recordsend
74
ARes Avalanchenaturalkinds - flickr
75
Processing MoviesController#index (for 127.0.0.1 at 2008-04-20 17:34:22) [GET] Parameters: {"format"=>"xml", "action"=>"index", "controller"=>"movies"}
Processing MoviesController#index (for 127.0.0.1 at 2008-04-20 17:34:22) [GET] Parameters: {"format"=>"xml", "action"=>"index", "controller"=>"movies"}
Processing MoviesController#index (for 127.0.0.1 at 2008-04-20 17:34:22) [GET] Parameters: {"format"=>"xml", "action"=>"index", "controller"=>"movies"}
Processing MoviesController#show (for 127.0.0.1 at 2008-04-20 17:34:22) [GET] Parameters: {"format"=>"xml", "action"=>"show", "id"=>"1", "controller"=>"movies"}
Processing ReleasesController#index (for 127.0.0.1 at 2008-04-20 17:34:22) [GET] Parameters: {"format"=>"xml", "movie_id"=>"1", "action"=>"index", "controller"=>"releases"}
Processing ReleasesController#index (for 127.0.0.1 at 2008-04-20 17:34:22) [GET] Parameters: {"format"=>"xml", "action"=>"index", "controller"=>"releases"}
ARes Avalanche76
SinatraWaves
Halcyon?
77
Thanks!ben [email protected]://www.viget.com/extendhttp://www.culann.com
☜78