actionpack 2.1.2 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (200) hide show
  1. data/CHANGELOG +223 -7
  2. data/README +6 -12
  3. data/Rakefile +11 -11
  4. data/lib/action_controller.rb +9 -9
  5. data/lib/action_controller/assertions/response_assertions.rb +29 -78
  6. data/lib/action_controller/assertions/routing_assertions.rb +33 -33
  7. data/lib/action_controller/assertions/selector_assertions.rb +9 -5
  8. data/lib/action_controller/base.rb +227 -161
  9. data/lib/action_controller/benchmarking.rb +37 -24
  10. data/lib/action_controller/caching/actions.rb +53 -21
  11. data/lib/action_controller/caching/fragments.rb +10 -36
  12. data/lib/action_controller/caching/sweeping.rb +3 -3
  13. data/lib/action_controller/cgi_ext/session.rb +2 -22
  14. data/lib/action_controller/cgi_process.rb +8 -46
  15. data/lib/action_controller/components.rb +4 -1
  16. data/lib/action_controller/cookies.rb +10 -0
  17. data/lib/action_controller/dispatcher.rb +49 -15
  18. data/lib/action_controller/filters.rb +48 -10
  19. data/lib/action_controller/headers.rb +16 -14
  20. data/lib/action_controller/helpers.rb +2 -2
  21. data/lib/action_controller/http_authentication.rb +1 -1
  22. data/lib/action_controller/integration.rb +57 -60
  23. data/lib/action_controller/layout.rb +27 -53
  24. data/lib/action_controller/mime_responds.rb +5 -1
  25. data/lib/action_controller/mime_type.rb +64 -42
  26. data/lib/action_controller/mime_types.rb +2 -1
  27. data/lib/action_controller/performance_test.rb +16 -0
  28. data/lib/action_controller/polymorphic_routes.rb +16 -9
  29. data/lib/action_controller/rack_process.rb +303 -0
  30. data/lib/action_controller/request.rb +205 -97
  31. data/lib/action_controller/request_forgery_protection.rb +2 -2
  32. data/lib/action_controller/request_profiler.rb +0 -0
  33. data/lib/action_controller/rescue.rb +20 -115
  34. data/lib/action_controller/resources.rb +186 -83
  35. data/lib/action_controller/response.rb +140 -26
  36. data/lib/action_controller/routing.rb +28 -30
  37. data/lib/action_controller/routing/builder.rb +45 -54
  38. data/lib/action_controller/routing/optimisations.rb +31 -21
  39. data/lib/action_controller/routing/recognition_optimisation.rb +33 -27
  40. data/lib/action_controller/routing/route.rb +162 -147
  41. data/lib/action_controller/routing/route_set.rb +8 -7
  42. data/lib/action_controller/routing/routing_ext.rb +4 -1
  43. data/lib/action_controller/routing/segments.rb +50 -21
  44. data/lib/action_controller/session/cookie_store.rb +3 -2
  45. data/lib/action_controller/session/drb_server.rb +7 -7
  46. data/lib/action_controller/session_management.rb +6 -2
  47. data/lib/action_controller/streaming.rb +15 -8
  48. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  49. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  50. data/lib/action_controller/test_case.rb +66 -2
  51. data/lib/action_controller/test_process.rb +71 -66
  52. data/lib/action_controller/translation.rb +13 -0
  53. data/lib/action_controller/url_rewriter.rb +90 -13
  54. data/lib/action_controller/vendor/html-scanner/html/node.rb +9 -2
  55. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +1 -1
  56. data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -2
  57. data/lib/action_controller/verification.rb +2 -2
  58. data/lib/action_pack/version.rb +1 -1
  59. data/lib/action_view.rb +19 -11
  60. data/lib/action_view/base.rb +184 -150
  61. data/lib/action_view/helpers.rb +38 -0
  62. data/lib/action_view/helpers/active_record_helper.rb +56 -27
  63. data/lib/action_view/helpers/asset_tag_helper.rb +356 -153
  64. data/lib/action_view/helpers/atom_feed_helper.rb +74 -19
  65. data/lib/action_view/helpers/benchmark_helper.rb +3 -3
  66. data/lib/action_view/helpers/cache_helper.rb +1 -2
  67. data/lib/action_view/helpers/capture_helper.rb +19 -44
  68. data/lib/action_view/helpers/date_helper.rb +486 -296
  69. data/lib/action_view/helpers/debug_helper.rb +20 -13
  70. data/lib/action_view/helpers/form_helper.rb +71 -30
  71. data/lib/action_view/helpers/form_options_helper.rb +15 -85
  72. data/lib/action_view/helpers/form_tag_helper.rb +61 -38
  73. data/lib/action_view/helpers/javascript_helper.rb +80 -89
  74. data/lib/action_view/helpers/number_helper.rb +179 -74
  75. data/lib/action_view/helpers/prototype_helper.rb +216 -201
  76. data/lib/action_view/helpers/record_tag_helper.rb +4 -5
  77. data/lib/action_view/helpers/sanitize_helper.rb +65 -33
  78. data/lib/action_view/helpers/scriptaculous_helper.rb +2 -2
  79. data/lib/action_view/helpers/tag_helper.rb +39 -22
  80. data/lib/action_view/helpers/text_helper.rb +212 -118
  81. data/lib/action_view/helpers/translation_helper.rb +21 -0
  82. data/lib/action_view/helpers/url_helper.rb +100 -58
  83. data/lib/action_view/inline_template.rb +13 -14
  84. data/lib/action_view/locale/en.yml +91 -0
  85. data/lib/action_view/partials.rb +100 -55
  86. data/lib/action_view/paths.rb +125 -0
  87. data/lib/action_view/renderable.rb +102 -0
  88. data/lib/action_view/renderable_partial.rb +48 -0
  89. data/lib/action_view/template.rb +90 -101
  90. data/lib/action_view/template_error.rb +11 -21
  91. data/lib/action_view/template_handler.rb +8 -28
  92. data/lib/action_view/template_handlers.rb +45 -0
  93. data/lib/action_view/template_handlers/builder.rb +5 -15
  94. data/lib/action_view/template_handlers/erb.rb +9 -6
  95. data/lib/action_view/template_handlers/rjs.rb +2 -17
  96. data/lib/action_view/test_case.rb +7 -4
  97. data/test/abstract_unit.rb +4 -1
  98. data/test/active_record_unit.rb +28 -30
  99. data/test/activerecord/render_partial_with_record_identification_test.rb +25 -12
  100. data/test/controller/action_pack_assertions_test.rb +8 -37
  101. data/test/controller/addresses_render_test.rb +0 -3
  102. data/test/controller/assert_select_test.rb +51 -24
  103. data/test/controller/base_test.rb +4 -4
  104. data/test/controller/caching_test.rb +136 -66
  105. data/test/controller/capture_test.rb +1 -21
  106. data/test/controller/cgi_test.rb +157 -10
  107. data/test/controller/components_test.rb +41 -25
  108. data/test/controller/content_type_test.rb +49 -17
  109. data/test/controller/cookie_test.rb +1 -1
  110. data/test/controller/deprecation/deprecated_base_methods_test.rb +0 -3
  111. data/test/controller/dispatcher_test.rb +9 -1
  112. data/test/controller/filter_params_test.rb +2 -2
  113. data/test/controller/filters_test.rb +13 -13
  114. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  115. data/test/controller/html-scanner/node_test.rb +21 -0
  116. data/test/controller/html-scanner/sanitizer_test.rb +14 -0
  117. data/test/controller/integration_test.rb +167 -6
  118. data/test/controller/layout_test.rb +11 -68
  119. data/test/controller/logging_test.rb +46 -0
  120. data/test/controller/mime_responds_test.rb +61 -59
  121. data/test/controller/mime_type_test.rb +6 -6
  122. data/test/controller/polymorphic_routes_test.rb +37 -2
  123. data/test/controller/rack_test.rb +323 -0
  124. data/test/controller/redirect_test.rb +72 -71
  125. data/test/controller/render_test.rb +1120 -108
  126. data/test/controller/request_forgery_protection_test.rb +66 -52
  127. data/test/controller/request_test.rb +103 -146
  128. data/test/controller/rescue_test.rb +20 -24
  129. data/test/controller/resources_test.rb +408 -25
  130. data/test/controller/routing_test.rb +1774 -1774
  131. data/test/controller/send_file_test.rb +0 -4
  132. data/test/controller/session/cookie_store_test.rb +53 -1
  133. data/test/controller/test_test.rb +15 -37
  134. data/test/controller/translation_test.rb +26 -0
  135. data/test/controller/url_rewriter_test.rb +27 -28
  136. data/test/controller/view_paths_test.rb +48 -47
  137. data/test/fixtures/_top_level_partial.html.erb +1 -0
  138. data/test/fixtures/_top_level_partial_only.erb +1 -0
  139. data/test/fixtures/developers/_developer.erb +1 -0
  140. data/test/fixtures/fun/games/_game.erb +1 -0
  141. data/test/fixtures/fun/serious/games/_game.erb +1 -0
  142. data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
  143. data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
  144. data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
  145. data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
  146. data/test/fixtures/layouts/_column.html.erb +2 -0
  147. data/test/fixtures/projects/_project.erb +1 -0
  148. data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
  149. data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
  150. data/test/fixtures/replies/_reply.erb +1 -0
  151. data/test/fixtures/test/_counter.html.erb +1 -0
  152. data/test/fixtures/test/_customer.erb +1 -1
  153. data/test/fixtures/test/_customer_with_var.erb +1 -0
  154. data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
  155. data/test/fixtures/test/_local_inspector.html.erb +1 -0
  156. data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
  157. data/test/fixtures/test/hello.builder +1 -1
  158. data/test/fixtures/test/hyphen-ated.erb +1 -0
  159. data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
  160. data/test/fixtures/test/nested_layout.erb +3 -0
  161. data/test/fixtures/test/non_erb_block_content_for.builder +1 -1
  162. data/test/fixtures/test/sub_template_raise.html.erb +1 -0
  163. data/test/fixtures/test/template.erb +1 -0
  164. data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
  165. data/test/template/active_record_helper_i18n_test.rb +46 -0
  166. data/test/template/active_record_helper_test.rb +24 -24
  167. data/test/template/asset_tag_helper_test.rb +161 -29
  168. data/test/template/atom_feed_helper_test.rb +114 -5
  169. data/test/template/compiled_templates_test.rb +59 -0
  170. data/test/template/date_helper_i18n_test.rb +113 -0
  171. data/test/template/date_helper_test.rb +403 -109
  172. data/test/template/form_helper_test.rb +213 -154
  173. data/test/template/form_options_helper_test.rb +249 -897
  174. data/test/template/form_tag_helper_test.rb +80 -32
  175. data/test/template/javascript_helper_test.rb +17 -18
  176. data/test/template/number_helper_i18n_test.rb +54 -0
  177. data/test/template/number_helper_test.rb +43 -13
  178. data/test/template/prototype_helper_test.rb +101 -84
  179. data/test/template/record_tag_helper_test.rb +24 -20
  180. data/test/template/render_test.rb +193 -0
  181. data/test/template/sanitize_helper_test.rb +3 -3
  182. data/test/template/tag_helper_test.rb +34 -14
  183. data/test/template/text_helper_test.rb +83 -9
  184. data/test/template/translation_helper_test.rb +28 -0
  185. data/test/template/url_helper_test.rb +55 -18
  186. metadata +57 -18
  187. data/lib/action_view/helpers/javascripts/controls.js +0 -963
  188. data/lib/action_view/helpers/javascripts/dragdrop.js +0 -972
  189. data/lib/action_view/helpers/javascripts/effects.js +0 -1120
  190. data/lib/action_view/helpers/javascripts/prototype.js +0 -4225
  191. data/lib/action_view/partial_template.rb +0 -70
  192. data/lib/action_view/template_finder.rb +0 -177
  193. data/lib/action_view/template_handlers/compilable.rb +0 -128
  194. data/test/controller/custom_handler_test.rb +0 -45
  195. data/test/controller/new_render_test.rb +0 -945
  196. data/test/fixtures/test/block_content_for.erb +0 -2
  197. data/test/fixtures/test/erb_content_for.erb +0 -2
  198. data/test/template/deprecated_erb_variable_test.rb +0 -9
  199. data/test/template/template_finder_test.rb +0 -73
  200. data/test/template/template_object_test.rb +0 -95
