public
Fork of rails/rails
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/lukemelia/rails.git
Add extra flexibility for STI to polymorphic_url by walking the inheritance 
chain if the route is missing
  i.e. given an an Editorial record, where Editorial is a subclass of Article 
  and routes exist for article_* but not editorial_*
  polymorphic_url(editorial_record) #=> article_url(editorial_record)
lukemelia (author)
Sun Apr 13 15:49:08 -0700 2008
commit  4eecef63ece3927b2a432df217b180c9aa4d3df5
tree    521a8aa8f0a2999fb547a39520843a3f1d5029f3
parent  9620372a6dc7eeebdb04f1fdb7f150d29e60fc00
...
67
68
69
 
 
 
 
 
 
 
70
71
72
...
138
139
140
141
142
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
145
146
...
67
68
69
70
71
72
73
74
75
76
77
78
79
...
145
146
147
 
 
 
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
0
@@ -67,6 +67,13 @@ module ActionController
0
     #   record = Comment.new
0
     #   polymorphic_url(record)  #->  comments_url()
0
     #
0
+    #   # it supports extra flexibility for STI by walking the inheritance chain if the route is missing
0
+    #   # (note this only works for the last (leaf) argument in nested routes)
0
+    #   #
0
+    #   # an Editorial record, where Editorial is a subclass of Article and routes exist for article_* but not editorial_*
0
+    #   polymorphic_url(editorial_record) #=> article_url(editorial_record)
0
+    #   polymorphic_url(editorial_record, comment_record) #=> editorial_comment_url(editorial_record, comment_record) #not yet smart enough to find the article_comment_url
0
+    #
0
     def polymorphic_url(record_or_hash_or_array, options = {})
0
       if record_or_hash_or_array.kind_of?(Array)
0
         record_or_hash_or_array = record_or_hash_or_array.dup
0
@@ -138,9 +145,20 @@ module ActionController
0
             string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
0
           end
0
         end
0
-
0
-        route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
0
-
0
+        
0
+        candidate_class = record.class
0
+        initial_candidate = candidate_route = build_candidate_named_route_call(route, namespace, inflection, candidate_class, options)
0
+        while (!respond_to?(candidate_route)) #walk up the inheritance chain to find an existing route, supports routing flexibility with STI
0
+          candidate_class = candidate_class.superclass
0
+          return initial_candidate if candidate_class == Object
0
+          candidate_route = build_candidate_named_route_call(route, namespace, inflection, candidate_class, options)
0
+        end
0
+        candidate_route || initial_candidate
0
+      end
0
+      
0
+      def build_candidate_named_route_call(route_base, namespace, inflection, candidate_class, options = {})
0
+        route = route_base.dup
0
+        route << "#{RecordIdentifier.send!("#{inflection}_class_name", candidate_class)}_"
0
         action_prefix(options) + namespace + route + routing_type(options).to_s
0
       end
0
 
...
14
15
16
 
 
 
17
18
19
...
29
30
31
 
32
33
34
...
37
38
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
41
42
43
44
 
 
 
 
 
 
 
 
45
46
47
...
49
50
51
 
 
 
 
 
 
 
 
52
53
54
55
56
 
 
 
 
 
 
 
57
58
59
60
61
62
 
 
 
 
 
 
 
 
63
64
65
66
67
 
 
 
 
 
 
 
68
69
70
71
72
73
 
 
 
 
 
 
 
 
74
75
76
77
78
79
 
 
 
 
 
 
 
 
80
81
82
83
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
86
87
88
89
 
 
 
 
 
 
 
90
91
92
93
94
95
 
 
 
 
 
 
 
96
97
98
99
 
 
 
 
 
 
 
100
101
102
...
106
107
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
110
111
...
117
118
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
121
122
...
129
130
131
 
 
 
 
 
 
 
132
133
134
...
14
15
16
17
18
19
20
21
22
...
32
33
34
35
36
37
38
...
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
...
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
...
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
...
273
274
275
276
277
278
279
280
281
282
283
284
285
0
@@ -14,6 +14,9 @@ class Response < Article
0
   def post_id; 1 end
0
 end
0
 
0
+class Editorial < Article
0
+end
0
+
0
 class Tag < Article
0
   def response_id; 1 end
0
 end
0
@@ -29,6 +32,7 @@ uses_mocha 'polymorphic URL helpers' do
0
     def setup
0
       @article = Article.new
0
       @response = Response.new
0
+      @editorial = Editorial.new
0
     end
0
   
0
     def test_with_record
0
@@ -37,11 +41,35 @@ uses_mocha 'polymorphic URL helpers' do
0
       polymorphic_url(@article)
