actionpack 3.0.0.beta → 3.0.0.beta2

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 (118) hide show
  1. data/CHANGELOG +291 -260
  2. data/lib/abstract_controller.rb +5 -2
  3. data/lib/abstract_controller/assigns.rb +21 -0
  4. data/lib/abstract_controller/base.rb +13 -5
  5. data/lib/abstract_controller/collector.rb +2 -0
  6. data/lib/abstract_controller/helpers.rb +4 -14
  7. data/lib/abstract_controller/layouts.rb +50 -99
  8. data/lib/abstract_controller/logger.rb +2 -2
  9. data/lib/abstract_controller/rendering.rb +105 -173
  10. data/lib/abstract_controller/view_paths.rb +69 -0
  11. data/lib/action_controller.rb +1 -2
  12. data/lib/action_controller/base.rb +10 -32
  13. data/lib/action_controller/caching.rb +19 -18
  14. data/lib/action_controller/caching/actions.rb +17 -11
  15. data/lib/action_controller/caching/fragments.rb +5 -17
  16. data/lib/action_controller/caching/pages.rb +24 -24
  17. data/lib/action_controller/caching/sweeping.rb +1 -3
  18. data/lib/action_controller/deprecated.rb +0 -2
  19. data/lib/action_controller/deprecated/base.rb +143 -0
  20. data/lib/action_controller/metal.rb +29 -26
  21. data/lib/action_controller/metal/compatibility.rb +18 -87
  22. data/lib/action_controller/metal/cookies.rb +0 -1
  23. data/lib/action_controller/metal/head.rb +1 -0
  24. data/lib/action_controller/metal/helpers.rb +2 -2
  25. data/lib/action_controller/metal/hide_actions.rb +4 -6
  26. data/lib/action_controller/metal/http_authentication.rb +18 -33
  27. data/lib/action_controller/metal/implicit_render.rb +21 -0
  28. data/lib/action_controller/metal/instrumentation.rb +1 -1
  29. data/lib/action_controller/metal/mime_responds.rb +2 -1
  30. data/lib/action_controller/metal/rack_delegation.rb +3 -8
  31. data/lib/action_controller/metal/redirecting.rb +2 -1
  32. data/lib/action_controller/metal/renderers.rb +4 -2
  33. data/lib/action_controller/metal/rendering.rb +31 -44
  34. data/lib/action_controller/metal/request_forgery_protection.rb +41 -4
  35. data/lib/action_controller/metal/responder.rb +2 -0
  36. data/lib/action_controller/metal/session_management.rb +0 -36
  37. data/lib/action_controller/metal/streaming.rb +20 -47
  38. data/lib/action_controller/metal/testing.rb +0 -1
  39. data/lib/action_controller/metal/url_for.rb +11 -148
  40. data/lib/action_controller/middleware.rb +2 -1
  41. data/lib/action_controller/polymorphic_routes.rb +1 -2
  42. data/lib/action_controller/railtie.rb +63 -10
  43. data/lib/action_controller/railties/{subscriber.rb → log_subscriber.rb} +5 -12
  44. data/lib/action_controller/railties/url_helpers.rb +14 -0
  45. data/lib/action_controller/record_identifier.rb +20 -1
  46. data/lib/action_controller/test_case.rb +123 -12
  47. data/lib/action_dispatch.rb +1 -0
  48. data/lib/action_dispatch/http/cache.rb +20 -3
  49. data/lib/action_dispatch/http/filter_parameters.rb +40 -25
  50. data/lib/action_dispatch/http/mime_negotiation.rb +6 -17
  51. data/lib/action_dispatch/http/mime_type.rb +2 -7
  52. data/lib/action_dispatch/http/request.rb +12 -33
  53. data/lib/action_dispatch/http/response.rb +35 -15
  54. data/lib/action_dispatch/http/upload.rb +2 -0
  55. data/lib/action_dispatch/http/url.rb +5 -32
  56. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  57. data/lib/action_dispatch/middleware/cookies.rb +4 -3
  58. data/lib/action_dispatch/middleware/params_parser.rb +4 -3
  59. data/lib/action_dispatch/middleware/remote_ip.rb +51 -0
  60. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -0
  61. data/lib/action_dispatch/middleware/session/cookie_store.rb +6 -8
  62. data/lib/action_dispatch/middleware/show_exceptions.rb +0 -14
  63. data/lib/action_dispatch/middleware/stack.rb +6 -2
  64. data/lib/action_dispatch/railtie.rb +3 -1
  65. data/lib/action_dispatch/routing.rb +2 -0
  66. data/lib/action_dispatch/routing/deprecated_mapper.rb +35 -7
  67. data/lib/action_dispatch/routing/mapper.rb +134 -48
  68. data/lib/action_dispatch/routing/route.rb +2 -2
  69. data/lib/action_dispatch/routing/route_set.rb +217 -158
  70. data/lib/action_dispatch/routing/url_for.rb +139 -0
  71. data/lib/action_dispatch/testing/assertions/response.rb +14 -61
  72. data/lib/action_dispatch/testing/assertions/routing.rb +25 -14
  73. data/lib/action_dispatch/testing/integration.rb +32 -50
  74. data/lib/action_dispatch/testing/performance_test.rb +3 -1
  75. data/lib/action_dispatch/testing/test_process.rb +2 -0
  76. data/lib/action_dispatch/testing/test_request.rb +2 -0
  77. data/lib/action_pack/version.rb +4 -3
  78. data/lib/action_view.rb +11 -6
  79. data/lib/action_view/base.rb +33 -121
  80. data/lib/action_view/context.rb +0 -2
  81. data/lib/action_view/helpers.rb +26 -23
  82. data/lib/action_view/helpers/active_model_helper.rb +28 -18
  83. data/lib/action_view/helpers/asset_tag_helper.rb +109 -54
  84. data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
  85. data/lib/action_view/helpers/cache_helper.rb +22 -1
  86. data/lib/action_view/helpers/capture_helper.rb +22 -22
  87. data/lib/action_view/helpers/date_helper.rb +6 -5
  88. data/lib/action_view/helpers/form_helper.rb +78 -63
  89. data/lib/action_view/helpers/form_options_helper.rb +6 -4
  90. data/lib/action_view/helpers/form_tag_helper.rb +26 -15
  91. data/lib/action_view/helpers/javascript_helper.rb +90 -10
  92. data/lib/action_view/helpers/number_helper.rb +315 -118
  93. data/lib/action_view/helpers/prototype_helper.rb +19 -46
  94. data/lib/action_view/helpers/record_tag_helper.rb +4 -4
  95. data/lib/action_view/helpers/tag_helper.rb +7 -24
  96. data/lib/action_view/helpers/text_helper.rb +8 -7
  97. data/lib/action_view/helpers/translation_helper.rb +7 -5
  98. data/lib/action_view/helpers/url_helper.rb +19 -16
  99. data/lib/action_view/locale/en.yml +45 -6
  100. data/lib/action_view/lookup_context.rb +190 -0
  101. data/lib/action_view/paths.rb +22 -63
  102. data/lib/action_view/railtie.rb +14 -4
  103. data/lib/action_view/railties/{subscriber.rb → log_subscriber.rb} +1 -1
  104. data/lib/action_view/render/layouts.rb +73 -0
  105. data/lib/action_view/render/partials.rb +15 -41
  106. data/lib/action_view/render/rendering.rb +27 -78
  107. data/lib/action_view/template.rb +20 -24
  108. data/lib/action_view/template/error.rb +22 -2
  109. data/lib/action_view/template/handlers/erb.rb +33 -9
  110. data/lib/action_view/template/handlers/rjs.rb +1 -2
  111. data/lib/action_view/template/resolver.rb +46 -104
  112. data/lib/action_view/template/text.rb +5 -12
  113. data/lib/action_view/test_case.rb +14 -23
  114. metadata +83 -40
  115. data/lib/abstract_controller/compatibility.rb +0 -18
  116. data/lib/abstract_controller/localized_cache.rb +0 -49
  117. data/lib/action_controller/metal/configuration.rb +0 -28
  118. data/lib/action_controller/url_rewriter.rb +0 -76