@@ -17,7 +17,7 @@ module ActionController #:nodoc:
17
17
  # forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
18
18
  # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
19
19
  # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
20
- # scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
20
+ # scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
21
21
  #
22
22
  # This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
23
23
  # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in
@@ -99,7 +99,7 @@ module ActionController #:nodoc:
99
99
  end
100
100
 
101
101
  def verifiable_request_format?
102
- request.content_type.nil? || request.content_type.verify_request?
102
+ !request.content_type.nil? && request.content_type.verify_request?
103
103
  end
104
104
 
105
105
  # Sets the token value for the current session. Pass a <tt>:secret</tt> option
File without changes
@@ -41,10 +41,9 @@ module ActionController #:nodoc:
41
41
  base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
42
42
  base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
43
43
 
44
- base.class_inheritable_array :rescue_handlers
45
- base.rescue_handlers = []
46
-
47
44
  base.extend(ClassMethods)
45
+ base.send :include, ActiveSupport::Rescuable
46
+
48
47
  base.class_eval do
49
48
  alias_method_chain :perform_action, :rescue
50
49
  end
@@ -54,78 +53,12 @@ module ActionController #:nodoc:
54
53
  def process_with_exception(request, response, exception) #:nodoc:
55
54
  new.process(request, response, :rescue_action, exception)