0
     end
0
 
0
+    def test_with_fallthrough_to_route_for_base_class
0
+      @editorial.save
0
+      stubs(:respond_to?).with('editorial_url').returns(false)
0
+      stubs(:respond_to?).with('article_url').returns(true)
0
+      expects(:article_url).with(@editorial)
0
+      polymorphic_url(@editorial)
0
+    end
0
+    
0
+    def test_with_no_matching_routes
0
+      @editorial.save
0
+      stubs(:respond_to?).with('article_url').returns(false)
0
+      stubs(:respond_to?).with('editorial_url').returns(false)
0
+      expects(:editorial_url).with(@editorial)
0
+      polymorphic_url(@editorial)
0
+    end
0
+    
0
     def test_with_new_record
0
       expects(:articles_url).with()
0
       @article.expects(:new_record?).returns(true)
0
       polymorphic_url(@article)
0
     end
0
+    
0
+    def test_fallthrough_with_new_record
0
+      @editorial.stubs(:new_record?).returns(true)
0
+      stubs(:respond_to?).with('editorials_url').returns(false)
0
+      stubs(:respond_to?).with('articles_url').returns(true)
0
+      expects(:articles_url).with()
0
+      polymorphic_url(@editorial)
0
+    end
0
 
0
     def test_with_record_and_action
0
       expects(:new_article_url).with()
0
@@ -49,54 +77,137 @@ uses_mocha 'polymorphic URL helpers' do
0
       polymorphic_url(@article, :action => 'new')
0
     end
0
 
0
+    def test_fallthrough_with_record_and_action
0
+      stubs(:respond_to?).with('new_editorial_url').returns(false)
0
+      stubs(:respond_to?).with('new_article_url').returns(true)
0
+      expects(:new_article_url).with()
0
+      @editorial.expects(:new_record?).never
0
+      polymorphic_url(@editorial, :action => 'new')
0
+    end
0
+
0
     def test_url_helper_prefixed_with_new
0
       expects(:new_article_url).with()
0
       new_polymorphic_url(@article)
0
     end
0
 
0
+    def test_fallthrough_with_url_helper_prefixed_with_new
0
+      stubs(:respond_to?).with('new_editorial_url').returns(false)
0
+      stubs(:respond_to?).with('new_article_url').returns(true)
0
+      expects(:new_article_url).with()
0
+      new_polymorphic_url(@editorial)
0
+    end
0
+
0
     def test_url_helper_prefixed_with_edit
0
       @article.save
0
       expects(:edit_article_url).with(@article)
0
       edit_polymorphic_url(@article)
0
     end
0
 
0
+    def test_fallthrough_with_url_helper_prefixed_with_edit
0
+      @editorial.save
0
+      stubs(:respond_to?).with('edit_editorial_url').returns(false)
0
+      stubs(:respond_to?).with('edit_article_url').returns(true)
0
+      expects(:edit_article_url).with(@editorial)
0
+      edit_polymorphic_url(@editorial)
0
+    end
0
+
0
     def test_formatted_url_helper
0
       expects(:formatted_article_url).with(@article, :pdf)
0
       formatted_polymorphic_url([@article, :pdf])
0
     end
0
 
0
+    def test_fallthrough_with_formatted_url_helper
0
+      stubs(:respond_to?).with('formatted_editorial_url').returns(false)
0
+      stubs(:respond_to?).with('formatted_article_url').returns(true)
0
+      expects(:formatted_article_url).with(@editorial, :pdf)
0
+      formatted_polymorphic_url([@editorial, :pdf])
0
+    end
0
+
0
     def test_format_option
0
       @article.save
0
       expects(:article_url).with(@article, :pdf)
0
       polymorphic_url(@article, :format => :pdf)
0
     end
0
 
0
+    def test_fallthrough_with_format_option
0
+      @editorial.save
0
+      stubs(:respond_to?).with('editorial_url').returns(false)
0
+      stubs(:respond_to?).with('article_url').returns(true)
0
+      expects(:article_url).with(@editorial, :pdf)
0
+      polymorphic_url(@editorial, :format => :pdf)
0
+    end
0
+
0
     def test_id_and_format_option
0
       @article.save
0
       expects(:article_url).with(:id => @article, :format => :pdf)
0
       polymorphic_url(:id => @article, :format => :pdf)
0
     end
0
 
