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

Changeset 5701

Show
Ignore:
Timestamp:
12/07/06 15:36:08 (1 year ago)
Author:
rick
Message:

Add singleton resources. [Rick Olson]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r5696 r5701  
    11*SVN* 
     2 
     3* Add singleton resources. [Rick Olson] 
     4 
     5  map.resource :account 
     6   
     7  GET /account 
     8  GET /account;edit 
     9  UPDATE /account 
     10  DELEE /account 
    211 
    312* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument.  #4185 [Scott Raymond, eventualbuddha] 
  • trunk/actionpack/lib/action_controller/resources.rb

    r5195 r5701  
    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        @singular = entity 
     72        @plural   = options[:plural] || singular.to_s.pluralize 
     73         
     74        @options  = options 
     75        arrange_actions 
     76        add_default_actions 
     77        set_prefixes 
     78      end 
     79 
     80      def controller 
     81        @controller ||= (options[:controller] || singular).to_s 
     82      end 
     83       
     84      def path 
     85        @path ||= "#{path_prefix}/#{singular}" 
     86      end 
     87       
     88      def new_path 
     89        nil 
     90      end 
     91       
     92      alias_method :member_path,         :path 
     93      alias_method :nesting_path_prefix, :path 
     94    end 
     95 
    6996    # Creates named routes for implementing verb-oriented controllers. This is 
    7097    # useful for implementing REST API's, where a single resource has different 
     
    210237    end 
    211238 
     239    # Creates named routes for implementing verb-oriented controllers for a singleton resource.  
     240    # A singleton resource is global to the current user visiting the application, such as a user's 
     241    # /account profile. 
     242    #  
     243    # See map.resources for general conventions.  These are the main differences: 
     244    #   - a singular name is given to map.resource.  The default controller name is taken from the singular name. 
     245    #   - To specify a custom plural name, use the :plural option.  There is no :singular option 
     246    #   - No default index, new, or create routes are created for the singleton resource controller. 
     247    #   - When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1') 
     248    # 
     249    # Example: 
     250    # 
     251    #   map.resource :account  
     252    # 
     253    #   class AccountController < ActionController::Base 
     254    #     # GET account_url 
     255    #     def show 
     256    #       # find and return a specific message 
     257    #     end 
     258    #  
     259    #     # GET edit_account_url 
     260    #     def edit 
     261    #       # return an HTML form for editing a specific message 
     262    #     end 
     263    #  
     264    #     # PUT account_url 
     265    #     def update 
     266    #       # find and update a specific message 
     267    #     end 
     268    #  
     269    #     # DELETE account_url 
     270    #     def destroy 
     271    #       # delete a specific message 
     272    #     end 
     273    #   end 
     274    #  
     275    # Along with the routes themselves, #resource generates named routes for use in 
     276    # controllers and views. <tt>map.resource :account</tt> produces the following named routes and helpers: 
     277    #  
     278    #   Named Route   Helpers 
     279    #   account       account_url(id), hash_for_account_url(id),  
     280    #                 account_path(id), hash_for_account_path(id) 
     281    #   edit_account  edit_account_url(id), hash_for_edit_account_url(id), 
     282    #                 edit_account_path(id), hash_for_edit_account_path(id) 
     283    def resource(*entities, &block) 
     284      options = entities.last.is_a?(Hash) ? entities.pop : { } 
     285      entities.each { |entity| map_singleton_resource entity, options.dup, &block } 
     286    end 
     287 
    212288    private 
    213289      def map_resource(entities, options = {}, &block) 
     
    221297          if block_given? 
    222298            with_options(:path_prefix => resource.nesting_path_prefix, &block) 
     299          end 
     300        end 
     301      end 
     302 
     303      def map_singleton_resource(entities, options = {}, &block) 
     304        resource = SingletonResource.new(entities, options) 
     305 
     306        with_options :controller => resource.controller do |map| 
     307          map_member_actions(map, resource) 
     308 
     309          if block_given? 
     310            with_options(:path_prefix => resource.singular, &block) 
    223311          end 
    224312        end 
     
    285373        map.connect("#{resource.member_path}.:format", :action => "destroy", :conditions => { :method => :delete }) 
    286374      end 
    287      
     375 
    288376      def requirements_for(method) 
    289377        method == :any ? {} : { :conditions => { :method => method } } 
  • trunk/actionpack/test/controller/resources_test.rb

    r5195 r5701  
    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 
     
    173176  end 
    174177 
     178  def test_should_create_singleton_resource_routes 
     179    with_singleton_resources :account do 
     180      assert_singleton_restful_for :account 
     181    end 
     182  end 
     183 
     184  def test_should_create_multiple_singleton_resource_routes 
     185    with_singleton_resources :account, :admin do 
     186      assert_singleton_restful_for :account 
     187      assert_singleton_restful_for :admin 
     188    end 
     189  end 
     190 
     191  def test_should_create_nested_singleton_resource_routes 
     192    with_routing do |set| 
     193      set.draw do |map| 
     194        map.resource :admin do |admin| 
     195          admin.resource :account 
     196        end 
     197      end 
     198       
     199      assert_singleton_restful_for :admin 
     200      assert_singleton_restful_for :account, :path_prefix => 'admin/' 
     201    end 
     202  end 
     203 
     204  def test_singleton_resource_with_member_action 
     205    [:put, :post].each do |method| 
     206      with_singleton_resources :account, :member => { :reset => method } do 
     207        reset_options = {:action => 'reset'} 
     208        reset_path    = "/account;reset" 
     209        assert_singleton_routes_for :account do |options| 
     210          assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method) 
     211        end 
     212 
     213        assert_singleton_named_routes_for :account do |options| 
     214          assert_named_route reset_path, :reset_account_path, reset_options 
     215        end 
     216      end 
     217    end 
     218  end 
     219 
     220  def test_singleton_resource_with_two_member_actions_with_same_method 
     221    [:put, :post].each do |method| 
     222      with_singleton_resources :account, :member => { :reset => method, :disable => method } do 
     223        %w(reset disable).each do |action| 
     224          action_options = {:action => action} 
     225          action_path    = "/account;#{action}" 
     226          assert_singleton_routes_for :account do |options| 
     227            assert_recognizes(options.merge(action_options), :path => action_path, :method => method) 
     228          end 
     229 
     230          assert_singleton_named_routes_for :account do |options| 
     231            assert_named_route action_path, "#{action}_account_path".to_sym, action_options 
     232          end 
     233        end 
     234      end 
     235    end 
     236  end 
     237 
    175238  protected 
    176239    def with_restful_routing(*args) 
     
    180243      end 
    181244    end 
     245     
     246    def with_singleton_resources(*args) 
     247      with_routing do |set| 
     248        set.draw { |map| map.resource(*args) } 
     249        yield 
     250      end 
     251    end 
    182252 
    183253    # runs assert_restful_routes_for and assert_restful_named_routes for on the controller_name and options, without passing a block. 
     
    185255      assert_restful_routes_for       controller_name, options 
    186256      assert_restful_named_routes_for controller_name, options 
     257    end 
     258 
     259    def assert_singleton_restful_for(singleton_name, options = {}) 
     260      assert_singleton_routes_for       singleton_name, options 
     261      assert_singleton_named_routes_for singleton_name, options 
    187262    end 
    188263 
     
    243318    end 
    244319 
     320    def assert_singleton_routes_for(singleton_name, options = {}) 
     321      (options[:options] ||= {})[:controller] ||= singleton_name.to_s 
     322       
     323      full_path = "/#{options[:path_prefix]}#{singleton_name}" 
     324      with_options options[:options] do |controller| 
     325        controller.assert_routing full_path,          :action => 'show' 
     326        controller.assert_routing "#{full_path}.xml", :action => 'show', :format => 'xml' 
     327      end 
     328 
     329      assert_recognizes(options[:options].merge(:action => 'update'),  :path => full_path, :method => :put) 
     330      assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete) 
     331 
     332      yield options[:options] if block_given? 
     333    end 
     334 
     335    def assert_singleton_named_routes_for(singleton_name, options = {}) 
     336      (options[:options] ||= {})[:controller] ||= singleton_name.to_s 
     337      @controller = "#{options[:options][:controller].camelize}Controller".constantize.new 
     338      @request    = ActionController::TestRequest.new 
     339      @response   = ActionController::TestResponse.new 
     340      get :show, options[:options] 
     341      options[:options].delete :action 
     342       
     343      full_path = "/#{options[:path_prefix]}#{singleton_name}" 
     344       
     345      assert_named_route "#{full_path}",    "#{singleton_name}_path",            options[:options] 
     346      assert_named_route "#{full_path}.xml", "formatted_#{singleton_name}_path", options[:options].merge(:format => 'xml') 
     347    end 
     348 
    245349    def assert_named_route(expected, route, options) 
    246350      actual =  @controller.send(route, options) rescue $!.class.name