56
55
  end
57
-
58
- # Rescue exceptions raised in controller actions.
59
- #
60
- # <tt>rescue_from</tt> receives a series of exception classes or class
61
- # names, and a trailing <tt>:with</tt> option with the name of a method
62
- # or a Proc object to be called to handle them. Alternatively a block can
63
- # be given.
64
- #
65
- # Handlers that take one argument will be called with the exception, so
66
- # that the exception can be inspected when dealing with it.
67
- #
68
- # Handlers are inherited. They are searched from right to left, from
69
- # bottom to top, and up the hierarchy. The handler of the first class for
70
- # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
71
- # any.
72
- #
73
- # class ApplicationController < ActionController::Base
74
- # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
75
- # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
76
- #
77
- # rescue_from 'MyAppError::Base' do |exception|
78
- # render :xml => exception, :status => 500
79
- # end
80
- #
81
- # protected
82
- # def deny_access
83
- # ...
84
- # end
85
- #
86
- # def show_errors(exception)
87
- # exception.record.new_record? ? ...
88
- # end
89
- # end
90
- def rescue_from(*klasses, &block)
91
- options = klasses.extract_options!
92
- unless options.has_key?(:with)
93
- block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
94
- end
95
-
96
- klasses.each do |klass|
97
- key = if klass.is_a?(Class) && klass <= Exception
98
- klass.name
99
- elsif klass.is_a?(String)
100
- klass
101
- else
102
- raise(ArgumentError, "#{klass} is neither an Exception nor a String")
103
- end
104
-
105
- # Order is important, we put the pair at the end. When dealing with an
106
- # exception we will follow the documented order going from right to left.
107
- rescue_handlers << [key, options[:with]]
108
- end
109
- end
110
56
  end
