Writing resources_controller: Discovering REST Patterns in Rails

Loading...

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

0 comments

Post a comment

    Post a comment
    Embed Video
    Edit your comment Cancel

    6 Favorites

    Writing resources_controller: Discovering REST Patterns in Rails - Presentation Transcript

    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
    102. resources_controller: Goals 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
    109. Scaffolding vs abstraction 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
    117. resources_controller: abstraction ardes.com
    118. resources_controller: abstraction What’s the problem? 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
    143. 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
    150. resource specification 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
    162. Common patterns, rc’d 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[\"#{resource_name}_order\"] 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 \"script/generate rspec -f\" } end run do cd ‘vendor/plugins/resources_controller’ do sh \"rake spec:rcov:verify\" end ardes.com end end
    178. RC summary ardes.com
    179. RC summary  makes RESTful controllers and views easier ardes.com
    180. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required ardes.com
    181. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings ardes.com
    182. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings - rc just got forked by revolutionhealth on github ardes.com
    183. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings - rc just got forked by revolutionhealth on github - Lots of production apps ardes.com
    184. RC summary  makes RESTful controllers and views easier - Start with fewer controllers/views - make more when required  sightings - rc just got forked by revolutionhealth on github - Lots of production apps  well speced ardes.com
    185. What next? ardes.com
    186. What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource) ardes.com
    187. What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource)  refactoring? ardes.com
    188. What next?  abstract idea of resource, passed by routing to controller on the request (you were invoked with this resource)  refactoring?  core-list talk about loading resources in controllers ardes.com
    189. Writing resources_controller Discovering REST patterns in Rails Ian White github.com/ianwhite blog.ardes.com/ian Argument from Design ardes.com Sheffield, UK

    + guest234ec21guest234ec21, 2 years ago

    custom

    720 views, 6 favs, 0 embeds more stats

    RailsConf Europe 2008 - Ian White wrote resources_c more

    More info about this presentation

    © All Rights Reserved

    • Total Views 720
      • 720 on SlideShare
      • 0 from embeds
    • Comments 0
    • Favorites 6
    • Downloads 53
    Most viewed embeds

    more

    All embeds

    less

    Flagged as inappropriate Flag as inappropriate
    Flag as inappropriate

    Select your reason for flagging this presentation as inappropriate. If needed, use the feedback form to let us know more details.

    Cancel
    File a copyright complaint
    Having problems? Go to our helpdesk?

    Categories