@@ -5,20 +5,6 @@ require 'action_dispatch/http/request'
5
5
  module ActionDispatch
6
6
  # This middleware rescues any exception returned by the application and renders
7
7
  # nice exception pages if it's being rescued locally.
8
- #
9
- # Every time an exception is caught, a notification is published, becoming a good API
10
- # to deal with exceptions. So, if you want send an e-mail through ActionMailer
11
- # everytime this notification is published, you just need to do the following:
12
- #
13
- # ActiveSupport::Notifications.subscribe "action_dispatch.show_exception" do |name, start, end, instrumentation_id, payload|
14
- # ExceptionNotifier.deliver_exception(start, payload)
15
- # end
16
- #
17
- # The payload is a hash which has two pairs:
18
- #
19
- # * :env - Contains the rack env for the given request;
20
- # * :exception - The exception raised;
21
- #
22
8
  class ShowExceptions
23
9
  LOCALHOST = ['127.0.0.1', '::1'].freeze
24
10
 
@@ -58,7 +58,7 @@ module ActionDispatch
58
58
  if lazy_compare?(@klass) && lazy_compare?(middleware)
59
59
  normalize(@klass) == normalize(middleware)
60
60
  else
61
- klass == ActiveSupport::Inflector.constantize(middleware.to_s)
61
+ klass.name == middleware.to_s
62
62
  end