111
57
 
112
58
  protected
113
59
  # Exception handler called when the performance of an action raises an exception.
114
60
  def rescue_action(exception)
115
- log_error(exception) if logger
116
- erase_results if performed?
117
-
118
- # Let the exception alter the response if it wants.
119
- # For example, MethodNotAllowed sets the Allow header.
120
- if exception.respond_to?(:handle_response!)
121
- exception.handle_response!(response)
122
- end
123
-
124
- if consider_all_requests_local || local_request?
125
- rescue_action_locally(exception)
126
- else
127
- rescue_action_in_public(exception)
128
- end
61
+ rescue_with_handler(exception) || rescue_action_without_handler(exception)
129
62
  end
130
63
 
131
64
  # Overwrite to implement custom logging of errors. By default logs as fatal.
@@ -144,7 +77,7 @@ module ActionController #:nodoc:
144
77
  end
145
78
 
146
79
  # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
147
- # default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
80
+ # default will call render_optional_error_file. Override this method to provide more user friendly error messages.
148
81
  def rescue_action_in_public(exception) #:doc:
149
82
  render_optional_error_file response_code_for_rescue(exception)
150
83
  end
@@ -173,26 +106,28 @@ module ActionController #:nodoc:
173
106
  # Render detailed diagnostics for unhandled exceptions rescued from
174
107
  # a controller action.
175
108
  def rescue_action_locally(exception)
176
- add_variables_to_assigns
177
109
  @template.instance_variable_set("@exception", exception)
178
110
  @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
179
- @template.send!(:assign_variables_from_controller)
180
-
181
- @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
111
+ @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
182
112
 
183
113
  response.content_type = Mime::HTML
184
114
  render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
185
115
  end
186
116
 
187
- # Tries to rescue the exception by looking up and calling a registered handler.
188
- def rescue_action_with_handler(exception)
189
- if handler = handler_for_rescue(exception)
190
- if handler.arity != 0
191
- handler.call(exception)
192
- else
193
- handler.call
194
- end
195
- true # don't rely on the return value of the handler
117
+ def rescue_action_without_handler(exception)
118
+ log_error(exception) if logger
119
+ erase_results if performed?
120
+
121
+ # Let the exception alter the response if it wants.
122
+ # For example, MethodNotAllowed sets the Allow header.
123
+ if exception.respond_to?(:handle_response!)
124
+ exception.handle_response!(response)
125
+ end
126
+
127
+ if consider_all_requests_local || local_request?
128
+ rescue_action_locally(exception)
129
+ else
130
+ rescue_action_in_public(exception)
196
131
  end
197
132
  end
198
133
 
@@ -200,7 +135,7 @@ module ActionController #:nodoc:
200
135
  def perform_action_with_rescue #:nodoc:
201
136
  perform_action_without_rescue
202
137
  rescue Exception => exception
203
- rescue_action_with_handler(exception) || rescue_action(exception)
138
+ rescue_action(exception)
204
139
  end
205
140
 
206
141
  def rescues_path(template_name)
@@ -215,36 +150,6 @@ module ActionController #:nodoc:
215
150
  rescue_responses[exception.class.name]
