actionpack 3.0.0.beta4 → 3.0.0.rc

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 (102) hide show
  1. data/CHANGELOG +36 -0
  2. data/{README → README.rdoc} +79 -137
  3. data/lib/abstract_controller.rb +1 -0
  4. data/lib/abstract_controller/asset_paths.rb +1 -1
  5. data/lib/abstract_controller/base.rb +3 -12
  6. data/lib/abstract_controller/rendering.rb +2 -2
  7. data/lib/abstract_controller/view_paths.rb +2 -1
  8. data/lib/action_controller.rb +1 -2
  9. data/lib/action_controller/base.rb +3 -9
  10. data/lib/action_controller/log_subscriber.rb +56 -0
  11. data/lib/action_controller/metal.rb +10 -3
  12. data/lib/action_controller/metal/helpers.rb +5 -4
  13. data/lib/action_controller/metal/hide_actions.rb +3 -3
  14. data/lib/action_controller/metal/instrumentation.rb +2 -1
  15. data/lib/action_controller/metal/mime_responds.rb +13 -10
  16. data/lib/action_controller/metal/rack_delegation.rb +0 -4
  17. data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
  18. data/lib/action_controller/metal/rescue.rb +9 -0
  19. data/lib/action_controller/metal/responder.rb +13 -5
  20. data/lib/action_controller/metal/streaming.rb +2 -0
  21. data/lib/action_controller/metal/url_for.rb +5 -5
  22. data/lib/action_controller/railtie.rb +14 -23
  23. data/lib/action_controller/record_identifier.rb +6 -25
  24. data/lib/action_controller/test_case.rb +18 -6
  25. data/lib/action_controller/vendor/html-scanner/html/node.rb +1 -0
  26. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -0
  27. data/lib/action_dispatch.rb +6 -0
  28. data/lib/action_dispatch/http/cache.rb +2 -2
  29. data/lib/action_dispatch/http/filter_parameters.rb +10 -66
  30. data/lib/action_dispatch/http/mime_type.rb +1 -1
  31. data/lib/action_dispatch/http/parameter_filter.rb +72 -0
  32. data/lib/action_dispatch/http/parameters.rb +31 -2
  33. data/lib/action_dispatch/http/request.rb +4 -1
  34. data/lib/action_dispatch/http/upload.rb +2 -2
  35. data/lib/action_dispatch/middleware/callbacks.rb +4 -4
  36. data/lib/action_dispatch/middleware/cookies.rb +39 -6
  37. data/lib/action_dispatch/middleware/flash.rb +9 -2
  38. data/lib/action_dispatch/middleware/session/abstract_store.rb +121 -36
  39. data/lib/action_dispatch/middleware/session/cookie_store.rb +26 -19
  40. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -1
  41. data/lib/action_dispatch/middleware/show_exceptions.rb +2 -2
  42. data/lib/action_dispatch/middleware/stack.rb +12 -5
  43. data/lib/action_dispatch/railtie.rb +1 -1
  44. data/lib/action_dispatch/routing.rb +11 -13
  45. data/lib/action_dispatch/routing/deprecated_mapper.rb +6 -388
  46. data/lib/action_dispatch/routing/mapper.rb +364 -234
  47. data/lib/action_dispatch/routing/polymorphic_routes.rb +186 -0
  48. data/lib/action_dispatch/routing/route.rb +11 -2
  49. data/lib/action_dispatch/routing/route_set.rb +62 -28
  50. data/lib/action_dispatch/routing/url_for.rb +2 -1
  51. data/lib/action_dispatch/testing/assertions.rb +0 -2
  52. data/lib/action_dispatch/testing/assertions/routing.rb +0 -1
  53. data/lib/action_dispatch/testing/assertions/selector.rb +20 -24
  54. data/lib/action_dispatch/testing/integration.rb +2 -2
  55. data/lib/action_dispatch/testing/test_response.rb +2 -2
  56. data/lib/action_pack/version.rb +1 -1
  57. data/lib/action_view.rb +1 -0
  58. data/lib/action_view/base.rb +20 -21
  59. data/lib/action_view/context.rb +9 -12
  60. data/lib/action_view/helpers.rb +0 -2
  61. data/lib/action_view/helpers/active_model_helper.rb +17 -2
  62. data/lib/action_view/helpers/asset_tag_helper.rb +15 -33
  63. data/lib/action_view/helpers/atom_feed_helper.rb +5 -3
  64. data/lib/action_view/helpers/cache_helper.rb +4 -2
  65. data/lib/action_view/helpers/capture_helper.rb +4 -4
  66. data/lib/action_view/helpers/csrf_helper.rb +3 -1
  67. data/lib/action_view/helpers/date_helper.rb +10 -5
  68. data/lib/action_view/helpers/debug_helper.rb +3 -1
  69. data/lib/action_view/helpers/form_helper.rb +36 -30
  70. data/lib/action_view/helpers/form_options_helper.rb +7 -6
  71. data/lib/action_view/helpers/form_tag_helper.rb +17 -6
  72. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  73. data/lib/action_view/helpers/number_helper.rb +16 -45
  74. data/lib/action_view/helpers/prototype_helper.rb +14 -16
  75. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  76. data/lib/action_view/helpers/record_tag_helper.rb +5 -0
  77. data/lib/action_view/helpers/sanitize_helper.rb +26 -20
  78. data/lib/action_view/helpers/scriptaculous_helper.rb +6 -5
  79. data/lib/action_view/helpers/tag_helper.rb +2 -1
  80. data/lib/action_view/helpers/text_helper.rb +24 -111
  81. data/lib/action_view/helpers/translation_helper.rb +17 -10
  82. data/lib/action_view/helpers/url_helper.rb +26 -33
  83. data/lib/action_view/log_subscriber.rb +28 -0
  84. data/lib/action_view/lookup_context.rb +2 -0
  85. data/lib/action_view/paths.rb +1 -0
  86. data/lib/action_view/railtie.rb +15 -3
  87. data/lib/action_view/render/layouts.rb +2 -1
  88. data/lib/action_view/render/partials.rb +3 -1
  89. data/lib/action_view/render/rendering.rb +2 -1
  90. data/lib/action_view/template.rb +12 -8
  91. data/lib/action_view/template/error.rb +1 -0
  92. data/lib/action_view/template/handlers.rb +1 -0
  93. data/lib/action_view/template/resolver.rb +2 -1
  94. data/lib/action_view/template/text.rb +1 -0
  95. data/lib/action_view/test_case.rb +42 -20
  96. metadata +44 -23
  97. data/lib/action_controller/polymorphic_routes.rb +0 -182
  98. data/lib/action_controller/railties/log_subscriber.rb +0 -56
  99. data/lib/action_controller/railties/url_helpers.rb +0 -14
  100. data/lib/action_dispatch/testing/assertions/model.rb +0 -19
  101. data/lib/action_view/helpers/record_identification_helper.rb +0 -20
  102. data/lib/action_view/railties/log_subscriber.rb +0 -24
