actionpack 1.8.1 → 1.9.0
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.
- data/CHANGELOG +309 -16
- data/README +1 -1
- data/lib/action_controller.rb +5 -0
- data/lib/action_controller/assertions.rb +57 -12
- data/lib/action_controller/auto_complete.rb +47 -0
- data/lib/action_controller/base.rb +288 -258
- data/lib/action_controller/benchmarking.rb +8 -3
- data/lib/action_controller/caching.rb +88 -42
- data/lib/action_controller/cgi_ext/cgi_ext.rb +1 -1
- data/lib/action_controller/cgi_ext/cgi_methods.rb +41 -11
- data/lib/action_controller/cgi_ext/multipart_progress.rb +169 -0
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +30 -12
- data/lib/action_controller/cgi_process.rb +39 -11
- data/lib/action_controller/code_generation.rb +235 -0
- data/lib/action_controller/cookies.rb +14 -8
- data/lib/action_controller/deprecated_renders_and_redirects.rb +76 -0
- data/lib/action_controller/filters.rb +8 -7
- data/lib/action_controller/helpers.rb +41 -6
- data/lib/action_controller/layout.rb +45 -16
- data/lib/action_controller/request.rb +86 -23
- data/lib/action_controller/rescue.rb +1 -0
- data/lib/action_controller/response.rb +1 -1
- data/lib/action_controller/routing.rb +536 -272
- data/lib/action_controller/scaffolding.rb +30 -25
- data/lib/action_controller/session/active_record_store.rb +251 -50
- data/lib/action_controller/streaming.rb +133 -0
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -7
- data/lib/action_controller/templates/scaffolds/edit.rhtml +2 -2
- data/lib/action_controller/templates/scaffolds/layout.rhtml +22 -18
- data/lib/action_controller/templates/scaffolds/list.rhtml +3 -3
- data/lib/action_controller/templates/scaffolds/new.rhtml +2 -2
- data/lib/action_controller/templates/scaffolds/show.rhtml +1 -1
- data/lib/action_controller/test_process.rb +68 -47
- data/lib/action_controller/upload_progress.rb +421 -0
- data/lib/action_controller/url_rewriter.rb +8 -11
- data/lib/action_controller/vendor/html-scanner/html/document.rb +6 -5
- data/lib/action_controller/vendor/html-scanner/html/node.rb +70 -14
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +17 -10
- data/lib/action_controller/vendor/html-scanner/html/version.rb +3 -3
- data/lib/action_controller/vendor/xml_simple.rb +1019 -0
- data/lib/action_controller/verification.rb +36 -30
- data/lib/action_view/base.rb +21 -14
- data/lib/action_view/helpers/active_record_helper.rb +15 -13
- data/lib/action_view/helpers/asset_tag_helper.rb +26 -9
- data/lib/action_view/helpers/benchmark_helper.rb +24 -0
- data/lib/action_view/helpers/capture_helper.rb +7 -5
- data/lib/action_view/helpers/date_helper.rb +63 -46
- data/lib/action_view/helpers/form_helper.rb +7 -1
- data/lib/action_view/helpers/form_options_helper.rb +19 -11
- data/lib/action_view/helpers/form_tag_helper.rb +5 -1
- data/lib/action_view/helpers/javascript_helper.rb +403 -35
- data/lib/action_view/helpers/javascripts/controls.js +261 -0
- data/lib/action_view/helpers/javascripts/dragdrop.js +476 -0
- data/lib/action_view/helpers/javascripts/effects.js +570 -0
- data/lib/action_view/helpers/javascripts/prototype.js +633 -371
- data/lib/action_view/helpers/number_helper.rb +11 -13
- data/lib/action_view/helpers/tag_helper.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +69 -6
- data/lib/action_view/helpers/upload_progress_helper.rb +433 -0
- data/lib/action_view/helpers/url_helper.rb +98 -3
- data/lib/action_view/partials.rb +14 -8
- data/lib/action_view/vendor/builder/xmlmarkup.rb +11 -0
- data/rakefile +13 -5
- data/test/abstract_unit.rb +1 -1
- data/test/controller/action_pack_assertions_test.rb +52 -9
- data/test/controller/active_record_assertions_test.rb +119 -120
- data/test/controller/active_record_store_test.rb +111 -0
- data/test/controller/addresses_render_test.rb +45 -0
- data/test/controller/caching_filestore.rb +92 -0
- data/test/controller/capture_test.rb +39 -0
- data/test/controller/cgi_test.rb +40 -3
- data/test/controller/helper_test.rb +65 -13
- data/test/controller/multipart_progress_testx.rb +365 -0
- data/test/controller/new_render_test.rb +263 -0
- data/test/controller/redirect_test.rb +64 -0
- data/test/controller/render_test.rb +20 -21
- data/test/controller/request_test.rb +83 -3
- data/test/controller/routing_test.rb +702 -0
- data/test/controller/send_file_test.rb +2 -0
- data/test/controller/test_test.rb +44 -8
- data/test/controller/upload_progress_testx.rb +89 -0
- data/test/controller/verification_test.rb +94 -29
- data/test/fixtures/addresses/list.rhtml +1 -0
- data/test/fixtures/test/capturing.rhtml +4 -0
- data/test/fixtures/test/list.rhtml +1 -1
- data/test/fixtures/test/update_element_with_capture.rhtml +9 -0
- data/test/template/active_record_helper_test.rb +30 -15
- data/test/template/asset_tag_helper_test.rb +12 -5
- data/test/template/benchmark_helper_test.rb +72 -0
- data/test/template/date_helper_test.rb +69 -0
- data/test/template/form_helper_test.rb +18 -10
- data/test/template/form_options_helper_test.rb +40 -5
- data/test/template/javascript_helper.rb +149 -2
- data/test/template/number_helper_test.rb +2 -0
- data/test/template/tag_helper_test.rb +4 -0
- data/test/template/text_helper_test.rb +36 -0
- data/test/template/upload_progress_helper_testx.rb +272 -0
- data/test/template/url_helper_test.rb +30 -0
- metadata +30 -6
- data/test/controller/layout_test.rb +0 -49
- data/test/controller/routing_tests.rb +0 -543
@@ -69,7 +69,7 @@ module ActionController #:nodoc:
|
|
69
69
|
# Or just as a quick test. It works like this:
|
70
70
|
#
|
71
71
|
# class WeblogController < ActionController::Base
|
72
|
-
# before_filter { |controller|
|
72
|
+
# before_filter { |controller| false if controller.params["stop_action"] }
|
73
73
|
# end
|
74
74
|
#
|
75
75
|
# As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
|
@@ -202,10 +202,11 @@ module ActionController #:nodoc:
|
|
202
202
|
# A#after
|
203
203
|
# B#after
|
204
204
|
def append_around_filter(*filters)
|
205
|
+
conditions = extract_conditions!(filters)
|
205
206
|
for filter in filters.flatten
|
206
207
|
ensure_filter_responds_to_before_and_after(filter)
|
207
|
-
append_before_filter { |c| filter.before(c) }
|
208
|
-
prepend_after_filter { |c| filter.after(c) }
|
208
|
+
append_before_filter(conditions || {}) { |c| filter.before(c) }
|
209
|
+
prepend_after_filter(conditions || {}) { |c| filter.after(c) }
|
209
210
|
end
|
210
211
|
end
|
211
212
|
|
@@ -338,10 +339,10 @@ module ActionController #:nodoc:
|
|
338
339
|
|
339
340
|
def action_exempted?(filter)
|
340
341
|
case
|
341
|
-
when self.class.included_actions[filter]
|
342
|
-
!
|
343
|
-
when self.class.excluded_actions[filter]
|
344
|
-
|
342
|
+
when ia = self.class.included_actions[filter]
|
343
|
+
!ia.include?(action_name)
|
344
|
+
when ea = self.class.excluded_actions[filter]
|
345
|
+
ea.include?(action_name)
|
345
346
|
end
|
346
347
|
end
|
347
348
|
end
|
@@ -2,8 +2,26 @@ module ActionController #:nodoc:
|
|
2
2
|
module Helpers #:nodoc:
|
3
3
|
def self.append_features(base)
|
4
4
|
super
|
5
|
-
|
5
|
+
|
6
|
+
# Initialize the base module to aggregate its helpers.
|
7
|
+
base.class_inheritable_accessor :master_helper_module
|
8
|
+
base.master_helper_module = Module.new
|
9
|
+
|
10
|
+
# Extend base with class methods to declare helpers.
|
6
11
|
base.extend(ClassMethods)
|
12
|
+
|
13
|
+
base.class_eval do
|
14
|
+
# Wrap inherited to create a new master helper module for subclasses.
|
15
|
+
class << self
|
16
|
+
alias_method :inherited_without_helper, :inherited
|
17
|
+
alias_method :inherited, :inherited_with_helper
|
18
|
+
end
|
19
|
+
|
20
|
+
# Wrap initialize_template_class to extend new template class
|
21
|
+
# instances with the master helper module.
|
22
|
+
alias_method :initialize_template_class_without_helper, :initialize_template_class
|
23
|
+
alias_method :initialize_template_class, :initialize_template_class_with_helper
|
24
|
+
end
|
7
25
|
end
|
8
26
|
|
9
27
|
# The template helpers serves to relieve the templates from including the same inline code again and again. It's a
|
@@ -32,7 +50,7 @@ module ActionController #:nodoc:
|
|
32
50
|
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
|
33
51
|
# available to the templates.
|
34
52
|
def add_template_helper(helper_module) #:nodoc:
|
35
|
-
|
53
|
+
master_helper_module.module_eval "include #{helper_module}"
|
36
54
|
end
|
37
55
|
|
38
56
|
# Declare a helper:
|
@@ -68,7 +86,7 @@ module ActionController #:nodoc:
|
|
68
86
|
end
|
69
87
|
|
70
88
|
# Evaluate block in template class if given.
|
71
|
-
|
89
|
+
master_helper_module.module_eval(&block) if block_given?
|
72
90
|
end
|
73
91
|
|
74
92
|
# Declare a controller method as a helper. For example,
|
@@ -76,7 +94,13 @@ module ActionController #:nodoc:
|
|
76
94
|
# def link_to(name, options) ... end
|
77
95
|
# makes the link_to controller method available in the view.
|
78
96
|
def helper_method(*methods)
|
79
|
-
|
97
|
+
methods.flatten.each do |method|
|
98
|
+
master_helper_module.module_eval <<-end_eval
|
99
|
+
def #{method}(*args, &block)
|
100
|
+
controller.send(%(#{method}), *args, &block)
|
101
|
+
end
|
102
|
+
end_eval
|
103
|
+
end
|
80
104
|
end
|
81
105
|
|
82
106
|
# Declare a controller attribute as a helper. For example,
|
@@ -89,13 +113,24 @@ module ActionController #:nodoc:
|
|
89
113
|
end
|
90
114
|
|
91
115
|
private
|
92
|
-
def
|
116
|
+
def inherited_with_helper(child)
|
93
117
|
inherited_without_helper(child)
|
94
|
-
begin
|
118
|
+
begin
|
119
|
+
child.master_helper_module = Module.new
|
120
|
+
child.master_helper_module.send :include, master_helper_module
|
121
|
+
child.helper child.controller_path
|
95
122
|
rescue MissingSourceFile => e
|
96
123
|
raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
|
97
124
|
end
|
98
125
|
end
|
99
126
|
end
|
127
|
+
|
128
|
+
private
|
129
|
+
# Extend the template class instance with our controller's helper module.
|
130
|
+
def initialize_template_class_with_helper(response)
|
131
|
+
returning(initialize_template_class_without_helper(response)) do
|
132
|
+
response.template.extend self.class.master_helper_module
|
133
|
+
end
|
134
|
+
end
|
100
135
|
end
|
101
136
|
end
|
@@ -3,8 +3,9 @@ module ActionController #:nodoc:
|
|
3
3
|
def self.append_features(base)
|
4
4
|
super
|
5
5
|
base.class_eval do
|
6
|
-
alias_method :
|
7
|
-
alias_method :render, :
|
6
|
+
alias_method :render_with_no_layout, :render
|
7
|
+
alias_method :render, :render_with_a_layout
|
8
|
+
|
8
9
|
class << self
|
9
10
|
alias_method :inherited_without_layout, :inherited
|
10
11
|
end
|
@@ -124,7 +125,7 @@ module ActionController #:nodoc:
|
|
124
125
|
#
|
125
126
|
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
|
126
127
|
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
|
127
|
-
#
|
128
|
+
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
|
128
129
|
#
|
129
130
|
# class WeblogController < ActionController::Base
|
130
131
|
# layout "weblog_standard", :except => :rss
|
@@ -136,19 +137,19 @@ module ActionController #:nodoc:
|
|
136
137
|
# This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
|
137
138
|
# around the rendered view.
|
138
139
|
#
|
139
|
-
# Both the
|
140
|
-
# is valid, as is
|
140
|
+
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
|
141
|
+
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
|
141
142
|
#
|
142
143
|
# == Using a different layout in the action render call
|
143
144
|
#
|
144
145
|
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
|
145
146
|
# Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
|
146
|
-
# This is possible using <tt>
|
147
|
+
# This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
|
147
148
|
# qualified template and layout names as this example shows:
|
148
149
|
#
|
149
150
|
# class WeblogController < ActionController::Base
|
150
151
|
# def help
|
151
|
-
#
|
152
|
+
# render :action => "help/index", :layout => "help"
|
152
153
|
# end
|
153
154
|
# end
|
154
155
|
#
|
@@ -201,21 +202,50 @@ module ActionController #:nodoc:
|
|
201
202
|
active_layout.include?("/") ? active_layout : "layouts/#{active_layout}" if active_layout
|
202
203
|
end
|
203
204
|
|
204
|
-
def
|
205
|
-
|
205
|
+
def render_with_a_layout(options = {}, deprecated_status = nil, deprecated_layout = nil) #:nodoc:
|
206
|
+
options = render_with_a_layout_options(options)
|
207
|
+
if (layout = pick_layout(options, deprecated_layout))
|
208
|
+
logger.info("Rendering #{options[:template]} within #{layout}") unless logger.nil?
|
209
|
+
|
210
|
+
@content_for_layout = render_with_no_layout(options.merge(:layout => false))
|
211
|
+
erase_render_results
|
212
|
+
|
206
213
|
add_variables_to_assigns
|
207
|
-
|
208
|
-
@content_for_layout = @template.render_file(template_name, true)
|
209
|
-
render_without_layout(layout, status)
|
214
|
+
render_with_no_layout(options.merge({ :text => @template.render_file(layout, true), :status => options[:status] || deprecated_status }))
|
210
215
|
else
|
211
|
-
|
216
|
+
render_with_no_layout(options, deprecated_status)
|
212
217
|
end
|
213
218
|
end
|
214
219
|
|
215
220
|
private
|
216
|
-
|
221
|
+
def render_with_a_layout_options(options)
|
222
|
+
return { :template => options } unless options.is_a?(Hash)
|
223
|
+
if options.values_at(:text, :file, :inline, :partial, :nothing).compact.empty?
|
224
|
+
options
|
225
|
+
else
|
226
|
+
{ :layout => false }.merge(options)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def pick_layout(options = {}, deprecated_layout = nil)
|
231
|
+
return deprecated_layout if !deprecated_layout.nil?
|
232
|
+
|
233
|
+
if options.is_a?(Hash)
|
234
|
+
case options[:layout]
|
235
|
+
when FalseClass
|
236
|
+
nil
|
237
|
+
when NilClass, TrueClass
|
238
|
+
active_layout if action_has_layout?
|
239
|
+
else
|
240
|
+
active_layout(options[:layout])
|
241
|
+
end
|
242
|
+
else
|
243
|
+
(deprecated_layout || active_layout) if action_has_layout?
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
217
247
|
def action_has_layout?
|
218
|
-
conditions = self.class.layout_conditions
|
248
|
+
conditions = self.class.layout_conditions || {}
|
219
249
|
case
|
220
250
|
when conditions[:only]
|
221
251
|
conditions[:only].include?(action_name)
|
@@ -225,6 +255,5 @@ module ActionController #:nodoc:
|
|
225
255
|
true
|
226
256
|
end
|
227
257
|
end
|
228
|
-
|
229
258
|
end
|
230
259
|
end
|
@@ -1,37 +1,90 @@
|
|
1
1
|
module ActionController
|
2
2
|
# These methods are available in both the production and test Request objects.
|
3
3
|
class AbstractRequest
|
4
|
+
cattr_accessor :relative_url_root
|
5
|
+
|
4
6
|
# Returns both GET and POST parameters in a single hash.
|
5
7
|
def parameters
|
6
|
-
# puts "#{request_parameters.inspect} | #{query_parameters.inspect} | #{path_parameters.inspect}"
|
7
8
|
@parameters ||= request_parameters.merge(query_parameters).merge(path_parameters).with_indifferent_access
|
8
9
|
end
|
9
10
|
|
11
|
+
# Returns the HTTP request method as a lowercase symbol (:get, for example)
|
10
12
|
def method
|
11
|
-
env['REQUEST_METHOD'].downcase.
|
13
|
+
env['REQUEST_METHOD'].downcase.to_sym
|
12
14
|
end
|
13
15
|
|
16
|
+
# Is this a GET request? Equivalent to request.method == :get
|
14
17
|
def get?
|
15
18
|
method == :get
|
16
19
|
end
|
17
20
|
|
21
|
+
# Is this a POST request? Equivalent to request.method == :post
|
18
22
|
def post?
|
19
23
|
method == :post
|
20
24
|
end
|
21
25
|
|
26
|
+
# Is this a PUT request? Equivalent to request.method == :put
|
22
27
|
def put?
|
23
28
|
method == :put
|
24
29
|
end
|
25
30
|
|
31
|
+
# Is this a DELETE request? Equivalent to request.method == :delete
|
26
32
|
def delete?
|
27
33
|
method == :delete
|
28
34
|
end
|
29
35
|
|
36
|
+
# Is this a HEAD request? Equivalent to request.method == :head
|
30
37
|
def head?
|
31
38
|
method == :head
|
32
39
|
end
|
33
|
-
|
34
|
-
|
40
|
+
|
41
|
+
# Determine whether the body of a POST request is URL-encoded (default),
|
42
|
+
# XML, or YAML by checking the Content-Type HTTP header:
|
43
|
+
#
|
44
|
+
# Content-Type Post Format
|
45
|
+
# application/xml :xml
|
46
|
+
# text/xml :xml
|
47
|
+
# application/x-yaml :yaml
|
48
|
+
# text/x-yaml :yaml
|
49
|
+
# * :url_encoded
|
50
|
+
#
|
51
|
+
# For backward compatibility, the post format is extracted from the
|
52
|
+
# X-Post-Data-Format HTTP header if present.
|
53
|
+
def post_format
|
54
|
+
if env['HTTP_X_POST_DATA_FORMAT']
|
55
|
+
env['HTTP_X_POST_DATA_FORMAT'].downcase.to_sym
|
56
|
+
else
|
57
|
+
case env['CONTENT_TYPE'].to_s.downcase
|
58
|
+
when 'application/xml', 'text/xml' then :xml
|
59
|
+
when 'application/x-yaml', 'text/x-yaml' then :yaml
|
60
|
+
else :url_encoded
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Is this a POST request formatted as XML or YAML?
|
66
|
+
def formatted_post?
|
67
|
+
[ :xml, :yaml ].include?(post_format) && post?
|
68
|
+
end
|
69
|
+
|
70
|
+
# Is this a POST request formatted as XML?
|
71
|
+
def xml_post?
|
72
|
+
post_format == :xml && post?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Is this a POST request formatted as YAML?
|
76
|
+
def yaml_post?
|
77
|
+
post_format == :yaml && post?
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns true if the request's "X-Requested-With" header contains
|
81
|
+
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
|
82
|
+
# every Ajax request.)
|
83
|
+
def xml_http_request?
|
84
|
+
not /XMLHttpRequest/i.match(env['HTTP_X_REQUESTED_WITH']).nil?
|
85
|
+
end
|
86
|
+
alias xhr? :xml_http_request?
|
87
|
+
|
35
88
|
# Determine originating IP address. REMOTE_ADDR is the standard
|
36
89
|
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
|
37
90
|
# HTTP_X_FORWARDED_FOR are set by proxies so check for these before
|
@@ -83,61 +136,71 @@ module ActionController
|
|
83
136
|
request_uri += '?' + env["QUERY_STRING"] unless env["QUERY_STRING"].nil? || env["QUERY_STRING"].empty?
|
84
137
|
return request_uri
|
85
138
|
end
|
86
|
-
|
139
|
+
end
|
87
140
|
|
141
|
+
# Return 'https://' if this is an SSL request and 'http://' otherwise.
|
88
142
|
def protocol
|
89
143
|
env["HTTPS"] == "on" ? 'https://' : 'http://'
|
90
144
|
end
|
91
145
|
|
146
|
+
# Is this an SSL request?
|
92
147
|
def ssl?
|
93
148
|
protocol == 'https://'
|
94
149
|
end
|
95
150
|
|
96
|
-
#
|
97
|
-
# all the installation directory of this application was taken into account
|
151
|
+
# Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
|
98
152
|
def path
|
99
|
-
path = request_uri ?
|
153
|
+
path = (uri = request_uri) ? uri.split('?').first : ''
|
100
154
|
|
101
|
-
#
|
102
|
-
|
155
|
+
# Cut off the path to the installation directory if given
|
156
|
+
if root = relative_url_root
|
157
|
+
path[root.length..-1]
|
158
|
+
else
|
159
|
+
path
|
160
|
+
end
|
103
161
|
end
|
104
162
|
|
105
|
-
#
|
106
|
-
#
|
163
|
+
# Returns the path minus the web server relative installation directory.
|
164
|
+
# This method returns nil unless the web server is apache.
|
107
165
|
def relative_url_root
|
108
|
-
File.dirname(env["SCRIPT_NAME"].to_s).gsub
|
166
|
+
@@relative_url_root ||= File.dirname(env["SCRIPT_NAME"].to_s).gsub(/(^\.$|^\/$)/, '') if server_software == 'apache'
|
109
167
|
end
|
110
168
|
|
169
|
+
# Returns the port number of this request as an integer.
|
111
170
|
def port
|
112
171
|
env['SERVER_PORT'].to_i
|
113
172
|
end
|
114
173
|
|
115
|
-
# Returns a
|
174
|
+
# Returns a port suffix like ":8080" if the port number of this request
|
175
|
+
# is not the default HTTP port 80 or HTTPS port 443.
|
116
176
|
def port_string
|
117
177
|
(protocol == 'http://' && port == 80) || (protocol == 'https://' && port == 443) ? '' : ":#{port}"
|
118
178
|
end
|
119
179
|
|
180
|
+
# Returns a host:port string for this request, such as example.com or
|
181
|
+
# example.com:8080.
|
120
182
|
def host_with_port
|
121
183
|
env['HTTP_HOST'] || host + port_string
|
122
184
|
end
|
123
185
|
|
124
186
|
def path_parameters=(parameters)
|
125
187
|
@path_parameters = parameters
|
126
|
-
@parameters = nil
|
188
|
+
@symbolized_path_parameters = @parameters = nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def symbolized_path_parameters
|
192
|
+
@symbolized_path_parameters ||= path_parameters.symbolize_keys
|
127
193
|
end
|
128
194
|
|
129
195
|
def path_parameters
|
130
196
|
@path_parameters ||= {}
|
131
197
|
end
|
132
|
-
|
133
|
-
# Returns
|
134
|
-
|
135
|
-
|
136
|
-
def xml_http_request?
|
137
|
-
env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
|
198
|
+
|
199
|
+
# Returns the lowercase name of the HTTP server software.
|
200
|
+
def server_software
|
201
|
+
(env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
138
202
|
end
|
139
|
-
|
140
|
-
|
203
|
+
|
141
204
|
#--
|
142
205
|
# Must be implemented in the concrete request
|
143
206
|
#++
|
@@ -25,6 +25,7 @@ module ActionController #:nodoc:
|
|
25
25
|
# Exception handler called when the performance of an action raises an exception.
|
26
26
|
def rescue_action(exception)
|
27
27
|
log_error(exception) unless logger.nil?
|
28
|
+
erase_render_results if performed?
|
28
29
|
|
29
30
|
if consider_all_requests_local || local_request?
|
30
31
|
rescue_action_locally(exception)
|
@@ -8,7 +8,7 @@ module ActionController
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def redirect(to_url, permanently = false)
|
11
|
-
@headers["Status"] =
|
11
|
+
@headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
|
12
12
|
@headers["location"] = to_url
|
13
13
|
|
14
14
|
@body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
@@ -1,342 +1,606 @@
|
|
1
1
|
module ActionController
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
2
|
+
module Routing #:nodoc:
|
3
|
+
class << self
|
4
|
+
def expiry_hash(options, recall)
|
5
|
+
k = v = nil
|
6
|
+
expire_on = {}
|
7
|
+
options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))}
|
8
|
+
expire_on
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract_parameter_value(parameter) #:nodoc:
|
12
|
+
CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s)
|
13
|
+
end
|
14
|
+
def controller_relative_to(controller, previous)
|
15
|
+
if controller.nil? then previous
|
16
|
+
elsif controller[0] == ?/ then controller[1..-1]
|
17
|
+
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
|
18
|
+
else controller
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def treat_hash(hash)
|
23
|
+
k = v = nil
|
12
24
|
hash.each do |k, v|
|
13
|
-
|
14
|
-
if v.kind_of? Regexp
|
15
|
-
raise ArgumentError, "Regexp requirement on #{k}, but #{k} is not in this route's path!" unless @items.include? k
|
16
|
-
@requirements[k] = v
|
17
|
-
else
|
18
|
-
(@items.include?(k) ? @defaults : @requirements)[k] = (v.nil? ? nil : v.to_s)
|
19
|
-
end
|
25
|
+
hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
|
20
26
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def test_condition(expression, condition)
|
33
|
+
case condition
|
34
|
+
when String then "(#{expression} == #{condition.inspect})"
|
35
|
+
when Regexp then
|
36
|
+
condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source
|
37
|
+
"(#{condition.inspect} =~ #{expression})"
|
38
|
+
when true then expression
|
39
|
+
when nil then "! #{expression}"
|
40
|
+
else
|
41
|
+
raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil"
|
25
42
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Component #:nodoc:
|
47
|
+
def dynamic?() false end
|
48
|
+
def optional?() false end
|
49
|
+
|
50
|
+
def key() nil end
|
51
|
+
|
52
|
+
def self.new(string, *args)
|
53
|
+
return super(string, *args) unless self == Component
|
54
|
+
case string
|
55
|
+
when ':controller' then ControllerComponent.new(:controller, *args)
|
56
|
+
when /^:(\w+)$/ then DynamicComponent.new($1, *args)
|
57
|
+
when /^\*(\w+)$/ then PathComponent.new($1, *args)
|
58
|
+
else StaticComponent.new(string, *args)
|
31
59
|
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class StaticComponent < Component #:nodoc:
|
64
|
+
attr_reader :value
|
65
|
+
|
66
|
+
def initialize(value)
|
67
|
+
@value = value
|
32
68
|
end
|
33
|
-
|
34
|
-
# Generate a URL given the provided options.
|
35
|
-
# All values in options should be symbols.
|
36
|
-
# Returns the path and the unused names in a 2 element array.
|
37
|
-
# If generation fails, [nil, nil] is returned
|
38
|
-
# Generation can fail because of a missing value, or because an equality check fails.
|
39
|
-
#
|
40
|
-
# Generate urls will be as short as possible. If the last component of a url is equal to the default value,
|
41
|
-
# then that component is removed. This is applied as many times as possible. So, your index controller's
|
42
|
-
# index action will generate []
|
43
|
-
def generate(options, defaults={})
|
44
|
-
non_matching = @requirements.keys.select {|name| ! passes_requirements?(name, options[name] || defaults[name])}
|
45
|
-
non_matching.collect! {|name| requirements_for(name)}
|
46
|
-
return nil, "Mismatching option#{'s' if non_matching.length > 1}:\n #{non_matching.join '\n '}" unless non_matching.empty?
|
47
|
-
|
48
|
-
used_names = @requirements.inject({}) {|hash, (k, v)| hash[k] = true; hash} # Mark requirements as used so they don't get put in the query params
|
49
|
-
components = @items.collect do |item|
|
50
69
|
|
51
|
-
|
52
|
-
|
70
|
+
def write_recognition(g)
|
71
|
+
g.if_next_matches(value) do |gp|
|
72
|
+
gp.move_forward {|gpp| gpp.continue}
|
73
|
+
end
|
74
|
+
end
|
53
75
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
76
|
+
def write_generation(g)
|
77
|
+
g.add_segment(value) {|gp| gp.continue }
|
78
|
+
end
|
79
|
+
end
|
58
80
|
|
59
|
-
|
60
|
-
|
61
|
-
|
81
|
+
class DynamicComponent < Component #:nodoc:
|
82
|
+
attr_reader :key, :default
|
83
|
+
attr_accessor :condition
|
84
|
+
|
85
|
+
def dynamic?() true end
|
86
|
+
def optional?() @optional end
|
62
87
|
|
63
|
-
|
88
|
+
def default=(default)
|
89
|
+
@optional = true
|
90
|
+
@default = default
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize(key, options = {})
|
94
|
+
@key = key.to_sym
|
95
|
+
@default, @condition = options[:default], options[:condition]
|
96
|
+
@optional = options.key?(:default)
|
97
|
+
end
|
64
98
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
value = Routing.extract_parameter_value(value).gsub(/%2F/, "/")
|
72
|
-
end
|
73
|
-
value
|
74
|
-
else
|
75
|
-
Routing.extract_parameter_value(value)
|
76
|
-
end
|
77
|
-
else
|
78
|
-
item
|
79
|
-
end
|
99
|
+
def default_check(g)
|
100
|
+
presence = "#{g.hash_value(key, !! default)}"
|
101
|
+
if default
|
102
|
+
"!(#{presence} && #{g.hash_value(key, false)} != #{default.inspect})"
|
103
|
+
else
|
104
|
+
"! #{presence}"
|
80
105
|
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def write_generation(g)
|
109
|
+
wrote_dropout = write_dropout_generation(g)
|
110
|
+
write_continue_generation(g, wrote_dropout)
|
111
|
+
end
|
112
|
+
|
113
|
+
def write_dropout_generation(g)
|
114
|
+
return false unless optional? && g.after.all? {|c| c.optional?}
|
115
|
+
|
116
|
+
check = [default_check(g)]
|
117
|
+
gp = g.dup # Use another generator to write the conditions after the first &&
|
118
|
+
# We do this to ensure that the generator will not assume x_value is set. It will
|
119
|
+
# not be set if it follows a false condition -- for example, false && (x = 2)
|
81
120
|
|
82
|
-
|
83
|
-
|
84
|
-
|
121
|
+
check += gp.after.map {|c| c.default_check gp}
|
122
|
+
gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def write_continue_generation(g, use_else)
|
127
|
+
test = Routing.test_condition(g.hash_value(key, true, default), condition || true)
|
128
|
+
check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test]
|
129
|
+
|
130
|
+
g.send(*check) do |gp|
|
131
|
+
gp.expire_for_keys(key) unless gp.after.empty?
|
132
|
+
add_segments_to(gp) {|gpp| gpp.continue}
|
85
133
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_segments_to(g)
|
137
|
+
g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp}
|
138
|
+
end
|
139
|
+
|
140
|
+
def recognition_check(g)
|
141
|
+
test_type = [true, nil].include?(condition) ? :presence : :constraint
|
142
|
+
|
143
|
+
prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : ''
|
144
|
+
check = prefix + Routing.test_condition(g.next_segment(true), condition || true)
|
145
|
+
|
146
|
+
g.if(check) {|gp| yield gp, test_type}
|
147
|
+
end
|
148
|
+
|
149
|
+
def write_recognition(g)
|
150
|
+
test_type = nil
|
151
|
+
recognition_check(g) do |gp, test_type|
|
152
|
+
assign_result(gp) {|gpp| gpp.continue}
|
95
153
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
154
|
+
|
155
|
+
if optional? && g.after.all? {|c| c.optional?}
|
156
|
+
call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"]
|
157
|
+
|
158
|
+
g.send(*call) do |gp|
|
159
|
+
assign_default(gp)
|
160
|
+
gp.after.each {|c| c.assign_default(gp)}
|
161
|
+
gp.finish(false)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def assign_result(g, with_default = false)
|
167
|
+
g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})"
|
168
|
+
g.move_forward {|gp| yield gp}
|
169
|
+
end
|
170
|
+
|
171
|
+
def assign_default(g)
|
172
|
+
g.constant_result key, default unless default.nil?
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class ControllerComponent < DynamicComponent #:nodoc:
|
177
|
+
def key() :controller end
|
178
|
+
|
179
|
+
def add_segments_to(g)
|
180
|
+
g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp}
|
181
|
+
end
|
182
|
+
|
183
|
+
def recognition_check(g)
|
184
|
+
g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"
|
185
|
+
g.if('controller_result') do |gp|
|
186
|
+
gp << 'controller_value, segments_to_controller = controller_result'
|
187
|
+
gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def assign_result(g)
|
192
|
+
g.result key, 'controller_value'
|
193
|
+
yield g
|
99
194
|
end
|
195
|
+
|
196
|
+
def assign_default(g)
|
197
|
+
ControllerComponent.assign_controller(g, default)
|
198
|
+
end
|
199
|
+
|
200
|
+
class << self
|
201
|
+
def assign_controller(g, controller)
|
202
|
+
expr = "::Controllers::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller"
|
203
|
+
g.result :controller, expr, true
|
204
|
+
end
|
205
|
+
|
206
|
+
def traverse_to_controller(segments, start_at = 0)
|
207
|
+
mod = ::Controllers
|
208
|
+
length = segments.length
|
209
|
+
index = start_at
|
210
|
+
mod_name = controller_name = segment = nil
|
100
211
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
def recognize(components, options={})
|
105
|
-
options = options.clone
|
106
|
-
components = components.clone
|
107
|
-
controller_class = nil
|
212
|
+
while index < length
|
213
|
+
return nil unless /^[a-z][a-z\d_]*$/ =~ (segment = segments[index])
|
214
|
+
index += 1
|
108
215
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
controller_class, remaining_components = eat_path_to_controller(components)
|
116
|
-
return nil, "No controller found at subpath #{components.join('/')}" if controller_class.nil?
|
117
|
-
components = remaining_components
|
118
|
-
end
|
119
|
-
options[:controller] = controller_class.controller_path
|
120
|
-
return nil, requirements_for(:controller) unless passes_requirements?(:controller, options[:controller])
|
121
|
-
elsif /^\*/ =~ item.to_s
|
122
|
-
if components.empty?
|
123
|
-
value = @defaults.has_key?(item) ? @defaults[item].clone : []
|
124
|
-
else
|
125
|
-
value = components.clone
|
126
|
-
end
|
127
|
-
value.collect! {|c| CGI.unescape c}
|
128
|
-
components = []
|
129
|
-
def value.to_s() self.join('/') end
|
130
|
-
options[item.to_s.sub(/^\*/,"").intern] = value
|
131
|
-
elsif item.kind_of? Symbol
|
132
|
-
value = components.shift || @defaults[item]
|
133
|
-
return nil, requirements_for(item) unless passes_requirements?(item, value)
|
134
|
-
options[item] = value.nil? ? value : CGI.unescape(value)
|
135
|
-
else
|
136
|
-
return nil, "No value available for component #{item.inspect}" if components.empty?
|
137
|
-
component = components.shift
|
138
|
-
return nil, "Value for component #{item.inspect} doesn't match #{component}" if component != item
|
216
|
+
mod_name = segment.camelize
|
217
|
+
controller_name = "#{mod_name}Controller"
|
218
|
+
|
219
|
+
return eval("mod::#{controller_name}", nil, 'routing.rb', __LINE__), (index - start_at) if mod.const_available?(controller_name)
|
220
|
+
return nil unless mod.const_available?(mod_name)
|
221
|
+
mod = eval("mod::#{mod_name}", nil, 'routing.rb', __LINE__)
|
139
222
|
end
|
140
223
|
end
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class PathComponent < DynamicComponent #:nodoc:
|
228
|
+
def optional?() true end
|
229
|
+
def default() '' end
|
230
|
+
def condition() nil end
|
231
|
+
|
232
|
+
def write_generation(g)
|
233
|
+
raise RoutingError, 'Path components must occur last' unless g.after.empty?
|
234
|
+
g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do
|
235
|
+
g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)"
|
236
|
+
g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish }
|
146
237
|
end
|
147
|
-
|
238
|
+
g.else { g.finish }
|
239
|
+
end
|
240
|
+
|
241
|
+
def write_recognition(g)
|
242
|
+
raise RoutingError, "Path components must occur last" unless g.after.empty?
|
243
|
+
|
244
|
+
start = g.index_name
|
245
|
+
start = "(#{start})" unless /^\w+$/ =~ start
|
246
|
+
|
247
|
+
value_expr = "#{g.path_name}[#{start}..-1] || []"
|
248
|
+
g.result key, "ActionController::Routing::PathComponent::Result.new(#{value_expr})"
|
249
|
+
g.finish(false)
|
250
|
+
end
|
251
|
+
|
252
|
+
class Result < ::Array
|
253
|
+
def to_s() join '/' end
|
254
|
+
end
|
255
|
+
end
|
148
256
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
257
|
+
class Route #:nodoc:
|
258
|
+
attr_accessor :components, :known
|
259
|
+
attr_reader :path, :options, :keys
|
260
|
+
|
261
|
+
def initialize(path, options = {})
|
262
|
+
@path, @options = path, options
|
263
|
+
|
264
|
+
initialize_components path
|
265
|
+
defaults, conditions = initialize_hashes options.dup
|
266
|
+
configure_components(defaults, conditions)
|
267
|
+
initialize_keys
|
153
268
|
end
|
154
|
-
|
269
|
+
|
155
270
|
def inspect
|
156
|
-
|
157
|
-
default_str = @defaults.empty? ? "" : " || #{@defaults.inspect}"
|
158
|
-
"<#{self.class.to_s} #{@items.collect{|c| c.kind_of?(String) ? c : c.inspect}.join('/').inspect}#{default_str}#{when_str}>"
|
271
|
+
"<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>"
|
159
272
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
273
|
+
|
274
|
+
def write_generation(generator = CodeGeneration::GenerationGenerator.new)
|
275
|
+
generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])
|
276
|
+
|
277
|
+
if known.empty? then generator.go
|
278
|
+
else generator.if(generator.check_conditions(known)) {|gp| gp.go }
|
279
|
+
end
|
280
|
+
|
281
|
+
generator
|
282
|
+
end
|
283
|
+
|
284
|
+
def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
|
285
|
+
g = generator.dup
|
286
|
+
g.share_locals_with generator
|
287
|
+
g.before, g.current, g.after = [], components.first, (components[1..-1] || [])
|
288
|
+
|
289
|
+
known.each do |key, value|
|
290
|
+
if key == :controller then ControllerComponent.assign_controller(g, value)
|
291
|
+
else g.constant_result(key, value)
|
172
292
|
end
|
173
|
-
return nil, nil # Path ended, but no controller found.
|
174
293
|
end
|
294
|
+
|
295
|
+
g.go
|
296
|
+
|
297
|
+
generator
|
298
|
+
end
|
299
|
+
|
300
|
+
def initialize_keys
|
301
|
+
@keys = (components.collect {|c| c.key} + known.keys).compact
|
302
|
+
@keys.freeze
|
303
|
+
end
|
304
|
+
|
305
|
+
def extra_keys(options)
|
306
|
+
options.keys - @keys
|
307
|
+
end
|
308
|
+
|
309
|
+
def matches_controller?(controller)
|
310
|
+
if known[:controller] then known[:controller] == controller
|
311
|
+
else
|
312
|
+
c = components.find {|c| c.key == :controller}
|
313
|
+
return false unless c
|
314
|
+
return c.condition.nil? || eval(Routing.test_condition('controller', c.condition))
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
protected
|
319
|
+
|
320
|
+
def initialize_components(path)
|
321
|
+
path = path.split('/') if path.is_a? String
|
322
|
+
path.shift if path.first.blank?
|
323
|
+
self.components = path.collect {|str| Component.new str}
|
324
|
+
end
|
325
|
+
|
326
|
+
def initialize_hashes(options)
|
327
|
+
path_keys = components.collect {|c| c.key }.compact
|
328
|
+
self.known = {}
|
329
|
+
defaults = options.delete(:defaults) || {}
|
330
|
+
conditions = options.delete(:require) || {}
|
331
|
+
conditions.update(options.delete(:requirements) || {})
|
175
332
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
items.pop if items.last == ""
|
180
|
-
@items = items
|
181
|
-
|
182
|
-
# Verify uniqueness of each component.
|
183
|
-
@items.inject({}) do |seen, item|
|
184
|
-
if item.kind_of? Symbol
|
185
|
-
raise ArgumentError, "Illegal route path -- duplicate item #{item}\n #{path.inspect}" if seen.key? item
|
186
|
-
seen[item] = true
|
333
|
+
options.each do |k, v|
|
334
|
+
if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v
|
335
|
+
else known[k] = v
|
187
336
|
end
|
188
|
-
seen
|
189
337
|
end
|
338
|
+
[defaults, conditions]
|
190
339
|
end
|
340
|
+
|
341
|
+
def configure_components(defaults, conditions)
|
342
|
+
components.each do |component|
|
343
|
+
if defaults.key?(component.key) then component.default = defaults[component.key]
|
344
|
+
elsif component.key == :action then component.default = 'index'
|
345
|
+
elsif component.key == :id then component.default = nil
|
346
|
+
end
|
191
347
|
|
192
|
-
|
193
|
-
def passes_requirements?(name, value)
|
194
|
-
return @defaults.key?(name) && @defaults[name].nil? if value.nil? # Make sure it's there if it should be
|
195
|
-
|
196
|
-
case @requirements[name]
|
197
|
-
when nil then true
|
198
|
-
when Regexp then
|
199
|
-
value = value.to_s
|
200
|
-
match = @requirements[name].match(value)
|
201
|
-
match && match[0].length == value.length
|
202
|
-
else
|
203
|
-
@requirements[name] == value.to_s
|
204
|
-
end
|
205
|
-
end
|
206
|
-
def requirements_for(name)
|
207
|
-
name = name.to_s.sub(/^\*/,"").intern if (/^\*/ =~ name.inspect)
|
208
|
-
presence = (@defaults.key?(name) && @defaults[name].nil?)
|
209
|
-
requirement = case @requirements[name]
|
210
|
-
when nil then nil
|
211
|
-
when Regexp then "match #{@requirements[name].inspect}"
|
212
|
-
else "be equal to #{@requirements[name].inspect}"
|
213
|
-
end
|
214
|
-
if presence && requirement then "#{name} must be present and #{requirement}"
|
215
|
-
elsif presence || requirement then "#{name} must #{requirement || 'be present'}"
|
216
|
-
else "#{name} has no requirements"
|
348
|
+
component.condition = conditions[component.key] if conditions.key?(component.key)
|
217
349
|
end
|
218
350
|
end
|
219
351
|
end
|
220
|
-
|
221
|
-
class RouteSet#:nodoc:
|
352
|
+
|
353
|
+
class RouteSet #:nodoc:
|
354
|
+
attr_reader :routes, :categories, :controller_to_selector
|
222
355
|
def initialize
|
223
356
|
@routes = []
|
357
|
+
@generation_methods = Hash.new(:generate_default_path)
|
224
358
|
end
|
225
359
|
|
226
|
-
def
|
227
|
-
|
228
|
-
|
360
|
+
def generate(options, request_or_recall_hash = {})
|
361
|
+
recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters
|
362
|
+
use_recall = true
|
363
|
+
|
364
|
+
controller = options[:controller]
|
365
|
+
options[:action] ||= 'index' if controller
|
366
|
+
recall_controller = recall[:controller]
|
367
|
+
if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/))
|
368
|
+
recall = {} if controller && controller[0] == ?/
|
369
|
+
options[:controller] = Routing.controller_relative_to(controller, recall_controller)
|
370
|
+
end
|
371
|
+
options = recall.dup if options.empty? # XXX move to url_rewriter?
|
372
|
+
Routing.treat_hash(options) # XXX Move inwards (to generated code) or inline?
|
373
|
+
merged = recall.merge(options)
|
374
|
+
expire_on = Routing.expiry_hash(options, recall)
|
375
|
+
|
376
|
+
path, keys = generate_path(merged, options, expire_on)
|
377
|
+
|
378
|
+
# Factor out?
|
379
|
+
extras = {}
|
380
|
+
k = nil
|
381
|
+
keys.each {|k| extras[k] = options[k]}
|
382
|
+
[path, extras]
|
229
383
|
end
|
230
|
-
|
231
|
-
|
384
|
+
|
385
|
+
def generate_path(merged, options, expire_on)
|
386
|
+
send @generation_methods[merged[:controller]], merged, options, expire_on
|
232
387
|
end
|
233
|
-
def
|
234
|
-
|
388
|
+
def generate_default_path(*args)
|
389
|
+
write_generation
|
390
|
+
generate_default_path(*args)
|
235
391
|
end
|
392
|
+
|
393
|
+
def write_generation
|
394
|
+
method_sources = []
|
395
|
+
@generation_methods = Hash.new(:generate_default_path)
|
396
|
+
categorize_routes.each do |controller, routes|
|
397
|
+
next unless routes.length < @routes.length
|
398
|
+
|
399
|
+
ivar = controller.gsub('/', '__')
|
400
|
+
method_name = "generate_path_for_#{ivar}".to_sym
|
401
|
+
instance_variable_set "@#{ivar}", routes
|
402
|
+
code = generation_code_for(ivar, method_name).to_s
|
403
|
+
method_sources << code
|
404
|
+
|
405
|
+
filename = "generated_code/routing/generation_for_controller_#{controller}.rb"
|
406
|
+
eval(code, nil, filename)
|
236
407
|
|
237
|
-
|
238
|
-
|
239
|
-
# Raises RoutingError if not route can handle the provided components.
|
240
|
-
#
|
241
|
-
# Note that we don't return the first generated path. We do this so that when a route
|
242
|
-
# generates a path from a subset of the available options we can keep looking for a
|
243
|
-
# route which can generate a path that uses more options.
|
244
|
-
# Note that we *do* return immediately if
|
245
|
-
def generate(options, request)
|
246
|
-
raise RoutingError, "There are no routes defined!" if @routes.empty?
|
247
|
-
|
248
|
-
options = options.symbolize_keys
|
249
|
-
defaults = request.path_parameters.symbolize_keys
|
250
|
-
if options.empty? then options = defaults.clone # Get back the current url if no options was passed
|
251
|
-
else expand_controller_path!(options, defaults) # Expand the supplied controller path.
|
408
|
+
@generation_methods[controller.to_s] = method_name
|
409
|
+
@generation_methods[controller.to_sym] = method_name
|
252
410
|
end
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
411
|
+
|
412
|
+
|
413
|
+
code = generation_code_for('routes', 'generate_default_path').to_s
|
414
|
+
eval(code, nil, 'generated_code/routing/generation.rb')
|
415
|
+
|
416
|
+
return (method_sources << code)
|
417
|
+
end
|
418
|
+
|
419
|
+
def recognize(request)
|
420
|
+
string_path = request.path
|
421
|
+
string_path.chomp! if string_path[0] == ?/
|
422
|
+
path = string_path.split '/'
|
423
|
+
path.shift
|
424
|
+
|
425
|
+
hash = recognize_path(path)
|
426
|
+
recognition_failed(request) unless hash && hash['controller']
|
427
|
+
|
428
|
+
controller = hash['controller']
|
429
|
+
hash['controller'] = controller.controller_path
|
430
|
+
request.path_parameters = hash
|
431
|
+
controller.new
|
432
|
+
end
|
433
|
+
alias :recognize! :recognize
|
434
|
+
|
435
|
+
def recognition_failed(request)
|
436
|
+
raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}"
|
437
|
+
end
|
438
|
+
|
439
|
+
def write_recognition
|
440
|
+
g = generator = CodeGeneration::RecognitionGenerator.new
|
441
|
+
g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"}
|
442
|
+
|
443
|
+
g.def "self.recognize_path(path)" do
|
444
|
+
each do |route|
|
445
|
+
g << 'index = 0'
|
446
|
+
route.write_recognition(g)
|
267
447
|
end
|
268
448
|
end
|
269
|
-
|
270
|
-
|
271
|
-
|
449
|
+
|
450
|
+
eval g.to_s, nil, 'generated/routing/recognition.rb'
|
451
|
+
return g.to_s
|
272
452
|
end
|
273
|
-
|
274
|
-
# Recognize the provided path.
|
275
|
-
# Raise RoutingError if the path can't be recognized.
|
276
|
-
def recognize!(request)
|
277
|
-
path = ((%r{^/?(.*)/?$} =~ request.path) ? $1 : request.path).split('/')
|
278
|
-
raise RoutingError, "There are no routes defined!" if @routes.empty?
|
279
453
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
454
|
+
def generation_code_for(ivar = 'routes', method_name = nil)
|
455
|
+
routes = instance_variable_get('@' + ivar)
|
456
|
+
key_ivar = "@keys_for_#{ivar}"
|
457
|
+
instance_variable_set(key_ivar, routes.collect {|route| route.keys})
|
458
|
+
|
459
|
+
g = generator = CodeGeneration::GenerationGenerator.new
|
460
|
+
g.def "self.#{method_name}(merged, options, expire_on)" do
|
461
|
+
g << 'unused_count = options.length + 1'
|
462
|
+
g << "unused_keys = keys = options.keys"
|
463
|
+
g << 'path = nil'
|
464
|
+
|
465
|
+
routes.each_with_index do |route, index|
|
466
|
+
g << "new_unused_keys = keys - #{key_ivar}[#{index}]"
|
467
|
+
g << 'new_path = ('
|
468
|
+
g.source.indent do
|
469
|
+
if index.zero?
|
470
|
+
g << "new_unused_count = new_unused_keys.length"
|
471
|
+
g << "hash = merged; not_expired = true"
|
472
|
+
route.write_generation(g.dup)
|
473
|
+
else
|
474
|
+
g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp|
|
475
|
+
gp << "hash = merged; not_expired = true"
|
476
|
+
route.write_generation(gp)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
g.source.lines.last << ' )' # Add the closing brace to the end line
|
481
|
+
g.if 'new_path' do
|
482
|
+
g << 'return new_path, [] if new_unused_count.zero?'
|
483
|
+
g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count'
|
484
|
+
end
|
288
485
|
end
|
486
|
+
|
487
|
+
g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path"
|
488
|
+
g << "return path, unused_keys"
|
289
489
|
end
|
290
490
|
|
291
|
-
|
491
|
+
return g
|
292
492
|
end
|
293
493
|
|
294
|
-
def
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
options[:controller] = relative_to.empty? ? options[:controller] : "#{relative_to}/#{options[:controller]}"
|
302
|
-
defaults.delete(:action) if options.key?(:controller)
|
494
|
+
def categorize_routes
|
495
|
+
@categorized_routes = by_controller = Hash.new(self)
|
496
|
+
|
497
|
+
known_controllers.each do |name|
|
498
|
+
set = by_controller[name] = []
|
499
|
+
each do |route|
|
500
|
+
set << route if route.matches_controller? name
|
303
501
|
end
|
304
|
-
else
|
305
|
-
options[:controller] = defaults[:controller]
|
306
502
|
end
|
503
|
+
|
504
|
+
@categorized_routes
|
307
505
|
end
|
308
506
|
|
309
|
-
def
|
310
|
-
|
507
|
+
def known_controllers
|
508
|
+
@routes.inject([]) do |known, route|
|
509
|
+
if (controller = route.known[:controller])
|
510
|
+
if controller.is_a?(Regexp)
|
511
|
+
known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word}
|
512
|
+
else known << controller
|
513
|
+
end
|
514
|
+
end
|
515
|
+
known
|
516
|
+
end.uniq
|
311
517
|
end
|
312
|
-
|
313
|
-
|
518
|
+
|
314
519
|
def reload
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
raise RoutingError.new("Cannot load config/routes.rb:\n #{e.message}").copy_blame!(e)
|
320
|
-
ensure # Ensure that there is at least one route:
|
321
|
-
connect(':controller/:action/:id', :action => 'index', :id => nil) if @routes.empty?
|
520
|
+
NamedRoutes.clear
|
521
|
+
|
522
|
+
if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb'))
|
523
|
+
else connect(':controller/:action/:id', :action => 'index', :id => nil)
|
322
524
|
end
|
525
|
+
|
526
|
+
NamedRoutes.install
|
323
527
|
end
|
324
|
-
|
528
|
+
|
529
|
+
def connect(*args)
|
530
|
+
new_route = Route.new(*args)
|
531
|
+
@routes << new_route
|
532
|
+
return new_route
|
533
|
+
end
|
534
|
+
|
325
535
|
def draw
|
326
|
-
@routes
|
327
|
-
|
536
|
+
old_routes = @routes
|
537
|
+
@routes = []
|
538
|
+
|
539
|
+
begin yield self
|
540
|
+
rescue
|
541
|
+
@routes = old_routes
|
542
|
+
raise
|
543
|
+
end
|
544
|
+
write_generation
|
545
|
+
write_recognition
|
546
|
+
end
|
547
|
+
|
548
|
+
def empty?() @routes.empty? end
|
549
|
+
|
550
|
+
def each(&block) @routes.each(&block) end
|
551
|
+
|
552
|
+
def method_missing(name, *args)
|
553
|
+
return super(name, *args) unless (1..2).include?(args.length)
|
554
|
+
|
555
|
+
route = connect(*args)
|
556
|
+
NamedRoutes.name_route(route, name)
|
557
|
+
route
|
558
|
+
end
|
559
|
+
|
560
|
+
def extra_keys(options, recall = {})
|
561
|
+
generate(options.dup, recall).last.keys
|
328
562
|
end
|
329
563
|
end
|
564
|
+
|
565
|
+
module NamedRoutes #:nodoc:
|
566
|
+
Helpers = []
|
567
|
+
class << self
|
568
|
+
def clear() Helpers.clear end
|
569
|
+
|
570
|
+
def hash_access_name(name)
|
571
|
+
"hash_for_#{name}_url"
|
572
|
+
end
|
573
|
+
|
574
|
+
def url_helper_name(name)
|
575
|
+
"#{name}_url"
|
576
|
+
end
|
577
|
+
|
578
|
+
def name_route(route, name)
|
579
|
+
hash = route.known.symbolize_keys
|
580
|
+
hash[:controller] = "/#{hash[:controller]}"
|
581
|
+
|
582
|
+
define_method(hash_access_name(name)) { hash }
|
583
|
+
module_eval(%{def #{url_helper_name name}(options = {})
|
584
|
+
url_for(#{hash_access_name(name)}.merge(options))
|
585
|
+
end}, "generated/routing/named_routes/#{name}.rb")
|
586
|
+
|
587
|
+
protected url_helper_name(name), hash_access_name(name)
|
588
|
+
|
589
|
+
Helpers << url_helper_name(name).to_sym
|
590
|
+
Helpers.uniq!
|
591
|
+
end
|
330
592
|
|
331
|
-
|
332
|
-
|
333
|
-
|
593
|
+
def install(cls = ActionController::Base)
|
594
|
+
cls.send :include, self
|
595
|
+
if cls.respond_to? :helper_method
|
596
|
+
Helpers.each do |helper_name|
|
597
|
+
cls.send :helper_method, helper_name
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|
601
|
+
end
|
334
602
|
end
|
335
603
|
|
336
|
-
def self.draw(*args, &block) #:nodoc:
|
337
|
-
Routes.draw(*args) {|*args| block.call(*args)}
|
338
|
-
end
|
339
|
-
|
340
604
|
Routes = RouteSet.new
|
341
605
|
end
|
342
606
|
end
|