63
63
  end
64
64
  end
@@ -122,7 +122,11 @@ module ActionDispatch
122
122
  find_all { |middleware| middleware.active? }
123
123
  end
124
124
 
125
- def build(app)
125
+ def build(app = nil, &blk)
126
+ app ||= blk
127
+
128
+ raise "MiddlewareStack#build requires an app" unless app
129
+
126
130
  active.reverse.inject(app) { |a, e| e.build(a) }
127
131
  end
128
132
  end
@@ -3,7 +3,9 @@ require "rails"
3
3
 
4
4
  module ActionDispatch
5
5
  class Railtie < Rails::Railtie
6
- railtie_name :action_dispatch
6
+ config.action_dispatch = ActiveSupport::OrderedOptions.new
7
+ config.action_dispatch.x_sendfile_header = ""
8
+ config.action_dispatch.ip_spoofing_check = true
7
9
 
8
10
  # Prepare dispatcher callbacks and run 'prepare' callbacks
9
11
  initializer "action_dispatch.prepare_dispatcher" do |app|
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/object/to_param'
2
2
  require 'active_support/core_ext/regexp'
3
+ require 'action_controller/polymorphic_routes'
3
4
 
4
5
  module ActionDispatch
5
6
  # == Routing
@@ -205,6 +206,7 @@ module ActionDispatch
205
206
  autoload :Mapper, 'action_dispatch/routing/mapper'
206
207
  autoload :Route, 'action_dispatch/routing/route'
207
208
  autoload :RouteSet, 'action_dispatch/routing/route_set'
209
+ autoload :UrlFor, 'action_dispatch/routing/url_for'
208
210
 
209
211
  SEPARATORS = %w( / . ? )
210
212
  HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
@@ -1,5 +1,32 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
1
3
  module ActionDispatch
2
4
  module Routing
5
+ class RouteSet
6
+ attr_accessor :controller_namespaces
7
+
8
+ CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/
9
+
10
+ def controller_constraints
11
+ @controller_constraints ||= begin
12
+ namespaces = controller_namespaces + in_memory_controller_namespaces
13
+ source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
14
+ source << CONTROLLER_REGEXP.source
15
+ Regexp.compile(source.sort.reverse.join('|'))
16
+ end
17
+ end
18
+
19
+ def in_memory_controller_namespaces
20
+ namespaces = Set.new
21
+ ActionController::Base.subclasses.each do |klass|
22
+ controller_name = klass.underscore
23
+ namespaces << controller_name.split('/')[0...-1].join('/')
24
+ end
25
+ namespaces.delete('')
26
+ namespaces
27
+ end
28
+ end
29
+
3
30
  # Mapper instances are used to build routes. The object passed to the draw
