78
Advanced RESTful Rails Ben Scofield 4 September 2008 RailsConf Europe 1

Advanced Restful Rails - Europe

Embed Size (px)

DESCRIPTION

This is the revised version of my Advanced RESTful Rails session, as presented in Berlin at Railsconf Europe 2008

Citation preview

Page 1: Advanced Restful Rails - Europe

Advanced RESTful RailsBen Scofield 4 September 2008

RailsConf Europe

1

Page 2: Advanced Restful Rails - Europe

What is REST?

2

Page 3: Advanced Restful Rails - Europe

Resources

hey-helen - flickr3

Page 4: Advanced Restful Rails - Europe

Representations

stevedave - flickr4

Page 5: Advanced Restful Rails - Europe

Tools

5

Page 6: Advanced Restful Rails - Europe

Singletonsahmedrabea - flickr

6

Page 7: Advanced Restful Rails - Europe

ActionController::Routing::Routes.draw do |map| map.resource :session map.resource :profile map.resource :password map.resource :searchend

Singletons7

Page 8: Advanced Restful Rails - Europe

Namespacingmarxpix - flickr

8

Page 9: Advanced Restful Rails - Europe

ActionController::Routing::Routes.draw do |map| map.namespace :admin do |admin| admin.resources :books admin.resources :users admin.resources :invitations endend

Namespacing9

Page 10: Advanced Restful Rails - Europe

Nestingangelrays - flickr

10

Page 11: Advanced Restful Rails - Europe

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

Page 12: Advanced Restful Rails - Europe

Polymorphism12

Page 13: Advanced Restful Rails - Europe

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

Page 14: Advanced Restful Rails - Europe

Custom Routeshi-phi - flickr

14

Page 15: Advanced Restful Rails - Europe

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

Page 16: Advanced Restful Rails - Europe

Pluginsimjustincognito - flickr

16

Page 17: Advanced Restful Rails - Europe

resource_this

class UsersController < ActionController::Base resource_thisend

17

Page 18: Advanced Restful Rails - Europe

resource_controller

class UsersController < ResourceController::Baseend

18

Page 19: Advanced Restful Rails - Europe

resources_controller

class UsersController < ApplicationController resources_controller_for :usersend

19

Page 20: Advanced Restful Rails - Europe

class UsersController < ApplicationController make_resourceful do actions :all endend

make_resourceful20

Page 21: Advanced Restful Rails - Europe

Modeling

21

Page 22: Advanced Restful Rails - Europe

tiptoe - flickr

22

Page 23: Advanced Restful Rails - Europe

Domain

ejpphoto - flickr

23

Page 24: Advanced Restful Rails - Europe

Modeled

kerim - flickr24

Page 25: Advanced Restful Rails - Europe

Identifyresources

25

Page 26: Advanced Restful Rails - Europe

Selectmethods to expose

26

Page 27: Advanced Restful Rails - Europe

Tips

27

Page 28: Advanced Restful Rails - Europe

When in Doubtdanielygo - flickr

28

Page 29: Advanced Restful Rails - Europe

Respect the Middleman29

Page 30: Advanced Restful Rails - Europe

Resources != Modelswilliamhook - flickr

30

Page 31: Advanced Restful Rails - Europe

Examples

31

Page 32: Advanced Restful Rails - Europe

Password Resetschromewavesdotorg - flickr

32

Page 33: Advanced Restful Rails - Europe

Password Resets

ActionController::Routing::Routes.draw do |map| map.resource :password map.resources :users, :has_one => [:password]end

33

Page 34: Advanced Restful Rails - Europe

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

Page 35: Advanced Restful Rails - Europe

Homepageseandreilinger - flickr

35

Page 36: Advanced Restful Rails - Europe

class HomepagesController < ApplicationController # homepage def show; endend

ActionController::Routing::Routes.draw do |map| map.resource :homepage map.root :homepageend

Homepage36