0
+    def test_fallthrough_with_id_and_format_option
0
+      @editorial.save
0
+      stubs(:respond_to?).with('editorial_url').returns(false)
0
+      stubs(:respond_to?).with('article_url').returns(true)
0
+      expects(:article_url).with(:id => @editorial, :format => :pdf)
0
+      polymorphic_url(:id => @editorial, :format => :pdf)
0
+    end
0
+
0
     def test_with_nested
0
       @response.save
0
       expects(:article_response_url).with(@article, @response)
0
       polymorphic_url([@article, @response])
0
     end
0
+    
0
+    def test_fallthrough_for_child_with_nested
0
+      @editorial.save
0
+      stubs(:respond_to?).with('response_editorial_url').returns(false)
0
+      stubs(:respond_to?).with('response_article_url').returns(true)
0
+      expects(:response_article_url).with(@response, @editorial)
0
+      polymorphic_url([@response, @editorial])
0
+    end
0
+
0
+    def test_fallthrough_for_parent_with_nested #fallthrough only currently works for the targeted resource, not for parents it is nested inside
0
+      @response.save
0
+      stubs(:respond_to?).with('editorial_article_url').returns(false)
0
+      stubs(:respond_to?).with('editorial_response_url').returns(false)
0
+      expects(:editorial_response_url).with(@editorial, @response) #TODO: this should be expects(:article_response_url).with(@editorial, @response)
0
+      polymorphic_url([@editorial, @response])
0
+    end
0
 
0
     def test_with_nested_unsaved
0
       expects(:article_responses_url).with(@article)
0
       polymorphic_url([@article, @response])
0
     end
0
+    
0
+    def test_fallthrough_for_child_with_nested_unsaved
0
+      stubs(:respond_to?).with('response_editorials_url').returns(false)
0
+      stubs(:respond_to?).with('response_articles_url').returns(true)
0
+      expects(:response_articles_url).with(@response)
0
+      polymorphic_url([@response, @editorial])
0
+    end
0
 
0
     def test_new_with_array_and_namespace
0
       expects(:new_admin_article_url).with()
0
       polymorphic_url([:admin, @article], :action => 'new')
0
     end
0
 
0
+    def test_fallthrough_for_new_with_array_and_namespace
0
+      stubs(:respond_to?).with('new_admin_editorial_url').returns(false)
0
+      stubs(:respond_to?).with('new_admin_article_url').returns(true)
0
+      expects(:new_admin_article_url).with()
0
+      polymorphic_url([:admin, @editorial], :action => 'new')
0
+    end
0
+
0
     def test_unsaved_with_array_and_namespace
0
       expects(:admin_articles_url).with()
0
       polymorphic_url([:admin, @article])
0
     end
0
+    
0
+    def test_fallthrough_for_unsaved_with_array_and_namespace
0
+      stubs(:respond_to?).with('admin_editorials_url').returns(false)
0
+      stubs(:respond_to?).with('admin_articles_url').returns(true)
0
+      expects(:admin_articles_url).with()
0
+      polymorphic_url([:admin, @editorial])
0
+    end
0
 
0
     def test_nested_unsaved_with_array_and_namespace
0
       @article.save
0
@@ -106,6 +217,21 @@ uses_mocha 'polymorphic URL helpers' do
0
       polymorphic_url([:admin, @article, @response])
0
     end
0
 
0
+    def test_fallthrough_for_nested_unsaved_with_array_and_namespace
0
+      @editorial.save
0
+      stubs(:respond_to?).with('admin_editorial_url').returns(false)
0
+      stubs(:respond_to?).with('admin_article_url').returns(true)
0
+      expects(:admin_article_url).with(@editorial)
0
+      polymorphic_url([:admin, @editorial])
0
+      
0
+      #fallthrough only currently works for the targeted resource, not for parents it is nested inside
0
+      stubs(:respond_to?).with('admin_editorial_responses_url').returns(false)
0
+      stubs(:respond_to?).with('admin_editorial_articles_url').returns(false)
0
+      stubs(:respond_to?).with('admin_article_responses_url').returns(true)
0
+      expects(:admin_editorial_responses_url).with(@editorial) #TODO: this should be expects(:admin_article_responses_url).with(@editorial)
0
+      polymorphic_url([:admin, @editorial, @response])
0
+    end
0
+
0
     def test_nested_with_array_and_namespace
0
       @response.save
0
       expects(:admin_article_response_url).with(@article, @response)
0
@@ -117,6 +243,24 @@ uses_mocha 'polymorphic URL helpers' do
0
       expects(:site_admin_article_response_tag_url).with(@article, @response, @tag)
0
       polymorphic_url([:site, :admin, @article, @response, @tag])
0
     end
