Skip to content

Commit

Permalink
Merge branch 'concerns'
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelfranca committed Aug 14, 2012
2 parents fa736e6 + 0bd7b07 commit 2d9dbf4
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 30 deletions.
30 changes: 30 additions & 0 deletions actionpack/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
## Rails 4.0.0 (unreleased) ##

* Add Routing Concerns to declare common routes that can be reused inside
others resources and routes.

Code before:

resources :messages do
resources :comments
end

resources :posts do
resources :comments
resources :images, only: :index
end

Code after:

concern :commentable do
resources :comments
end

concern :image_attachable do
resources :images, only: :index
end

resources :messages, concerns: :commentable

resources :posts, concerns: [:commentable, :image_attachable]

*David Heinemeier Hansson + Rafael Mendonça França*

* Add start_hour and end_hour options to the select_hour helper. *Evan Tann*

* Raises an ArgumentError when the first argument in `form_for` contain `nil`
Expand Down
62 changes: 61 additions & 1 deletion actionpack/lib/action_dispatch/routing/mapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ module Resources
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
# a path appended since they fit properly in their scope level.
VALID_ON_OPTIONS = [:new, :collection, :member]
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param]
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS = %w(index create new show update destroy)

class Resource #:nodoc:
Expand Down Expand Up @@ -1046,6 +1046,8 @@ def resource(*resources, &block)
resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
yield if block_given?

concerns(options[:concerns]) if options[:concerns]

collection do
post :create
end if parent_resource.actions.include?(:create)
Expand Down Expand Up @@ -1210,6 +1212,8 @@ def resources(*resources, &block)
resource_scope(:resources, Resource.new(resources.pop, options)) do
yield if block_given?

concerns(options[:concerns]) if options[:concerns]

collection do
get :index if parent_resource.actions.include?(:index)
post :create if parent_resource.actions.include?(:create)
Expand Down Expand Up @@ -1580,15 +1584,71 @@ def name_for_action(as, action) #:nodoc:
end
end

# Routing Concerns allows you to declare common routes that can be reused
# inside others resources and routes.
#
# concern :commentable do
# resources :comments
# end
#
# concern :image_attachable do
# resources :images, only: :index
# end
#
# These concerns are used in Resources routing:
#
# resources :messages, concerns: [:commentable, :image_attachable]
#
# or in a scope or namespace:
#
# namespace :posts do
# concerns :commentable
# end
module Concerns
# Define a routing concern using a name.
#
# concern :commentable do
# resources :comments
# end
#
# Any routing helpers can be used inside a concern.
def concern(name, &block)
@concerns[name] = block
end

# Use the named concerns
#
# resources :posts do
# concerns :commentable
# end
#
# concerns also work in any routes helper that you want to use:
#
# namespace :posts do
# concerns :commentable
# end
def concerns(*names)
names.flatten.each do |name|
if concern = @concerns[name]
instance_eval(&concern)
else
raise ArgumentError, "No concern named #{name} was found!"
end
end
end
end

def initialize(set) #:nodoc:
@set = set
@scope = { :path_names => @set.resources_path_names }
@concerns = {}
end

include Base
include HttpHelpers
include Redirection
include Scoping
include Concerns
include Resources
end
end
Expand Down
29 changes: 29 additions & 0 deletions actionpack/test/abstract_unit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,32 @@ def url_for(set, options, recall = nil)
set.send(:url_for, options.merge(:only_path => true, :_recall => recall))
end
end

class ResourcesController < ActionController::Base
def index() render :nothing => true end
alias_method :show, :index
end

class ThreadsController < ResourcesController; end
class MessagesController < ResourcesController; end
class CommentsController < ResourcesController; end
class AuthorsController < ResourcesController; end
class LogosController < ResourcesController; end

class AccountsController < ResourcesController; end
class AdminController < ResourcesController; end
class ProductsController < ResourcesController; end
class ImagesController < ResourcesController; end
class PreferencesController < ResourcesController; end

module Backoffice
class ProductsController < ResourcesController; end
class TagsController < ResourcesController; end
class ManufacturersController < ResourcesController; end
class ImagesController < ResourcesController; end

