Skip to content
This repository
Browse code

Make enhanced routing Concerns more tell-don't-ask

  • Loading branch information...
commit 05136e5c0b3d7b841bdec53847879321309604d3 1 parent eb43d3d
Ernie Miller authored August 26, 2012
80  actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1585,7 +1585,7 @@ def name_for_action(as, action) #:nodoc:
1585 1585
           end
1586 1586
       end
1587 1587
 
1588  
-      # Routing Concerns allows you to declare common routes that can be reused
  1588
+      # Routing Concerns allow you to declare common routes that can be reused
1589 1589
       # inside others resources and routes.
1590 1590
       #
1591 1591
       #   concern :commentable do
@@ -1606,32 +1606,65 @@ def name_for_action(as, action) #:nodoc:
1606 1606
       #     concerns :commentable
1607 1607
       #   end
1608 1608
       module Concerns
1609  
-        # Define a routing concern using a name. If a second parameter is
1610  
-        # supplied, it should respond to call, which will receive the mapper
1611  
-        # as a parameter, allowing for customized behavior based on the current
1612  
-        # scope.
  1609
+        # Define a routing concern using a name.
1613 1610
         #
1614  
-        #   concern :commentable do
1615  
-        #     resources :comments
  1611
+        # Concerns may be defined inline, using a block, or handled by
  1612
+        # another object, by passing that object as the second parameter.
  1613
+        #
  1614
+        # The concern object, if supplied, should respond to <tt>call</tt>,
  1615
+        # which will receive two parameters:
  1616
+        #
  1617
+        #   * The current mapper
  1618
+        #   * A hash of options which the concern object may use
  1619
+        #
  1620
+        # Options may also be used by concerns defined in a block by accepting
  1621
+        # a block parameter. So, using a block, you might do something as
  1622
+        # simple as limit the actions available on certain resources, passing
  1623
+        # standard resource options through the concern:
  1624
+        #
  1625
+        #   concern :commentable do |options|
  1626
+        #     resources :comments, options
  1627
+        #   end
  1628
+        #
  1629
+        #   resources :posts, concerns: :commentable
  1630
+        #   resources :archived_posts do
  1631
+        #     # Don't allow comments on archived posts
  1632
+        #     concerns :commentable, only: [:index, :show]
1616 1633
         #   end
1617 1634
         #
1618  
-        #   # - or -
  1635
+        # Or, using a callable object, you might implement something more
  1636
+        # specific to your application, which would be out of place in your
  1637
+        # routes file.
1619 1638
         #
1620  
-        #   class Commentable
1621  
-        #     def self.call(mapper)
1622  
-        #       if mapper.current_scope[:controller] == 'videos'
1623  
-        #         mapper.resources :video_comments, as: :comments
1624  
-        #       else
1625  
-        #         mapper.resources :comments
1626  
-        #       end
  1639
+        #   # purchasable.rb
  1640
+        #   class Purchasable
  1641
+        #     def initialize(defaults = {})
  1642
+        #       @defaults = defaults
  1643
+        #     end
  1644
+        #
  1645
+        #     def call(mapper, options = {})
  1646
+        #       options = @defaults.merge(options)
  1647
+        #       mapper.resources :purchases
  1648
+        #       mapper.resources :receipts
  1649
+        #       mapper.resources :returns if options[:returnable]
1627 1650
         #     end
1628 1651
         #   end
1629 1652
         #
1630  
-        #   concern :commentable, Commentable
  1653
+        #   # routes.rb
  1654
+        #   concern :purchasable, Purchasable.new(returnable: true)
  1655
+        #
  1656
+        #   resources :toys, concerns: :purchasable
  1657
+        #   resources :electronics, concerns: :purchasable
  1658
+        #   resources :pets do
  1659
+        #     concerns :purchasable, returnable: false
  1660
+        #   end
1631 1661
         #
1632  
-        # Any routing helpers can be used inside a concern.
  1662
+        # Any routing helpers can be used inside a concern. If using a
  1663
+        # callable, they're accessible from the Mapper that's passed to
  1664
+        # <tt>call</tt>.
1633 1665
         def concern(name, callable = nil, &block)
1634  
-          @concerns[name] = callable || lambda { |m| m.instance_eval(&block) }
  1666
+          callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
  1667
