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
@@ -1,14 +1,14 @@
1
1
  module ActionController
2
2
  module Routing
3
- # Much of the slow performance from routes comes from the
3
+ # Much of the slow performance from routes comes from the
4
4
  # complexity of expiry, <tt>:requirements</tt> matching, defaults providing
5
- # and figuring out which url pattern to use. With named routes
6
- # we can avoid the expense of finding the right route. So if
5
+ # and figuring out which url pattern to use. With named routes
6
+ # we can avoid the expense of finding the right route. So if
7
7
  # they've provided the right number of arguments, and have no
8
8
  # <tt>:requirements</tt>, we can just build up a string and return it.
9
- #
10
- # To support building optimisations for other common cases, the
11
- # generation code is separated into several classes
9
+ #
10
+ # To support building optimisations for other common cases, the
11
+ # generation code is separated into several classes
12
12
  module Optimisation
13
13
  def generate_optimisation_block(route, kind)
14
14
  return "" unless route.optimise?
@@ -20,13 +20,20 @@ module ActionController
20
20
 
21
21
  class Optimiser
22
22
  attr_reader :route, :kind
23
+ GLOBAL_GUARD_CONDITIONS = [
24
+ "(!defined?(default_url_options) || default_url_options.blank?)",
25
+ "(!defined?(controller.default_url_options) || controller.default_url_options.blank?)",
26
+ "defined?(request)",
27
+ "request"
28
+ ]
29
+
23
30
  def initialize(route, kind)
24
31
  @route = route
25
32
  @kind = kind
26
33
  end
27
34
 
28
- def guard_condition
29
- 'false'
35
+ def guard_conditions
36
+ ["false"]
30
37
  end
31
38
 
32
39
  def generation_code
@@ -35,6 +42,7 @@ module ActionController
35
42
 
36
43
  def source_code
37
44
  if applicable?
45
+ guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ")
38
46
  "return #{generation_code} if #{guard_condition}\n"
39
47
  else
40
48
  "\n"
@@ -53,17 +61,17 @@ module ActionController
53
61
  # map.person '/people/:id'
54
62
  #
55
63
  # If the user calls <tt>person_url(@person)</tt>, we can simply
56
- # return a string like "/people/#{@person.to_param}"
64
+ # return a string like "/people/#{@person.to_param}"
57
65
  # rather than triggering the expensive logic in +url_for+.
58
66
  class PositionalArguments < Optimiser
59
- def guard_condition
67
+ def guard_conditions
60
68
  number_of_arguments = route.segment_keys.size
61
- # if they're using foo_url(:id=>2) it's one
69
+ # if they're using foo_url(:id=>2) it's one
62
70
  # argument, but we don't want to generate /foos/id2
63
71
  if number_of_arguments == 1
64
- "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
72
+ ["args.size == 1", "!args.first.is_a?(Hash)"]
65
73
  else
66
- "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{number_of_arguments}"
74
+ ["args.size == #{number_of_arguments}"]
67
75
  end
68
76
  end
69
77
 
@@ -76,7 +84,7 @@ module ActionController
76
84
  elements << '#{request.host_with_port}'
77
85
  end
78
86
 
79
- elements << '#{request.relative_url_root if request.relative_url_root}'
87
+ elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
80
88
 
81
89
  # The last entry in <tt>route.segments</tt> appears to *always* be a
82
90
  # 'divider segment' for '/' but we have assertions to ensure that
@@ -94,23 +102,25 @@ module ActionController
94
102
  end
95
103
 
96
104
  # This case is mostly the same as the positional arguments case
97
- # above, but it supports additional query parameters as the last
105
+ # above, but it supports additional query parameters as the last
98
106
  # argument
99
107
  class PositionalArgumentsWithAdditionalParams < PositionalArguments
100
- def guard_condition
101
- "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
108
+ def guard_conditions
109
+ ["args.size == #{route.segment_keys.size + 1}"] +
110
+ UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
102
111
  end
103
112
 
104
- # This case uses almost the same code as positional arguments,
105
- # but add an args.last.to_query on the end
113
+ # This case uses almost the same code as positional arguments,
114
+ # but add a question mark and args.last.to_query on the end,
115
+ # unless the last arg is empty
106
116
  def generation_code
107
- super.insert(-2, '?#{args.last.to_query}')
117
+ super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
108
118
  end
109
119
 
110
120
  # To avoid generating "http://localhost/?host=foo.example.com" we
111
121
  # can't use this optimisation on routes without any segments
112
122
  def applicable?
113
- super && route.segment_keys.size > 0
123
+ super && route.segment_keys.size > 0
114
124
  end
115
125
  end
116
126
 
@@ -51,7 +51,6 @@ module ActionController
51
51
  # 3) segm test for /users/:id