216
151
  end
217
152
 
218
- def handler_for_rescue(exception)
219
- # We go from right to left because pairs are pushed onto rescue_handlers
220
- # as rescue_from declarations are found.
221
- _, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
222
- # The purpose of allowing strings in rescue_from is to support the
223
- # declaration of handler associations for exception classes whose
224
- # definition is yet unknown.
225
- #
226
- # Since this loop needs the constants it would be inconsistent to
227
- # assume they should exist at this point. An early raised exception
228
- # could trigger some other handler and the array could include
229
- # precisely a string whose corresponding constant has not yet been
230
- # seen. This is why we are tolerant to unknown constants.
231
- #
232
- # Note that this tolerance only matters if the exception was given as
233
- # a string, otherwise a NameError will be raised by the interpreter
234
- # itself when rescue_from CONSTANT is executed.
235
- klass = self.class.const_get(klass_name) rescue nil
236
- klass ||= klass_name.constantize rescue nil
237
- exception.is_a?(klass) if klass
238
- end
239
-
240
- case handler
241
- when Symbol
242
- method(handler)
243
- when Proc
244
- handler.bind(self)
245
- end
246
- end
247
-
248
153
  def clean_backtrace(exception)
249
154
  if backtrace = exception.backtrace
250
155
  if defined?(RAILS_ROOT)
@@ -1,23 +1,23 @@
1
1
  module ActionController
2
2
  # == Overview
3
3
  #
4
- # ActionController::Resources are a way of defining RESTful resources. A RESTful resource, in basic terms,
4
+ # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
5
5
  # is something that can be pointed at and it will respond with a representation of the data requested.
6
6
  # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
7
7
  # requests XML data.
8
8
  #
9
9
  # RESTful design is based on the assumption that there are four generic verbs that a user of an
10
- # application can request from a resource (the noun).
10
+ # application can request from a \resource (the noun).
11
11
  #
12
- # Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
12
+ # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
13
13
  # denotes the type of action that should take place.
14
14
  #
15
15
  # === The Different Methods and their Usage
16
16
  #
17
- # +GET+ Requests for a resource, no saving or editing of a resource should occur in a GET request
18
- # +POST+ Creation of resources
19
- # +PUT+ Editing of attributes on a resource
20
- # +DELETE+ Deletion of a resource
17
+ # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
18
+ # * POST - Creation of \resources.
19
+ # * PUT - Editing of attributes on a \resource.
20
+ # * DELETE - Deletion of a \resource.
21
21
  #
22
22
  # === Examples
23
23
  #
@@ -42,7 +42,11 @@ module ActionController
42
42
  #
43
43
  # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
44
44
  module Resources
45
+ INHERITABLE_OPTIONS = :namespace, :shallow, :actions
46
+
45
47
  class Resource #:nodoc:
48
+ DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
49
+
46
50
  attr_reader :collection_methods, :member_methods, :new_methods
47
51
  attr_reader :path_prefix, :name_prefix, :path_segment
48
52
  attr_reader :plural, :singular
@@ -57,6 +61,7 @@ module ActionController
57
61
 
58
62
  arrange_actions
59
63
  add_default_actions
64
+ set_allowed_actions
60
65
  set_prefixes
61
66
  end
62
67
 
@@ -72,7 +77,7 @@ module ActionController
72
77
  end
73
78
 
74
79
  def conditions
75
- @conditions = @options[:conditions] || {}
80
+ @conditions ||= @options[:conditions] || {}
76
81
  end
77
82
 
78
83
  def path
@@ -80,21 +85,29 @@ module ActionController
80
85
  end
81
86
 
82
87
  def new_path
83
- new_action = self.options[:path_names][:new] if self.options[:path_names]
88
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
84
89
  new_action ||= Base.resources_path_names[:new]
85
- @new_path ||= "#{path}/#{new_action}"
90
+ @new_path ||= "#{path}/#{new_action}"
91
+ end
92
+
93
+ def shallow_path_prefix
94
+ @shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}"
86
95
  end
87
96
 
88
97
  def member_path
89
- @member_path ||= "#{path}/:id"
98
+ @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
90
99
  end
91
100
 
92
101
  def nesting_path_prefix
93
- @nesting_path_prefix ||= "#{path}/:#{singular}_id"
102
+ @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
103
+ end
104
+
105
+ def shallow_name_prefix
106
+ @shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}"
94
107
  end
95
108
 
96
109
  def nesting_name_prefix
97
- "#{name_prefix}#{singular}_"
110
+ "#{shallow_name_prefix}#{singular}_"
98
111
  end
99
112
 
100
113
  def action_separator
