Your SlideShare is downloading. ×
0
Writing resources_controller

Discovering REST patterns in Rails




           Ian White ian.w.white@gmail.com @ Argument...
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 ourselv...
What I’ll cover
 Example of RESTful app in vanilla Rails
 How was that experience? Where did we have to
  repeat ourselv...
What I’ll cover
 Example of RESTful app in vanilla Rails
 How was that experience? Where did we have to
  repeat ourselv...
What I’ll cover
 Example of RESTful app in vanilla Rails
 How was that experience? Where did we have to
  repeat ourselv...
What I’ll cover
 Example of RESTful app in vanilla Rails
 How was that experience? Where did we have to
  repeat ourselv...
REST on Rails




                ardes.com
REST on Rails
 Lets look at what’s involved in creating a simple
  RESTful app using Rails




                          ...
REST on Rails
 Lets look at what’s involved in creating a simple
  RESTful app using Rails
 We’ll then look at adding fe...
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




                      ...
Video sharing app
class User < ActiveRecord::Base
  has_manycan contribute Videos
     Users :videos
end
  Videos can be...
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




                            ...
Video sharing app

   Our API?
   Lets define the URLs
   we want to see all videos: /videos
   we want to see all cat...
Video sharing app

   Our API?
   Lets define the URLs
   we want to see all videos: /videos
   we want to see all cat...
Video sharing app

 Our API?
 Lets define the URLs
 we want to see all videos: /videos
 we want to see all categories:...
Video sharing app

 Our API?
 Lets define the URLs
 we want to see all videos: /videos
 we want to see all categories:...
Video sharing app

  Our API?
  Lets define the URLs
  we want to see all videos: /videos
  we want to see all categor...
Video sharing app




                    ardes.com
Video sharing app
 Because this is a community site, lets not have admin
  namespaced controllers just yet




          ...
Video sharing app
 Because this is a community site, lets not have admin
  namespaced controllers just yet
 We want user...
Video sharing app
 Because this is a community site, lets not have admin
  namespaced controllers just yet
 We want user...
Video sharing app
       Because this is a community site, lets not have admin
        namespaced controllers just yet
cl...
Video sharing app
       Because this is a community site, lets not have admin
        namespaced controllers just yet
cl...
class CategoryVideosController < ApplicationC
                 Video sharing app
             before_filter :load_category...
map.resources :videos
           class CategoryVideosController < ApplicationC
 map.resourcesbefore_filter |category|
    ...
map.resources :videos
           class CategoryVideosController < ApplicationC
 map.resourcesbefore_filter |category|
    ...
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?




                     ...
Video sharing app

   What views do we need?
   CRUD stuff
   What about videos vs category_videos?
   Can use partial...
Video sharing app

 What views do we need?
 CRUD stuff
 What about videos vs category_videos?
 Can use partials to fac...
Video sharing app

 What views do we need?
 CRUD stuff
 What about videos vs category_videos?
 Can use partials to fac...
Video sharing app

 What views do we need?
 CRUD stuff
 What about videos vs category_videos?
 Can use partials to fac...
Video sharing app
views/categories/index.html.erb
      What views do we need?
views/categories/show.html.erb
      CRUD...
Video sharing app
views/categories/index.html.erb
      What views do we need?
views/categories/show.html.erb
      CRUD...
Video sharing app
views/categories/index.html.erb
      What views do we need?
views/categories/show.html.erb
      CRUD...
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




   ...
Video sharing app

 With vanilla Rails, it’s not too bad
 4 models, 3 controllers, 12 views, 3 lines of
  routes
 But, ...
