actionpack 1.13.1 → 1.13.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.

data/CHANGELOG CHANGED
@@ -1,3 +1,32 @@
1
+ *1.13.2* (February 5th, 2007)
2
+
3
+ * Add much-needed html-scanner tests. Fixed CDATA parsing bug. [Rick]
4
+
5
+ * improve error message for Routing for named routes. [Rob Sanheim]
6
+
7
+ * Added enhanced docs to routing assertions. [Rob Sanheim]
8
+
9
+ * fix form_for example in ActionController::Resources documentation. [gnarg]
10
+
11
+ * Add singleton resources from trunk [Rick Olson]
12
+
13
+ * TestSession supports indifferent access so session['foo'] == session[:foo] in your tests. #7372 [julik, jean.helou]
14
+
15
+ * select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 [nik.kakelin, ben, julik]
16
+
17
+ * Improve routes documentation. #7095 [zackchandler]
18
+
19
+ * Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 [dkubb]
20
+
21
+ * Fixed NumberHelper#number_with_delimiter to use "." always for splitting the original number, not the delimiter parameter #7389 [ceefour]
22
+
23
+ * Autolinking recognizes trailing and embedded . , : ; #7354 [Jarkko Laine]
24
+
25
+ * Make TextHelper::auto_link recognize URLs with colons in path correctly, fixes #7268. [imajes]
26
+
27
+ * Improved auto_link to match more valid urls correctly [Tobias Luetke]
28
+
29
+
1
30
  *1.13.1* (January 18th, 2007)
2
31
 
3
32
  * Fixed content-type bug in Prototype [sam]
data/Rakefile CHANGED
@@ -75,7 +75,7 @@ spec = Gem::Specification.new do |s|
75
75
  s.has_rdoc = true
76
76
  s.requirements << 'none'
77
77
 
78
- s.add_dependency('activesupport', '= 1.4.0' + PKG_BUILD)
78
+ s.add_dependency('activesupport', '= 1.4.1' + PKG_BUILD)
79
79
 
80
80
  s.require_path = 'lib'
81
81
  s.autorequire = 'action_controller'
@@ -30,7 +30,7 @@ unless defined?(ActiveSupport)
30
30
  require 'active_support'
31
31
  rescue LoadError
32
32
  require 'rubygems'
33
- require_gem 'activesupport'
33
+ gem 'activesupport'
34
34
  end
35
35
  end
36
36
 
@@ -3,13 +3,23 @@ module ActionController
3
3
  module RoutingAssertions
4
4
  # Asserts that the routing of the given path was handled correctly and that the parsed options match.
5
5
  #
6
- # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
6
+ # assert_recognizes({:controller => 'items', :action => 'index'}, 'items') # check the default action
7
+ # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list') # check a specific action
8
+ # assert_recognizes({:controller => 'items', :action => 'list', :id => '1'}, 'items/list/1') # check an action with a parameter
7
9
  #
8
10
  # Pass a hash in the second argument to specify the request method. This is useful for routes
9
- # requiring a specific method.
11
+ # requiring a specific HTTP method. The hash should contain a :path with the incoming request path
12
+ # and a :method containing the required HTTP verb.
10
13
  #
14
+ # # assert that POSTing to /items will call the create action on ItemsController
11
15
  # assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
12
16
  #
17
+ # You can also pass in "extras" with a hash containing URL parameters that would normally be in the query string. This can be used
18
+ # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
19
+ # extras argument, appending the query string on the path directly will not work. For example:
20
+ #
21
+ # # assert that a path of '/items/list/1?view=print' returns the correct options
22
+ # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
13
23
  def assert_recognizes(expected_options, path, extras={}, message=nil)
14
24
  if path.is_a? Hash
15
25
  request_method = path[:method]
@@ -33,7 +43,12 @@ module ActionController
33
43
  end
34
44
  end
35
45
 