@@ -105,6 +118,10 @@ module ActionController
105
118
  @singular.to_s == @plural.to_s
106
119
  end
107
120
 
121
+ def has_action?(action)
122
+ !DEFAULT_ACTIONS.include?(action) || @options[:actions].nil? || @options[:actions].include?(action)
123
+ end
124
+
108
125
  protected
109
126
  def arrange_actions
110
127
  @collection_methods = arrange_actions_by_methods(options.delete(:collection))
@@ -117,6 +134,25 @@ module ActionController
117
134
  add_default_action(new_methods, :get, :new)
118
135
  end
119
136
 
137
+ def set_allowed_actions
138
+ only = @options.delete(:only)
139
+ except = @options.delete(:except)
140
+
141
+ if only && except
142
+ raise ArgumentError, 'Please supply either :only or :except, not both.'
143
+ elsif only == :all || except == :none
144
+ options[:actions] = DEFAULT_ACTIONS
145
+ elsif only == :none || except == :all
146
+ options[:actions] = []
147
+ elsif only
148
+ options[:actions] = DEFAULT_ACTIONS & Array(only).map(&:to_sym)
149
+ elsif except
150
+ options[:actions] = DEFAULT_ACTIONS - Array(except).map(&:to_sym)
151
+ else
152
+ # leave options[:actions] alone
153
+ end
154
+ end
155
+
120
156
  def set_prefixes
121
157
  @path_prefix = options.delete(:path_prefix)
122
158
  @name_prefix = options.delete(:name_prefix)
@@ -141,12 +177,14 @@ module ActionController
141
177
  super
142
178
  end
143
179
 
180
+ alias_method :shallow_path_prefix, :path_prefix
181
+ alias_method :shallow_name_prefix, :name_prefix
144
182
  alias_method :member_path, :path
145
183
  alias_method :nesting_path_prefix, :path
146
184
  end
147
185
 
148
186
  # Creates named routes for implementing verb-oriented controllers
149
- # for a collection resource.
187
+ # for a collection \resource.
150
188
  #
151
189
  # For example:
152
190
  #
@@ -238,23 +276,24 @@ module ActionController
238
276
  #
239
277
  # The +resources+ method accepts the following options to customize the resulting routes:
240
278
  # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
241
- # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
242
- # or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
279
+ # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
280
+ # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
281
+ # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
243
282
  # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
244
- # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new resource action.
283
+ # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
245
284
  # * <tt>:controller</tt> - Specify the controller name for the routes.
246
285
  # * <tt>:singular</tt> - Specify the singular name used in the member routes.
247
286
  # * <tt>:requirements</tt> - Set custom routing parameter requirements.
248
- # * <tt>:conditions</tt> - Specify custom routing recognition conditions. Resources sets the <tt>:method</tt> value for the method-specific routes.
249
- # * <tt>:as</tt> - Specify a different resource name to use in the URL path. For example:
287
+ # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
288
+ # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
250
289
  # # products_path == '/productos'
251
290
  # map.resources :products, :as => 'productos' do |product|
252
291
  # # product_reviews_path(product) == '/productos/1234/comentarios'
253
292
  # product.resources :product_reviews, :as => 'comentarios'
254
293
  # end
255
294
  #
256
- # * <tt>:has_one</tt> - Specify nested resources, this is a shorthand for mapping singleton resources beneath the current.
257
- # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural resources.
295
+ # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
296
+ # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
258
297
  #
259
298
  # You may directly specify the routing association with +has_one+ and +has_many+ like:
260
299
  #
@@ -277,18 +316,18 @@ module ActionController
277
316
  #
278
317
  # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
279
318
  #
280
- # Weblog comments usually belong to a post, so you might use resources like:
319
+ # Weblog comments usually belong to a post, so you might use +resources+ like:
281
320
  #
282
321
  # map.resources :articles
283
322
  # map.resources :comments, :path_prefix => '/articles/:article_id'
284
323
  #
285
- # You can nest resources calls to set this automatically:
324
+ # You can nest +resources+ calls to set this automatically:
286
325
  #
287
326
  # map.resources :articles do |article|
288
327
  # article.resources :comments
289
328
  # end
290
329
  #
291
- # The comment resources work the same, but must now include a value for <tt>:article_id</tt>.
330
+ # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
292
331
  #
293
332
  # article_comments_url(@article)
294
333
  # article_comment_url(@article, @comment)
@@ -296,23 +335,71 @@ module ActionController
296
335
  # article_comments_url(:article_id => @article)
297
336
  # article_comment_url(:article_id => @article, :id => @comment)
298
337
  #
338
+ # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
339
+ #
340
+ # articles_comments_url(@comment.article_id, @comment)
341
+ #
299
342
  # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