@@ -19,70 +19,22 @@ module ActionDispatch
19
19
 
20
20
  def in_memory_controller_namespaces
21
21
  namespaces = Set.new
22
- ActionController::Base.subclasses.each do |klass|
23
- controller_name = klass.underscore
24
- namespaces << controller_name.split('/')[0...-1].join('/')
22
+ ActionController::Base.descendants.each do |klass|
23
+ next if klass.anonymous?
24
+ namespaces << klass.name.underscore.split('/')[0...-1].join('/')
25
25
  end
26
26
  namespaces.delete('')
27
27
  namespaces
28
28
  end
29
29
  end
30
30
 
31
- # Mapper instances are used to build routes. The object passed to the draw
32
- # block in config/routes.rb is a Mapper instance.
33
- #
34
- # Mapper instances have relatively few instance methods, in order to avoid
35
- # clashes with named routes.
36
- #
37
- # == Overview
38
- #
39
- # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
40
- # is something that can be pointed at and it will respond with a representation of the data requested.
41
- # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
42
- # requests XML data.
43
- #
44
- # RESTful design is based on the assumption that there are four generic verbs that a user of an
45
- # application can request from a \resource (the noun).
46
- #
47
- # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
48
- # denotes the type of action that should take place.
49
- #
50
- # === The Different Methods and their Usage
51
- #
52
- # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
53
- # * POST - Creation of \resources.
54
- # * PUT - Editing of attributes on a \resource.
55
- # * DELETE - Deletion of a \resource.
56
- #
57
- # === Examples
58
- #
59
- # # A GET request on the Posts resource is asking for all Posts
60
- # GET /posts
61
- #
62
- # # A GET request on a single Post resource is asking for that particular Post
63
- # GET /posts/1
64
- #
65
- # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
66
- # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
67
- #
68
- # # A PUT request on a single Post resource is asking for a Post to be updated
69
- # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
70
- #
71
- # # A DELETE request on a single Post resource is asking for it to be deleted
72
- # DELETE /posts # with => { :id => 1 }
73
- #
74
- # By using the REST convention, users of our application can assume certain things about how the data
75
- # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
76
- # supplying you with methods to create them in your routes.rb file.
77
- #
78
- # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
79
31
  class DeprecatedMapper #:nodoc:
80
32
  def initialize(set) #:nodoc:
33
+ ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " <<
34
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
81
35
  @set = set
82
36
  end
83
37
 
84
- # Create an unnamed route with the provided +path+ and +options+. See
85
- # ActionDispatch::Routing for an introduction to routes.
86
38
  def connect(path, options = {})
87
39
  options = options.dup
88
40
 
@@ -240,17 +192,6 @@ module ActionDispatch
240
192
  connect(path, options)
241
193
  end
242
194
 
243
- # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
244
- # Example:
245
- #
246
- # map.namespace(:admin) do |admin|
247
- # admin.resources :products,
248
- # :has_many => [ :tags, :images, :variants ]
249
- # end
250
- #
251
- # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
252
- # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
253
- # Admin::TagsController.
254
195
  def namespace(name, options = {}, &block)
255
196
  if options[:namespace]
256
197
  with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
@@ -411,334 +352,11 @@ module ActionDispatch
411
352
  alias_method :nesting_path_prefix, :path
412
353
  end
413
354
 