52
52
  # (jump to list index = 5)
53
53
  # 4) full test for /users/:id => here we are!
54
-
55
54
  class RouteSet
56
55
  def recognize_path(path, environment={})
57
56
  result = recognize_optimized(path, environment) and return result
@@ -68,32 +67,6 @@ module ActionController
68
67
  end
69
68
  end
70
69
 
71
- def clear_recognize_optimized!
72
- instance_eval %{
73
- def recognize_optimized(path, env)
74
- write_recognize_optimized!
75
- recognize_optimized(path, env)
76
- end
77
- }, __FILE__, __LINE__
78
- end
79
-
80
- def write_recognize_optimized!
81
- tree = segment_tree(routes)
82
- body = generate_code(tree)
83
- instance_eval %{
84
- def recognize_optimized(path, env)
85
- segments = to_plain_segments(path)
86
- index = #{body}
87
- return nil unless index
88
- while index < routes.size
89
- result = routes[index].recognize(path, env) and return result
90
- index += 1
91
- end
92
- nil
93
- end
94
- }, __FILE__, __LINE__
95
- end
96
-
97
70
  def segment_tree(routes)
98
71
  tree = [0]
99
72
 
@@ -157,6 +130,39 @@ module ActionController
157
130
  segments
158
131
  end
159
132
 
133
+ private
134
+ def write_recognize_optimized!
135
+ tree = segment_tree(routes)
136
+ body = generate_code(tree)
137
+
138
+ remove_recognize_optimized!
139
+
140
+ instance_eval %{
141
+ def recognize_optimized(path, env)
142
+ segments = to_plain_segments(path)
143
+ index = #{body}
144
+ return nil unless index
145
+ while index < routes.size
146
+ result = routes[index].recognize(path, env) and return result
147
+ index += 1
148
+ end
149
+ nil
150
+ end
151
+ }, '(recognize_optimized)', 1
152
+ end
153
+
154
+ def clear_recognize_optimized!
155
+ remove_recognize_optimized!
156
+ write_recognize_optimized!
157
+ end
158
+
159
+ def remove_recognize_optimized!
160
+ if respond_to?(:recognize_optimized)
161
+ class << self
162
+ remove_method :recognize_optimized
163
+ end
164
+ end
165
+ end
160
166
  end
161
167
  end
162
168
  end
@@ -3,11 +3,25 @@ module ActionController
3
3
  class Route #:nodoc:
4
4
  attr_accessor :segments, :requirements, :conditions, :optimise
5
5
 
6
- def initialize
7
- @segments = []
8
- @requirements = {}
9
- @conditions = {}
10
- @optimise = true
6
+ def initialize(segments = [], requirements = {}, conditions = {})
7
+ @segments = segments
8
+ @requirements = requirements
9
+ @conditions = conditions
10
+
11
+ if !significant_keys.include?(:action) && !requirements[:action]
12
+ @requirements[:action] = "index"
13
+ @significant_keys << :action
14
+ end
15
+
16
+ # Routes cannot use the current string interpolation method
17
+ # if there are user-supplied <tt>:requirements</tt> as the interpolation
18
+ # code won't raise RoutingErrors when generating
19
+ has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
20
+ if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
21
+ @optimise = false
22
+ else
23
+ @optimise = true
24
+ end
11
25
  end
12
26
 
13
27
  # Indicates whether the routes should be optimised with the string interpolation
@@ -22,129 +36,6 @@ module ActionController
22
36
  end.compact
23
37
  end
24
38
 