36
- # Asserts that the provided options can be used to generate the provided path.
46
+ # Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes.
47
+ # For example:
48
+ #
49
+ # assert_generates("/items", :controller => "items", :action => "index")
50
+ # assert_generates("/items/list", :controller => "items", :action => "list")
51
+ # assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
37
52
  def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
38
53
  clean_backtrace do
39
54
  expected_path = "/#{expected_path}" unless expected_path[0] == ?/
@@ -53,7 +68,8 @@ module ActionController
53
68
  end
54
69
 
55
70
  # Asserts that path and options match both ways; in other words, the URL generated from
56
- # options is the same as path, and also that the options recognized from path are the same as options
71
+ # options is the same as path, and also that the options recognized from path are the same as options. This
72
+ # essentially combines assert_recognizes and assert_generates into one step.
57
73
  def assert_routing(path, options, defaults={}, extras={}, message=nil)
58
74
  assert_recognizes(options, path, extras, message)
59
75
 
@@ -18,26 +18,26 @@ module ActionController
18
18
  end
19
19
 
20
20
  def controller
21
- (options[:controller] || plural).to_s
21
+ @controller ||= (options[:controller] || plural).to_s
22
22
  end
23
23
 
24
24
  def path
25
- "#{path_prefix}/#{plural}"
25
+ @path ||= "#{path_prefix}/#{plural}"
26
26
  end
27
27
 
28
28
  def new_path
29
- "#{path}/new"
29
+ @new_path ||= "#{path}/new"
30
30
  end
31
31
 
32
32
  def member_path
33
- "#{path}/:id"
33
+ @member_path ||= "#{path}/:id"
34
34
  end
35
35
 
36
36
  def nesting_path_prefix
37
- "#{path}/:#{singular}_id"
37
+ @nesting_path_prefix ||= "#{path}/:#{singular}_id"
38
38
  end
39
39
 
40
- private
40
+ protected
41
41
  def arrange_actions
42
42
  @collection_methods = arrange_actions_by_methods(options.delete(:collection))
43
43
  @member_methods = arrange_actions_by_methods(options.delete(:member))
@@ -65,7 +65,20 @@ module ActionController
65
65
  (collection[method] ||= []).unshift(action)
66
66
  end
67
67
  end
68
-
68
+
69
+ class SingletonResource < Resource #:nodoc:
70
+ def initialize(entity, options)
71
+ @plural = @singular = entity
72
+ @options = options
73
+ arrange_actions
74
+ add_default_actions
75
+ set_prefixes
76
+ end
77
+
78
+ alias_method :member_path, :path
79
+ alias_method :nesting_path_prefix, :path
80
+ end
81
+
69
82
  # Creates named routes for implementing verb-oriented controllers. This is
70
83
  # useful for implementing REST API's, where a single resource has different
71
84
  # behavior based on the HTTP verb (method) used to access it.
@@ -145,7 +158,7 @@ module ActionController
145
158
  #
146
159
  # or
147
160
  #
148
- # <% form_for :message, @message, message_path(@message), :html => {:method => :put} do |f| %>
161
+ # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
149
162
  #
150
163
  # The #resources method accepts various options, too, to customize the resulting
151
164
  # routes:
@@ -217,12 +230,87 @@ module ActionController
217
230
  entities.each { |entity| map_resource entity, options.dup, &block }
218
231
  end
219
232
 