4
31
  # block in config/routes.rb is a Mapper instance.
5
32
  #
@@ -244,14 +271,15 @@ module ActionDispatch
244
271
  attr_reader :collection_methods, :member_methods, :new_methods
245
272
  attr_reader :path_prefix, :name_prefix, :path_segment
246
273
  attr_reader :plural, :singular
247
- attr_reader :options
274
+ attr_reader :options, :defaults
248
275
 
249
- def initialize(entities, options)
276
+ def initialize(entities, options, defaults)
250
277
  @plural ||= entities
251
278
  @singular ||= options[:singular] || plural.to_s.singularize
252
279
  @path_segment = options.delete(:as) || @plural
253
280
 
254
281
  @options = options
282
+ @defaults = defaults
255
283
 
256
284
  arrange_actions
257
285
  add_default_actions
@@ -280,7 +308,7 @@ module ActionDispatch
280
308
 
281
309
  def new_path
282
310
  new_action = self.options[:path_names][:new] if self.options[:path_names]
283
- new_action ||= ActionController::Base.resources_path_names[:new]
311
+ new_action ||= self.defaults[:path_names][:new]
284
312
  @new_path ||= "#{path}/#{new_action}"
285
313
  end
286
314
 
@@ -370,7 +398,7 @@ module ActionDispatch
370
398
  end
371
399
 
372
400
  class SingletonResource < Resource #:nodoc:
373
- def initialize(entity, options)
401
+ def initialize(entity, options, defaults)
374
402
  @singular = @plural = entity
375
403
  options[:controller] ||= @singular.to_s.pluralize
376
404
  super
@@ -717,7 +745,7 @@ module ActionDispatch
717
745
 
718
746
  private
719
747
  def map_resource(entities, options = {}, &block)
720
- resource = Resource.new(entities, options)
748
+ resource = Resource.new(entities, options, :path_names => @set.resources_path_names)
721
749
 
722
750
  with_options :controller => resource.controller do |map|
723
751
  map_associations(resource, options)
@@ -734,7 +762,7 @@ module ActionDispatch
734
762
  end
735
763
 
736
764
  def map_singleton_resource(entities, options = {}, &block)
737
- resource = SingletonResource.new(entities, options)
765
+ resource = SingletonResource.new(entities, options, :path_names => @set.resources_path_names)
738
766
 
739
767
  with_options :controller => resource.controller do |map|
740
768
  map_associations(resource, options)
@@ -826,7 +854,7 @@ module ActionDispatch
826
854
  actions.each do |action|
827
855
  [method].flatten.each do |m|
828
856
  action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
829
- action_path ||= ActionController::Base.resources_path_names[action] || action
857
+ action_path ||= @set.resources_path_names[action] || action
830
858
 
831
859
  map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
832
860
  end
@@ -1,3 +1,6 @@
1
+ require 'active_support/core_ext/hash/except'
2
+ require 'active_support/core_ext/object/blank'
3
+
1
4
  module ActionDispatch
2
5
  module Routing
3
6
  class Mapper
@@ -30,31 +33,36 @@ module ActionDispatch
30
33
  end
31
34
 
32
35
  class Mapping
36
+ IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor]
37
+
33
38
  def initialize(set, scope, args)
34
39
  @set, @scope = set, scope
35
40
  @path, @options = extract_path_and_options(args)
36
41
  end
37
42
 
38
43
  def to_route