Video sharing app

 With vanilla Rails, it’s not too bad
 4 models, 3 controllers, 12 views, 3 lines of
  routes
 But, ...
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 (simil...
Video sharing app: new feature

 We want to be able browse videos in the context
  of the user that submitted them (simil...
map.resources :videos

map.resources :categories do |category|
  category.resources :videos, :controller => 'category_vide...
map.resources :videos

map.resources :categories do |category|
  category.resources :videos, :controller => 'category_vide...
map.resources :videos

map.resources :categories do |category|
  category.resources :videos, :controller => 'category_vide...
Video sharing app: new feature




                           ardes.com
Video sharing app: new feature

 Another controller, and 4 more views, not bad




                                      ...
Video sharing app: new feature

 Another controller, and 4 more views, not bad
 But our videos controllers all look very...
Video sharing app: new feature

 Another controller, and 4 more views, not bad
 But our videos controllers all look very...
Video sharing app: new feature

   Another controller, and 4 more views, not bad
   But our videos controllers all look ...
# VideosController (simplified)
def destroy
  @video = Video.find(params[:id])
               Video sharing app:
  @video....
# VideosController (simplified)
def destroy
  @video = Video.find(params[:id])
               Video sharing app:
  @video....
# VideosController (simplified)
def destroy
  @video = Video.find(params[:id])
               Video sharing app:
  @video....
Video sharing app: new feature




                           ardes.com
Video sharing app: new feature

 Users should be able to comment on videos,
  users, and categories




                 ...
Video sharing app: new feature

 Users should be able to comment on videos,
  users, and categories
 This looks like a j...
Video sharing app: new feature
class User < ActiveRecord::Base
  has_many :comments, :as => :commentable
    Users should...
Video sharing app: new feature




                           ardes.com
Video sharing app: new feature

 Now, one controller - or three controllers




                                         ...
Video sharing app: new feature

 Now, one controller - or three controllers
 If we have one controller we face these pro...
Video sharing app: new feature

 Now, one controller - or three controllers
 If we have one controller we face these pro...
Video sharing app: new feature

 Now, one controller - or three controllers
 If we have one controller we face these pro...
Video sharing app: new feature

 Now, one controller - or three controllers
 If we have one controller we face these pro...
map.resources :videos do |video|
  video.resources :comments, :controller => 'video_comments'
end

map.resources :categori...
map.resources :videos do |video|
  video.resources :comments, :controller => 'video_comments'
end

map.resources :categori...
map.resources :videos do |video|
  video.resources :comments, :controller => 'video_comments'
end

map.resources :categori...
map.resources :videos do |video|
  video.resources :comments, :controller => 'video_comments'
end

map.resources :categori...
map.resources :videos do |video|
  video.resources :comments, :controller => 'video_comments'
end

map.resources :categori...
map.resources :videos do |video|
  video.resources :comments, :controller => 'video_comments'
end

map.resources :categori...
map.resources :videos do |video|
  video.resources :comments, :controller => 'video_comments'
end

map.resources :categori...
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...
Video sharing app: new feature

 Damn, we forgot about commenting on videos
  within the user and category contexts. (Let...
Video sharing app: new feature

 Damn, we forgot about commenting on videos
  within the user and category contexts. (Let...
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...
class UsersController < ApplicationController
             Video sharing app: rc’d
  resources_controller_for :users
end

...
Where is the logic?

class VideosController < ApplicationController
  resources_controller_for :videos
end




           ...
Where is the logic?

     class VideosController < ApplicationController
       resources_controller_for :videos
     end
...
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




                              ...
back to Rails w/o rc


 Seems like we have to do a lot of almost repeating
   ourselves
  - in actions




              ...
back to Rails w/o rc


 Seems like we have to do a lot of almost repeating
   ourselves
  - in actions
  - redirecting


...
back to Rails w/o rc


 Seems like we have to do a lot of almost repeating
   ourselves
  - in actions
  - redirecting
  ...
back to Rails w/o rc


 Seems like we have to do a lot of almost repeating
   ourselves
  - in actions
  - redirecting
  ...
back to Rails w/o rc


 Seems like we have to do a lot of almost repeating
   ourselves
  - in actions
  - redirecting
  ...
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
  - ...
Plugins to the rescue!
 resources_controller
 others
  - make_resourceful
  - resource_this
  - resource_controller
  - ...
Plugins to the rescue!
 resources_controller
 others
  - make_resourceful
  - resource_this
  - resource_controller
  - ...
resources_controller: Goals




                              ardes.com
resources_controller: Goals
NOT scaffolding - but it can be used for that




                                            ...
resources_controller: Goals
NOT scaffolding - but it can be used for that
     stop repeating all of those actions




   ...
resources_controller: Goals
NOT scaffolding - but it can be used for that
     stop repeating all of those actions

      ...
resources_controller: Goals
 NOT scaffolding - but it can be used for that
      stop repeating all of those actions

    ...
resources_controller: Goals
 NOT scaffolding - but it can be used for that
      stop repeating all of those actions

    ...
resources_controller: Goals
 NOT scaffolding - but it can be used for that
      stop repeating all of those actions

    ...
Scaffolding vs abstraction




                             ardes.com
Scaffolding vs abstraction

 Both are appropriate solutions for different problems




                                  ...
Scaffolding vs abstraction

 Both are appropriate solutions for different problems
 In my view, many of the patterns tha...
Scaffolding vs abstraction

 Both are appropriate solutions for different problems
 In my view, many of the patterns tha...
Scaffolding vs abstraction

 Both are appropriate solutions for different problems
 In my view, many of the patterns tha...
Scaffolding vs abstraction

 Both are appropriate solutions for different problems
 In my view, many of the patterns tha...
Scaffolding vs abstraction

 Both are appropriate solutions for different problems
 In my view, many of the patterns tha...
Scaffolding vs abstraction

 Both are appropriate solutions for different problems
 In my view, many of the patterns tha...
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 a...
resources_controller: abstraction

      What’s the problem?

The model name is tightly coupled
 with controller actions a...
resources_controller: abstraction

      What’s the problem?

The model name is tightly coupled
 with controller actions a...
class ForumsController <               class UsersController <
ApplicationController                  ApplicationControlle...
class ForumsController <               class UsersController <
ApplicationController                  ApplicationControlle...
class ForumsController <               class UsersController <
ApplicationController                  ApplicationControlle...
class ForumsController <               class UsersController <
ApplicationController                  ApplicationControlle...
de-couple the name: resource




                           ardes.com
de-couple the name: resource
   def destroy
     self.resource = find_resource
     resource.destroy
     #
   end




   ...
de-couple the name: resource
       def destroy
         self.resource = find_resource
         resource.destroy
         ...
de-couple the name: resource
           def destroy
             self.resource = find_resource
             resource.destr...
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...
de-couple named routes:
      resource(s)_path
def destroy
  self.resource = find_resource
  resource.destroy
  redirect_t...
de-couple named routes:
      resource(s)_path
def destroy
  self.resource = find_resource
  resource.destroy
  redirect_t...
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...
de-couple class or association:
          resource_service

def find_resource(id = params[:id])
  resource_service.find id...
de-couple class or association:
              resource_service

 def find_resource(id = params[:id])
   resource_service.f...
resource_service
def find_resources
  resource_service.find :all
end

def find_resource(id = params[:id])
  resource_servi...
resource_service
def find_resources
  resource_service.find :all, :order => params[:order]
end

def find_resource(id = par...
load enclosing resources
        it’s easy, but repetitive.

you need to do it, for url/model integrity




              ...
load enclosing resources
        it’s easy, but repetitive.

you need to do it, for url/model integrity

  the information...
load enclosing resources
           it’s easy, but repetitive.

   you need to do it, for url/model integrity

     the in...
ardes.com
/users/2/forums/3




                    ardes.com
@user = User.find(2)
/users/2/forums/3
                    @forum = @user.forums.find(3)




                             ...
@user = User.find(2)
/users/2/forums/3
                    @forum = @user.forums.find(3)

/users/2/forums/3/tags




     ...
@user = User.find(2)
/users/2/forums/3
                    @forum = @user.forums.find(3)

/users/2/forums/3/tags
   @user ...
@user = User.find(2)
/users/2/forums/3
                    @forum = @user.forums.find(3)

/users/2/forums/3/tags
   @user ...
@user = User.find(2)
/users/2/forums/3
                    @forum = @user.forums.find(3)

/users/2/forums/3/tags
   @user ...
resource specification




                     ardes.com
resource specification
routing does the work of extracting
   logical reosurces from the url




                         ...
resource specification
routing does the work of extracting
   logical reosurces from the url

    and then THROWS IT AWAY
...
resource specification
   routing does the work of extracting
      logical reosurces from the url

        and then THROW...
principle of least surprise




                              ardes.com
principle of least surprise
class UserForumsController < ApplicationController
  resources_controller_for :forums, :in => ...
principle of least surprise
         class UserForumsController < ApplicationController
           resources_controller_fo...
principle of least surprise
             class UserForumsController < ApplicationController
               resources_contr...
Back to our video app
 What if we want to add comments on videos, users,
  categories - and also have them in user_videos...
class UsersController < ApplicationController
  resources_controller_for :users
end

 Back to our video app
class VideosCo...
map.resources :videos, :has_many => :comments

map.resources :categories ApplicationController
   class UsersController < ...
map.resources :videos, :has_many => :comments

 map.resources :categories ApplicationController
    class UsersController ...
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




                                  ard...
account pattern

 Account pattern




                               ardes.com
account pattern
                    map.resource :account do |account|
 Account pattern     account.resources :images
   ...
account pattern
                             map.resource :account do |account|
        Account pattern       account.res...
account pattern
                                  map.resource :account do |account|
        Account pattern            a...
ordering a collection




                        ardes.com
ordering a collection
map.resources :users do |user|
  users.resources :things, :collection => {:order => :put}
end




  ...
ordering a collection
map.resources :users do |user|
  users.resources :things, :collection => {:order => :put}
end




cl...
ordering a collection
map.resources :users do |user|
  users.resources :things, :collection => {:order => :put}
end

 def ...
Join model controllers




class ModeratorshipsController < Applica...
  resources_controller_for :moderatorships,
     :a...
class ModeratorshipsController < Applica...
  resources_controller_for :moderatorships,
     :actions => nil

  def create...
class ModeratorshipsController < Applica...
  resources_controller_for :moderatorships,
     :actions => nil

  def create...
test controller plugins

garlic   do
  repo   'rails', :url => 'git://github.com/rails/rails'
  repo   'rspec', :url => 'g...
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
    requir...
RC summary
 makes RESTful controllers and views easier
  - Start with fewer controllers/views - make more when
     requi...
RC summary
 makes RESTful controllers and views easier
  - Start with fewer controllers/views - make more when
     requi...
RC summary
 makes RESTful controllers and views easier
  - Start with fewer controllers/views - make more when
     requi...
RC summary
 makes RESTful controllers and views easier
  - Start with fewer controllers/views - make more when
     requi...
What next?




             ardes.com
What next?
 abstract idea of resource, passed by routing to
  controller on the request (you were invoked with this
  res...
What next?
 abstract idea of resource, passed by routing to
  controller on the request (you were invoked with this
  res...
What next?
 abstract idea of resource, passed by routing to
  controller on the request (you were invoked with this
  res...
Writing resources_controller
Discovering REST patterns in Rails


Ian White
github.com/ianwhite
blog.ardes.com/ian

Argume...
Writing resources_controller: Discovering REST Patterns in Rails
Upcoming SlideShare
Loading in...5
×

Writing resources_controller: Discovering REST Patterns in Rails

1,260

Published on

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.

0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,260
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
64
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide

Transcript of "Writing resources_controller: Discovering REST Patterns in Rails"

  1. 1. Writing resources_controller Discovering REST patterns in Rails Ian White ian.w.white@gmail.com @ Argument from Design ardes.com
  2. 2. What I’ll cover ardes.com
  3. 3. What I’ll cover  Example of RESTful app in vanilla Rails ardes.com
  4. 4. What I’ll cover  Example of RESTful app in vanilla Rails  How was that experience? Where did we have to repeat ourselves? ardes.com
  5. 5. 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
  6. 6. 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
  7. 7. 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
  8. 8. 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
  9. 9. REST on Rails ardes.com
  10. 10. REST on Rails  Lets look at what’s involved in creating a simple RESTful app using Rails ardes.com
  11. 11. 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
  12. 12. Video sharing app ardes.com
  13. 13. Video sharing app  Users can contribute Videos ardes.com
  14. 14. Video sharing app  Users can contribute Videos  Videos can be categorised in many Categories ardes.com
  15. 15. 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
  16. 16. Video sharing app ardes.com
  17. 17. Video sharing app  Our API? ardes.com
  18. 18. Video sharing app  Our API?  Lets define the URLs ardes.com
  19. 19. Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos ardes.com
  20. 20. 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
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. Video sharing app ardes.com
  26. 26. Video sharing app  Because this is a community site, lets not have admin namespaced controllers just yet ardes.com
  27. 27. 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
  28. 28. 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
  29. 29. 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
  30. 30. 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 ...
  31. 31. 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 ...
  32. 32. 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 ...
  33. 33. 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 ...
  34. 34. Video sharing app ardes.com
  35. 35. Video sharing app  What views do we need? ardes.com
  36. 36. Video sharing app  What views do we need?  CRUD stuff ardes.com
  37. 37. Video sharing app  What views do we need?  CRUD stuff  What about videos vs category_videos? ardes.com
  38. 38. 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
  39. 39. 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
  40. 40. 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
  41. 41. 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
  42. 42. 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
  43. 43. 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
  44. 44. 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
  45. 45. Video sharing app ardes.com
  46. 46. Video sharing app  With vanilla Rails, it’s not too bad ardes.com
  47. 47. Video sharing app  With vanilla Rails, it’s not too bad  4 models, 3 controllers, 12 views, 3 lines of routes ardes.com
  48. 48. 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
  49. 49. 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
  50. 50. Video sharing app: new feature ardes.com
  51. 51. 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
  52. 52. 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
  53. 53. 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
  54. 54. 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
  55. 55. 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
  56. 56. Video sharing app: new feature ardes.com
  57. 57. Video sharing app: new feature  Another controller, and 4 more views, not bad ardes.com
  58. 58. Video sharing app: new feature  Another controller, and 4 more views, not bad  But our videos controllers all look very similar ardes.com
  59. 59. 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
  60. 60. 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
  61. 61. # 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
  62. 62. # 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
  63. 63. # 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
  64. 64. Video sharing app: new feature ardes.com
  65. 65. Video sharing app: new feature  Users should be able to comment on videos, users, and categories ardes.com
  66. 66. 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
  67. 67. 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
  68. 68. Video sharing app: new feature ardes.com
  69. 69. Video sharing app: new feature  Now, one controller - or three controllers ardes.com
  70. 70. Video sharing app: new feature  Now, one controller - or three controllers  If we have one controller we face these problems ardes.com
  71. 71. 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
  72. 72. 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
  73. 73. 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
  74. 74. 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
  75. 75. 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
  76. 76. 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
  77. 77. 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
  78. 78. 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
  79. 79. 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
  80. 80. 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
  81. 81. Video sharing app: new feature ardes.com
  82. 82. 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
  83. 83. 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
  84. 84. 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
  85. 85. Video sharing app: rc’d ardes.com
  86. 86. 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
  87. 87. 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
  88. 88. Where is the logic? class VideosController < ApplicationController resources_controller_for :videos end ardes.com
  89. 89. 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
  90. 90. back to Rails w/o rc ardes.com
  91. 91. back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves ardes.com
  92. 92. back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions ardes.com
  93. 93. back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions - redirecting ardes.com
  94. 94. 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
  95. 95. 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
  96. 96. 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
  97. 97. Plugins to the rescue! ardes.com
  98. 98. Plugins to the rescue!  resources_controller ardes.com
  99. 99. Plugins to the rescue!  resources_controller  others - make_resourceful - resource_this - resource_controller - and more... ardes.com
  100. 100. 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
  101. 101. 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
  102. 102. resources_controller: Goals ardes.com
  103. 103. resources_controller: Goals NOT scaffolding - but it can be used for that ardes.com
  104. 104. resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions ardes.com
  105. 105. resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions loading enclosing resources ardes.com
  106. 106. 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
  107. 107. 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
  108. 108. 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
  109. 109. Scaffolding vs abstraction ardes.com
  110. 110. Scaffolding vs abstraction  Both are appropriate solutions for different problems ardes.com
  111. 111. 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
  112. 112. 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
  113. 113. 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
  114. 114. 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
  115. 115. 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
  116. 116. 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
  117. 117. resources_controller: abstraction ardes.com
  118. 118. resources_controller: abstraction What’s the problem? ardes.com
  119. 119. resources_controller: abstraction What’s the problem? The model name is tightly coupled with controller actions and views ardes.com
  120. 120. 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
  121. 121. 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
  122. 122. 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
  123. 123. 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
  124. 124. 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
  125. 125. 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
  126. 126. de-couple the name: resource ardes.com
  127. 127. de-couple the name: resource def destroy self.resource = find_resource resource.destroy # end ardes.com
  128. 128. 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
  129. 129. 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
  130. 130. de-couple named routes: resource(s)_path ardes.com
  131. 131. de-couple named routes: resource(s)_path def destroy self.resource = find_resource resource.destroy redirect_to resources_path end ardes.com
  132. 132. 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
  133. 133. 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
  134. 134. de-couple class or association: resource_service ardes.com
  135. 135. de-couple class or association: resource_service def find_resource(id = params[:id]) resource_service.find id end ardes.com
  136. 136. 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
  137. 137. 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
  138. 138. 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
  139. 139. 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
  140. 140. load enclosing resources it’s easy, but repetitive. you need to do it, for url/model integrity ardes.com
  141. 141. 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
  142. 142. 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
  143. 143. ardes.com
  144. 144. /users/2/forums/3 ardes.com
  145. 145. @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) ardes.com
  146. 146. @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) /users/2/forums/3/tags ardes.com
  147. 147. @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
  148. 148. @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
  149. 149. @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
  150. 150. resource specification ardes.com
  151. 151. resource specification routing does the work of extracting logical reosurces from the url ardes.com
  152. 152. resource specification routing does the work of extracting logical reosurces from the url and then THROWS IT AWAY ardes.com
  153. 153. 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
  154. 154. principle of least surprise ardes.com
  155. 155. 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
  156. 156. 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
  157. 157. 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
  158. 158. 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
  159. 159. 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
  160. 160. 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
  161. 161. 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
  162. 162. Common patterns, rc’d ardes.com
  163. 163. Common patterns, rc’d  account pattern ardes.com
  164. 164. Common patterns, rc’d  account pattern  ordering a collection ardes.com
  165. 165. Common patterns, rc’d  account pattern  ordering a collection  join models ardes.com
  166. 166. account pattern  Account pattern ardes.com
  167. 167. account pattern map.resource :account do |account|  Account pattern account.resources :images account.resources :posts end ardes.com
  168. 168. 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
  169. 169. 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
  170. 170. ordering a collection ardes.com
  171. 171. ordering a collection map.resources :users do |user| users.resources :things, :collection => {:order => :put} end ardes.com
  172. 172. 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
  173. 173. 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
  174. 174. Join model controllers class ModeratorshipsController < Applica... resources_controller_for :moderatorships, :actions => nil def create # ... def destroy # ... end
  175. 175. 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 # ...
  176. 176. 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
  177. 177. 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
  178. 178. RC summary ardes.com
  179. 179. RC summary  makes RESTful controllers and views easier ardes.com
  180. 180. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required ardes.com
  181. 181. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings ardes.com
  182. 182. 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
  183. 183. 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
  184. 184. 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
  185. 185. What next? ardes.com
  186. 186. What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource) ardes.com
  187. 187. What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource)  refactoring? ardes.com
  188. 188. 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
  189. 189. Writing resources_controller Discovering REST patterns in Rails Ian White github.com/ianwhite blog.ardes.com/ian Argument from Design ardes.com Sheffield, UK
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×