414
- # Creates named routes for implementing verb-oriented controllers
415
- # for a collection \resource.
416
- #
417
- # For example:
418
- #
419
- # map.resources :messages
420
- #
421
- # will map the following actions in the corresponding controller:
422
- #
423
- # class MessagesController < ActionController::Base
424
- # # GET messages_url
425
- # def index
426
- # # return all messages
427
- # end
428
- #
429
- # # GET new_message_url
430
- # def new
431
- # # return an HTML form for describing a new message
432
- # end
433
- #
434
- # # POST messages_url
435
- # def create
436
- # # create a new message
437
- # end
438
- #
439
- # # GET message_url(:id => 1)
440
- # def show
441
- # # find and return a specific message
442
- # end
443
- #
444
- # # GET edit_message_url(:id => 1)
445
- # def edit
446
- # # return an HTML form for editing a specific message
447
- # end
448
- #
449
- # # PUT message_url(:id => 1)
450
- # def update
451
- # # find and update a specific message
452
- # end
453
- #
454
- # # DELETE message_url(:id => 1)
455
- # def destroy
456
- # # delete a specific message
457
- # end
458
- # end
459
- #
460
- # Along with the routes themselves, +resources+ generates named routes for use in
461
- # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
462
- #
463
- # Named Route Helpers
464
- # ============ =====================================================
465
- # messages messages_url, hash_for_messages_url,
466
- # messages_path, hash_for_messages_path
467
- #
468
- # message message_url(id), hash_for_message_url(id),
469
- # message_path(id), hash_for_message_path(id)
470
- #
471
- # new_message new_message_url, hash_for_new_message_url,
472
- # new_message_path, hash_for_new_message_path
473
- #
474
- # edit_message edit_message_url(id), hash_for_edit_message_url(id),
475
- # edit_message_path(id), hash_for_edit_message_path(id)
476
- #
477
- # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
478
- #
479
- # redirect_to :controller => 'messages', :action => 'index'
480
- # # and
481
- # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
482
- #
483
- # now become:
484
- #
485
- # redirect_to messages_url
486
- # # and
487
- # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
488
- #
489
- # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
490
- # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
491
- #
492
- # <%= form_tag message_path(@message), :method => :put %>
493
- #
494
- # or
495
- #
496
- # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
497
- #
498
- # or
499
- #
500
- # <% form_for @message do |f| %>
501
- #
502
- # which takes into account whether <tt>@message</tt> is a new record or not and generates the
503
- # path and method accordingly.
504
- #
505
- # The +resources+ method accepts the following options to customize the resulting routes:
506
- # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
507
- # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
508
- # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
509
- # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
510
- # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
511
- # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
512
- # * <tt>:controller</tt> - Specify the controller name for the routes.
513
- # * <tt>:singular</tt> - Specify the singular name used in the member routes.
514
- # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
515
- # regular expressions (which must match for the route to match) or extra parameters. For example:
516
- #
517
- # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
518
- #
519
- # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
520
- # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
521
- # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
522
- # # products_path == '/productos'
523
- # map.resources :products, :as => 'productos' do |product|
524
- # # product_reviews_path(product) == '/productos/1234/comentarios'
525
- # product.resources :product_reviews, :as => 'comentarios'
526
- # end
527
- #
528
- # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
529
- # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
530
- #
531
- # You may directly specify the routing association with +has_one+ and +has_many+ like:
532
- #
533
- # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
534
- #
535
- # This is the same as:
536
- #
537
- # map.resources :notes do |notes|
538
- # notes.resource :author
539
- # notes.resources :comments
540
- # notes.resources :attachments
541
- # end
542
- #
543
- # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
544
- # # new_products_path == '/productos/nuevo'
545
- # # bids_product_path(1) == '/productos/1/licitacoes'
546
- # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
547
- #
548
- # You can also set default action names from an environment, like this:
549
- # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
550
- #
551
- # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
552
- #
553
- # Weblog comments usually belong to a post, so you might use +resources+ like:
554
- #
555
- # map.resources :articles
556
- # map.resources :comments, :path_prefix => '/articles/:article_id'
557
- #
558
- # You can nest +resources+ calls to set this automatically:
559
- #
560
- # map.resources :articles do |article|
561
- # article.resources :comments
562
- # end
563
- #
564
- # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
565
- #
566
- # article_comments_url(@article)
567
- # article_comment_url(@article, @comment)
568
- #
569
- # article_comments_url(:article_id => @article)
570
- # article_comment_url(:article_id => @article, :id => @comment)
571
- #
572
- # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
573
- #
574
- # articles_comments_url(@comment.article_id, @comment)
575
- #
576
- # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
577
- # Use this if you have named routes that may clash.
578
- #
579
- # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
580
- # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
581
- #
582
- # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
583
- #
584
- # map.resources :articles do |article|
585
- # article.resources :comments, :name_prefix => nil
586
- # end
587
- #
588
- # This will yield named \resources like so:
589
- #
590
- # comments_url(@article)
591
- # comment_url(@article, @comment)
592
- #
593
- # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
594
- # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
595
- #
596
- # The <tt>:shallow</tt> option is inherited by any nested resource(s).
597
- #
598
- # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
599
- #
600
- # map.resources :users, :shallow => true do |user|
601
- # user.resources :posts do |post|
602
- # post.resources :comments
603
- # end
604
- # end
605
- # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
606
- # # also adds the usual named route called "user_posts"
607
- # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
608
- # # also adds the named route called "post"
609
- # # --> GET /posts/2/comments (maps to the CommentsController#index action)
610
- # # also adds the named route called "post_comments"
611
- # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
612
- # # also adds the named route called "comment"
613
- #
614
- # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
615
- #
616
- # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
617
- #
618
- # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
619
- #
620
- # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
621
- # list of action names. By default, routes are generated for all seven actions.
622
- #
623
- # For example:
624
- #
625
- # map.resources :posts, :only => [:index, :show] do |post|
626
- # post.resources :comments, :except => [:update, :destroy]
627
- # end
628
- # # --> GET /posts (maps to the PostsController#index action)
629
- # # --> POST /posts (fails)
630
- # # --> GET /posts/1 (maps to the PostsController#show action)
631
- # # --> DELETE /posts/1 (fails)
632
- # # --> POST /posts/1/comments (maps to the CommentsController#create action)
633
- # # --> PUT /posts/1/comments/1 (fails)
634
- #
635
- # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
636
- #
637
- # Examples:
638
- #
639
- # map.resources :messages, :path_prefix => "/thread/:thread_id"
640
- # # --> GET /thread/7/messages/1
641
- #
642
- # map.resources :messages, :collection => { :rss => :get }
643
- # # --> GET /messages/rss (maps to the #rss action)
644
- # # also adds a named route called "rss_messages"
645
- #
646
- # map.resources :messages, :member => { :mark => :post }
647
- # # --> POST /messages/1/mark (maps to the #mark action)
648
- # # also adds a named route called "mark_message"
649
- #
650
- # map.resources :messages, :new => { :preview => :post }
651
- # # --> POST /messages/new/preview (maps to the #preview action)
652
- # # also adds a named route called "preview_new_message"
653
- #
654
- # map.resources :messages, :new => { :new => :any, :preview => :post }
655
- # # --> POST /messages/new/preview (maps to the #preview action)
656
- # # also adds a named route called "preview_new_message"
657
- # # --> /messages/new can be invoked via any request method
658
- #
659
- # map.resources :messages, :controller => "categories",
660
- # :path_prefix => "/category/:category_id",
661
- # :name_prefix => "category_"
662
- # # --> GET /categories/7/messages/1
663
- # # has named route "category_message"
664
- #
665
- # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
666
- # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
667
- # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
668
355
  def resources(*entities, &block)
669
356
  options = entities.extract_options!
670
357
  entities.each { |entity| map_resource(entity, options.dup, &block) }
671
358
  end
672
359
 