25
- # Write and compile a +generate+ method for this Route.
26
- def write_generation
27
- # Build the main body of the generation
28
- body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
29
-
30
- # If we have conditions that must be tested first, nest the body inside an if
31
- body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
32
- args = "options, hash, expire_on = {}"
33
-
34
- # Nest the body inside of a def block, and then compile it.
35
- raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
36
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
37
-
38
- # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
39
- # are the same as the keys that were recalled from the previous request. Thus,
40
- # we can use the expire_on.keys to determine which keys ought to be used to build
41
- # the query string. (Never use keys from the recalled request when building the
42
- # query string.)
43
-
44
- method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
45
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
46
-
47
- method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
48
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
49
- raw_method
50
- end
51
-
52
- # Build several lines of code that extract values from the options hash. If any
53
- # of the values are missing or rejected then a return will be executed.
54
- def generation_extraction
55
- segments.collect do |segment|
56
- segment.extraction_code
57
- end.compact * "\n"
58
- end
59
-
60
- # Produce a condition expression that will check the requirements of this route
61
- # upon generation.
62
- def generation_requirements
63
- requirement_conditions = requirements.collect do |key, req|
64
- if req.is_a? Regexp
65
- value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
66
- "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
67
- else
68
- "hash[:#{key}] == #{req.inspect}"
69
- end
70
- end
71
- requirement_conditions * ' && ' unless requirement_conditions.empty?
72
- end
73
-
74
- def generation_structure
75
- segments.last.string_structure segments[0..-2]
76
- end
77
-
78
- # Write and compile a +recognize+ method for this Route.
79
- def write_recognition
80
- # Create an if structure to extract the params from a match if it occurs.
81
- body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
82
- body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
83
-
84
- # Build the method declaration and compile it
85
- method_decl = "def recognize(path, env={})\n#{body}\nend"
86
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
87
- method_decl
88
- end
89
-
90
- # Plugins may override this method to add other conditions, like checks on
91
- # host, subdomain, and so forth. Note that changes here only affect route
92
- # recognition, not generation.
93
- def recognition_conditions
94
- result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
95
- result << "conditions[:method] === env[:method]" if conditions[:method]
96
- result
97
- end
98
-
99
- # Build the regular expression pattern that will match this route.
100
- def recognition_pattern(wrap = true)
101
- pattern = ''
102
- segments.reverse_each do |segment|
103
- pattern = segment.build_pattern pattern
104
- end
105
- wrap ? ("\\A" + pattern + "\\Z") : pattern
106
- end
107
-
108
- # Write the code to extract the parameters from a matched route.
109
- def recognition_extraction
110
- next_capture = 1
111
- extraction = segments.collect do |segment|
112
- x = segment.match_extraction(next_capture)
113
- next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
114
- x
115
- end
116
- extraction.compact
117
- end
118
-
119
- # Write the real generation implementation and then resend the message.
120
- def generate(options, hash, expire_on = {})
121
- write_generation
122
- generate options, hash, expire_on
123
- end
124
-
125
- def generate_extras(options, hash, expire_on = {})
126
- write_generation
127
- generate_extras options, hash, expire_on
128
- end
129
-
130
- # Generate the query string with any extra keys in the hash and append
131
- # it to the given path, returning the new path.
132
- def append_query_string(path, hash, query_keys=nil)
133
- return nil unless path
134
- query_keys ||= extra_keys(hash)
135
- "#{path}#{build_query_string(hash, query_keys)}"
136
- end
137
-
138
- # Determine which keys in the given hash are "extra". Extra keys are
139
- # those that were not used to generate a particular route. The extra
140
- # keys also do not include those recalled from the prior request, nor
141
- # do they include any keys that were implied in the route (like a
142
- # <tt>:controller</tt> that is required, but not explicitly used in the
143
- # text of the route.)
144
- def extra_keys(hash, recall={})
145
- (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
146
- end
147
-
148
39
  # Build a query string from the keys of the given hash. If +only_keys+
149
40
  # is given (as an array), only the keys indicated will be used to build
150
41
  # the query string. The query string will correctly build array parameter
@@ -161,12 +52,6 @@ module ActionController
161
52
  elements.empty? ? '' : "?#{elements.sort * '&'}"
162
53
  end
163
54
 
164
- # Write the real recognition implementation and then resend the message.
165
- def recognize(path, environment={})
166
- write_recognition
167
- recognize path, environment
168
- end
169
-
170
55
  # A route's parameter shell contains parameter values that are not in the
171
56
  # route's path, but should be placed in the recognized hash.
172
57
  #
@@ -186,7 +71,7 @@ module ActionController
186
71
  # includes keys that appear inside the path, and keys that have requirements
187
72
  # placed upon them.
188
73
  def significant_keys
189
- @significant_keys ||= returning [] do |sk|
74
+ @significant_keys ||= returning([]) do |sk|
190
75
  segments.each { |segment| sk << segment.key if segment.respond_to? :key }
191
76
  sk.concat requirements.keys
192
77
  sk.uniq!
@@ -209,12 +94,7 @@ module ActionController
209
94
  end
210
95
 
211
96
  def matches_controller_and_action?(controller, action)
212
- unless defined? @matching_prepared
213
- @controller_requirement = requirement_for(:controller)
214
- @action_requirement = requirement_for(:action)
215
- @matching_prepared = true
216
- end
217
-
97
+ prepare_matching!
218
98
  (@controller_requirement.nil? || @controller_requirement === controller) &&
219
99
  (@action_requirement.nil? || @action_requirement === action)
220
100
  end
@@ -226,15 +106,150 @@ module ActionController
226
106
  end
227
107
  end
228
108
 
229
- protected
230
- def requirement_for(key)
231
- return requirements[key] if requirements.key? key
232
- segments.each do |segment|
233
- return segment.regexp if segment.respond_to?(:key) && segment.key == key
109
+ # TODO: Route should be prepared and frozen on initialize
110
+ def freeze
111
+ unless frozen?
112
+ write_generation!
113
+ write_recognition!
114
+ prepare_matching!
115
+
116
+ parameter_shell
117
+ significant_keys
118
+ defaults
119
+ to_s
234
120
  end
235
- nil
121
+
122
+ super
236
123
  end
237
124
 
125
+ private
126
+ def requirement_for(key)
127
+ return requirements[key] if requirements.key? key
128
+ segments.each do |segment|
129
+ return segment.regexp if segment.respond_to?(:key) && segment.key == key
130
+ end
131
+ nil
132
+ end
133
+
134
+ # Write and compile a +generate+ method for this Route.
135
+ def write_generation!
136
+ # Build the main body of the generation
137
+ body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
138
+
139
+ # If we have conditions that must be tested first, nest the body inside an if
140
+ body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
141
+ args = "options, hash, expire_on = {}"
142
+
143
+ # Nest the body inside of a def block, and then compile it.
144
+ raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
145
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
146
+
147
+ # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
148
+ # are the same as the keys that were recalled from the previous request. Thus,
149
+ # we can use the expire_on.keys to determine which keys ought to be used to build
150
+ # the query string. (Never use keys from the recalled request when building the
151
+ # query string.)
152
+
153
+ method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
154
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
155
+
156
+ method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
157
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
158
+ raw_method
159
+ end
160
+
161
+ # Build several lines of code that extract values from the options hash. If any
162
+ # of the values are missing or rejected then a return will be executed.
163
+ def generation_extraction
164
+ segments.collect do |segment|
165
+ segment.extraction_code
166
+ end.compact * "\n"
167
+ end
168
+
169
+ # Produce a condition expression that will check the requirements of this route
170
+ # upon generation.
171
+ def generation_requirements
172
+ requirement_conditions = requirements.collect do |key, req|
173
+ if req.is_a? Regexp
174
+ value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
175
+ "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
176
+ else
177
+ "hash[:#{key}] == #{req.inspect}"
178
+ end
179
+ end
180
+ requirement_conditions * ' && ' unless requirement_conditions.empty?
181
+ end
182
+
183
+ def generation_structure
184
+ segments.last.string_structure segments[0..-2]
185
+ end
186
+
187
+ # Write and compile a +recognize+ method for this Route.
188
+ def write_recognition!
189
+ # Create an if structure to extract the params from a match if it occurs.
190
+ body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
191
+ body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
192
+
193
+ # Build the method declaration and compile it
194
+ method_decl = "def recognize(path, env = {})\n#{body}\nend"
195
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
196
+ method_decl
197
+ end
198
+
199
+ # Plugins may override this method to add other conditions, like checks on
200
+ # host, subdomain, and so forth. Note that changes here only affect route
201
+ # recognition, not generation.
202
+ def recognition_conditions
203
+ result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
204
+ result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
205
+ result
206
+ end
207
+
208
+ # Build the regular expression pattern that will match this route.
209
+ def recognition_pattern(wrap = true)
210
+ pattern = ''
211
+ segments.reverse_each do |segment|
212
+ pattern = segment.build_pattern pattern
213
+ end
214
+ wrap ? ("\\A" + pattern + "\\Z") : pattern
215
+ end
216
+
217
+ # Write the code to extract the parameters from a matched route.
218
+ def recognition_extraction
219
+ next_capture = 1
220
+ extraction = segments.collect do |segment|
221
+ x = segment.match_extraction(next_capture)
222
+ next_capture += segment.number_of_captures
223
+ x
224
+ end
225
+ extraction.compact
226
+ end
227
+
228
+ # Generate the query string with any extra keys in the hash and append
229
+ # it to the given path, returning the new path.
230
+ def append_query_string(path, hash, query_keys = nil)
231
+ return nil unless path
232
+ query_keys ||= extra_keys(hash)
233
+ "#{path}#{build_query_string(hash, query_keys)}"
234
+ end
235
+
236
+ # Determine which keys in the given hash are "extra". Extra keys are
237
+ # those that were not used to generate a particular route. The extra
238
+ # keys also do not include those recalled from the prior request, nor
239
+ # do they include any keys that were implied in the route (like a
240
+ # <tt>:controller</tt> that is required, but not explicitly used in the
241
+ # text of the route.)
242
+ def extra_keys(hash, recall = {})
243
+ (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
244
+ end
245
+
246
+ def prepare_matching!
247
+ unless defined? @matching_prepared
248
+ @controller_requirement = requirement_for(:controller)
249
+ @action_requirement = requirement_for(:action)
250
+ @matching_prepared = true
251
+ end
252
+ end
238
253
  end
239
254
  end
240
255
  end