300
343
  # Use this if you have named routes that may clash.
301
344
  #
302
345
  # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
303
346
  # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
304
347
  #
305
- # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource:
306
- #
348
+ # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
349
+ #
307
350
  # map.resources :articles do |article|
308
351
  # article.resources :comments, :name_prefix => nil
309
- # end
310
- #
311
- # This will yield named resources like so:
312
- #
352
+ # end
353
+ #
354
+ # This will yield named \resources like so:
355
+ #
313
356
  # comments_url(@article)
314
357
  # comment_url(@article, @comment)
315
358
  #
359
+ # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
360
+ # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
361
+ #
362
+ # The <tt>:shallow</tt> option is inherited by any nested resource(s).
363
+ #
364
+ # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
365
+ #
366
+ # map.resources :users, :shallow => true do |user|
367
+ # user.resources :posts do |post|
368
+ # post.resources :comments
369
+ # end
370
+ # end
371
+ # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
372
+ # # also adds the usual named route called "user_posts"
373
+ # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
374
+ # # also adds the named route called "post"
375
+ # # --> GET /posts/2/comments (maps to the CommentsController#index action)
376
+ # # also adds the named route called "post_comments"
377
+ # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
378
+ # # also adds the named route called "comment"
379
+ #
380
+ # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
381
+ #
382
+ # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
383
+ #
384
+ # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
385
+ #
386
+ # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
387
+ # list of action names. By default, routes are generated for all seven actions.
388
+ #
389
+ # For example:
390
+ #
391
+ # map.resources :posts, :only => [:index, :show] do |post|
392
+ # post.resources :comments, :except => [:update, :destroy]
393
+ # end
394
+ # # --> GET /posts (maps to the PostsController#index action)
395
+ # # --> POST /posts (fails)
396
+ # # --> GET /posts/1 (maps to the PostsController#show action)
397
+ # # --> DELETE /posts/1 (fails)
398
+ # # --> POST /posts/1/comments (maps to the CommentsController#create action)
399
+ # # --> PUT /posts/1/comments/1 (fails)
400
+ #
401
+ # The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
402
+ #
316
403
  # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
317
404
  #
318
405
  # Examples:
@@ -345,28 +432,28 @@ module ActionController
345
432
  #
346
433
  # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
347
434
  # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
348
- # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
435
+ # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
349
436
  def resources(*entities, &block)
350
437
  options = entities.extract_options!
351
438
  entities.each { |entity| map_resource(entity, options.dup, &block) }
352
439
  end
353
440
 
354
- # Creates named routes for implementing verb-oriented controllers for a singleton resource.
355
- # A singleton resource is global to its current context. For unnested singleton resources,
356
- # the resource is global to the current user visiting the application, such as a user's
357
- # /account profile. For nested singleton resources, the resource is global to its parent
358
- # resource, such as a <tt>projects</tt> resource that <tt>has_one :project_manager</tt>.
359
- # The <tt>project_manager</tt> should be mapped as a singleton resource under <tt>projects</tt>:
441
+ # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
442
+ # A singleton \resource is global to its current context. For unnested singleton \resources,
443
+ # the \resource is global to the current user visiting the application, such as a user's
444
+ # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
445
+ # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
446
+ # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
360
447
  #
361
448
  # map.resources :projects do |project|
362
449
  # project.resource :project_manager
363
450
  # end
364
451
  #
365
- # See map.resources for general conventions. These are the main differences:
366
- # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
452
+ # See +resources+ for general conventions. These are the main differences:
453
+ # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
367
454
  # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
368
- # * No default index route is created for the singleton resource controller.
369
- # * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
455
+ # * No default index route is created for the singleton \resource controller.
456
+ # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
370
457
  #
371
458
  # For example:
372
459
  #
@@ -438,7 +525,7 @@ module ActionController
438
525
  map_associations(resource, options)
439
526
 
440
527
  if block_given?
441
- with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
528
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
442
529
  end
443
530
  end
444
531
  end
@@ -455,67 +542,75 @@ module ActionController
455
542
  map_associations(resource, options)
456
543
 
457
544
  if block_given?
458
- with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
545
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
459
546
  end
460
547
  end
461
548
  end
462
549
 
463
550
  def map_associations(resource, options)
551
+ map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
552
+
464
553
  path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
465
554
  name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
466
555
 
467
- Array(options[:has_many]).each do |association|
468
- resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
556
+ Array(options[:has_one]).each do |association|
557
+ resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
469
558
  end
559
+ end
470
560
 
