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

Advanced Restful Rails - Europe

May 17, 2015

Download

Technology

Ben Scofield

This is the revised version of my Advanced RESTful Rails session, as presented in Berlin at Railsconf Europe 2008
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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