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

Parameter wrapping doesn't support aliased attributes #10375

Closed
wants to merge 2 commits into from
Closed
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
Expand Up @@ -35,4 +35,8 @@


*Bryan Ricker* *Bryan Ricker*


* Add support for wrapping parameters matching aliased attribute names.

*Patrick Van Stee*

Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes. Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes.
6 changes: 3 additions & 3 deletions actionpack/lib/action_controller/metal/params_wrapper.rb
Expand Up @@ -44,7 +44,7 @@ module ActionController
# #
# On ActiveRecord models with no +:include+ or +:exclude+ option set, # On ActiveRecord models with no +:include+ or +:exclude+ option set,
# it will only wrap the parameters returned by the class method # it will only wrap the parameters returned by the class method
# <tt>attribute_names</tt>. # <tt>attribute_method_names</tt>.
# #
# If you're going to pass the parameters to an +ActiveModel+ object (such as # If you're going to pass the parameters to an +ActiveModel+ object (such as
# <tt>User.new(params[:user])</tt>), you might consider passing the model class to # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
Expand Down Expand Up @@ -106,8 +106,8 @@ def include
@include_set = true @include_set = true


unless super || exclude unless super || exclude
if m.respond_to?(:attribute_names) && m.attribute_names.any? if m.respond_to?(:attribute_method_names) && m.attribute_method_names.any?
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to check any? before assigning?

Copy link
Author

Choose a reason for hiding this comment

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

I'm not actually sure. It seems like you could unintentionally set self.include to nil considering we set @include_set to true outside the conditional. Hrm. I'll try removing that.

Copy link
Author

Choose a reason for hiding this comment

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

This change seems to be causing a failure here: https://github.com/rails/rails/blob/master/actionpack/test/controller/params_wrapper_test.rb#L181-L191

Seems interesting. I guess if you have an abstract model, the attribute_names array would be empty so you fallback to including everything?

self.include = m.attribute_names self.include = m.attribute_method_names
end end
end end
end end
Expand Down
18 changes: 9 additions & 9 deletions actionpack/test/controller/params_wrapper_test.rb
Expand Up @@ -155,8 +155,8 @@ def test_nested_params
end end


def test_derived_wrapped_keys_from_matching_model def test_derived_wrapped_keys_from_matching_model
User.expects(:respond_to?).with(:attribute_names).returns(true) User.expects(:respond_to?).with(:attribute_method_names).returns(true)
User.expects(:attribute_names).twice.returns(["username"]) User.expects(:attribute_method_names).twice.returns(["username"])


with_default_wrapper_options do with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json' @request.env['CONTENT_TYPE'] = 'application/json'
Expand All @@ -167,8 +167,8 @@ def test_derived_wrapped_keys_from_matching_model


def test_derived_wrapped_keys_from_specified_model def test_derived_wrapped_keys_from_specified_model
with_default_wrapper_options do with_default_wrapper_options do
Person.expects(:respond_to?).with(:attribute_names).returns(true) Person.expects(:respond_to?).with(:attribute_method_names).returns(true)
Person.expects(:attribute_names).twice.returns(["username"]) Person.expects(:attribute_method_names).twice.returns(["username"])


UsersController.wrap_parameters Person UsersController.wrap_parameters Person


Expand All @@ -179,8 +179,8 @@ def test_derived_wrapped_keys_from_specified_model
end end


def test_not_wrapping_abstract_model def test_not_wrapping_abstract_model
User.expects(:respond_to?).with(:attribute_names).returns(true) User.expects(:respond_to?).with(:attribute_method_names).returns(true)
User.expects(:attribute_names).returns([]) User.expects(:attribute_method_names).returns([])


with_default_wrapper_options do with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json' @request.env['CONTENT_TYPE'] = 'application/json'
Expand Down Expand Up @@ -209,13 +209,13 @@ def parse
end end


class SampleOne class SampleOne
def self.attribute_names def self.attribute_method_names
["username"] ["username"]
end end
end end