39
- [ app, conditions, requirements, defaults, @options[:as] ]
44
+ [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
40
45
  end
41
46
 
42
47
  private
43
48
  def extract_path_and_options(args)
44
49
  options = args.extract_options!
45
50
 
46
- case
47
- when using_to_shorthand?(args, options)
51
+ if using_to_shorthand?(args, options)
48
52
  path, to = options.find { |name, value| name.is_a?(String) }
49
53
  options.merge!(:to => to).delete(path) if path
50
- when using_match_shorthand?(args, options)
51
- path = args.first
52
- options = { :to => path.gsub("/", "#"), :as => path.gsub("/", "_") }
53
54
  else
54
55
  path = args.first
55
56
  end
56
57
 
57
- [ normalize_path(path), options ]
58
+ path = normalize_path(path)
59
+
60
+ if using_match_shorthand?(path, options)
61
+ options[:to] ||= path[1..-1].sub(%r{/([^/]*)$}, '#\1')
62
+ options[:as] ||= path[1..-1].gsub("/", "_")
63
+ end
64
+
65
+ [ path, options ]
58
66
  end
59
67
 
60
68
  # match "account" => "account#index"
@@ -63,14 +71,13 @@ module ActionDispatch
63
71
  end
64
72
 
65
73
  # match "account/overview"
66
- def using_match_shorthand?(args, options)
67
- args.present? && options.except(:via).empty? && !args.first.include?(':')
74
+ def using_match_shorthand?(path, options)
75
+ path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$}
68
76
  end
69
77
 
70
78
  def normalize_path(path)
71
- path = "#{@scope[:path]}/#{path}"
72
- raise ArgumentError, "path is required" if path.empty?
73
- Mapper.normalize_path(path)
79
+ raise ArgumentError, "path is required" if @scope[:path].blank? && path.blank?
80
+ Mapper.normalize_path("#{@scope[:path]}/#{path}")
74
81
  end
75
82
 
76
83
  def app
@@ -85,15 +92,22 @@ module ActionDispatch
85
92
  end
86
93
 
87
94
  def requirements
88
- @requirements ||= returning(@options[:constraints] || {}) do |requirements|
95
+ @requirements ||= (@options[:constraints] || {}).tap do |requirements|
89
96
  requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
90
97
  @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
91
- requirements[:controller] ||= @set.controller_constraints
92
98
  end
93
99
  end
94
100
 
95
101
  def defaults
96
- @defaults ||= if to.respond_to?(:call)
102
+ @defaults ||= (@options[:defaults] || {}).tap do |defaults|
103
+ defaults.merge!(default_controller_and_action)
104
+ defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
105
+ @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
106
+ end
107
+ end
108
+
109
+ def default_controller_and_action
110
+ if to.respond_to?(:call)
97
111
  { }
98
112
  else
99
113
  defaults = case to
@@ -143,8 +157,8 @@ module ActionDispatch
143
157
 
144
158
  def segment_keys
145
159
  @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new(
146
- Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
147
- ).names
160
+ Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
161
+ ).names
148
162
  end
149
163
 
150
164
  def to
@@ -175,9 +189,15 @@ module ActionDispatch
175
189
  end
176
190
 
177
191
  def match(*args)
178
- @set.add_route(*Mapping.new(@set, @scope, args).to_route)
192
+ mapping = Mapping.new(@set, @scope, args).to_route
193
+ @set.add_route(*mapping)
179
194
  self
180
195
  end
196
+
197
+ def default_url_options=(options)
198
+ @set.default_url_options = options
199
+ end
200
+ alias_method :default_url_options, :default_url_options=
181
201
  end
182
202
 
183
203
  module HttpHelpers
@@ -239,6 +259,7 @@ module ActionDispatch
239
259
 
240
260
  def scope(*args)
241
261
  options = args.extract_options!
262
+ options = options.dup
242
263
 
243
264
  case args.first
244
265
  when String
@@ -283,13 +304,17 @@ module ActionDispatch
283
304
  end
284
305
 
285
306
  def namespace(path)
286
- scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield }
307
+ scope(path.to_s, :name_prefix => path.to_s, :controller_namespace => path.to_s) { yield }
287
308
  end
288
309
 
289
310
  def constraints(constraints = {})
290
311
  scope(:constraints => constraints) { yield }
291
312
  end
292
313
 
314
+ def defaults(defaults = {})
315
+ scope(:defaults => defaults) { yield }
316
+ end
317
+
293
318
  def match(*args)
294
319
  options = args.extract_options!
295
320
 
@@ -318,12 +343,12 @@ module ActionDispatch
318
343
  parent ? "#{parent}_#{child}" : child
319
344
  end
320
345
 
321
- def merge_namespace_scope(parent, child)
346
+ def merge_controller_namespace_scope(parent, child)
322
347
  parent ? "#{parent}/#{child}" : child
