Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix #51463] Raise an error when invalid :on or :except options are given to #resource or #resources #51464

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions actionpack/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
* Raise an `ArgumentError` when invalid `:on` or `:except` options are passed into `#resouce` and `#resources`.

*Joshua Young*

* Request Forgery takes relative paths into account.

*Stefan Wienert*
Expand Down
63 changes: 47 additions & 16 deletions actionpack/lib/action_dispatch/routing/mapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1168,13 +1168,27 @@ module Resources
CANONICAL_ACTIONS = %w(index create new show update destroy)

class Resource # :nodoc:
class << self
def default_actions(api_only)
if api_only
[:index, :create, :show, :update, :destroy]
else
[:index, :create, :new, :show, :update, :destroy, :edit]
end
end
end

attr_reader :controller, :path, :param

def initialize(entities, api_only, shallow, options = {})
if options[:param].to_s.include?(":")
raise ArgumentError, ":param option can't contain colons"
end

if (invalid_actions = (options.values_at(:only, :except).flatten.compact - default_actions)).any?
raise ArgumentError, "expected :only and :except to be one or more of #{default_actions}, got #{invalid_actions}"
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method assert_valid_keys would be a better approach as it's used elsewhere within Rails to perform the same task, e.g.

def to_sentence(array, options = {})
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
default_connectors = {
words_connector: ", ",
two_words_connector: " and ",
last_word_connector: ", and "
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it works here since we're asserting the values and not the keys.


@name = entities.to_s
@path = (options[:path] || @name).to_s
@controller = (options[:controller] || @name).to_s
Expand All @@ -1188,11 +1202,7 @@ def initialize(entities, api_only, shallow, options = {})
end

def default_actions
if @api_only
[:index, :create, :show, :update, :destroy]
else
[:index, :create, :new, :show, :update, :destroy, :edit]
end
self.class.default_actions(@api_only)
end

def actions
Expand Down Expand Up @@ -1263,6 +1273,16 @@ def singleton?; false; end
end

class SingletonResource < Resource # :nodoc:
class << self
def default_actions(api_only)
if api_only
[:show, :create, :update, :destroy]
else
[:show, :create, :update, :destroy, :new, :edit]
end
end
end

def initialize(entities, api_only, shallow, options)
super
@as = nil
Expand All @@ -1271,11 +1291,7 @@ def initialize(entities, api_only, shallow, options)
end

def default_actions
if @api_only
[:show, :create, :update, :destroy]
else
[:show, :create, :update, :destroy, :new, :edit]
end
self.class.default_actions(@api_only)
end

def plural
Expand Down Expand Up @@ -1336,7 +1352,7 @@ def resource(*resources, &block)
end

with_scope_level(:resource) do
options = apply_action_options options
options = apply_action_options :resource, options
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
yield if block_given?

Expand Down Expand Up @@ -1506,7 +1522,7 @@ def resources(*resources, &block)
end

with_scope_level(:resources) do
options = apply_action_options options
options = apply_action_options :resources, options
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
yield if block_given?

Expand Down Expand Up @@ -1776,17 +1792,32 @@ def apply_common_behavior_for(method, resources, options, &block)
false
end

def apply_action_options(options)
def apply_action_options(method, options)
return options if action_options? options
options.merge scope_action_options
options.merge scope_action_options(method)
end

def action_options?(options)
options[:only] || options[:except]
end

def scope_action_options
@scope[:action_options] || {}
def scope_action_options(method)
return {} unless @scope[:action_options]

actions = applicable_actions_for(method)
@scope[:action_options].dup.tap do |options|
(options[:only] = Array(options[:only]) & actions) if options[:only]
(options[:except] = Array(options[:except]) & actions) if options[:except]
end
end

def applicable_actions_for(method)
case method
when :resource
SingletonResource.default_actions(api_only?)
when :resources
Resource.default_actions(api_only?)
end
end

def resource_scope?
Expand Down
45 changes: 45 additions & 0 deletions actionpack/test/controller/resources_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,51 @@ def test_singleton_resource_name_is_not_singularized
end
end

def test_invalid_only_option_for_resources
expected_message = "expected :only and :except to be one or more of [:index, :create, :new, :show, :update, :destroy, :edit], got [:foo, :bar]"
assert_raise(ArgumentError, match: expected_message) do
with_routing do |set|
set.draw do
resources :products, only: [:foo, :bar]
end
end
end
end

def test_invalid_only_option_for_singleton_resource
expected_message = "expected :only and :except to be one or more of [:show, :create, :update, :destroy, :new, :edit], got [:foo, :bar]"
assert_raise(ArgumentError, match: expected_message) do
with_routing do |set|
set.draw do
resource :products, only: [:foo, :bar]
end
end
end
end

def test_invalid_except_option_for_resources
expected_message = "expected :only and :except to be one or more of [:index, :create, :new, :show, :update, :destroy, :edit], got [:foo]"

assert_raise(ArgumentError, match: expected_message) do
with_routing do |set|
set.draw do
resources :products, except: :foo
end
end
end
end

def test_invalid_except_option_for_singleton_resource
expected_message = "expected :only and :except to be one or more of [:show, :create, :update, :destroy, :new, :edit], got [:foo]"
assert_raise(ArgumentError, match: expected_message) do
with_routing do |set|
set.draw do
resource :products, except: :foo
end
end
end
end

private
def with_restful_routing(*args)
options = args.extract_options!
Expand Down