• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Writing resources_controller: Discovering REST Patterns in Rails
 

Writing resources_controller: Discovering REST Patterns in Rails

on

  • 1,690 views

RailsConf Europe 2008 - Ian White wrote resources_controller in the beginning of 2007 to DRY up RESTful controllers. In its first iteration it simplified controllers by providing CRUD actions, and ...

RailsConf Europe 2008 - Ian White wrote resources_controller in the beginning of 2007 to DRY up RESTful controllers. In its first iteration it simplified controllers by providing CRUD actions, and loading enclosing resources.

Statistics

Views

Total Views
1,690
Views on SlideShare
1,685
Embed Views
5

Actions

Likes
6
Downloads
61
Comments
0

2 Embeds 5

http://www.slideshare.net 4
http://ruby-on-rails.chayom.com 1

Accessibility

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Writing resources_controller: Discovering REST Patterns in Rails Writing resources_controller: Discovering REST Patterns in Rails Presentation Transcript

    • Writing resources_controller Discovering REST patterns in Rails Ian White ian.w.white@gmail.com @ Argument from Design ardes.com
    • What I’ll cover ardes.com
    • What I’ll cover  Example of RESTful app in vanilla Rails ardes.com
    • What I’ll cover  Example of RESTful app in vanilla Rails  How was that experience? Where did we have to repeat ourselves? ardes.com
    • What I’ll cover  Example of RESTful app in vanilla Rails  How was that experience? Where did we have to repeat ourselves?  Plugins to the rescue! ardes.com
    • What I’ll cover  Example of RESTful app in vanilla Rails  How was that experience? Where did we have to repeat ourselves?  Plugins to the rescue!  resources_controller ardes.com
    • What I’ll cover  Example of RESTful app in vanilla Rails  How was that experience? Where did we have to repeat ourselves?  Plugins to the rescue!  resources_controller  Benefits of abstraction ardes.com
    • What I’ll cover  Example of RESTful app in vanilla Rails  How was that experience? Where did we have to repeat ourselves?  Plugins to the rescue!  resources_controller  Benefits of abstraction  Where to next? ardes.com
    • REST on Rails ardes.com
    • REST on Rails  Lets look at what’s involved in creating a simple RESTful app using Rails ardes.com
    • REST on Rails  Lets look at what’s involved in creating a simple RESTful app using Rails  We’ll then look at adding features ardes.com
    • Video sharing app ardes.com
    • Video sharing app  Users can contribute Videos ardes.com
    • Video sharing app  Users can contribute Videos  Videos can be categorised in many Categories ardes.com
    • Video sharing app class User < ActiveRecord::Base  has_manycan contribute Videos Users :videos end  Videos can be categorised in many Categories class Categorisation < ActiveRecord::Base belongs_to :video belongs_to :category end class Category < ActiveRecord::Base has_many :categorisations has_many :videos, :through => :categorisations end class Video belongs_to :user has_many :categorisations has_many :categories, :through => :categorisations ardes.com end
    • Video sharing app ardes.com
    • Video sharing app  Our API? ardes.com
    • Video sharing app  Our API?  Lets define the URLs ardes.com
    • Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos ardes.com
    • Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos  we want to see all categories: /categories ardes.com
    • Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos  we want to see all categories: /categories  we want to look at videos in the context of one of their categories: /categories/:category_id/videos/:id ardes.com
    • Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos  we want to see all categories: /categories  we want to look at videos in the context of one of their categories: /categories/:category_id/videos/:id  WAIT! does this violate some community norm about nesting? ardes.com
    • Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos  we want to see all categories: /categories  we want to look at videos in the context of one of their categories: /categories/:category_id/videos/:id  WAIT! does this violate some community norm about nesting?  No - in this case, it’s necessary, because videos can be categorised in many categories, and because we just want to show the video in the context of a category that the user has selected. ardes.com
    • Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos  we want to see all categories: /categories  we want to look at videos in the context of one of their categories: /categories/:category_id/videos/:id  WAIT! does this violate some community norm about map.resources :videos nesting?  No - in this case, it’s necessary, because videos can map.resources in:categories and because we be categorised many categories, do |category| category.resources in the context of a category just want to show the video :videos that the user has selected. end ardes.com
    • Video sharing app ardes.com
    • Video sharing app  Because this is a community site, lets not have admin namespaced controllers just yet ardes.com
    • Video sharing app  Because this is a community site, lets not have admin namespaced controllers just yet  We want users to be able submit videos in the context of a category, and have that categorisation automatically applied ardes.com
    • Video sharing app  Because this is a community site, lets not have admin namespaced controllers just yet  We want users to be able submit videos in the context of a category, and have that categorisation automatically applied  What controllers do we need? ardes.com
    • Video sharing app  Because this is a community site, lets not have admin namespaced controllers just yet class  We want users to be able submit videos in the context VideosController < ApplicationController of a category, and have that categorisation # GET /videos, GET /videos.xml automatically applied def index  What= Video.find(:all) @videos controllers do we need? respond_to do |format| format.html # index.html.erb format.xml { render :xml => @videos } end end # And the rest of CRUD ... end ardes.com
    • Video sharing app  Because this is a community site, lets not have admin namespaced controllers just yet class  We want users to be able submit videos in the context VideosController < ApplicationController of a category, and have that categorisation # GET /videos, GET /videos.xml automatically applied def index  What= Video.find(:all) controllers do we need? class CategoriesController < ApplicationControl @videos respond_to /categories, GET /categories.xml # GET do |format| def index format.html # index.html.erb @categories = Category.find(:all) format.xml { render :xml => @videos } end respond_to do |format| end format.html # index.html.erb format.xml { render :xml => @categories } end # And the rest of CRUD ... end end ardes.com # And the rest of CRUD ...
    • class CategoryVideosController < ApplicationC Video sharing app before_filter :load_category # GET /categories/:category_id/videos  Because this is a community site, lets not have admin # GET /categories/:category_id/videos.xml namespaced controllers just yet def index  We want users to be able submit videos in the context class VideosController < ApplicationController of a category, and=have that categorisation @videos @category.videos.find(:all) # GET /videos, GET /videos.xml automatically applied do |format| respond_to def index format.html # index.html.erb  What= Video.find(:all) controllers do we need? class CategoriesController < ApplicationControl @videos # GET /categories,{ GET /categories.xml format.xml respond_to do |format| render :xml => @videos } end def index format.html # index.html.erb end @categories = Category.find(:all) format.xml { render :xml => @videos } end respond_to do |format| # And the rest of CRUD ... end format.html # index.html.erb format.xml { render :xml => @categories } protected end # And the def load_category rest of CRUD ... end end @category = Category.find(params[:categor ardes.com end # And the rest of CRUD ...
    • map.resources :videos class CategoryVideosController < ApplicationC map.resourcesbefore_filter |category| :categories do :load_category Video sharing app category.resources :videos, :controller => 'category_videos' end # GET /categories/:category_id/videos  Because this is a community site, lets not have admin # GET /categories/:category_id/videos.xml namespaced controllers just yet def index  We want users to be able submit videos in the context class VideosController < ApplicationController of a category, and=have that categorisation @videos @category.videos.find(:all) # GET /videos, GET /videos.xml automatically applied do |format| respond_to def index format.html # index.html.erb  What= Video.find(:all) controllers do we need? class CategoriesController < ApplicationControl @videos # GET /categories,{ GET /categories.xml format.xml respond_to do |format| render :xml => @videos } end def index format.html # index.html.erb end @categories = Category.find(:all) format.xml { render :xml => @videos } end respond_to do |format| # And the rest of CRUD ... end format.html # index.html.erb format.xml { render :xml => @categories } protected end # And the def load_category rest of CRUD ... end end @category = Category.find(params[:categor ardes.com end # And the rest of CRUD ...
    • map.resources :videos class CategoryVideosController < ApplicationC map.resourcesbefore_filter |category| :categories do :load_category Video sharing app category.resources :videos, :controller => 'category_videos' end # GET /categories/:category_id/videos  Because this is a community site, lets not have admin # GET /categories/:category_id/videos.xml namespaced controllers just yet def index  We want users to be able submit videos in the context class VideosController < ApplicationController of a category, and=have that categorisation @videos @category.videos.find(:all) # GET /videos, GET /videos.xml automatically applied do |format| respond_to def index format.html # index.html.erb  What= Video.find(:all) controllers do we need? class CategoriesController < ApplicationControl @videos # GET /categories,{ GET /categories.xml format.xml respond_to do |format| render :xml => @videos } end def index format.html # index.html.erb class Video < ActiveRecord::Base attr_accessor end @categories = Category.find(:all) :category_for_new_record # and a create hook to create the:xml => @videos } format.xml { render categorisation end end respond_to do |format| # And the rest of CRUD ... end format.html # index.html.erb class Category < format.xml { render :xml => @categories } ActiveRecord::Base protected has_many :videos, :through => :categorisations do end #def new(*args) load_category And the defrest of CRUD ... end @category = Category.find(params[:categor end # pass proxy_owner through as :category_for_new_record end ardes.com end end end # And the rest of CRUD ...
    • Video sharing app ardes.com
    • Video sharing app  What views do we need? ardes.com
    • Video sharing app  What views do we need?  CRUD stuff ardes.com
    • Video sharing app  What views do we need?  CRUD stuff  What about videos vs category_videos? ardes.com
    • Video sharing app  What views do we need?  CRUD stuff  What about videos vs category_videos?  Can use partials to factor out common stuff - but there’s a cruical difference throughout ardes.com
    • Video sharing app  What views do we need?  CRUD stuff  What about videos vs category_videos?  Can use partials to factor out common stuff - but there’s a cruical difference throughout  In videos we want links and forms to new_video, video ardes.com
    • Video sharing app  What views do we need?  CRUD stuff  What about videos vs category_videos?  Can use partials to factor out common stuff - but there’s a cruical difference throughout  In videos we want links and forms to new_video, video  In category_videos we want links to new_category_video, and category_video. ardes.com
    • Video sharing app  What views do we need?  CRUD stuff  What about videos vs category_videos?  Can use partials to factor out common stuff - but there’s a cruical difference throughout  In videos we want links and forms to new_video, video  In category_videos we want links to new_category_video, and category_video.  We could use polymorphic_url, but we still need to give it different arguments. (just the same as for the controllers) ardes.com
    • Video sharing app views/categories/index.html.erb  What views do we need? views/categories/show.html.erb  CRUD stuff views/categories/edit.html.erb views/categories/new.html.erb  What about videos vs category_videos?  Can use partials to factor out common stuff - but there’s a cruical difference throughout  In videos we want links and forms to new_video, video  In category_videos we want links to new_category_video, and category_video.  We could use polymorphic_url, but we still need to give it different arguments. (just the same as for the controllers) ardes.com
    • Video sharing app views/categories/index.html.erb  What views do we need? views/categories/show.html.erb  CRUD stuff views/categories/edit.html.erb views/categories/new.html.erb  What about videos vs category_videos?  Can use partials to factor out common stuff - but views/videos/index.html.erbthroughout there’s a cruical difference views/videos/show.html.erb forms to new_video,  In videos we want links and views/videos/edit.html.erb video views/videos/new.html.erb want links to  In category_videos we new_category_video, and category_video.  We could use polymorphic_url, but we still need to give it different arguments. (just the same as for the controllers) ardes.com
    • Video sharing app views/categories/index.html.erb  What views do we need? views/categories/show.html.erb  CRUD stuff views/categories/edit.html.erb views/categories/new.html.erb  What about videos vs category_videos?  Can use partials to factor out common stuff - but views/videos/index.html.erbthroughout there’s a cruical difference views/videos/show.html.erb forms to new_video,  In videos we want links and views/videos/edit.html.erb video views/videos/new.html.erb want links to  In category_videos we new_category_video, and category_video. views/category_videos/index.html.erb still need to  We could use polymorphic_url, but we views/category_videos/show.html.erb give it different arguments. (just the same as for the controllers) views/category_videos/edit.html.erb views/category_videos/new.html.erb ardes.com
    • Video sharing app ardes.com
    • Video sharing app  With vanilla Rails, it’s not too bad ardes.com
    • Video sharing app  With vanilla Rails, it’s not too bad  4 models, 3 controllers, 12 views, 3 lines of routes ardes.com
    • Video sharing app  With vanilla Rails, it’s not too bad  4 models, 3 controllers, 12 views, 3 lines of routes  But, it doesn’t feel very DRY, does it? ardes.com
    • Video sharing app  With vanilla Rails, it’s not too bad  4 models, 3 controllers, 12 views, 3 lines of routes  But, it doesn’t feel very DRY, does it?  Let’s see what happens if we want to add some features ardes.com
    • Video sharing app: new feature ardes.com
    • Video sharing app: new feature  We want to be able browse videos in the context of the user that submitted them (similar to the way we can browse by category) ardes.com
    • Video sharing app: new feature  We want to be able browse videos in the context of the user that submitted them (similar to the way we can browse by category)  (Lets assume we’ve added a UsersController and views for browsing users themselves) ardes.com
    • map.resources :videos map.resources :categories do |category| category.resources :videos, :controller => 'category_videos' end Video sharing app: new feature map.resources :users do |user| user.resources :videos, :controller => 'user_videos' end  We want to be able browse videos in the context of the user that submitted them (similar to the way we can browse by category)  (Lets assume we’ve added a UsersController and views for browsing users themselves) ardes.com
    • map.resources :videos map.resources :categories do |category| category.resources :videos, :controller => 'category_videos' end class UserVideosControllerapp: new feature Video sharing < ApplicationController before_filter :load_user map.resources :users do |user| user.resources :videos, :controller => 'user_videos' # GET /users/:user_id/videos end  We want /users/:user_id/videos.xml the context # GET to be able browse videos in of the user that submitted them (similar to the def index way we can browse by category) @videos = @user.videos.find(:all)  (Letsrespond_to do |format| UsersController assume we’ve added a and views for browsing users themselves) format.html # index.html.erb format.xml { render :xml => @videos } end end # And the rest of CRUD ... protected def load_user @user = User.find(params[:user_id]) end ardes.com end
    • map.resources :videos map.resources :categories do |category| category.resources :videos, :controller => 'category_videos' end class UserVideosControllerapp: new feature Video sharing < ApplicationController before_filter :load_user map.resources :users do |user| user.resources :videos, :controller => 'user_videos' # GET /users/:user_id/videos end  We want /users/:user_id/videos.xml the context # GET to be able browse videos in of the user that submitted them (similar to the def index way we can browse by category) @videos = @user.videos.find(:all)  (Letsrespond_to do |format| UsersController assume we’ve added a and views for browsing users themselves) format.html # index.html.erb format.xml { render :xml => @videos } end end # And the rest of CRUD ... views/user_videos/index.html.erb protected views/user_videos/show.html.erb def load_user views/user_videos/edit.html.erb @user = User.find(params[:user_id]) views/user_videos/new.html.erb end ardes.com end
    • Video sharing app: new feature ardes.com
    • Video sharing app: new feature  Another controller, and 4 more views, not bad ardes.com
    • Video sharing app: new feature  Another controller, and 4 more views, not bad  But our videos controllers all look very similar ardes.com
    • Video sharing app: new feature  Another controller, and 4 more views, not bad  But our videos controllers all look very similar  How about a superclass abstraction? ardes.com
    • Video sharing app: new feature  Another controller, and 4 more views, not bad  But our videos controllers all look very similar  How about a superclass abstraction?  Problem: the action methods don’t look very easy to abstract ardes.com
    • # VideosController (simplified) def destroy @video = Video.find(params[:id]) Video sharing app: @video.destroy new feature redirect_to videos_path end  Another controller, and 4 more views, not bad  But our videos controllers all look very similar  How about a superclass abstraction?  Problem: the action methods don’t look very easy to abstract ardes.com
    • # VideosController (simplified) def destroy @video = Video.find(params[:id]) Video sharing app: @video.destroy new feature redirect_to videos_path end  Another controller, and 4 more views, not bad # CategoryVideosController def destroy videos controllers all look very similar But our @video = @category.videos.find(params[:id])  How about a superclass abstraction? @video.destroy action methods don’t look very easy  Problem: the redirect_to category_videos_path(@category) to abstract end ardes.com
    • # VideosController (simplified) def destroy @video = Video.find(params[:id]) Video sharing app: @video.destroy new feature redirect_to videos_path end  Another controller, and 4 more views, not bad # CategoryVideosController def destroy videos controllers all look very similar But our @video = @category.videos.find(params[:id])  How about a superclass abstraction? @video.destroy action methods don’t look very easy  Problem: the redirect_to category_videos_path(@category) to abstract end # UserVideosController def destroy @video = @user.videos.find(params[:id]) @video.destroy redirect_to user_videos_path(@user) end ardes.com
    • Video sharing app: new feature ardes.com
    • Video sharing app: new feature  Users should be able to comment on videos, users, and categories ardes.com
    • Video sharing app: new feature  Users should be able to comment on videos, users, and categories  This looks like a job for :polymorphic ardes.com
    • Video sharing app: new feature class User < ActiveRecord::Base has_many :comments, :as => :commentable  Users should be able to comment on end videos, users, and categories  Video < ActiveRecord::Base class This looks like a job for :polymorphic has_many :comments, :as => :commentable end class Category < ActiveRecord::Base has_many :comments, :as => :commentable end class Comment belongs_to :commentable, :polymorphic => true end ardes.com
    • Video sharing app: new feature ardes.com
    • Video sharing app: new feature  Now, one controller - or three controllers ardes.com
    • Video sharing app: new feature  Now, one controller - or three controllers  If we have one controller we face these problems ardes.com
    • Video sharing app: new feature  Now, one controller - or three controllers  If we have one controller we face these problems - how to load up the comment? ardes.com
    • Video sharing app: new feature  Now, one controller - or three controllers  If we have one controller we face these problems - how to load up the comment? - how to redirect to the commentable? ardes.com
    • Video sharing app: new feature  Now, one controller - or three controllers  If we have one controller we face these problems - how to load up the comment? - how to redirect to the commentable?  So lets try three controllers ardes.com
    • map.resources :videos do |video| video.resources :comments, :controller => 'video_comments' end map.resources :categories do sharing app: new feature Video |category| category.resources :videos, :controller => 'category_videos' category.resources :comments, :controller => 'category_comments' end  Now, one controller - or three controllers map.resources :users do |user|  If we have one controller we face these problems user.resources :videos, :controller => 'user_videos' - how to load up :controller => user.resources :comments, the comment? 'user_comments' end - how to redirect to the commentable?  So lets try three controllers ardes.com
    • map.resources :videos do |video| video.resources :comments, :controller => 'video_comments' end map.resources :categories do sharing app: new feature Video |category| category.resources :videos, :controller => 'category_videos' category.resources :comments, :controller => 'category_comments' end class UserCommentsController < ApplicationController  Now, one controller - or three controllers before_filter :load_user map.resources :users do |user|  def index If we have one controller we face these problems user.resources :videos, :controller => 'user_videos' @comments = @user.comments.find(:all) - how to load up :controller => user.resources :comments, the comment? 'user_comments' respond_to do |format| format.html # index.html.erb end - how to redirect to the commentable? format.xml { render :xml => @comments } end end  So lets try three controllers # And the rest of CRUD ... protected def load_user @user = User.find(params[:user_id]) end end ardes.com
    • map.resources :videos do |video| video.resources :comments, :controller => 'video_comments' end map.resources :categories do sharing app: new feature Video |category| category.resources :videos, :controller => 'category_videos' category.resources :comments, :controller => 'category_comments' end class UserCommentsController < ApplicationController  Now, one controller - or three controllers before_filter :load_user map.resources :users do |user| ApplicationController  def index If we have one controller we face these problems class CategoryCommentsController < user.resources :videos, :controller => 'user_videos' before_filter :load_category @comments = @user.comments.find(:all) - how to load up :controller => user.resources :comments, the comment? 'user_comments' respond_to do |format| def index format.html # index.html.erb end - how |format| end respond_to doto redirect to the commentable? @comments = @category.comments.find(:all) format.xml { render :xml => @comments } end  format.html{try three controllers} So lets #render :xml => @comments format.xml index.html.erb # And end rest of CRUD ... the end protected def # And the rest of CRUD ... load_user @user = User.find(params[:user_id]) protected end end def load_category @category = Category.find(params[:category_id]) end end ardes.com
    • map.resources :videos do |video| video.resources :comments, :controller => 'video_comments' end map.resources :categories do sharing app: new feature Video |category| category.resources :videos, :controller => 'category_videos' category.resources :comments, :controller => 'category_comments' end class UserCommentsController < ApplicationController  Now, one controller - or three controllers before_filter :load_user map.resources :users do |user| ApplicationController  def index If we have one controller we face these problems class CategoryCommentsController < user.resources :videos, :controller => 'user_videos' before_filter :load_category @comments = @user.comments.find(:all) - VideoCommentsController < ApplicationController user.resources :comments, the comment? 'user_comments' class how to load up :controller => respond_to do |format| def index format.html # index.html.erb end before_filter :load_video - how |format| end respond_to doto redirect to the commentable? @comments = @category.comments.find(:all) format.xml { render :xml => @comments } def index format.html # index.html.erb end  format.xml {try@video.comments.find(:all) So lets =render :xmlcontrollers} @comments three => @comments the respond_to do |format| # And end rest of CRUD ... end format.html # index.html.erb protected format.xml { render :xml => @comments } end def # And the rest of CRUD ... load_user end @user = User.find(params[:user_id]) protected end end # And the rest of CRUD ... def load_category @category = Category.find(params[:category_id]) endprotected end def load_video @video = Video.find(params[:video_id]) end end ardes.com
    • map.resources :videos do |video| video.resources :comments, :controller => 'video_comments' end map.resources :categories do sharing app: new feature Video |category| views/user_comments/index.html.erb category.resources :videos, :controller => 'category_videos' views/user_comments/show.html.erb category.resources :comments, :controller => 'category_comments' views/user_comments/edit.html.erb end class UserCommentsController < ApplicationController views/user_comments/new.html.erb  Now, one controller - or three controllers before_filter :load_user map.resources :users do |user| ApplicationController  def index If we have one controller we face these problems class CategoryCommentsController < user.resources :videos, :controller => 'user_videos' before_filter :load_category @comments = @user.comments.find(:all) - VideoCommentsController < ApplicationController user.resources :comments, the comment? 'user_comments' class how to load up :controller => respond_to do |format| def index format.html # index.html.erb end before_filter :load_video - how |format| end respond_to doto redirect to the commentable? @comments = @category.comments.find(:all) format.xml { render :xml => @comments } def index format.html # index.html.erb end  format.xml {try@video.comments.find(:all) So lets =render :xmlcontrollers} @comments three => @comments the respond_to do |format| # And end rest of CRUD ... end format.html # index.html.erb protected format.xml { render :xml => @comments } end def # And the rest of CRUD ... load_user end @user = User.find(params[:user_id]) protected end end # And the rest of CRUD ... def load_category @category = Category.find(params[:category_id]) endprotected end def load_video @video = Video.find(params[:video_id]) end end ardes.com
    • map.resources :videos do |video| video.resources :comments, :controller => 'video_comments' end map.resources :categories do sharing app: new feature Video |category| views/user_comments/index.html.erb category.resources :videos, :controller => 'category_videos' views/user_comments/show.html.erb category.resources :comments, :controller => 'category_comments' views/user_comments/edit.html.erb end class UserCommentsController < ApplicationController views/user_comments/new.html.erb  Now, one controller - or three controllers before_filter :load_user map.resources :users do |user| views/category_comments/index.html.erb  def index If we have one controller we face these problems class CategoryCommentsController < ApplicationController user.resources :videos, :controller => 'user_videos' before_filter :load_category views/category_comments/show.html.erb @comments = @user.comments.find(:all) - VideoCommentsController < ApplicationController user.resources :comments, the comment? 'user_comments' class how to load up :controller => respond_to do |format| def index format.html # index.html.erb views/category_comments/edit.html.erb end before_filter :load_video views/category_comments/new.html.erb - how |format| end respond_to doto redirect to the commentable? @comments = @category.comments.find(:all) format.xml { render :xml => @comments } def index format.html # index.html.erb end  format.xml {try@video.comments.find(:all) So lets =render :xmlcontrollers} @comments three => @comments the respond_to do |format| # And end rest of CRUD ... end format.html # index.html.erb protected format.xml { render :xml => @comments } end def # And the rest of CRUD ... load_user end @user = User.find(params[:user_id]) protected end end # And the rest of CRUD ... def load_category @category = Category.find(params[:category_id]) endprotected end def load_video @video = Video.find(params[:video_id]) end end ardes.com
    • map.resources :videos do |video| video.resources :comments, :controller => 'video_comments' end map.resources :categories do sharing app: new feature Video |category| views/user_comments/index.html.erb category.resources :videos, :controller => 'category_videos' views/user_comments/show.html.erb category.resources :comments, :controller => 'category_comments' views/user_comments/edit.html.erb end class UserCommentsController < ApplicationController views/user_comments/new.html.erb  Now, one controller - or three controllers before_filter :load_user map.resources :users do |user| views/category_comments/index.html.erb  def index If we have one controller we face these problems class CategoryCommentsController < ApplicationController user.resources :videos, :controller => 'user_videos' before_filter :load_category views/category_comments/show.html.erb @comments = @user.comments.find(:all) - VideoCommentsController < ApplicationController user.resources :comments, the comment? 'user_comments' class how to load up :controller => respond_to do |format| def index format.html # index.html.erb views/category_comments/edit.html.erb end before_filter :load_video views/category_comments/new.html.erb - how |format| end respond_to doto redirect to the commentable? @comments = @category.comments.find(:all) format.xml { render :xml => @comments } def index format.html # index.html.erb end  format.xml {try@video.comments.find(:all) So lets =render :xmlcontrollers} @comments three => @comments views/video_comments/index.html.erb the respond_to do |format| # And end rest of CRUD ... views/video_comments/show.html.erb end format.html # index.html.erbviews/video_comments/edit.html.erb protected format.xml { render :xml => views/video_comments/new.html.erb @comments } end def # And the rest of CRUD ... load_user end @user = User.find(params[:user_id]) protected end end # And the rest of CRUD ... def load_category @category = Category.find(params[:category_id]) endprotected end def load_video @video = Video.find(params[:video_id]) end end ardes.com
    • Video sharing app: new feature ardes.com
    • Video sharing app: new feature  Damn, we forgot about commenting on videos within the user and category contexts. (Let’s just pretend that the customer, naively thinks that this is something easy and obvious to do). ardes.com
    • Video sharing app: new feature  Damn, we forgot about commenting on videos within the user and category contexts. (Let’s just pretend that the customer, naively thinks that this is something easy and obvious to do).  Faced with the prospect of creating a bunch more controllers and views, people start becoming religious about Never nesting more than two resources deep ardes.com
    • Video sharing app: new feature  Damn, we forgot about commenting on videos within the user and category contexts. (Let’s just pretend that the customer, naively thinks that this is something easy and obvious to do).  Faced with the prospect of creating a bunch more controllers and views, people start becoming religious about Never nesting more than two resources deep  But, the reasons we might want to do that haven’t gone away, so we need to resort to some other tricks, or just legislate against them ardes.com
    • Video sharing app: rc’d ardes.com
    • Video sharing app: rc’d  For the curious, this is what our app (without comments - will come back to that later) would look like when using resources_controller ardes.com
    • class UsersController < ApplicationController Video sharing app: rc’d resources_controller_for :users end  For the curious, this is what our app (without comments - will come back to that later) would look like when using resources_controller class VideosController < ApplicationController resources_controller_for :videos end class CategoriesController < ApplicationController resources_controller_for :categories end ardes.com
    • Where is the logic? class VideosController < ApplicationController resources_controller_for :videos end ardes.com
    • Where is the logic? class VideosController < ApplicationController resources_controller_for :videos end # the controller resource logic is in routes.rb! map.resources :videos map.resources :categories do |category| category.resources :videos end map.resources :users do |user| user.resources :videos end ardes.com
    • back to Rails w/o rc ardes.com
    • back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves ardes.com
    • back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions ardes.com
    • back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions - redirecting ardes.com
    • back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions - redirecting - when loading enclosing resources ardes.com
    • back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions - redirecting - when loading enclosing resources - in views ardes.com
    • back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions - redirecting - when loading enclosing resources - in views  It seems like the reverse of the way it should be (start with fewer classes and views, branch when behaviour requires it) ardes.com
    • Plugins to the rescue! ardes.com
    • Plugins to the rescue!  resources_controller ardes.com
    • Plugins to the rescue!  resources_controller  others - make_resourceful - resource_this - resource_controller - and more... ardes.com
    • Plugins to the rescue!  resources_controller  others - make_resourceful - resource_this - resource_controller - and more...  They all come with a set of actions that cover CRUD, ardes.com
    • Plugins to the rescue!  resources_controller  others - make_resourceful - resource_this - resource_controller - and more...  They all come with a set of actions that cover CRUD,  they all wrap up the pattern of loading enclosing resources in some fashion ardes.com
    • resources_controller: Goals ardes.com
    • resources_controller: Goals NOT scaffolding - but it can be used for that ardes.com
    • resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions ardes.com
    • resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions loading enclosing resources ardes.com
    • resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions loading enclosing resources make referring to related resource URLs easy ardes.com
    • resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions loading enclosing resources make referring to related resource URLs easy principle of least surprise ardes.com
    • resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions loading enclosing resources make referring to related resource URLs easy principle of least surprise Not a black box - RC internals should be able to be used for solving other problems ardes.com
    • Scaffolding vs abstraction ardes.com
    • Scaffolding vs abstraction  Both are appropriate solutions for different problems ardes.com
    • Scaffolding vs abstraction  Both are appropriate solutions for different problems  In my view, many of the patterns that we’ve seen in RESTful controllers warrant abstraction ardes.com
    • Scaffolding vs abstraction  Both are appropriate solutions for different problems  In my view, many of the patterns that we’ve seen in RESTful controllers warrant abstraction  They enable some nice features in rc ardes.com
    • Scaffolding vs abstraction  Both are appropriate solutions for different problems  In my view, many of the patterns that we’ve seen in RESTful controllers warrant abstraction  They enable some nice features in rc  They enable some nice solutions to REST oriented problems ardes.com
    • Scaffolding vs abstraction  Both are appropriate solutions for different problems  In my view, many of the patterns that we’ve seen in RESTful controllers warrant abstraction  They enable some nice features in rc  They enable some nice solutions to REST oriented problems  Analogy: has_many and belongs_to, are not implemented as sql scaffolds, but they could be. Why is abstraction more appropriate here? ardes.com
    • Scaffolding vs abstraction  Both are appropriate solutions for different problems  In my view, many of the patterns that we’ve seen in RESTful controllers warrant abstraction  They enable some nice features in rc  They enable some nice solutions to REST oriented problems  Analogy: has_many and belongs_to, are not implemented as sql scaffolds, but they could be. Why is abstraction more appropriate here?  The problem is clearly defined ardes.com
    • Scaffolding vs abstraction  Both are appropriate solutions for different problems  In my view, many of the patterns that we’ve seen in RESTful controllers warrant abstraction  They enable some nice features in rc  They enable some nice solutions to REST oriented problems  Analogy: has_many and belongs_to, are not implemented as sql scaffolds, but they could be. Why is abstraction more appropriate here?  The problem is clearly defined  Abstraction enables cool stuff (like association proxies) ardes.com
    • resources_controller: abstraction ardes.com
    • resources_controller: abstraction What’s the problem? ardes.com
    • resources_controller: abstraction What’s the problem? The model name is tightly coupled with controller actions and views ardes.com
    • resources_controller: abstraction What’s the problem? The model name is tightly coupled with controller actions and views so are the named routes ardes.com
    • resources_controller: abstraction What’s the problem? The model name is tightly coupled with controller actions and views so are the named routes and so is the resource service ardes.com
    • class ForumsController < class UsersController < ApplicationController ApplicationController # GET /forums # GET /users def index def index @forums = Forum.find(:all) @users = User.find(:all) end end # GET /forums/1 # GET /users/1 def show def show @forum = Forum.find(params[:id]) @user = User.find(params[:id]) end end # DELETE /forums/1 # DELETE /users/1 def destroy def destroy @forum = Forum.find(params[:id]) @user = User.find(params[:id]) @forum.destroy @user.destroy redirect_to forums_path redirect_to users_path end end
    • class ForumsController < class UsersController < ApplicationController ApplicationController # GET /forums # GET /users def index def index @forums = Forum.find(:all) @users = User.find(:all) end end # GET /forums/1 # GET /users/1 def show def show @forum = Forum.find(params[:id]) @user = User.find(params[:id]) end end # DELETE /forums/1 # DELETE /users/1 def destroy def destroy @forum = Forum.find(params[:id]) @user = User.find(params[:id]) @forum.destroy @user.destroy redirect_to forums_path redirect_to users_path end end
    • class ForumsController < class UsersController < ApplicationController ApplicationController # GET /forums # GET /users def index def index @forums = Forum.find(:all) @users = User.find(:all) end end # GET /forums/1 # GET /users/1 def show def show @forum = Forum.find(params[:id]) @user = User.find(params[:id]) end end # DELETE /forums/1 # DELETE /users/1 def destroy def destroy @forum = Forum.find(params[:id]) @user = User.find(params[:id]) @forum.destroy @user.destroy redirect_to forums_path redirect_to users_path end end
    • class ForumsController < class UsersController < ApplicationController ApplicationController # GET /forums # GET /users def index def index @forums = Forum.find(:all) @users = User.find(:all) end end # GET /forums/1 # GET /users/1 def show def show @forum = Forum.find(params[:id]) @user = User.find(params[:id]) end end # DELETE /forums/1 # DELETE /users/1 def destroy def destroy @forum = Forum.find(params[:id]) @user = User.find(params[:id]) @forum.destroy @user.destroy redirect_to forums_path redirect_to users_path end end
    • de-couple the name: resource ardes.com
    • de-couple the name: resource def destroy self.resource = find_resource resource.destroy # end ardes.com
    • de-couple the name: resource def destroy self.resource = find_resource resource.destroy # end this will, e.g. set @forum to Forums.find(params[:id]) ardes.com
    • de-couple the name: resource def destroy self.resource = find_resource resource.destroy # end this will, e.g. set @forum to Forums.find(params[:id]) we can refer to whatever the current resource is with resource ardes.com
    • de-couple named routes: resource(s)_path ardes.com
    • de-couple named routes: resource(s)_path def destroy self.resource = find_resource resource.destroy redirect_to resources_path end ardes.com
    • de-couple named routes: resource(s)_path def destroy self.resource = find_resource resource.destroy redirect_to resources_path end all named routes are supported, e.g. formatted_resource_tags_url(‘js’) ardes.com
    • de-couple named routes: resource(s)_path def destroy self.resource = find_resource resource.destroy redirect_to resources_path end all named routes are supported, e.g. formatted_resource_tags_url(‘js’) think of this as named routes relativised to the current resource ardes.com
    • de-couple class or association: resource_service ardes.com
    • de-couple class or association: resource_service def find_resource(id = params[:id]) resource_service.find id end ardes.com
    • de-couple class or association: resource_service def find_resource(id = params[:id]) resource_service.find id end resource_service is an ActiveRecord class, or an association proxy ardes.com
    • de-couple class or association: resource_service def find_resource(id = params[:id]) resource_service.find id end resource_service is an ActiveRecord class, or an association proxy (it’s actually a bit more than that - but think of it that way) ardes.com
    • resource_service def find_resources resource_service.find :all end def find_resource(id = params[:id]) resource_service.find id end def new_resource(attributes = params[resource_name]) resource_service.new attributes end ardes.com
    • resource_service def find_resources resource_service.find :all, :order => params[:order] end def find_resource(id = params[:id]) resource_service.find id end def new_resource(attributes = params[resource_name]) resource_service.new attributes end ardes.com
    • load enclosing resources it’s easy, but repetitive. you need to do it, for url/model integrity ardes.com
    • load enclosing resources it’s easy, but repetitive. you need to do it, for url/model integrity the information is already expressed in routes.rb (most of the time) ardes.com
    • load enclosing resources it’s easy, but repetitive. you need to do it, for url/model integrity the information is already expressed in routes.rb (most of the time) resources_controller thinks that one controller for multiple routes is a fine idea ardes.com
    • ardes.com
    • /users/2/forums/3 ardes.com
    • @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) ardes.com
    • @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) /users/2/forums/3/tags ardes.com
    • @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) /users/2/forums/3/tags @user = User.find(2) @forum = @user.forums.find(3) @tags = @forum.tags.find(:all) ardes.com
    • @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) /users/2/forums/3/tags @user = User.find(2) @forum = @user.forums.find(3) @tags = @forum.tags.find(:all) /users/2/image/comments ardes.com
    • @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) /users/2/forums/3/tags @user = User.find(2) @forum = @user.forums.find(3) @tags = @forum.tags.find(:all) /users/2/image/comments @user = User.find(2) @image = @user.image @comments = @image.comments.find(:all) ardes.com
    • resource specification ardes.com
    • resource specification routing does the work of extracting logical reosurces from the url ardes.com
    • resource specification routing does the work of extracting logical reosurces from the url and then THROWS IT AWAY ardes.com
    • resource specification routing does the work of extracting logical reosurces from the url and then THROWS IT AWAY in the mean time: RC re-recognizes the route, and parses it according to the specification ardes.com
    • principle of least surprise ardes.com
    • principle of least surprise class UserForumsController < ApplicationController resources_controller_for :forums, :in => :user def mark_as_boring @forum = @user.forums.find(params[:id]) @forum.boringed_by!(@user) redirect_to user_forums_path(@user) end end ardes.com
    • principle of least surprise class UserForumsController < ApplicationController resources_controller_for :forums, :in => :user def mark_as_boring @forum = @user.forums.find(params[:id]) @forum.boringed_by!(@user) redirect_to user_forums_path(@user) end end <%= @forum.title %> <%= @user.name %> ardes.com
    • principle of least surprise class UserForumsController < ApplicationController resources_controller_for :forums, :in => :user def mark_as_boring @forum = @user.forums.find(params[:id]) @forum.boringed_by!(@user) redirect_to user_forums_path(@user) end end <%= @forum.title %> <%= @user.name %> <%= link_to @forum.name, polymorphic_url [@user, @forum] %> ardes.com
    • Back to our video app  What if we want to add comments on videos, users, categories - and also have them in user_videos and category_videos? ardes.com
    • class UsersController < ApplicationController resources_controller_for :users end Back to our video app class VideosController < ApplicationController  What if we want to add comments on videos, users, resources_controller_for :videos end categories - and also have them in user_videos and category_videos? class CategoriesController < ApplicationController resources_controller_for :categories end class CommentsController < ApplicationController resources_controller_for :comments nested_in :commentable, :polymorphic => true end ardes.com
    • map.resources :videos, :has_many => :comments map.resources :categories ApplicationController class UsersController < do |category| category.resources :videos,:users resources_controller_for :has_many => :comments end end map.resources our video<app Back to :users do |user| class VideosController ApplicationController user.resources want to add :has_many on videos, users,  What if we :videos, comments => :comments resources_controller_for :videos endend categories - and also have them in user_videos and category_videos? class CategoriesController < ApplicationController resources_controller_for :categories end class CommentsController < ApplicationController resources_controller_for :comments nested_in :commentable, :polymorphic => true end ardes.com
    • map.resources :videos, :has_many => :comments map.resources :categories ApplicationController class UsersController < do |category| category.resources :videos,:users resources_controller_for :has_many => :comments end end map.resources our video<app Back to :users do |user| class VideosController ApplicationController user.resources want to add :has_many on videos, users,  What if we :videos, comments => :comments resources_controller_for :videos endend categories - and also have them in user_videos and category_videos? With RC class CategoriesController < ApplicationController resources_controller_for :categories You start with fewer controllers and views end You make more when you have too much behaviour crammed into them class CommentsController < ApplicationController resources_controller_for :comments nested_in :commentable, :polymorphic => true end ardes.com
    • Common patterns, rc’d ardes.com
    • Common patterns, rc’d  account pattern ardes.com
    • Common patterns, rc’d  account pattern  ordering a collection ardes.com
    • Common patterns, rc’d  account pattern  ordering a collection  join models ardes.com
    • account pattern  Account pattern ardes.com
    • account pattern map.resource :account do |account|  Account pattern account.resources :images account.resources :posts end ardes.com
    • account pattern map.resource :account do |account|  Account pattern account.resources :images account.resources :posts end class AccountController < ApplicationController resources_controller_for :account, :class => User, :singleton => true do current_user end end ardes.com
    • account pattern map.resource :account do |account|  Account pattern account.resources :images account.resources :posts end class AccountController < ApplicationController resources_controller_for :account, :class => User, :singleton => true do current_user end end # You don't need to make an AccountImages, or AccountPosts controller! class ApplicationController < ActionController::Base map_enclosing_resource :account, :class => User, :singleton => true do current_user end # or you can put the above in each controller which has :account in its # enclosing resources, or in a mixin end ardes.com
    • ordering a collection ardes.com
    • ordering a collection map.resources :users do |user| users.resources :things, :collection => {:order => :put} end ardes.com
    • ordering a collection map.resources :users do |user| users.resources :things, :collection => {:order => :put} end class Thing < ActiveRecord::Base acts_as_list def self.order_by_ids(ids) transaction do ids.each_index do |i| Thing.update_attribute! :position => i+1 end end end end ardes.com
    • ordering a collection map.resources :users do |user| users.resources :things, :collection => {:order => :put} end def order resource_service.order_by_ids[quot;#{resource_name}_orderquot;] end class Thing < ActiveRecord::Base acts_as_list def self.order_by_ids(ids) transaction do ids.each_index do |i| Thing.update_attribute! :position => i+1 end end end end ardes.com
    • Join model controllers class ModeratorshipsController < Applica... resources_controller_for :moderatorships, :actions => nil def create # ... def destroy # ... end
    • class ModeratorshipsController < Applica... resources_controller_for :moderatorships, :actions => nil def create self.resource = new_resource if resource.save flash[:notice] = “#{resource_name} created” else flash[:error] = ‘oops’ end redirect_to enclosing_resource_path end def destroy # ...
    • class ModeratorshipsController < Applica... resources_controller_for :moderatorships, :actions => nil def create # ... end def destroy self.resource = find_resource resource.destroy flash[:notice] = “#{resource_name} destroyed” redirect_to enclosing_resource_path end
    • test controller plugins garlic do repo 'rails', :url => 'git://github.com/rails/rails' repo 'rspec', :url => 'git://github.com/ianwhite/rspec' repo 'rspec-rails', :url => 'git://github.com/ianwhite/rspec-rails' repo 'resources_controller', :path => '.' target 'edge', :branch => 'master' target '2.0-stable', :branch => 'origin/2-0-stable' target '2.1-stable', :branch => 'origin/2-1-stable' target '2.1.0', :tag => 'v2.1.0' all_targets do prepare do plugin 'resources_controller', :clone => true plugin 'rspec' plugin('rspec-rails') { sh quot;script/generate rspec -fquot; } end run do cd ‘vendor/plugins/resources_controller’ do sh quot;rake spec:rcov:verifyquot; end ardes.com end end
    • RC summary ardes.com
    • RC summary  makes RESTful controllers and views easier ardes.com
    • RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required ardes.com
    • RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings ardes.com
    • RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings - rc just got forked by revolutionhealth on github ardes.com
    • RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings - rc just got forked by revolutionhealth on github - Lots of production apps ardes.com
    • RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings - rc just got forked by revolutionhealth on github - Lots of production apps  well speced ardes.com
    • What next? ardes.com
    • What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource) ardes.com
    • What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource)  refactoring? ardes.com
    • What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource)  refactoring?  core-list talk about loading resources in controllers ardes.com
    • Writing resources_controller Discovering REST patterns in Rails Ian White github.com/ianwhite blog.ardes.com/ian Argument from Design ardes.com Sheffield, UK