323
348
  end
324
349
 
325
350
  def merge_controller_scope(parent, child)
326
- @scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child
351
+ @scope[:controller_namespace] ? "#{@scope[:controller_namespace]}/#{child}" : child
327
352
  end
328
353
 
329
354
  def merge_resources_path_names_scope(parent, child)
@@ -334,6 +359,10 @@ module ActionDispatch
334
359
  merge_options_scope(parent, child)
335
360
  end
336
361
 
362
+ def merge_defaults_scope(parent, child)
363
+ merge_options_scope(parent, child)
364
+ end
365
+
337
366
  def merge_blocks_scope(parent, child)
338
367
  (parent || []) + [child]
339
368
  end
@@ -354,11 +383,11 @@ module ActionDispatch
354
383
  attr_reader :plural, :singular, :options
355
384
 
356
385
  def initialize(entities, options = {})
357
- entities = entities.to_s
386
+ @name = entities.to_s
358
387
  @options = options
359
388
 
360
- @plural = entities.pluralize
361
- @singular = entities.singularize
389
+ @plural = @name.pluralize
390
+ @singular = @name.singularize
362
391
  end
363
392
 
364
393
  def default_actions
@@ -367,9 +396,9 @@ module ActionDispatch
367
396
 
368
397
  def actions
369
398
  if only = options[:only]
370
- only.map(&:to_sym)
399
+ Array(only).map(&:to_sym)
371
400
  elsif except = options[:except]
372
- default_actions - except.map(&:to_sym)
401
+ default_actions - Array(except).map(&:to_sym)
373
402
  else
374
403
  default_actions
375
404
  end
@@ -385,7 +414,7 @@ module ActionDispatch
385
414
  end
386
415
 
387
416
  def name
388
- options[:as] || plural
417
+ options[:as] || @name
389
418
  end
390
419
 
391
420
  def controller
@@ -396,8 +425,13 @@ module ActionDispatch
396
425
  singular
397
426
  end
398
427
 
428
+ # Checks for uncountable plurals, and appends "_index" if they're.
399
429
  def collection_name
400
- plural
430
+ uncountable? ? "#{plural}_index" : plural
431
+ end
432
+
433
+ def uncountable?
434
+ singular == plural
401
435
  end
402
436
 
403
437
  def name_for_action(action)
@@ -412,6 +446,32 @@ module ActionDispatch
412
446
  def id_segment
413
447
  ":#{singular}_id"
414
448
  end
