Permalink
Browse files

Add :only/:except options to map.resources

This allows people with huge numbers of resource routes to cut down on the memory consumption caused by the generated code.

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1215 state:committed]
  • Loading branch information...
tomstuart authored and NZKoz committed Nov 12, 2008
1 parent c65075f commit 44a3009ff068bf080de6764a8c884fbf0ceb920e
Showing with 247 additions and 37 deletions.
  1. +85 −37 actionpack/lib/action_controller/resources.rb
  2. +162 −0 actionpack/test/controller/resources_test.rb
@@ -42,7 +42,11 @@ module ActionController
#
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
module Resources
INHERITABLE_OPTIONS = :namespace, :shallow, :only, :except
class Resource #:nodoc:
DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
attr_reader :collection_methods, :member_methods, :new_methods
attr_reader :path_prefix, :name_prefix, :path_segment
attr_reader :plural, :singular
@@ -57,6 +61,7 @@ def initialize(entities, options)
arrange_actions
add_default_actions
set_allowed_actions
set_prefixes
end
@@ -113,6 +118,10 @@ def uncountable?
@singular.to_s == @plural.to_s
end
def has_action?(action)
!DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
end
protected
def arrange_actions
@collection_methods = arrange_actions_by_methods(options.delete(:collection))
@@ -125,6 +134,30 @@ def add_default_actions
add_default_action(new_methods, :get, :new)
end
def set_allowed_actions
only, except = @options.values_at(:only, :except)
@allowed_actions ||= {}
if only == :all || except == :none
only = nil
except = []
elsif only == :none || except == :all
only = []
except = nil
end
if only
@allowed_actions[:only] = Array(only).map(&:to_sym)
elsif except
@allowed_actions[:except] = Array(except).map(&:to_sym)
end
end
def action_allowed?(action)
only, except = @allowed_actions.values_at(:only, :except)
(!only || only.include?(action)) && (!except || !except.include?(action))
end
def set_prefixes
@path_prefix = options.delete(:path_prefix)
@name_prefix = options.delete(:name_prefix)
@@ -353,6 +386,25 @@ def initialize(entity, options)
#
# map.resources :users, :has_many => { :posts => :comments }, :shallow => true
#
# * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
#
# <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
# list of action names. By default, routes are generated for all seven actions.
#
# For example:
#
# map.resources :posts, :only => [:index, :show] do |post|
# post.resources :comments, :except => [:update, :destroy]
# end
# # --> GET /posts (maps to the PostsController#index action)
# # --> POST /posts (fails)
# # --> GET /posts/1 (maps to the PostsController#show action)
# # --> DELETE /posts/1 (fails)
# # --> POST /posts/1/comments (maps to the CommentsController#create action)
# # --> PUT /posts/1/comments/1 (fails)
#
# The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
#
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
#
# Examples:
@@ -478,7 +530,7 @@ def map_resource(entities, options = {}, &block)
map_associations(resource, options)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
end
end
end
@@ -495,7 +547,7 @@ def map_singleton_resource(entities, options = {}, &block)
map_associations(resource, options)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
end
end
end
@@ -507,7 +559,7 @@ def map_associations(resource, options)
name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
Array(options[:has_one]).each do |association|
resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow])
resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
end
end
@@ -522,7 +574,7 @@ def map_has_many_associations(resource, associations, options)
map_has_many_associations(resource, association, options)
end
when Symbol, String
resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many])
resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
else
end
end
@@ -531,41 +583,39 @@ def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
[method].flatten.each do |m|
action_options = action_options_for(action, resource, m)
map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
end
end
end
end
def map_default_collection_actions(map, resource)
index_action_options = action_options_for("index", resource)
index_route_name = "#{resource.name_prefix}#{resource.plural}"
if resource.uncountable?
index_route_name << "_index"
end
map_named_routes(map, index_route_name, resource.path, index_action_options)
create_action_options = action_options_for("create", resource)
map_unnamed_routes(map, resource.path, create_action_options)
map_resource_routes(map, resource, :index, resource.path, index_route_name)
map_resource_routes(map, resource, :create, resource.path)
end
def map_default_singleton_actions(map, resource)
create_action_options = action_options_for("create", resource)
map_unnamed_routes(map, resource.path, create_action_options)
map_resource_routes(map, resource, :create, resource.path)
end
def map_new_actions(map, resource)
resource.new_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
if action == :new
map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
else
map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
route_path = resource.new_path
route_name = "new_#{resource.name_prefix}#{resource.singular}"
unless action == :new
route_path = "#{route_path}#{resource.action_separator}#{action}"
route_name = "#{action}_#{route_name}"
end
map_resource_routes(map, resource, action, route_path, route_name, method)
end
end
end
@@ -574,34 +624,32 @@ def map_member_actions(map, resource)
resource.member_methods.each do |method, actions|
actions.each do |action|
[method].flatten.each do |m|
action_options = action_options_for(action, resource, m)
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= Base.resources_path_names[action] || action
map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m)
end
end
end
show_action_options = action_options_for("show", resource)
map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options)
update_action_options = action_options_for("update", resource)
map_unnamed_routes(map, resource.member_path, update_action_options)
destroy_action_options = action_options_for("destroy", resource)
map_unnamed_routes(map, resource.member_path, destroy_action_options)
map_resource_routes(map, resource, :show, resource.member_path, "#{resource.shallow_name_prefix}#{resource.singular}")
map_resource_routes(map, resource, :update, resource.member_path)
map_resource_routes(map, resource, :destroy, resource.member_path)
end
def map_unnamed_routes(map, path_without_format, options)
map.connect(path_without_format, options)
map.connect("#{path_without_format}.:format", options)
end
def map_named_routes(map, name, path_without_format, options)
map.named_route(name, path_without_format, options)
map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil)
if resource.has_action?(action)
action_options = action_options_for(action, resource, method)
formatted_route_path = "#{route_path}.:format"
if route_name
map.named_route(route_name, route_path, action_options)
map.named_route("formatted_#{route_name}", formatted_route_path, action_options)
else
map.connect(route_path, action_options)
map.connect(formatted_route_path, action_options)
end
end
end
def add_conditions_for(conditions, method)
Oops, something went wrong.