673
- # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
674
- # A singleton \resource is global to its current context. For unnested singleton \resources,
675
- # the \resource is global to the current user visiting the application, such as a user's
676
- # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
677
- # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
678
- # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
679
- #
680
- # map.resources :projects do |project|
681
- # project.resource :project_manager
682
- # end
683
- #
684
- # See +resources+ for general conventions. These are the main differences:
685
- # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
686
- # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
687
- # * No default index route is created for the singleton \resource controller.
688
- # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
689
- #
690
- # For example:
691
- #
692
- # map.resource :account
693
- #
694
- # maps these actions in the Accounts controller:
695
- #
696
- # class AccountsController < ActionController::Base
697
- # # GET new_account_url
698
- # def new
699
- # # return an HTML form for describing the new account
700
- # end
701
- #
702
- # # POST account_url
703
- # def create
704
- # # create an account
705
- # end
706
- #
707
- # # GET account_url
708
- # def show
709
- # # find and return the account
710
- # end
711
- #
712
- # # GET edit_account_url
713
- # def edit
714
- # # return an HTML form for editing the account
715
- # end
716
- #
717
- # # PUT account_url
718
- # def update
719
- # # find and update the account
720
- # end
721
- #
722
- # # DELETE account_url
723
- # def destroy
724
- # # delete the account
725
- # end
726
- # end
727
- #
728
- # Along with the routes themselves, +resource+ generates named routes for
729
- # use in controllers and views. <tt>map.resource :account</tt> produces
730
- # these named routes and helpers:
731
- #
732
- # Named Route Helpers
733
- # ============ =============================================
734
- # account account_url, hash_for_account_url,
735
- # account_path, hash_for_account_path
736
- #
737
- # new_account new_account_url, hash_for_new_account_url,
738
- # new_account_path, hash_for_new_account_path
739
- #
740
- # edit_account edit_account_url, hash_for_edit_account_url,
741
- # edit_account_path, hash_for_edit_account_path
742
360
  def resource(*entities, &block)
743
361
  options = entities.extract_options!
744
362
  entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
@@ -882,7 +500,7 @@ module ActionDispatch
882
500
  end
883
501
 
884
502
  def add_conditions_for(conditions, method)
885
- returning({:conditions => conditions.dup}) do |options|
503
+ {:conditions => conditions.dup}.tap do |options|
886
504
  options[:conditions][:method] = method unless method == :any
887
505
  end
888
506
  end
@@ -13,6 +13,8 @@ module ActionDispatch
13
13
  end
14
14
  end
15
15
 
16
+ attr_reader :app
17
+
16
18
  def initialize(app, constraints, request)
17
19
  @app, @constraints, @request = app, constraints, request
18
20
  end
@@ -33,11 +35,12 @@ module ActionDispatch
33
35
  end
34
36
 
35
37
  class Mapping #:nodoc:
36
- IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor]
38
+ IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
37
39
 
38
40
  def initialize(set, scope, args)
39
41
  @set, @scope = set, scope
40
42
  @path, @options = extract_path_and_options(args)
43
+ normalize_options!
41
44
  end
42
45
 
43
46
  def to_route
@@ -55,23 +58,29 @@ module ActionDispatch
55
58
  path = args.first
56
59
  end
57
60
 
58
- if @scope[:module] && options[:to]
59
- if options[:to].to_s.include?("#")
60
- options[:to] = "#{@scope[:module]}/#{options[:to]}"
61
- elsif @scope[:controller].nil?
62
- options[:to] = "#{@scope[:module]}##{options[:to]}"
63
- end
61
+ if path.match(':controller')
62
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
63
+
64
+ # Add a default constraint for :controller path segments that matches namespaced
65
+ # controllers with default routes like :controller/:action/:id(.:format), e.g:
66
+ # GET /admin/products/show/1
67
+ # => { :controller => 'admin/products', :action => 'show', :id => '1' }
68
+ options.reverse_merge!(:controller => /.+?/)
64
69
  end
65
70
 
66
- path = normalize_path(path)
67
- path_without_format = path.sub(/\(\.:format\)$/, '')
71
+ [ normalize_path(path), options ]
72
+ end
73
+
74
+ def normalize_options!
75
+ path_without_format = @path.sub(/\(\.:format\)$/, '')
68
76
 
69
- if using_match_shorthand?(path_without_format, options)
70
- options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
71
- options[:as] ||= path_without_format[1..-1].gsub("/", "_")
77
+ if using_match_shorthand?(path_without_format, @options)
78
+ to_shorthand = @options[:to].blank?
79
+ @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
80
+ @options[:as] ||= path_without_format[1..-1].gsub("/", "_")
72
81
  end
73
82
 
74
- [ path, options ]
83
+ @options.merge!(default_controller_and_action(to_shorthand))
75
84
  end
76
85
 
77
86
  # match "account" => "account#index"
@@ -102,7 +111,7 @@ module ActionDispatch
102
111
  end
103
112
 
104
113
  def requirements
105
- @requirements ||= (@options[:constraints] || {}).tap do |requirements|
114
+ @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
106
115
  requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
107
116
  @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
108
117
  end
@@ -110,40 +119,43 @@ module ActionDispatch
110
119
 
111
120
  def defaults
112
121
  @defaults ||= (@options[:defaults] || {}).tap do |defaults|
113
- defaults.merge!(default_controller_and_action)
114
122
  defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
115
123
  @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
116
124
  end
117
125
  end
118
126
 
119
- def default_controller_and_action
127
+ def default_controller_and_action(to_shorthand=nil)
120
128
  if to.respond_to?(:call)
121
129
  { }
122
130
  else
123
- defaults = case to
124
- when String
131
+ if to.is_a?(String)
125
132
  controller, action = to.split('#')
126
- { :controller => controller, :action => action }
127
- when Symbol
128
- { :action => to.to_s }
129
- else
130
- {}
133
+ elsif to.is_a?(Symbol)
134
+ action = to.to_s
131
135
  end
132
136
 
133
- defaults[:controller] ||= default_controller
137
+ controller ||= default_controller
138
+ action ||= default_action
134
139
 
135
- defaults.delete(:controller) if defaults[:controller].blank?
136
- defaults.delete(:action) if defaults[:action].blank?
140
+ unless controller.is_a?(Regexp) || to_shorthand
141
+ controller = [@scope[:module], controller].compact.join("/").presence
142
+ end
143
+
144
+ controller = controller.to_s unless controller.is_a?(Regexp)
145
+ action = action.to_s unless action.is_a?(Regexp)
137
146
 
138
- if defaults[:controller].blank? && segment_keys.exclude?("controller")
147
+ if controller.blank? && segment_keys.exclude?("controller")
139
148
  raise ArgumentError, "missing :controller"
140
149
  end
141
150
 
142
- if defaults[:action].blank? && segment_keys.exclude?("action")
151
+ if action.blank? && segment_keys.exclude?("action")
143
152
  raise ArgumentError, "missing :action"
144
153
  end
145
154
 
146
- defaults
155
+ { :controller => controller, :action => action }.tap do |hash|
156
+ hash.delete(:controller) if hash[:controller].blank?
157
+ hash.delete(:action) if hash[:action].blank?
158
+ end
147
159
  end