471
- Array(options[:has_one]).each do |association|
472
- resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
561
+ def map_has_many_associations(resource, associations, options)
562
+ case associations
563
+ when Hash
564
+ associations.each do |association,has_many|
565
+ map_has_many_associations(resource, association, options.merge(:has_many => has_many))
566
+ end
567
+ when Array
568
+ associations.each do |association|
569
+ map_has_many_associations(resource, association, options)
570
+ end
571
+ when Symbol, String
572
+ resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
573
+ else
473
574
  end
474
575
  end
475
576
 
476
577
  def map_collection_actions(map, resource)
477
578
  resource.collection_methods.each do |method, actions|
478
579
  actions.each do |action|
479
- action_options = action_options_for(action, resource, method)
480
- map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
481
- map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
580
+ [method].flatten.each do |m|
581
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
582
+ end
482
583
  end
483
584
  end
484
585
  end
485
586
 
486
587
  def map_default_collection_actions(map, resource)
487
- index_action_options = action_options_for("index", resource)
488
588
  index_route_name = "#{resource.name_prefix}#{resource.plural}"
489
589
 
490
590
  if resource.uncountable?
491
591
  index_route_name << "_index"
492
592
  end
493
593
 
494
- map.named_route(index_route_name, resource.path, index_action_options)
495
- map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
496
-
497
- create_action_options = action_options_for("create", resource)
498
- map.connect(resource.path, create_action_options)
499
- map.connect("#{resource.path}.:format", create_action_options)
594
+ map_resource_routes(map, resource, :index, resource.path, index_route_name)
595
+ map_resource_routes(map, resource, :create, resource.path, index_route_name)
500
596
  end
501
597
 
502
598
  def map_default_singleton_actions(map, resource)
503
- create_action_options = action_options_for("create", resource)
504
- map.connect(resource.path, create_action_options)
505
- map.connect("#{resource.path}.:format", create_action_options)
599
+ map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
506
600
  end
507
601
 
508
602
  def map_new_actions(map, resource)
509
603
  resource.new_methods.each do |method, actions|
510
604
  actions.each do |action|
511
- action_options = action_options_for(action, resource, method)
512
- if action == :new
513
- map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
514
- map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
515
- else
516
- map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
517
- map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
605
+ route_path = resource.new_path
606
+ route_name = "new_#{resource.name_prefix}#{resource.singular}"
607
+
608
+ unless action == :new
609
+ route_path = "#{route_path}#{resource.action_separator}#{action}"
610
+ route_name = "#{action}_#{route_name}"
518
611
  end
612
+
613
+ map_resource_routes(map, resource, action, route_path, route_name, method)
519
614
  end
520
615
  end
521
616
  end
@@ -523,27 +618,34 @@ module ActionController
523
618
  def map_member_actions(map, resource)
524
619
  resource.member_methods.each do |method, actions|
525
620
  actions.each do |action|
526
- action_options = action_options_for(action, resource, method)
527
-
528
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
529
- action_path ||= Base.resources_path_names[action] || action
621
+ [method].flatten.each do |m|
622
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
623
+ action_path ||= Base.resources_path_names[action] || action
530
624
 
531
- map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
532
- map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
625
+ map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m)
626
+ end
533
627
  end
534
628
  end
535
629
 
536
- show_action_options = action_options_for("show", resource)
537
- map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
538
- map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
539
-
540
- update_action_options = action_options_for("update", resource)
541
- map.connect(resource.member_path, update_action_options)
542
- map.connect("#{resource.member_path}.:format", update_action_options)
630
+ route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
631
+ map_resource_routes(map, resource, :show, resource.member_path, route_path)
632
+ map_resource_routes(map, resource, :update, resource.member_path, route_path)
633
+ map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
634
+ end
543
635
 
544
- destroy_action_options = action_options_for("destroy", resource)
545
- map.connect(resource.member_path, destroy_action_options)
546
- map.connect("#{resource.member_path}.:format", destroy_action_options)
636
+ def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil)
637
+ if resource.has_action?(action)
638
+ action_options = action_options_for(action, resource, method)
639
+ formatted_route_path = "#{route_path}.:format"
640
+
641
+ if route_name && @set.named_routes[route_name.to_sym].nil?
642
+ map.named_route(route_name, route_path, action_options)
643
+ map.named_route("formatted_#{route_name}", formatted_route_path, action_options)
644
+ else
645
+ map.connect(route_path, action_options)
646
+ map.connect(formatted_route_path, action_options)
647
+ end
648
+ end
547
649
  end
548
650
 
549
651
  def add_conditions_for(conditions, method)
@@ -555,6 +657,7 @@ module ActionController
555
657
  def action_options_for(action, resource, method = nil)
556
658
  default_options = { :action => action.to_s }
557
659
  require_id = !resource.kind_of?(SingletonResource)
660
+
558
661
  case default_options[:action]
559
662
  when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
560
663
  when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)