16 comments on commit 44a3009

@adkron

This comment has been minimized.

Show comment
Hide comment
@adkron

adkron Nov 12, 2008

Contributor

woot. I was working on this, but now I can stop.

Contributor

adkron replied Nov 12, 2008

woot. I was working on this, but now I can stop.

@samgranieri

This comment has been minimized.

Show comment
Hide comment
@samgranieri

samgranieri Nov 12, 2008

Contributor

This is fantastic! I hope this makes it into 2.2

Contributor

samgranieri replied Nov 12, 2008

This is fantastic! I hope this makes it into 2.2

@joshuaclayton

This comment has been minimized.

Show comment
Hide comment
@joshuaclayton

joshuaclayton Nov 12, 2008

I would love to see this included in 2.2. Totally awesome.

joshuaclayton replied Nov 12, 2008

I would love to see this included in 2.2. Totally awesome.

@adkron

This comment has been minimized.

Show comment
Hide comment
@adkron

adkron Nov 13, 2008

Contributor

+1 for 2.2 inclusion

Contributor

adkron replied Nov 13, 2008

+1 for 2.2 inclusion

@sob

This comment has been minimized.

Show comment
Hide comment
@sob

sob Nov 13, 2008

+1 for 2.2 inclusion as well. This is sweet.

sob replied Nov 13, 2008

+1 for 2.2 inclusion as well. This is sweet.

@jonnii

This comment has been minimized.

Show comment
Hide comment
@jonnii

jonnii Nov 13, 2008

Contributor

+1 for 2.2 inclusion.

Contributor

jonnii replied Nov 13, 2008

+1 for 2.2 inclusion.

@NZKoz

This comment has been minimized.

Show comment
Hide comment
@NZKoz

NZKoz Nov 13, 2008

Member

Guys, this is still the 2.2 branch, it’s going in. ;)

Member

NZKoz replied Nov 13, 2008

Guys, this is still the 2.2 branch, it’s going in. ;)