module Admin
class ProductsController < ResourcesController; end
class ImagesController < ResourcesController; end
end
end
29 changes: 0 additions & 29 deletions actionpack/test/controller/resources_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,6 @@
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/with_options'

class ResourcesController < ActionController::Base
def index() render :nothing => true end
alias_method :show, :index
end

class ThreadsController < ResourcesController; end
class MessagesController < ResourcesController; end
class CommentsController < ResourcesController; end
class AuthorsController < ResourcesController; end
class LogosController < ResourcesController; end

class AccountsController < ResourcesController; end
class AdminController < ResourcesController; end
class ProductsController < ResourcesController; end
class ImagesController < ResourcesController; end
class PreferencesController < ResourcesController; end

module Backoffice
class ProductsController < ResourcesController; end
class TagsController < ResourcesController; end
class ManufacturersController < ResourcesController; end
class ImagesController < ResourcesController; end

module Admin
class ProductsController < ResourcesController; end
class ImagesController < ResourcesController; end
end
end

class ResourcesTest < ActionController::TestCase
def test_default_restful_routes
with_restful_routing :messages do
Expand Down
82 changes: 82 additions & 0 deletions actionpack/test/dispatch/routing/concerns_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require 'abstract_unit'

class RoutingConcernsTest < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
concern :commentable do
resources :comments
end

concern :image_attachable do
resources :images, only: :index
end

resources :posts, concerns: [:commentable, :image_attachable] do
resource :video, concerns: :commentable
end

resource :picture, concerns: :commentable do
resources :posts, concerns: :commentable
end

scope "/videos" do
concerns :commentable
end
end
end

include Routes.url_helpers
def app; Routes end

def test_accessing_concern_from_resources
get "/posts/1/comments"
assert_equal "200", @response.code
assert_equal "/posts/1/comments", post_comments_path(post_id: 1)
end

def test_accessing_concern_from_resource
get "/picture/comments"
assert_equal "200", @response.code
assert_equal "/picture/comments", picture_comments_path
end

def test_accessing_concern_from_nested_resource
get "/posts/1/video/comments"
assert_equal "200", @response.code
assert_equal "/posts/1/video/comments", post_video_comments_path(post_id: 1)
end

def test_accessing_concern_from_nested_resources
get "/picture/posts/1/comments"
assert_equal "200", @response.code
assert_equal "/picture/posts/1/comments", picture_post_comments_path(post_id: 1)
end

def test_accessing_concern_from_resources_with_more_than_one_concern
get "/posts/1/images"
assert_equal "200", @response.code
assert_equal "/posts/1/images", post_images_path(post_id: 1)
end

def test_accessing_concern_from_resources_using_only_option
get "/posts/1/image/1"
assert_equal "404", @response.code
end

def test_accessing_concern_from_a_scope
get "/videos/comments"
assert_equal "200", @response.code
end

def test_with_an_invalid_concern_name
e = assert_raise ArgumentError do
ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
resources :posts, concerns: :foo
end
end
end

assert_equal "No concern named foo was found!", e.message
end
end
30 changes: 30 additions & 0 deletions guides/source/routing.textile
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,36 @@ The corresponding route helper would be +publisher_magazine_photo_url+, requirin

TIP: _Resources should never be nested more than 1 level deep._

h4. Routing concerns

Routing Concerns allows you to declare common routes that can be reused inside others resources and routes.

<ruby>
concern :commentable do
resources :comments
end

concern :image_attachable do
resources :images, only: :index
end
</ruby>

These concerns can be used in resources to avoid code duplication and share behavior across routes.

<ruby>
resources :messages, concerns: :commentable

resources :posts, concerns: [:commentable, :image_attachable]
</ruby>

Also you can use them in any place that you want inside the routes, for example in a scope or namespace call:

<ruby>
namespace :posts do
concerns :commentable
end
</ruby>

h4. Creating Paths and URLs From Objects

In addition to using the routing helpers, Rails can also create paths and URLs from an array of parameters. For example, suppose you have this set of routes:
Expand Down

0 comments on commit 2d9dbf4

Please sign in to comment.