Advanced Restful Rails - Europe

Post on 17-May-2015

2957 Views

Category:

Technology

4 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

Transcript

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

top related