148
160
  end
149
161
 
@@ -182,9 +194,17 @@ module ActionDispatch
182
194
 
183
195
  def default_controller
184
196
  if @options[:controller]
185
- @options[:controller].to_s
197
+ @options[:controller]
186
198
  elsif @scope[:controller]
187
- @scope[:controller].to_s
199
+ @scope[:controller]
200
+ end
201
+ end
202
+
203
+ def default_action
204
+ if @options[:action]
205
+ @options[:action]
206
+ elsif @scope[:action]
207
+ @scope[:action]
188
208
  end
189
209
  end
190
210
  end
@@ -194,7 +214,7 @@ module ActionDispatch
194
214
  # for root cases, where the latter is the correct one.
195
215
  def self.normalize_path(path)
196
216
  path = Rack::Mount::Utils.normalize_path(path)
197
- path.sub!(%r{/(\(+)/?:}, '\1/:') unless path =~ %r{^/\(+:.*\)$}
217
+ path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$}
198
218
  path
199
219
  end
200
220
 
@@ -299,6 +319,11 @@ module ActionDispatch
299
319
  options = args.extract_options!
300
320
  options = options.dup
301
321
 
322
+ if name_prefix = options.delete(:name_prefix)
323
+ options[:as] ||= name_prefix
324
+ ActiveSupport::Deprecation.warn ":name_prefix was deprecated in the new router syntax. Use :as instead.", caller
325
+ end
326
+
302
327
  case args.first
303
328
  when String
304
329
  options[:path] = args.first
@@ -341,9 +366,11 @@ module ActionDispatch
341
366
  scope(controller.to_sym) { yield }
342
367
  end
343
368
 
344
- def namespace(path)
369
+ def namespace(path, options = {})
345
370
  path = path.to_s
346
- scope(:path => path, :name_prefix => path, :module => path) { yield }
371
+ options = { :path => path, :as => path, :module => path,
372
+ :shallow_path => path, :shallow_prefix => path }.merge!(options)
373
+ scope(options) { yield }
347
374
  end
348
375
 
349
376
  def constraints(constraints = {})
@@ -359,10 +386,10 @@ module ActionDispatch
359
386
 
360
387
  options = (@scope[:options] || {}).merge(options)
361
388
 
362
- if @scope[:name_prefix] && !options[:as].blank?
363
- options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}"
364
- elsif @scope[:name_prefix] && options[:as] == ""
365
- options[:as] = @scope[:name_prefix].to_s
389
+ if @scope[:as] && !options[:as].blank?
390
+ options[:as] = "#{@scope[:as]}_#{options[:as]}"
391
+ elsif @scope[:as] && options[:as] == ""
392
+ options[:as] = @scope[:as].to_s
366
393
  end
367
394
 
368
395
  args.push(options)
@@ -378,7 +405,15 @@ module ActionDispatch
378
405
  Mapper.normalize_path("#{parent}/#{child}")
379
406
  end
380
407
 
381
- def merge_name_prefix_scope(parent, child)
408
+ def merge_shallow_path_scope(parent, child)
409
+ Mapper.normalize_path("#{parent}/#{child}")
410
+ end
411
+
412
+ def merge_as_scope(parent, child)
413
+ parent ? "#{parent}_#{child}" : child
414
+ end
415
+
416
+ def merge_shallow_prefix_scope(parent, child)
382
417
  parent ? "#{parent}_#{child}" : child
383
418
  end
384
419
 
@@ -387,7 +422,7 @@ module ActionDispatch
387
422
  end
388
423
 
389
424
  def merge_controller_scope(parent, child)
390
- @scope[:module] ? "#{@scope[:module]}/#{child}" : child
425
+ child
391
426
  end
392
427
 
393
428
  def merge_path_names_scope(parent, child)
@@ -403,56 +438,60 @@ module ActionDispatch
403
438
  end
404
439
 
405
440
  def merge_blocks_scope(parent, child)
406
- (parent || []) + [child]
441
+ merged = parent ? parent.dup : []
442
+ merged << child if child
443
+ merged
407
444
  end
408
445
 
409
446
  def merge_options_scope(parent, child)
410
- (parent || {}).merge(child)
447
+ (parent || {}).except(*override_keys(child)).merge(child)
448
+ end
449
+
450
+ def merge_shallow_scope(parent, child)
451
+ child ? true : false
452
+ end
453
+
454
+ def override_keys(child)
455
+ child.key?(:only) || child.key?(:except) ? [:only, :except] : []
411
456
  end
412
457
  end
413
458
 
414
459
  module Resources
415
- CRUD_ACTIONS = [:index, :show, :create, :update, :destroy] #:nodoc:
460
+ # CANONICAL_ACTIONS holds all actions that does not need a prefix or
461
+ # a path appended since they fit properly in their scope level.
462
+ VALID_ON_OPTIONS = [:new, :collection, :member]
463
+ CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy]
464
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
416
465
 
417
466
  class Resource #:nodoc:
418
- def self.default_actions
419
- [:index, :create, :new, :show, :update, :destroy, :edit]
420
- end
467
+ DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
421
468
 
422
469
  attr_reader :controller, :path, :options
423
470
 
424
471
  def initialize(entities, options = {})
425
472
  @name = entities.to_s
426
473
  @path = options.delete(:path) || @name
427
- @controller = options.delete(:controller) || @name.to_s.pluralize
474
+ @controller = (options.delete(:controller) || @name).to_s
475
+ @as = options.delete(:as)
428
476
  @options = options
429
477
  end
430
478
 
431
479
  def default_actions
432
- self.class.default_actions
480
+ self.class::DEFAULT_ACTIONS
433
481
  end
434
482
 
435
483
  def actions
436
- if only = options[:only]
484
+ if only = @options[:only]
437
485
  Array(only).map(&:to_sym)
438
- elsif except = options[:except]
486
+ elsif except = @options[:except]
439
487
  default_actions - Array(except).map(&:to_sym)
440
488
  else
441
489
  default_actions
442
490
  end
443
491
  end
444
492
 
445
- def action_type(action)
446
- case action
447
- when :index, :create
448
- :collection
449
- when :show, :update, :destroy
450
- :member
451
- end
452
- end
453
-
454
493
  def name
455
- options[:as] || @name
494
+ @as || @name
456
495
  end
457
496
 
458
497
  def plural