449
+
450
+ def constraints
451
+ options[:constraints] || {}
452
+ end
453
+
454
+ def id_constraint?
455
+ options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp)
456
+ end
457
+
458
+ def id_constraint
459
+ options[:id] || constraints[:id]
460
+ end
461
+
462
+ def collection_options
463
+ (options || {}).dup.tap do |options|
464
+ options.delete(:id)
465
+ options[:constraints] = options[:constraints].dup if options[:constraints]
466
+ options[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
467
+ end
468
+ end
469
+
470
+ def nested_options
471
+ options = { :name_prefix => member_name }
472
+ options["#{singular}_id".to_sym] = id_constraint if id_constraint?
473
+ options
474
+ end
415
475
  end
416
476
 
417
477
  class SingletonResource < Resource #:nodoc:
@@ -430,8 +490,8 @@ module ActionDispatch
430
490
  end
431
491
  end
432
492
 
433
- def name
434
- options[:as] || singular
493
+ def member_name
494
+ name
435
495
  end
436
496
  end
437
497
 
@@ -443,7 +503,7 @@ module ActionDispatch
443
503
  def resource(*resources, &block)
444
504
  options = resources.extract_options!
445
505
 
446
- if verify_common_behavior_for(:resource, resources, options, &block)
506
+ if apply_common_behavior_for(:resource, resources, options, &block)
447
507
  return self
448
508
  end
449
509
 
@@ -451,14 +511,19 @@ module ActionDispatch
451
511
 
452
512
  scope(:path => resource.name.to_s, :controller => resource.controller) do
453
513
  with_scope_level(:resource, resource) do
454
- yield if block_given?
455
514
 
456
- get :show if resource.actions.include?(:show)
457
- post :create if resource.actions.include?(:create)
458
- put :update if resource.actions.include?(:update)
459
- delete :destroy if resource.actions.include?(:destroy)
460
- get :new, :as => resource.singular if resource.actions.include?(:new)
461
- get :edit, :as => resource.singular if resource.actions.include?(:edit)
515
+ scope(:name_prefix => resource.name.to_s, :as => "") do
516
+ yield if block_given?
517
+ end
518
+
519
+ scope(resource.options) do
520
+ get :show if resource.actions.include?(:show)
521
+ post :create if resource.actions.include?(:create)
522
+ put :update if resource.actions.include?(:update)
523
+ delete :destroy if resource.actions.include?(:destroy)
524
+ get :new, :as => resource.name if resource.actions.include?(:new)
525
+ get :edit, :as => resource.name if resource.actions.include?(:edit)
526
+ end
462
527
  end
463
528
  end
464
529
 
@@ -468,7 +533,7 @@ module ActionDispatch
468
533
  def resources(*resources, &block)
469
534
  options = resources.extract_options!
470
535
 
471
- if verify_common_behavior_for(:resources, resources, options, &block)
536
+ if apply_common_behavior_for(:resources, resources, options, &block)
472
537
  return self
473
538
  end
474
539
 
@@ -479,17 +544,21 @@ module ActionDispatch
479
544
  yield if block_given?
480
545
 
481
546
  with_scope_level(:collection) do
482
- get :index if resource.actions.include?(:index)
483
- post :create if resource.actions.include?(:create)
484
- get :new, :as => resource.singular if resource.actions.include?(:new)
547
+ scope(resource.collection_options) do
548
+ get :index if resource.actions.include?(:index)
549
+ post :create if resource.actions.include?(:create)
550
+ get :new, :as => resource.singular if resource.actions.include?(:new)
551
+ end
485
552
  end
486
553
 
487
554
  with_scope_level(:member) do
488
555
  scope(':id') do
489
- get :show if resource.actions.include?(:show)
490
- put :update if resource.actions.include?(:update)
491
- delete :destroy if resource.actions.include?(:destroy)
492
- get :edit, :as => resource.singular if resource.actions.include?(:edit)
556
+ scope(resource.options) do
557
+ get :show if resource.actions.include?(:show)
558
+ put :update if resource.actions.include?(:update)
559
+ delete :destroy if resource.actions.include?(:destroy)
560
+ get :edit, :as => resource.singular if resource.actions.include?(:edit)
561
+ end
493
562
  end
494
563
  end
495
564
  end
@@ -528,15 +597,32 @@ module ActionDispatch
528
597
  end
529
598
 
530
599
  with_scope_level(:nested) do
531
- scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do
600
+ scope(parent_resource.id_segment, parent_resource.nested_options) do
532
601
  yield
533
602
  end
534
603
  end
535
604
  end
536
605
 
606
+ def mount(app, options = nil)
607
+ if options
608
+ path = options.delete(:at)
609
+ else
610
+ options = app
611
+ app, path = options.find { |k, v| k.respond_to?(:call) }
612
+ options.delete(app) if app
613
+ end
614
+
615
+ raise "A rack application must be specified" unless path
616
+
617
+ match(path, options.merge(:to => app, :anchor => false))
618
+ self
619
+ end
620
+
537
621
  def match(*args)
538
622
  options = args.extract_options!
539
623
 
624
+ options[:anchor] = true unless options.key?(:anchor)
625
+
540
626
  if args.length > 1
541
627
  args.each { |path| match(path, options) }
542
628
  return self
@@ -591,7 +677,7 @@ module ActionDispatch
591
677
  path_names[name.to_sym] || name.to_s
592
678
  end
593
679
 
594
- def verify_common_behavior_for(method, resources, options, &block)
680
+ def apply_common_behavior_for(method, resources, options, &block)
595
681
  if resources.length > 1
596
682
  resources.each { |r| send(method, r, options, &block) }
597
683
  return true