+          @concerns[name] = callable
1635 1668
         end
1636 1669
 
1637 1670
         # Use the named concerns
@@ -1645,10 +1678,11 @@ def concern(name, callable = nil, &block)
1645 1678
         #   namespace :posts do
1646 1679
         #     concerns :commentable
1647 1680
         #   end
1648  
-        def concerns(*names)
1649  
-          names.flatten.each do |name|
  1681
+        def concerns(*args)
  1682
+          options = args.extract_options!
  1683
+          args.flatten.each do |name|
1650 1684
             if concern = @concerns[name]
1651  
-              concern.call(self)
  1685
+              concern.call(self, options)
1652 1686
             else
1653 1687
               raise ArgumentError, "No concern named #{name} was found!"
1654 1688
             end
@@ -1662,10 +1696,6 @@ def initialize(set) #:nodoc:
1662 1696
         @concerns = {}
1663 1697
       end
1664 1698
 
1665  
-      def current_scope
1666  
-        @scope
1667  
-      end
1668  
-
1669 1699
       include Base
1670 1700
       include HttpHelpers
1671 1701
       include Redirection
27  actionpack/test/dispatch/routing/concerns_test.rb
@@ -2,19 +2,15 @@
2 2
 
3 3
 class RoutingConcernsTest < ActionDispatch::IntegrationTest
4 4
   class Reviewable
5  
-    def self.call(mapper)
6  
-      if mapper.current_scope[:controller] == 'posts'
7  
-        mapper.resources :reviews
8  
-      elsif mapper.current_scope[:controller] == 'videos'
9  
-        mapper.resources :reviews, as: :video_reviews
10  
-      end
  5
+    def self.call(mapper, options = {})
  6
+      mapper.resources :reviews, options
11 7
     end
12 8
   end
13 9
 
14 10
   Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
15 11
     app.draw do
16  
-      concern :commentable do
17  
-        resources :comments
  12
+      concern :commentable do |options|
  13
+        resources :comments, options
18 14
       end
19 15
 
20 16
       concern :image_attachable do
@@ -24,7 +20,9 @@ def self.call(mapper)
24 20
       concern :reviewable, Reviewable
25 21
 
26 22
       resources :posts, concerns: [:commentable, :image_attachable, :reviewable] do
27  
-        resource :video, concerns: [:commentable, :reviewable]
  23
+        resource :video, concerns: :commentable do
  24
+          concerns :reviewable, as: :video_reviews
  25
+        end
28 26
       end
29 27
 
30 28
       resource :picture, concerns: :commentable do
@@ -32,7 +30,7 @@ def self.call(mapper)
32 30
       end
33 31
 
34 32
       scope "/videos" do
35  
-        concerns :commentable
  33
+        concerns :commentable, except: :destroy
36 34
       end
37 35
     end
38 36
   end
@@ -75,13 +73,13 @@ def test_accessing_concern_from_resources_using_only_option
75 73
     assert_equal "404", @response.code
76 74
   end
77 75
 
78  
-  def test_accessing_callable_concern_from_resources
  76
+  def test_accessing_callable_concern_
79 77
     get "/posts/1/reviews/1"
80 78
     assert_equal "200", @response.code
81 79
     assert_equal "/posts/1/reviews/1", post_review_path(post_id: 1, id: 1)
82 80
   end
83 81
 
84  
-  def test_callable_concern_can_adapt_to_mapper
  82
+  def test_callable_concerns_accept_options
85 83
     get "/posts/1/video/reviews/1"
86 84
     assert_equal "200", @response.code
87 85
     assert_equal "/posts/1/video/reviews/1", post_video_video_review_path(post_id: 1, id: 1)
@@ -92,6 +90,11 @@ def test_accessing_concern_from_a_scope
92 90
     assert_equal "200", @response.code
93 91
   end
94 92
 
  93
+  def test_concerns_accept_options
  94
+    delete "/videos/comments/1"
  95
+    assert_equal "404", @response.code
  96
+  end
  97
+
95 98
   def test_with_an_invalid_concern_name
96 99
     e = assert_raise ArgumentError do
97 100
       ActionDispatch::Routing::RouteSet.new.tap do |app|

0 notes on commit 05136e5

Please sign in to comment.
Something went wrong with that request. Please try again.