@@ -463,98 +502,57 @@ module ActionDispatch
463
502
  name.to_s.singularize
464
503
  end
465
504
 
466
- def member_prefix
467
- ':id'
468
- end
469
-
470
505
  def member_name
471
506
  singular
472
507
  end
473
508
 
474
509
  # Checks for uncountable plurals, and appends "_index" if they're.
475
510
  def collection_name
476
- uncountable? ? "#{plural}_index" : plural
511
+ singular == plural ? "#{plural}_index" : plural
477
512
  end
478
513
 
479
- def uncountable?
480
- singular == plural
514
+ def resource_scope
515
+ { :controller => controller }
481
516
  end
482
517
 
483
- def name_for_action(action)
484
- case action_type(action)
485
- when :collection
486
- collection_name
487
- when :member
488
- member_name
489
- end
518
+ def collection_scope
519
+ path
490
520
  end
491
521
 
492
- def id_segment
493
- ":#{singular}_id"
522
+ def member_scope
523
+ "#{path}/:id"
494
524
  end
495
525
 
496
- def constraints
497
- options[:constraints] || {}
526
+ def new_scope(new_path)
527
+ "#{path}/#{new_path}"
498
528
  end
499
529
 
500
- def id_constraint?
501
- options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp)
530
+ def nested_scope
531
+ "#{path}/:#{singular}_id"
502
532
  end
503
533
 