Page 37: Advanced Restful Rails - Europe

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

Page 38: Advanced Restful Rails - Europe

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

Page 39: Advanced Restful Rails - Europe

Dashboardhel2005 - flickr

39

Page 40: Advanced Restful Rails - Europe

class DashboardsController < ApplicationController before_filter :require_login

# dashboard def show; endend

ActionController::Routing::Routes.draw do |map| map.resource :dashboardend

Dashboard40

Page 41: Advanced Restful Rails - Europe

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

Page 42: Advanced Restful Rails - Europe

Previewashoe - flickr

42

Page 43: Advanced Restful Rails - Europe

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

Page 44: Advanced Restful Rails - Europe

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

Page 45: Advanced Restful Rails - Europe

Searchseandreilinger - flickr

45

Page 46: Advanced Restful Rails - Europe

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

Page 47: Advanced Restful Rails - Europe

class SearchesController < ApplicationController def show @results = Searcher.find(params[:query]) endend

ActionController::Routing::Routes.draw do |map| map.resource :searchend

Search47

Page 48: Advanced Restful Rails - Europe

Wizardsdunechaser - flickr

48

Page 49: Advanced Restful Rails - Europe

49

Page 50: Advanced Restful Rails - Europe

/galleries/new50

Page 51: Advanced Restful Rails - Europe

/restaurants/:id/photos/new51

Page 52: Advanced Restful Rails - Europe

/restaurants/:id/photos/edit52

Page 53: Advanced Restful Rails - Europe

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

Page 54: Advanced Restful Rails - Europe

Bulk Actionzoomar - flickr

54

Page 55: Advanced Restful Rails - Europe

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

Page 56: Advanced Restful Rails - Europe

Transactionseb78 - flickr

56

Page 57: Advanced Restful Rails - Europe

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

Page 58: Advanced Restful Rails - Europe

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

Page 59: Advanced Restful Rails - Europe

Administration

this slide left intentionally blank

59

Page 60: Advanced Restful Rails - Europe

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

Page 61: Advanced Restful Rails - Europe

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

Page 62: Advanced Restful Rails - Europe

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

Page 63: Advanced Restful Rails - Europe

class Movie < ActiveResource::Base self.site = 'http://localhost:3000'end

Client Model63

Page 64: Advanced Restful Rails - Europe

class MoviesController < ApplicationController def index @movies = Movie.find(:all) end

def show @movie = Movie.find(params[:id]) endend

Client Controller64

Page 65: Advanced Restful Rails - Europe

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

Page 66: Advanced Restful Rails - Europe

Text

Inventory

Staff Directory

HR

etc.

RESTful APISearch Application

66

Page 67: Advanced Restful Rails - Europe

Text

Inventory

Staff Directory

HR

etc.

RESTful API

RESTful API

RESTful API

RESTful API

Search Application

67

Page 68: Advanced Restful Rails - Europe

Stumbling Blocks

68

Page 69: Advanced Restful Rails - Europe

<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

Page 70: Advanced Restful Rails - Europe

ActionController::Routing::Routes.draw do |map| map.users 'users', :controller => 'users', :action => 'index' map.users 'users', :controller => 'users', :action => 'create'end

Hand-Written Routes70

Page 71: Advanced Restful Rails - Europe

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

Page 72: Advanced Restful Rails - Europe

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

Page 73: Advanced Restful Rails - Europe

Collectionswooandy - flickr

73

Page 74: Advanced Restful Rails - Europe

Mixed Collection/Single

class RecordsController < ApplicationController def index; end

def show; end

# ...end

ActionController::Routing::Routes.draw do |map| map.resources :recordsend

74

Page 75: Advanced Restful Rails - Europe

ARes Avalanchenaturalkinds - flickr

75

Page 76: Advanced Restful Rails - Europe

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

Page 77: Advanced Restful Rails - Europe

SinatraWaves

Halcyon?

77