233
+ # Creates named routes for implementing verb-oriented controllers for a singleton resource.
234
+ # A singleton resource is global to the current user visiting the application, such as a user's
235
+ # /account profile.
236
+ #
237
+ # See map.resources for general conventions. These are the main differences:
238
+ # - a singular name is given to map.resource. The default controller name is taken from the singular name.
239
+ # - To specify a custom plural name, use the :plural option. There is no :singular option
240
+ # - No default index, new, or create routes are created for the singleton resource controller.
241
+ # - When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
242
+ #
243
+ # Example:
244
+ #
245
+ # map.resource :account
246
+ #
247
+ # class AccountController < ActionController::Base
248
+ # # POST account_url
249
+ # def create
250
+ # # create an account
251
+ # end
252
+ #
253
+ # # GET new_account_url
254
+ # def new
255
+ # # return an HTML form for describing the new account
256
+ # end
257
+ #
258
+ # # GET account_url
259
+ # def show
260
+ # # find and return the account
261
+ # end
262
+ #
263
+ # # GET edit_account_url
264
+ # def edit
265
+ # # return an HTML form for editing the account
266
+ # end
267
+ #
268
+ # # PUT account_url
269
+ # def update
270
+ # # find and update the account
271
+ # end
272
+ #
273
+ # # DELETE account_url
274
+ # def destroy
275
+ # # delete the account
276
+ # end
277
+ # end
278
+ #
279
+ # Along with the routes themselves, #resource generates named routes for use in
280
+ # controllers and views. <tt>map.resource :account</tt> produces the following named routes and helpers:
281
+ #
282
+ # Named Route Helpers
283
+ # account account_url, hash_for_account_url,
284
+ # account_path, hash_for_account_path
285
+ # edit_account edit_account_url, hash_for_edit_account_url,
286
+ # edit_account_path, hash_for_edit_account_path
287
+ def resource(*entities, &block)
288
+ options = entities.last.is_a?(Hash) ? entities.pop : { }
289
+ entities.each { |entity| map_singleton_resource entity, options.dup, &block }
290
+ end
291
+
220
292
  private
221
293
  def map_resource(entities, options = {}, &block)
222
294
  resource = Resource.new(entities, options)
223
295
 
224
296
  with_options :controller => resource.controller do |map|
225
297
  map_collection_actions(map, resource)
298
+ map_default_collection_actions(map, resource)
299
+ map_new_actions(map, resource)
300
+ map_member_actions(map, resource)
301
+
302
+ if block_given?
303
+ with_options(:path_prefix => resource.nesting_path_prefix, &block)
304
+ end
305
+ end
306
+ end
307
+
308
+ def map_singleton_resource(entities, options = {}, &block)
309
+ resource = SingletonResource.new(entities, options)
310
+
311
+ with_options :controller => resource.controller do |map|
312
+ map_collection_actions(map, resource)
313
+ map_default_singleton_actions(map, resource)
226
314
  map_new_actions(map, resource)
227
315
  map_member_actions(map, resource)
228
316
 
@@ -234,67 +322,82 @@ module ActionController
234
322
 
235
323
  def map_collection_actions(map, resource)
236
324
  resource.collection_methods.each do |method, actions|
237
- route_options = requirements_for(method)
238
-
239
325
  actions.each do |action|
240
- map.named_route(
241
- "#{resource.name_prefix}#{action}_#{resource.plural}",
242
- "#{resource.path};#{action}",
243
- route_options.merge(:action => action.to_s)
244
- )
245
-
246
- map.named_route(
247
- "formatted_#{resource.name_prefix}#{action}_#{resource.plural}",
248
- "#{resource.path}.:format;#{action}",
249
- route_options.merge(:action => action.to_s)
250
- )
326
+ action_options = action_options_for(action, resource, method)
327
+ map.named_route("#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path};#{action}", action_options)
328
+ map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}.:format;#{action}", action_options)
251
329
  end
252
330
  end
331
+ end
253
332
 
254
- map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, :action => "index", :conditions => { :method => :get })
255
- map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", :action => "index", :conditions => { :method => :get })
333
+ def map_default_collection_actions(map, resource)
334
+ index_action_options = action_options_for("index", resource)
335
+ map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, index_action_options)
336
+ map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", index_action_options)
337
+
338
+ create_action_options = action_options_for("create", resource)
339
+ map.connect(resource.path, create_action_options)
340
+ map.connect("#{resource.path}.:format", create_action_options)
341
+ end
256
342
 
257
- map.connect(resource.path, :action => "create", :conditions => { :method => :post })
258
- map.connect("#{resource.path}.:format", :action => "create", :conditions => { :method => :post })
343
+ def map_default_singleton_actions(map, resource)
344
+ create_action_options = action_options_for("create", resource)
345
+ map.connect(resource.path, create_action_options)
346
+ map.connect("#{resource.path}.:format", create_action_options)
259
347
  end
