Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 6107

Show
Ignore:
Timestamp:
02/04/07 17:19:45 (2 years ago)
Author:
rick
Message:

Add singleton resources from trunk [Rick Olson]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/1-2-stable/actionpack/CHANGELOG

    r6087 r6107  
    11*SVN* 
     2 
     3* Add singleton resources from trunk [Rick Olson] 
    24 
    35* TestSession supports indifferent access so session['foo'] == session[:foo] in your tests.  #7372 [julik, jean.helou] 
  • branches/1-2-stable/actionpack/lib/action_controller/resources.rb

    r6063 r6107  
    1919       
    2020      def controller 
    21         (options[:controller] || plural).to_s 
     21        @controller ||= (options[:controller] || plural).to_s 
    2222      end 
    2323       
    2424      def path 
    25         "#{path_prefix}/#{plural}" 
     25        @path ||= "#{path_prefix}/#{plural}" 
    2626      end 
    2727       
    2828      def new_path 
    29         "#{path}/new" 
     29        @new_path ||= "#{path}/new" 
    3030      end 
    3131       
    3232      def member_path 
    33         "#{path}/:id" 
     33        @member_path ||= "#{path}/:id" 
    3434      end 
    3535       
    3636      def nesting_path_prefix 
    37         "#{path}/:#{singular}_id" 
    38       end 
    39        
    40       private 
     37        @nesting_path_prefix ||= "#{path}/:#{singular}_id" 
     38      end 
     39       
     40      protected 
    4141        def arrange_actions 
    4242          @collection_methods = arrange_actions_by_methods(options.delete(:collection)) 
     
    6666        end 
    6767    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 
    6982    # Creates named routes for implementing verb-oriented controllers. This is 
    7083    # useful for implementing REST API's, where a single resource has different 
     
    218231    end 
    219232 
     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 
    220292    private 
    221293      def map_resource(entities, options = {}, &block) 
     
    224296        with_options :controller => resource.controller do |map| 
    225297          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) 
    226314          map_new_actions(map, resource) 
    227315          map_member_actions(map, resource) 
     
    241329          end 
    242330        end 
    243  
     331      end 
     332 
     333      def map_default_collection_actions(map, resource) 
    244334        index_action_options = action_options_for("index", resource) 
    245335        map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, index_action_options) 
    246336        map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", index_action_options) 
    247337 
     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 
     342 
     343      def map_default_singleton_actions(map, resource) 
    248344        create_action_options = action_options_for("create", resource) 
    249345        map.connect(resource.path, create_action_options) 
     
    287383        map.connect("#{resource.member_path}.:format", destroy_action_options) 
    288384      end 
    289      
     385 
    290386      def conditions_for(method) 
    291387        { :conditions => method == :any ? {} : { :method => method } } 
     
    294390      def action_options_for(action, resource, method = nil) 
    295391        default_options = { :action => action.to_s } 
    296         require_id = { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } } 
     392        require_id = resource.kind_of?(SingletonResource) ? {} : { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } } 
    297393        case default_options[:action] 
    298394          when "index", "new" : default_options.merge(conditions_for(method || :get)) 
  • branches/1-2-stable/actionpack/test/controller/resources_test.rb

    r6063 r6107  
    33class ResourcesController < ActionController::Base 
    44  def index() render :nothing => true end 
     5  alias_method :show, :index 
    56  def rescue_action(e) raise e end 
    67end 
     
    1011class CommentsController < ResourcesController; end 
    1112 
     13class AccountController <  ResourcesController; end 
     14class AdminController   <  ResourcesController; end 
    1215 
    1316class ResourcesTest < Test::Unit::TestCase 
     
    3336    resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts') 
    3437    assert_equal 'posts', resource.controller 
     38  end 
     39 
     40  def test_should_all_singleton_paths_be_the_same 
     41    [ :path, :nesting_path_prefix, :member_path ].each do |method| 
     42      resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin') 
     43      assert_equal 'admin/messages', resource.send(method) 
     44    end 
    3545  end 
    3646 
     
    188198  end 
    189199 
     200  def test_should_create_singleton_resource_routes 
     201    with_singleton_resources :account do 
     202      assert_singleton_restful_for :account 
     203    end 
     204  end 
     205 
     206  def test_should_create_multiple_singleton_resource_routes 
     207    with_singleton_resources :account, :admin do 
     208      assert_singleton_restful_for :account 
     209      assert_singleton_restful_for :admin 
     210    end 
     211  end 
     212 
     213  def test_should_create_nested_singleton_resource_routes 
     214    with_routing do |set| 
     215      set.draw do |map| 
     216        map.resource :admin do |admin| 
     217          admin.resource :account 
     218        end 
     219      end 
     220       
     221      assert_singleton_restful_for :admin 
     222      assert_singleton_restful_for :account, :path_prefix => 'admin/' 
     223    end 
     224  end 
     225 
     226  def test_singleton_resource_with_member_action 
     227    [:put, :post].each do |method| 
     228      with_singleton_resources :account, :member => { :reset => method } do 
     229        reset_options = {:action => 'reset'} 
     230        reset_path    = "/account;reset" 
     231        assert_singleton_routes_for :account do |options| 
     232          assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method) 
     233        end 
     234 
     235        assert_singleton_named_routes_for :account do |options| 
     236          assert_named_route reset_path, :reset_account_path, reset_options 
     237        end 
     238      end 
     239    end 
     240  end 
     241 
     242  def test_singleton_resource_with_two_member_actions_with_same_method 
     243    [:put, :post].each do |method| 
     244      with_singleton_resources :account, :member => { :reset => method, :disable => method } do 
     245        %w(reset disable).each do |action| 
     246          action_options = {:action => action} 
     247          action_path    = "/account;#{action}" 
     248          assert_singleton_routes_for :account do |options| 
     249            assert_recognizes(options.merge(action_options), :path => action_path, :method => method) 
     250          end 
     251 
     252          assert_singleton_named_routes_for :account do |options| 
     253            assert_named_route action_path, "#{action}_account_path".to_sym, action_options 
     254          end 
     255        end 
     256      end 
     257    end 
     258  end 
     259 
     260  def test_should_nest_resources_in_singleton_resource 
     261    with_routing do |set| 
     262      set.draw do |map| 
     263        map.resource :account do |account| 
     264          account.resources :messages 
     265        end 
     266      end 
     267       
     268      assert_singleton_restful_for :account 
     269      assert_simply_restful_for :messages, :path_prefix => 'account/' 
     270    end 
     271  end 
     272 
     273  def test_should_nest_resources_in_singleton_resource_with_path_prefix 
     274    with_routing do |set| 
     275      set.draw do |map| 
     276        map.resource(:account, :path_prefix => ':site_id') do |account| 
     277          account.resources :messages 
     278        end 
     279      end 
     280 
     281      assert_singleton_restful_for :account, :path_prefix => '7/', :options => { :site_id => '7' } 
     282      assert_simply_restful_for :messages, :path_prefix => '7/account/', :options => { :site_id => '7' } 
     283    end 
     284  end 
     285   
     286  def test_should_nest_singleton_resource_in_resources 
     287    with_routing do |set| 
     288      set.draw do |map| 
     289        map.resources :threads do |thread| 
     290          thread.resource :admin 
     291        end 
     292      end 
     293       
     294      assert_simply_restful_for :threads 
     295      assert_singleton_restful_for :admin, :path_prefix => 'threads/5/', :options => { :thread_id => '5' } 
     296    end 
     297  end 
     298 
    190299  def test_should_not_allow_delete_or_put_on_collection_path 
    191300    controller_name = :messages 
     
    211320      end 
    212321    end 
     322     
     323    def with_singleton_resources(*args) 
     324      with_routing do |set| 
     325        set.draw { |map| map.resource(*args) } 
     326        yield 
     327      end 
     328    end 
    213329 
    214330    # runs assert_restful_routes_for and assert_restful_named_routes for on the controller_name and options, without passing a block. 
     
    216332      assert_restful_routes_for       controller_name, options 
    217333      assert_restful_named_routes_for controller_name, options 
     334    end 
     335 
     336    def assert_singleton_restful_for(singleton_name, options = {}) 
     337      assert_singleton_routes_for       singleton_name, options 
     338      assert_singleton_named_routes_for singleton_name, options 
    218339    end 
    219340 
     
    285406    end 
    286407 
     408    def assert_singleton_routes_for(singleton_name, options = {}) 
     409      (options[:options] ||= {})[:controller] ||= singleton_name.to_s 
     410 
     411      full_path           = "/#{options[:path_prefix]}#{singleton_name}" 
     412      new_path            = "#{full_path}/new" 
     413      edit_path           = "#{full_path};edit" 
     414      formatted_edit_path = "#{full_path}.xml;edit" 
     415 
     416      with_options options[:options] do |controller| 
     417        controller.assert_routing full_path,           :action => 'show' 
     418        controller.assert_routing new_path,            :action => 'new' 
     419        controller.assert_routing edit_path,           :action => 'edit' 
     420        controller.assert_routing "#{full_path}.xml",  :action => 'show', :format => 'xml' 
     421        controller.assert_routing "#{new_path}.xml",   :action => 'new',  :format => 'xml' 
     422        controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml' 
     423      end 
     424 
     425      assert_recognizes(options[:options].merge(:action => 'show'),    :path => full_path, :method => :get) 
     426      assert_recognizes(options[:options].merge(:action => 'new'),     :path => new_path,  :method => :get) 
     427      assert_recognizes(options[:options].merge(:action => 'edit'),    :path => edit_path, :method => :get) 
     428      assert_recognizes(options[:options].merge(:action => 'create'),  :path => full_path, :method => :post) 
     429      assert_recognizes(options[:options].merge(:action => 'update'),  :path => full_path, :method => :put) 
     430      assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete) 
     431 
     432      assert_recognizes(options[:options].merge(:action => 'show',    :format => 'xml'), :path => "#{full_path}.xml",  :method => :get) 
     433      assert_recognizes(options[:options].merge(:action => 'new',     :format => 'xml'), :path => "#{new_path}.xml",   :method => :get) 
     434      assert_recognizes(options[:options].merge(:action => 'edit',    :format => 'xml'), :path => formatted_edit_path, :method => :get) 
     435      assert_recognizes(options[:options].merge(:action => 'create',  :format => 'xml'), :path => "#{full_path}.xml",  :method => :post) 
     436      assert_recognizes(options[:options].merge(:action => 'update',  :format => 'xml'), :path => "#{full_path}.xml",  :method => :put) 
     437      assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml",  :method => :delete) 
     438 
     439      yield options[:options] if block_given? 
     440    end 
     441 
     442    def assert_singleton_named_routes_for(singleton_name, options = {}) 
     443      (options[:options] ||= {})[:controller] ||= singleton_name.to_s 
     444      @controller = "#{options[:options][:controller].camelize}Controller".constantize.new 
     445      @request    = ActionController::TestRequest.new 
     446      @response   = ActionController::TestResponse.new 
     447      get :show, options[:options] 
     448      options[:options].delete :action 
     449 
     450      full_path = "/#{options[:path_prefix]}#{singleton_name}" 
     451 
     452      assert_named_route "#{full_path}",          "#{singleton_name}_path",                options[:options] 
     453      assert_named_route "#{full_path}/new",      "new_#{singleton_name}_path",            options[:options] 
     454      assert_named_route "#{full_path};edit",     "edit_#{singleton_name}_path",           options[:options] 
     455      assert_named_route "#{full_path}.xml",      "formatted_#{singleton_name}_path",      options[:options].merge(:format => 'xml') 
     456      assert_named_route "#{full_path}/new.xml",  "formatted_new_#{singleton_name}_path",  options[:options].merge(:format => 'xml') 
     457      assert_named_route "#{full_path}.xml;edit", "formatted_edit_#{singleton_name}_path", options[:options].merge(:format => 'xml') 
     458    end 
     459 
    287460    def assert_named_route(expected, route, options) 
    288461      actual =  @controller.send(route, options) rescue $!.class.name