class SampleTwo class SampleTwo
def self.attribute_names def self.attribute_method_names
["title"] ["title"]
end end
end end
Expand Down Expand Up @@ -298,7 +298,7 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase
include ParamsWrapperTestHelp include ParamsWrapperTestHelp


class ParamswrappernewsItem class ParamswrappernewsItem
def self.attribute_names def self.attribute_method_names
['test_attr'] ['test_attr']
end end
end end
Expand Down
5 changes: 5 additions & 0 deletions activemodel/CHANGELOG.md
Expand Up @@ -7,4 +7,9 @@


*Nick Sutterer* *Nick Sutterer*


* Add the `ActiveModel#attribute_method_names` method, which returns an
array of attribute names and aliases as strings.

*Patrick Van Stee*

Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for previous changes. Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for previous changes.
26 changes: 24 additions & 2 deletions activemodel/lib/active_model/attribute_methods.rb
Expand Up @@ -72,9 +72,10 @@ module AttributeMethods
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/ CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/


included do included do
class_attribute :attribute_aliases, :attribute_method_matchers, instance_writer: false class_attribute :attribute_aliases, :attribute_method_matchers, :defined_attribute_names, instance_writer: false
self.attribute_aliases = {} self.attribute_aliases = {}
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new] self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
self.defined_attribute_names = []
end end


module ClassMethods module ClassMethods
Expand Down Expand Up @@ -276,6 +277,10 @@ def define_attribute_methods(*attr_names)
# person.name # => "Bob" # person.name # => "Bob"
# person.name_short? # => true # person.name_short? # => true
def define_attribute_method(attr_name) def define_attribute_method(attr_name)
attr_name = attr_name.to_s

self.defined_attribute_names << attr_name unless defined_attribute_names.include?(attr_name)

attribute_method_matchers.each do |matcher| attribute_method_matchers.each do |matcher|
method_name = matcher.method_name(attr_name) method_name = matcher.method_name(attr_name)


Expand All @@ -285,10 +290,11 @@ def define_attribute_method(attr_name)
if respond_to?(generate_method, true) if respond_to?(generate_method, true)
send(generate_method, attr_name) send(generate_method, attr_name)
else else
define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name
end end
end end
end end

attribute_method_matchers_cache.clear attribute_method_matchers_cache.clear
end end


Expand Down Expand Up @@ -322,6 +328,22 @@ def undefine_attribute_methods
attribute_method_matchers_cache.clear attribute_method_matchers_cache.clear
end end


# Returns an array of attribute names and aliases as strings.
#
# class Person
# include ActiveModel::AttributeMethods
#
# define_attribute_method :name
#
# alias_attribute :nickname, :name
# end
#
# Person.attribute_method_names
# # => ["name", "nickname"]
def attribute_method_names
defined_attribute_names + attribute_aliases.keys
end

# Returns true if the attribute methods defined have been generated. # Returns true if the attribute methods defined have been generated.
def generated_attribute_methods #:nodoc: def generated_attribute_methods #:nodoc:
@generated_attribute_methods ||= Module.new.tap { |mod| include mod } @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
Expand Down
9 changes: 9 additions & 0 deletions activemodel/test/cases/attribute_methods_test.rb
Expand Up @@ -186,6 +186,15 @@ def foo
assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to
end end


test '#attribute_method_names returns attribute names and attribute aliases' do
klass = Class.new(ModelWithAttributes) do
define_attribute_methods :foo
alias_attribute :bar, :foo
end

assert_equal ["foo", "bar"], klass.attribute_method_names
end

test '#undefine_attribute_methods removes attribute methods' do test '#undefine_attribute_methods removes attribute methods' do
ModelWithAttributes.define_attribute_methods(:foo) ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods ModelWithAttributes.undefine_attribute_methods
Expand Down
2 changes: 1 addition & 1 deletion railties/test/application/configuration_test.rb
Expand Up @@ -514,7 +514,7 @@ def index


app_file 'app/models/post.rb', <<-RUBY app_file 'app/models/post.rb', <<-RUBY
class Post class Post
def self.attribute_names def self.attribute_method_names
%w(title) %w(title)
end end
end end
Expand Down