@zargony

This comment has been minimized.

Show comment
Hide comment
@zargony

zargony Nov 13, 2008

Contributor

nice :)

Contributor

zargony replied Nov 13, 2008

nice :)

@joshsusser

This comment has been minimized.

Show comment
Hide comment
@joshsusser

joshsusser Nov 13, 2008

Contributor

I’m all for the feature (been wanting it for ages), but I’d favor holding it to 2.2.1. Maintaining a feature freeze during a release cycle is important for the quality and stability of a release, and relaxing the discipline of a freeze for a shiny object is going to get you in trouble eventually.

Contributor

joshsusser replied Nov 13, 2008

I’m all for the feature (been wanting it for ages), but I’d favor holding it to 2.2.1. Maintaining a feature freeze during a release cycle is important for the quality and stability of a release, and relaxing the discipline of a freeze for a shiny object is going to get you in trouble eventually.

@andrzejsliwa

This comment has been minimized.

Show comment
Hide comment
@andrzejsliwa

andrzejsliwa Nov 13, 2008

+1 for 2.2 inclusion, great job

andrzejsliwa replied Nov 13, 2008

+1 for 2.2 inclusion, great job

@alloy

This comment has been minimized.

Show comment
Hide comment
@alloy

alloy Nov 13, 2008

Contributor

I have to agree with joshsusser on this.

Contributor

alloy replied Nov 13, 2008

I have to agree with joshsusser on this.

@NZKoz

This comment has been minimized.

Show comment
Hide comment
@NZKoz

NZKoz Nov 13, 2008

Member

We want it in for 2.2 final, if you think we should do another RC with it included, then we can do that. However it seems relatively harmless.

Pipe up on the ticket if you feel strongly about it. There’s an enhancement to come it seems

Member

NZKoz replied Nov 13, 2008

We want it in for 2.2 final, if you think we should do another RC with it included, then we can do that. However it seems relatively harmless.

Pipe up on the ticket if you feel strongly about it. There’s an enhancement to come it seems

@joshsusser

This comment has been minimized.

Show comment
Hide comment
@joshsusser

joshsusser Nov 13, 2008

Contributor

There have probably been enough changes since RC1 to merit an RC2, especially since there have been some feature additions. And I notice there was an issue with removing index and show helpers, which is a good indication this change needs some time to bake.

Contributor

joshsusser replied Nov 13, 2008

There have probably been enough changes since RC1 to merit an RC2, especially since there have been some feature additions. And I notice there was an issue with removing index and show helpers, which is a good indication this change needs some time to bake.

@sbfaulkner

This comment has been minimized.

Show comment
Hide comment
@sbfaulkner

sbfaulkner Nov 13, 2008

Contributor

lol… apparently I was ahead of my time, I was shot down for suggesting this feature – which lead to me adding “shallow” nesting instead… oh well, now we have both

Contributor

sbfaulkner replied Nov 13, 2008

lol… apparently I was ahead of my time, I was shot down for suggesting this feature – which lead to me adding “shallow” nesting instead… oh well, now we have both

@NZKoz

This comment has been minimized.

Show comment
Hide comment
@NZKoz

NZKoz Nov 13, 2008

Member

@sbfaulkner: heh, yeah. Specific numbers on the memory usage made me change my mind.

@joshsusser: Indeed, this particular changeset is only intended for people looking to cut down on memory usage with thousands of routes. I just tested with my app and it was a tiny tiny difference.

We’ll do an RC2 and keep it ‘frozen’ barring surprises.

Member

NZKoz replied Nov 13, 2008

@sbfaulkner: heh, yeah. Specific numbers on the memory usage made me change my mind.

@joshsusser: Indeed, this particular changeset is only intended for people looking to cut down on memory usage with thousands of routes. I just tested with my app and it was a tiny tiny difference.

We’ll do an RC2 and keep it ‘frozen’ barring surprises.

@lifo

This comment has been minimized.

Show comment
Hide comment
@lifo

lifo Jan 30, 2009

Member

Adding a blank space.

Member

lifo replied Jan 30, 2009

Adding a blank space.

Please sign in to comment.