504
- def id_constraint
505
- options[:id] || constraints[:id]
506
- end
507
-
508
- def collection_options
509
- (options || {}).dup.tap do |options|
510
- options.delete(:id)
511
- options[:constraints] = options[:constraints].dup if options[:constraints]
512
- options[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
513
- end
514
- end
515
-
516
- def nested_prefix
517
- id_segment
518
- end
519
-
520
- def nested_options
521
- options = { :name_prefix => member_name }
522
- options["#{singular}_id".to_sym] = id_constraint if id_constraint?
523
- options
524
- end
525
534
  end
526
535
 
527
536
  class SingletonResource < Resource #:nodoc:
528
- def self.default_actions
529
- [:show, :create, :update, :destroy, :new, :edit]
530
- end
537
+ DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
531
538
 
532
- def initialize(entity, options = {})
533
- super
534
- end
535
-
536
- def action_type(action)
537
- case action
538
- when :show, :create, :update, :destroy
539
- :member
540
- end
541
- end
542
-
543
- def member_prefix
544
- ''
539
+ def initialize(entities, options)
540
+ @name = entities.to_s
541
+ @path = options.delete(:path) || @name
542
+ @controller = (options.delete(:controller) || plural).to_s
543
+ @as = options.delete(:as)
544
+ @options = options
545
545
  end
546
546
 
547
547
  def member_name
548
548
  name
549
549
  end
550
+ alias :collection_name :member_name
550
551
 
551
- def nested_prefix
552
- ''
553
- end
554
-
555
- def nested_options
556
- { :name_prefix => member_name }
552
+ def member_scope
553
+ path
557
554
  end
555
+ alias :nested_scope :member_scope
558
556
  end
559
557
 
560
558
  def initialize(*args) #:nodoc:
@@ -562,31 +560,33 @@ module ActionDispatch
562
560
  @scope[:path_names] = @set.resources_path_names
563
561
  end
564
562
 
563
+ def resources_path_names(options)
564
+ @scope[:path_names].merge!(options)
565
+ end
566
+
565
567
  def resource(*resources, &block)
566
568
  options = resources.extract_options!
567
- options = (@scope[:options] || {}).merge(options)
568
569
 
569
570
  if apply_common_behavior_for(:resource, resources, options, &block)
570
571
  return self
571
572
  end
572
573
 
573
- resource = SingletonResource.new(resources.pop, options)
574
+ resource_scope(SingletonResource.new(resources.pop, options)) do
575
+ yield if block_given?
574
576
 
575
- scope(:path => resource.path, :controller => resource.controller) do
576
- with_scope_level(:resource, resource) do
577
+ collection_scope do
578
+ post :create
579
+ end if parent_resource.actions.include?(:create)
577
580
 
578
- yield if block_given?
581
+ new_scope do
582
+ get :new
583
+ end if parent_resource.actions.include?(:new)
579
584
 
580
- with_scope_level(:member) do
581
- scope(resource.options) do
582
- get :show if resource.actions.include?(:show)
583
- post :create if resource.actions.include?(:create)
584
- put :update if resource.actions.include?(:update)
585
- delete :destroy if resource.actions.include?(:destroy)
586
- get :new, :as => resource.name if resource.actions.include?(:new)
587
- get :edit, :as => resource.name if resource.actions.include?(:edit)
588
- end
589
- end
585
+ member_scope do
586
+ get :show if parent_resource.actions.include?(:show)
587
+ put :update if parent_resource.actions.include?(:update)
588
+ delete :destroy if parent_resource.actions.include?(:destroy)
589
+ get :edit if parent_resource.actions.include?(:edit)
590
590
  end
591
591
  end
592
592
 
@@ -595,36 +595,28 @@ module ActionDispatch
595
595
 
596
596
  def resources(*resources, &block)
597
597
  options = resources.extract_options!
598
- options = (@scope[:options] || {}).merge(options)
599
598
 
600
599
  if apply_common_behavior_for(:resources, resources, options, &block)
601
600
  return self
602
601
  end
603
602
 
604
- resource = Resource.new(resources.pop, options)
603
+ resource_scope(Resource.new(resources.pop, options)) do
604
+ yield if block_given?
605
605
 
606
- scope(:path => resource.path, :controller => resource.controller) do
607
- with_scope_level(:resources, resource) do
608
- yield if block_given?
606
+ collection_scope do
607
+ get :index if parent_resource.actions.include?(:index)
608
+ post :create if parent_resource.actions.include?(:create)
609
+ end
609
610
 
610
- with_scope_level(:collection) do
611
- scope(resource.collection_options) do
612
- get :index if resource.actions.include?(:index)
613
- post :create if resource.actions.include?(:create)
614
- get :new, :as => resource.singular if resource.actions.include?(:new)
615
- end
616
- end
611
+ new_scope do
612
+ get :new
613
+ end if parent_resource.actions.include?(:new)
617
614
 
618
- with_scope_level(:member) do
619
- scope(':id') do
620
- scope(resource.options) do
621
- get :show if resource.actions.include?(:show)
622
- put :update if resource.actions.include?(:update)
623
- delete :destroy if resource.actions.include?(:destroy)
624
- get :edit, :as => resource.singular if resource.actions.include?(:edit)
625
- end
626
- end
627
- end
615
+ member_scope do
616
+ get :show if parent_resource.actions.include?(:show)
617
+ put :update if parent_resource.actions.include?(:update)
618
+ delete :destroy if parent_resource.actions.include?(:destroy)
619
+ get :edit if parent_resource.actions.include?(:edit)
628
620
  end
629
621
  end
630
622
 
@@ -636,10 +628,8 @@ module ActionDispatch
636
628
  raise ArgumentError, "can't use collection outside resources scope"
637
629
  end
638
630
 
639
- with_scope_level(:collection) do
640
- scope(:name_prefix => parent_resource.collection_name, :as => "") do
641
- yield
642
- end
631
+ collection_scope do
632
+ yield
643
633
  end
644
634
  end
645
635
 
@@ -648,10 +638,8 @@ module ActionDispatch
648
638
  raise ArgumentError, "can't use member outside resource(s) scope"
649
639
  end
650
640
 
651
- with_scope_level(:member) do
652
- scope(parent_resource.member_prefix, :name_prefix => parent_resource.member_name, :as => "") do
653
- yield
654
- end
641
+ member_scope do
642
+ yield
655
643
  end
656
644
  end
657
645
 
@@ -659,11 +647,9 @@ module ActionDispatch
659
647
  unless resource_scope?
660
648
  raise ArgumentError, "can't use new outside resource(s) scope"
661
649
  end
662
-
663
- with_scope_level(:new) do
664
- scope(new_scope_prefix, :name_prefix => parent_resource.member_name, :as => "") do
665
- yield
666
- end
650
+
651
+ new_scope do
652
+ yield
667
653
  end
668
654
  end
669
655
 
@@ -673,13 +659,23 @@ module ActionDispatch
673
659
  end
674
660
 
675
661
  with_scope_level(:nested) do
676
- scope(parent_resource.nested_prefix, parent_resource.nested_options) do
677
- yield
662
+ if shallow?
663
+ with_exclusive_scope do
664
+ if @scope[:shallow_path].blank?
665
+ scope(parent_resource.nested_scope, nested_options) { yield }
666
+ else
667
+ scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
668
+ scope(parent_resource.nested_scope, nested_options) { yield }
669
+ end
670
+ end
671
+ end
672
+ else
673
+ scope(parent_resource.nested_scope, nested_options) { yield }
678
674
  end
679
675
  end
680
676
  end
681
677
 
682
- def namespace(path)
678
+ def namespace(path, options = {})
683
679
  if resource_scope?
684
680
  nested { super }
685
681
  else
@@ -687,93 +683,105 @@ module ActionDispatch
687
683
  end
688
684
  end
689
685
 
690
- def match(*args)
691
- options = args.extract_options!
686
+ def shallow
687
+ scope(:shallow => true) do
688
+ yield
689
+ end
690
+ end
692
691
 
692
+ def shallow?
693
+ parent_resource.instance_of?(Resource) && @scope[:shallow]
694
+ end
695
+
696
+ def match(*args)
697
+ options = args.extract_options!.dup
693
698
  options[:anchor] = true unless options.key?(:anchor)
694
699
 
695
700
  if args.length > 1
696
- args.each { |path| match(path, options) }
701
+ args.each { |path| match(path, options.dup) }
697
702
  return self
698
703
  end
699
704
 
700
- path_names = options.delete(:path_names)
701
-
702
- if args.first.is_a?(Symbol)
703
- action = args.first
704
- if CRUD_ACTIONS.include?(action)
705
- begin
706
- old_path = @scope[:path]
707
- @scope[:path] = "#{@scope[:path]}(.:format)"
708
- return match(options.reverse_merge(
709
- :to => action,
710
- :as => parent_resource.name_for_action(action)
711
- ))
712
- ensure
713
- @scope[:path] = old_path
714
- end
715
- else
716
- with_exclusive_name_prefix(action_name_prefix(action, options)) do
717
- return match("#{action_path(action, path_names)}(.:format)", options.reverse_merge(:to => action))
718
- end
719
- end
705
+ on = options.delete(:on)
706
+ if VALID_ON_OPTIONS.include?(on)
707
+ args.push(options)
708
+ return send(on){ match(*args) }
709
+ elsif on
710
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
720
711
  end
721
712
 
722
- args.push(options)
723
-
724
- case options.delete(:on)
725
- when :collection
726
- return collection { match(*args) }
727
- when :member
713
+ if @scope[:scope_level] == :resource
714
+ args.push(options)
728
715
  return member { match(*args) }
729
- when :new
730
- return new { match(*args) }
731
716
  end
732
717
 
733
- if @scope[:scope_level] == :resource
734
- return member { match(*args) }
718
+ path = options.delete(:path)
719
+ action = args.first
720
+
721
+ if action.is_a?(Symbol)
722
+ path = path_for_action(action, path)
723
+ options[:to] ||= action
724
+ options[:as] = name_for_action(action, options[:as])
725
+
726
+ with_exclusive_scope do
727
+ return super(path, options)
728
+ end
729
+ elsif resource_method_scope?
730
+ path = path_for_custom_action
731
+ options[:as] = name_for_action(options[:as]) if options[:as]
732
+ args.push(options)
733
+
734
+ with_exclusive_scope do
735
+ scope(path) do
736
+ return super
737
+ end
738
+ end
735
739
  end
736
740
 
737
741
  if resource_scope?
738
742
  raise ArgumentError, "can't define route directly in resource(s) scope"
739
743
  end
740
744
 
745
+ args.push(options)
741
746
  super
742
747
  end
743
748
 
744
749
  def root(options={})
745
- options[:on] ||= :collection if @scope[:scope_level] == :resources
746
- super(options)
750
+ if @scope[:scope_level] == :resources
751
+ with_scope_level(:nested) do
752
+ scope(parent_resource.path, :as => parent_resource.collection_name) do
753
+ super(options)
754
+ end
755
+ end
756
+ else
757
+ super(options)
758
+ end
747
759
  end
748
760
 
749
761
  protected
762
+
750
763
  def parent_resource #:nodoc:
751
764
  @scope[:scope_level_resource]
752
765
  end
753
766
 
754
- private
755
- def action_path(name, path_names = nil)
756
- path_names ||= @scope[:path_names]
757
- path_names[name.to_sym] || name.to_s
758
- end
759
-
760
- def action_name_prefix(action, options = {})
761
- (options[:on] == :new || @scope[:scope_level] == :new) ? "#{action}_new" : action
762
- end
763
-
764
767
  def apply_common_behavior_for(method, resources, options, &block)
765
768
  if resources.length > 1
766
769
  resources.each { |r| send(method, r, options, &block) }
767
770
  return true
768
771
  end
769
772
 
770
- if path_names = options.delete(:path_names)
771
- scope(:path_names => path_names) do
773
+ scope_options = options.slice!(*RESOURCE_OPTIONS)
774
+ unless scope_options.empty?
775
+ scope(scope_options) do
772
776
  send(method, resources.pop, options, &block)
773
777
  end
774
778
  return true
775
779
  end
776
780
 
781
+ unless action_options?(options)
782
+ options.merge!(scope_action_options) if scope_action_options?
783
+ end
784
+
777
785
  if resource_scope?
778
786
  nested do
779
787
  send(method, resources.pop, options, &block)
@@ -784,27 +792,36 @@ module ActionDispatch
784
792
  false
785
793
  end
786
794
 
787
- def new_scope_prefix
788
- @scope[:path_names][:new] || 'new'
795
+ def action_options?(options)
796
+ options[:only] || options[:except]
797
+ end
798
+
799
+ def scope_action_options?
800
+ @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
801
+ end
802
+
803
+ def scope_action_options
804
+ @scope[:options].slice(:only, :except)
789
805
  end
790
806
 
791
807
  def resource_scope?
792
808
  [:resource, :resources].include?(@scope[:scope_level])
793
809
  end
794
810
 
795
- def with_exclusive_name_prefix(prefix)
811
+ def resource_method_scope?
812
+ [:collection, :member, :new].include?(@scope[:scope_level])
813
+ end
814
+
815
+ def with_exclusive_scope
796
816
  begin
797
- old_name_prefix = @scope[:name_prefix]
817
+ old_name_prefix, old_path = @scope[:as], @scope[:path]
818
+ @scope[:as], @scope[:path] = nil, nil
798
819
 
799
- if !old_name_prefix.blank?
800
- @scope[:name_prefix] = "#{prefix}_#{@scope[:name_prefix]}"
801
- else
802
- @scope[:name_prefix] = prefix.to_s
820
+ with_scope_level(:exclusive) do
821
+ yield
803
822
  end
804
-
805
- yield
806
823
  ensure
807
- @scope[:name_prefix] = old_name_prefix
824
+ @scope[:as], @scope[:path] = old_name_prefix, old_path
808
825
  end
809
826
  end
810
827
 
@@ -816,6 +833,119 @@ module ActionDispatch
816
833
  @scope[:scope_level] = old
817
834
  @scope[:scope_level_resource] = old_resource
818
835
  end
836
+
837
+ def resource_scope(resource)
838
+ with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
839
+ scope(parent_resource.resource_scope) do
840
+ yield
841
+ end
842
+ end
843
+ end
844
+
845
+ def new_scope
846
+ with_scope_level(:new) do
847
+ scope(parent_resource.new_scope(action_path(:new))) do
848
+ yield
849
+ end
850
+ end
851
+ end
852
+
853
+ def collection_scope
854
+ with_scope_level(:collection) do
855
+ scope(parent_resource.collection_scope) do
856
+ yield
857
+ end
858
+ end
859
+ end
860
+
861
+ def member_scope
862
+ with_scope_level(:member) do
863
+ scope(parent_resource.member_scope) do
864
+ yield
865
+ end
866
+ end
867
+ end
868
+
869
+ def nested_options
870
+ {}.tap do |options|
871
+ options[:as] = parent_resource.member_name
872
+ options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
873
+ end
874
+ end
875
+
876
+ def id_constraint?
877
+ @scope[:id].is_a?(Regexp) || (@scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp))
878
+ end
879
+
880
+ def id_constraint
881
+ @scope[:id] || @scope[:constraints][:id]
882
+ end
883
+
884
+ def canonical_action?(action, flag)
885
+ flag && CANONICAL_ACTIONS.include?(action)
886
+ end
887
+
888
+ def shallow_scoping?
889
+ shallow? && @scope[:scope_level] == :member
890
+ end
891
+
892
+ def path_for_action(action, path)
893
+ prefix = shallow_scoping? ?
894
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
895
+
896
+ if canonical_action?(action, path.blank?)
897
+ "#{prefix}(.:format)"
898
+ else
899
+ "#{prefix}/#{action_path(action, path)}(.:format)"
900
+ end
901
+ end
902
+
903
+ def path_for_custom_action
904
+ if shallow_scoping?
905
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
906
+ else
907
+ @scope[:path]
908
+ end
909
+ end
910
+
911
+ def action_path(name, path = nil)
912
+ path || @scope[:path_names][name.to_sym] || name.to_s
913
+ end
914
+
915
+ def prefix_name_for_action(action, as)
916
+ if as.present?
917
+ "#{as}_"
918
+ elsif as
919
+ ""
920
+ elsif !canonical_action?(action, @scope[:scope_level])
921
+ "#{action}_"
922
+ end
923
+ end
924
+
925
+ def name_for_action(action, as=nil)
926
+ prefix = prefix_name_for_action(action, as)
927
+ name_prefix = @scope[:as]
928
+
929
+ if parent_resource
930
+ collection_name = parent_resource.collection_name
931
+ member_name = parent_resource.member_name
932
+ name_prefix = "#{name_prefix}_" if name_prefix.present?
933
+ end
934
+
935
+ case @scope[:scope_level]
936
+ when :collection
937
+ "#{prefix}#{name_prefix}#{collection_name}"
938
+ when :new
939
+ "#{prefix}new_#{name_prefix}#{member_name}"
940
+ else
941
+ if shallow_scoping?
942
+ shallow_prefix = "#{@scope[:shallow_prefix]}_" if @scope[:shallow_prefix].present?
943
+ "#{prefix}#{shallow_prefix}#{member_name}"
944
+ else
945
+ "#{prefix}#{name_prefix}#{member_name}"
946
+ end
947
+ end
948
+ end
819
949
  end
820
950
 
821
951
  include Base