0
+    
0
+    def test_fallthrough_for_nested_with_array_and_namespace
0
+      @response.save
0
+      stubs(:respond_to?).with('admin_editorial_response_url').returns(false)
0
+      stubs(:respond_to?).with('admin_editorial_article_url').returns(false)
0
+      stubs(:respond_to?).with('admin_article_response_url').returns(true)
0
+      expects(:admin_editorial_response_url).with(@editorial, @response) #TODO: this should be expects(:admin_article_response_url).with(@editorial, @response)
0
+      polymorphic_url([:admin, @editorial, @response])
0
+
0
+      # a ridiculously long named route tests correct ordering of namespaces and nesting:
0
+      @tag = Tag.new
0
+      @tag.save
0
+      stubs(:respond_to?).with('site_admin_editorial_response_tag_url').returns(false)
0
+      stubs(:respond_to?).with('site_admin_editorial_response_article_url').returns(false)
0
+      stubs(:respond_to?).with('site_admin_article_response_tag_url').returns(true)
0
+      expects(:site_admin_editorial_response_tag_url).with(@editorial, @response, @tag) #TODO: this should be expects(:site_admin_article_response_tag_url).with(@editorial, @response, @tag)
0
+      polymorphic_url([:site, :admin, @editorial, @response, @tag])
0
+    end
0
 
0
     # TODO: Needs to be updated to correctly know about whether the object is in a hash or not
0
     def xtest_with_hash
0
@@ -129,6 +273,13 @@ uses_mocha 'polymorphic URL helpers' do
0
       expects(:new_article_path).with()
0
       polymorphic_path(@article, :action => :new)
0
     end
0
+    
0
+    def test_fallthrough_with_polymorphic_path_accepts_options
0
+      stubs(:respond_to?).with('new_editorial_path').returns(false)
0
+      stubs(:respond_to?).with('new_article_path').returns(true)
0
+      expects(:new_article_path).with()
0
+      polymorphic_path(@editorial, :action => :new)
0
+    end
0
 
0
     def test_polymorphic_path_does_not_modify_arguments
0
       expects(:admin_article_responses_url).with(@article)

Comments

eadz Wed Jun 18 05:54:23 -0700 2008

+1 for something like this to appear in rails/rails/master.. needed.

zdennis Fri Aug 22 09:48:48 -0700 2008

I agree with eadz: +1

ledermann Wed Sep 17 06:53:23 -0700 2008

+1 from me

daemianmack Mon Nov 10 05:19:10 -0800 2008

+1

costan Wed Nov 19 11:44:39 -0800 2008

+1

Small comment: to make this work with Rails 2.2, replace send! with send in polymorphic_routes.rb

What happened to this patch?

daemianmack Thu Nov 20 08:44:38 -0800 2008

Thanks for documenting that, costan (looks like github ate the double underscores that go around your second send reference). I found the same fix but was having issues with the resulting URLs referenced—the app was breaking looking for parent_child_child_path, instead of parent_child_path. I had to roll back and haven’t yet gotten a chance to rule out my own routes.rb being the culprit, though.

What did happen to this patch?

lukemelia Thu Nov 20 09:23:03 -0800 2008

Never got picked up off the old trac. It’s been on my todo list to freshen up for edge and open a lighthouse ticket. If anyone wants to take the ball and run, go for it.

dagi3d Mon Dec 01 15:29:53 -0800 2008

it seems it doesn’t support resources under a namespace, does it?

dagi3d Mon Dec 01 15:33:34 -0800 2008

oh, I was mistaken. Just had to pass to namespace and objects inside an array

lylo Tue Jan 06 08:33:19 -0800 2009

+1

gueorgui Thu Jan 15 16:26:13 -0800 2009

+1 on this, ran into the same problem.

henrik Fri Jan 16 05:51:53 -0800 2009

Yeah, me too. The old ticket is here, for reference: http://dev.rubyonrails.org/ticket/10454

findchris Mon Mar 02 23:06:18 -0800 2009

+1

Is a lighthouse ticket filed for this yet?

eadz Tue Mar 03 01:28:19 -0800 2009

+1 Wow, Rails 2.3.0 and I’m still fighting with this.

Zeeba Fri Mar 06 04:51:29 -0800 2009

bump

greenbuilding Sun May 03 01:53:33 -0700 2009

bump

greenbuilding Sun May 03 02:53:51 -0700 2009

This actually seems to achieve what I most needed from this which is to let my STI types pass through the same controller and views, maybe some other people who need the same thing found their way here and can be helped by this suggestion.

map.resources :items map.resources :pages, :controller => 'items' map.resources :buildings, :controller => 'items'