260
348
 
261
349
  def map_new_actions(map, resource)
262
350
  resource.new_methods.each do |method, actions|
263
- route_options = requirements_for(method)
264
351
  actions.each do |action|
352
+ action_options = action_options_for(action, resource, method)
265
353
  if action == :new
266
- map.named_route("#{resource.name_prefix}new_#{resource.singular}", resource.new_path, route_options.merge(:action => "new"))
267
- map.named_route("formatted_#{resource.name_prefix}new_#{resource.singular}", "#{resource.new_path}.:format", route_options.merge(:action => "new"))
354
+ map.named_route("#{resource.name_prefix}new_#{resource.singular}", resource.new_path, action_options)
355
+ map.named_route("formatted_#{resource.name_prefix}new_#{resource.singular}", "#{resource.new_path}.:format", action_options)
268
356
  else
269
- map.named_route("#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path};#{action}", route_options.merge(:action => action.to_s))
270
- map.named_route("formatted_#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path}.:format;#{action}", route_options.merge(:action => action.to_s))
357
+ map.named_route("#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path};#{action}", action_options)
358
+ map.named_route("formatted_#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path}.:format;#{action}", action_options)
271
359
  end
272
360
  end
273
361
  end
274
362
  end
275
-
363
+
276
364
  def map_member_actions(map, resource)
277
365
  resource.member_methods.each do |method, actions|
278
- route_options = requirements_for(method)
279
-
280
366
  actions.each do |action|
281
- map.named_route("#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path};#{action}", route_options.merge(:action => action.to_s))
282
- map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path}.:format;#{action}", route_options.merge(:action => action.to_s))
367
+ action_options = action_options_for(action, resource, method)
368
+ map.named_route("#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path};#{action}", action_options)
369
+ map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path}.:format;#{action}",action_options)
283
370
  end
284
371
  end
285
372
 
286
- map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, :action => "show", :conditions => { :method => :get })
287
- map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", :action => "show", :conditions => { :method => :get })
373
+ show_action_options = action_options_for("show", resource)
374
+ map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
375
+ map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
288
376
 
289
- map.connect(resource.member_path, :action => "update", :conditions => { :method => :put })
290
- map.connect("#{resource.member_path}.:format", :action => "update", :conditions => { :method => :put })
377
+ update_action_options = action_options_for("update", resource)
378
+ map.connect(resource.member_path, update_action_options)
379
+ map.connect("#{resource.member_path}.:format", update_action_options)
291
380
 
292
- map.connect(resource.member_path, :action => "destroy", :conditions => { :method => :delete })
293
- map.connect("#{resource.member_path}.:format", :action => "destroy", :conditions => { :method => :delete })
381
+ destroy_action_options = action_options_for("destroy", resource)
382
+ map.connect(resource.member_path, destroy_action_options)
383
+ map.connect("#{resource.member_path}.:format", destroy_action_options)
294
384
  end
295
-
296
- def requirements_for(method)
297
- method == :any ? {} : { :conditions => { :method => method } }
385
+
386
+ def conditions_for(method)
387
+ { :conditions => method == :any ? {} : { :method => method } }
388
+ end
389
+
390
+ def action_options_for(action, resource, method = nil)
391
+ default_options = { :action => action.to_s }
392
+ require_id = resource.kind_of?(SingletonResource) ? {} : { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } }
393
+ case default_options[:action]
394
+ when "index", "new" : default_options.merge(conditions_for(method || :get))
395
+ when "create" : default_options.merge(conditions_for(method || :post))
396
+ when "show", "edit" : default_options.merge(conditions_for(method || :get)).merge(require_id)
397
+ when "update" : default_options.merge(conditions_for(method || :put)).merge(require_id)
398
+ when "destroy" : default_options.merge(conditions_for(method || :delete)).merge(require_id)
399
+ else default_options.merge(conditions_for(method))
400
+ end
298
401
  end
