SlideShare una empresa de Scribd logo
1 de 190
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

Más contenido relacionado

La actualidad más candente

Cross platform mobile web apps
Cross platform mobile web appsCross platform mobile web apps
Cross platform mobile web appsJames Pearce
 
[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...
[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...
[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...EnlightenmentProject
 
Real life-maf-2015-k scope-final
Real life-maf-2015-k scope-finalReal life-maf-2015-k scope-final
Real life-maf-2015-k scope-finalLuc Bors
 
How native is React Native? | React Native vs Native App Development
How native is React Native? | React Native vs Native App DevelopmentHow native is React Native? | React Native vs Native App Development
How native is React Native? | React Native vs Native App DevelopmentDevathon
 
Strategic guidance
Strategic guidanceStrategic guidance
Strategic guidanceRoger Pence
 
Managing Omnichannel Experiences with Adobe Experience Manager (AEM)
Managing Omnichannel Experiences with Adobe Experience Manager (AEM)Managing Omnichannel Experiences with Adobe Experience Manager (AEM)
Managing Omnichannel Experiences with Adobe Experience Manager (AEM)Gabriel Walt
 
Building Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsBuilding Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsJim Jeffers
 

La actualidad más candente (8)

Cross platform mobile web apps
Cross platform mobile web appsCross platform mobile web apps
Cross platform mobile web apps
 
[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...
[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...
[E-Dev-Day-US-2015][9/9] High Level Application Development with Elua (Daniel...
 
Real life-maf-2015-k scope-final
Real life-maf-2015-k scope-finalReal life-maf-2015-k scope-final
Real life-maf-2015-k scope-final
 
DIWD 2011
DIWD 2011DIWD 2011
DIWD 2011
 
How native is React Native? | React Native vs Native App Development
How native is React Native? | React Native vs Native App DevelopmentHow native is React Native? | React Native vs Native App Development
How native is React Native? | React Native vs Native App Development
 
Strategic guidance
Strategic guidanceStrategic guidance
Strategic guidance
 
Managing Omnichannel Experiences with Adobe Experience Manager (AEM)
Managing Omnichannel Experiences with Adobe Experience Manager (AEM)Managing Omnichannel Experiences with Adobe Experience Manager (AEM)
Managing Omnichannel Experiences with Adobe Experience Manager (AEM)
 
Building Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsBuilding Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in Rails
 

Similar a Writing resources_controller: Discovering REST Patterns in Rails

Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014
Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014
Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014DouglasWaterfall
 
Páginas Dinâmicas de Erro em Rails com Goalie
Páginas Dinâmicas de Erro em Rails com GoaliePáginas Dinâmicas de Erro em Rails com Goalie
Páginas Dinâmicas de Erro em Rails com GoalieHelder Ribeiro
 
Edpuzzle - Migrating to React progressively but painlessly
Edpuzzle - Migrating to React progressively but painlesslyEdpuzzle - Migrating to React progressively but painlessly
Edpuzzle - Migrating to React progressively but painlesslySantiago Herrero Bajo
 
From Code to a Running Container | AWS Floor28
From Code to a Running Container | AWS Floor28From Code to a Running Container | AWS Floor28
From Code to a Running Container | AWS Floor28Amazon Web Services
 
Introducing AWS Fargate - AWS Online Tech Talks
Introducing AWS Fargate - AWS Online Tech TalksIntroducing AWS Fargate - AWS Online Tech Talks
Introducing AWS Fargate - AWS Online Tech TalksAmazon Web Services
 
API Services: Building State-of-the-Art APIs
API Services: Building State-of-the-Art APIsAPI Services: Building State-of-the-Art APIs
API Services: Building State-of-the-Art APIsApigee | Google Cloud
 
Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018
Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018
Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018Amazon Web Services
 
Introducing AWS Fargate - Tiffany Jernigan
Introducing AWS Fargate - Tiffany JerniganIntroducing AWS Fargate - Tiffany Jernigan
Introducing AWS Fargate - Tiffany JerniganAmazon Web Services
 
SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...
SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...
SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...Amazon Web Services
 
Creating a World-Class RESTful Web Services API
Creating a World-Class RESTful Web Services APICreating a World-Class RESTful Web Services API
Creating a World-Class RESTful Web Services APIDavid Keener
 
AWS_DOP-C02_May_2023-v1.2.pdf
AWS_DOP-C02_May_2023-v1.2.pdfAWS_DOP-C02_May_2023-v1.2.pdf
AWS_DOP-C02_May_2023-v1.2.pdfCCIEHOMER
 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Codescidept
 
Ember.js - Harnessing Convention Over Configuration
Ember.js - Harnessing Convention Over ConfigurationEmber.js - Harnessing Convention Over Configuration
Ember.js - Harnessing Convention Over ConfigurationTracy Lee
 
Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018
Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018
Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018Amazon Web Services
 
[AWS Container Service] Introducing AWS Fargate
[AWS Container Service] Introducing AWS Fargate[AWS Container Service] Introducing AWS Fargate
[AWS Container Service] Introducing AWS FargateAmazon Web Services Korea
 
Native Payment - Part 2 - Transcript.pdf
Native Payment - Part 2 - Transcript.pdfNative Payment - Part 2 - Transcript.pdf
Native Payment - Part 2 - Transcript.pdfShaiAlmog1
 

Similar a Writing resources_controller: Discovering REST Patterns in Rails (20)

Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014
Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014
Advanced EPUB creation for iPad with Adobe InDesign CC - Digital Book World 2014
 
Páginas Dinâmicas de Erro em Rails com Goalie
Páginas Dinâmicas de Erro em Rails com GoaliePáginas Dinâmicas de Erro em Rails com Goalie
Páginas Dinâmicas de Erro em Rails com Goalie
 
Edpuzzle - Migrating to React progressively but painlessly
Edpuzzle - Migrating to React progressively but painlesslyEdpuzzle - Migrating to React progressively but painlessly
Edpuzzle - Migrating to React progressively but painlessly
 
From Code to a Running Container | AWS Floor28
From Code to a Running Container | AWS Floor28From Code to a Running Container | AWS Floor28
From Code to a Running Container | AWS Floor28
 
Introducing AWS Fargate
Introducing AWS FargateIntroducing AWS Fargate
Introducing AWS Fargate
 
Introducing AWS Fargate - AWS Online Tech Talks
Introducing AWS Fargate - AWS Online Tech TalksIntroducing AWS Fargate - AWS Online Tech Talks
Introducing AWS Fargate - AWS Online Tech Talks
 
API Services: Building State-of-the-Art APIs
API Services: Building State-of-the-Art APIsAPI Services: Building State-of-the-Art APIs
API Services: Building State-of-the-Art APIs
 
Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018
Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018
Deploying Microservices using AWS Fargate (CON315-R1) - AWS re:Invent 2018
 
Introducing AWS Fargate - Tiffany Jernigan
Introducing AWS Fargate - Tiffany JerniganIntroducing AWS Fargate - Tiffany Jernigan
Introducing AWS Fargate - Tiffany Jernigan
 
SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...
SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...
SRV331_Build a Multi-Region Serverless Application for Resilience and High Av...
 
CI/CD using AWS developer tools
CI/CD using AWS developer toolsCI/CD using AWS developer tools
CI/CD using AWS developer tools
 
Creating a World-Class RESTful Web Services API
Creating a World-Class RESTful Web Services APICreating a World-Class RESTful Web Services API
Creating a World-Class RESTful Web Services API
 
AWS_DOP-C02_May_2023-v1.2.pdf
AWS_DOP-C02_May_2023-v1.2.pdfAWS_DOP-C02_May_2023-v1.2.pdf
AWS_DOP-C02_May_2023-v1.2.pdf
 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Code
 
Deep Dive into Amazon Fargate
Deep Dive into Amazon FargateDeep Dive into Amazon Fargate
Deep Dive into Amazon Fargate
 
Ember.js - Harnessing Convention Over Configuration
Ember.js - Harnessing Convention Over ConfigurationEmber.js - Harnessing Convention Over Configuration
Ember.js - Harnessing Convention Over Configuration
 
Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018
Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018
Continuous Integration Best Practices (DEV319-R1) - AWS re:Invent 2018
 
[AWS Container Service] Introducing AWS Fargate
[AWS Container Service] Introducing AWS Fargate[AWS Container Service] Introducing AWS Fargate
[AWS Container Service] Introducing AWS Fargate
 
Sst hackathon express
Sst hackathon expressSst hackathon express
Sst hackathon express
 
Native Payment - Part 2 - Transcript.pdf
Native Payment - Part 2 - Transcript.pdfNative Payment - Part 2 - Transcript.pdf
Native Payment - Part 2 - Transcript.pdf
 

Último

2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilV3cube
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 

Último (20)

2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 

Writing resources_controller: Discovering REST Patterns in Rails

  • 1. Writing resources_controller Discovering REST patterns in Rails Ian White ian.w.white@gmail.com @ Argument from Design ardes.com
  • 2. What I’ll cover ardes.com
  • 3. What I’ll cover  Example of RESTful app in vanilla Rails ardes.com
  • 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. 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. 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. 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. 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. REST on Rails ardes.com
  • 10. REST on Rails  Lets look at what’s involved in creating a simple RESTful app using Rails ardes.com
  • 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. Video sharing app ardes.com
  • 13. Video sharing app  Users can contribute Videos ardes.com
  • 14. Video sharing app  Users can contribute Videos  Videos can be categorised in many Categories ardes.com
  • 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. Video sharing app ardes.com
  • 17. Video sharing app  Our API? ardes.com
  • 18. Video sharing app  Our API?  Lets define the URLs ardes.com
  • 19. Video sharing app  Our API?  Lets define the URLs  we want to see all videos: /videos ardes.com
  • 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. 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. 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. 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. 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. Video sharing app ardes.com
  • 26. Video sharing app  Because this is a community site, lets not have admin namespaced controllers just yet ardes.com
  • 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. 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. 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. 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. 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. 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. 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. Video sharing app ardes.com
  • 35. Video sharing app  What views do we need? ardes.com
  • 36. Video sharing app  What views do we need?  CRUD stuff ardes.com
  • 37. Video sharing app  What views do we need?  CRUD stuff  What about videos vs category_videos? ardes.com
  • 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. 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. 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. 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. 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. 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. 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. Video sharing app ardes.com
  • 46. Video sharing app  With vanilla Rails, it’s not too bad ardes.com
  • 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. 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. 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. Video sharing app: new feature ardes.com
  • 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. 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. 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. 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. 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. Video sharing app: new feature ardes.com
  • 57. Video sharing app: new feature  Another controller, and 4 more views, not bad ardes.com
  • 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. 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. 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. # 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. # 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. # 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. Video sharing app: new feature ardes.com
  • 65. Video sharing app: new feature  Users should be able to comment on videos, users, and categories ardes.com
  • 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. 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. Video sharing app: new feature ardes.com
  • 69. Video sharing app: new feature  Now, one controller - or three controllers ardes.com
  • 70. Video sharing app: new feature  Now, one controller - or three controllers  If we have one controller we face these problems ardes.com
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Video sharing app: new feature ardes.com
  • 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. 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. 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. Video sharing app: rc’d ardes.com
  • 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. 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. Where is the logic? class VideosController < ApplicationController resources_controller_for :videos end ardes.com
  • 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. back to Rails w/o rc ardes.com
  • 91. back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves ardes.com
  • 92. back to Rails w/o rc  Seems like we have to do a lot of almost repeating ourselves - in actions ardes.com
  • 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. 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. 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. 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. Plugins to the rescue! ardes.com
  • 98. Plugins to the rescue!  resources_controller ardes.com
  • 99. Plugins to the rescue!  resources_controller  others - make_resourceful - resource_this - resource_controller - and more... ardes.com
  • 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. 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
  • 103. resources_controller: Goals NOT scaffolding - but it can be used for that ardes.com
  • 104. resources_controller: Goals NOT scaffolding - but it can be used for that stop repeating all of those actions ardes.com
  • 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. 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. 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. 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
  • 110. Scaffolding vs abstraction  Both are appropriate solutions for different problems ardes.com
  • 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. 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. 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. 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. 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. 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
  • 119. resources_controller: abstraction What’s the problem? The model name is tightly coupled with controller actions and views ardes.com
  • 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. 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. 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. 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. 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. 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. de-couple the name: resource ardes.com
  • 127. de-couple the name: resource def destroy self.resource = find_resource resource.destroy # end ardes.com
  • 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. 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. de-couple named routes: resource(s)_path ardes.com
  • 131. de-couple named routes: resource(s)_path def destroy self.resource = find_resource resource.destroy redirect_to resources_path end ardes.com
  • 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. 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. de-couple class or association: resource_service ardes.com
  • 135. de-couple class or association: resource_service def find_resource(id = params[:id]) resource_service.find id end ardes.com
  • 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. 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. 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. 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. load enclosing resources it’s easy, but repetitive. you need to do it, for url/model integrity ardes.com
  • 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. 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
  • 144. /users/2/forums/3 ardes.com
  • 145. @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) ardes.com
  • 146. @user = User.find(2) /users/2/forums/3 @forum = @user.forums.find(3) /users/2/forums/3/tags ardes.com
  • 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. @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. @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
  • 151. resource specification routing does the work of extracting logical reosurces from the url ardes.com
  • 152. resource specification routing does the work of extracting logical reosurces from the url and then THROWS IT AWAY ardes.com
  • 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. principle of least surprise ardes.com
  • 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. 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. 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. 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. 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. 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. 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
  • 163. Common patterns, rc’d  account pattern ardes.com
  • 164. Common patterns, rc’d  account pattern  ordering a collection ardes.com
  • 165. Common patterns, rc’d  account pattern  ordering a collection  join models ardes.com
  • 166. account pattern  Account pattern ardes.com
  • 167. account pattern map.resource :account do |account|  Account pattern account.resources :images account.resources :posts end ardes.com
  • 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. 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. ordering a collection ardes.com
  • 171. ordering a collection map.resources :users do |user| users.resources :things, :collection => {:order => :put} end ardes.com
  • 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. 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. Join model controllers class ModeratorshipsController < Applica... resources_controller_for :moderatorships, :actions => nil def create # ... def destroy # ... end
  • 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. 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. 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.
  • 179. RC summary ardes.com
  • 180. RC summary  makes RESTful controllers and views easier ardes.com
  • 181. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required ardes.com
  • 182. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings ardes.com
  • 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 ardes.com
  • 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 ardes.com
  • 185. 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
  • 186. What next? ardes.com
  • 187. What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource) ardes.com
  • 188. What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource)  refactoring? ardes.com
  • 189. 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
  • 190. Writing resources_controller Discovering REST patterns in Rails Ian White github.com/ianwhite blog.ardes.com/ian Argument from Design ardes.com Sheffield, UK