299
402
  end
300
403
  end
@@ -126,7 +126,8 @@ module ActionController
126
126
  # == Named routes
127
127
  #
128
128
  # Routes can be named with the syntax <tt>map.name_of_route options</tt>,
129
- # allowing for easy reference within your source as +name_of_route_url+.
129
+ # allowing for easy reference within your source as +name_of_route_url+
130
+ # for the full URL and +name_of_route_path+ for the URI path.
130
131
  #
131
132
  # Example:
132
133
  # # In routes.rb
@@ -137,29 +138,39 @@ module ActionController
137
138
  #
138
139
  # Arguments can be passed as well.
139
140
  #
140
- # redirect_to show_item_url(:id => 25)
141
+ # redirect_to show_item_path(:id => 25)
141
142
  #
142
- # When using +with_options+, the name goes after the item passed to the block.
143
+ # Use <tt>map.root</tt> as a shorthand to name a route for the root path ""
143
144
  #
144
- # ActionController::Routing::Routes.draw do |map|
145
- # map.with_options :controller => 'blog' do |blog|
146
- # blog.show '', :action => 'list'
147
- # blog.delete 'delete/:id', :action => 'delete',
148
- # blog.edit 'edit/:id', :action => 'edit'
149
- # end
150
- # map.connect ':controller/:action/:view
151
- # end
145
+ # # In routes.rb
146
+ # map.root :controller => 'blogs'
147
+ #
148
+ # # would recognize http://www.example.com/ as
149
+ # params = { :controller => 'blogs', :action => 'index' }
150
+ #
151
+ # # and provide these named routes
152
+ # root_url # => 'http://www.example.com/'
153
+ # root_path # => ''
152
154
  #
153
- # You would then use the named routes in your views:
155
+ # Note: when using +with_options+, the route is simply named after the
156
+ # method you call on the block parameter rather than map.
154
157
  #
155
- # link_to @article.title, show_url(:id => @article.id)
158
+ # # In routes.rb
159
+ # map.with_options :controller => 'blog' do |blog|
160
+ # blog.show '', :action => 'list'
161
+ # blog.delete 'delete/:id', :action => 'delete',
162
+ # blog.edit 'edit/:id', :action => 'edit'
163
+ # end
164
+ #
165
+ # # provides named routes for show, delete, and edit
166
+ # link_to @article.title, show_path(:id => @article.id)
156
167
  #
157
- # == Pretty URL's
168
+ # == Pretty URLs
158
169
  #
159
170
  # Routes can generate pretty URLs. For example:
160
171
  #
161
172
  # map.connect 'articles/:year/:month/:day',
162
- # :controller => 'articles',
173
+ # :controller => 'articles',
163
174
  # :action => 'find_by_date',
164
175
  # :year => /\d{4}/,
165
176
  # :month => /\d{1,2}/,
@@ -1230,8 +1241,11 @@ module ActionController
1230
1241
 
1231
1242
  if named_route
1232
1243
  path = named_route.generate(options, merged, expire_on)
1233
- raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}" if path.nil?
1234
- return path
1244
+ if path.nil?
1245
+ raise_named_route_error(options, named_route, named_route_name)
1246
+ else
1247
+ return path
1248
+ end
1235
1249
  else
1236
1250
  merged[:action] ||= 'index'
1237
1251
  options[:action] ||= 'index'
@@ -1251,6 +1265,18 @@ module ActionController
1251
1265
 
1252
1266
  raise RoutingError, "No route matches #{options.inspect}"
1253
1267
  end
1268
+
1269
+ # try to give a helpful error message when named route generation fails
1270
+ def raise_named_route_error(options, named_route, named_route_name)
1271
+ diff = named_route.requirements.diff(options)
1272
+ unless diff.empty?
1273
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
1274
+ else
1275
+ required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
1276
+ required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
1277
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisifed?"
1278
+ end
1279
+ end
1254
1280
 
1255
1281
  def recognize(request)
1256
1282
  params = recognize_path(request.path, extract_request_environment(request))