Parameter wrapping doesn't support aliased attributes #10375

Closed
wants to merge 2 commits into
from
@@ -35,4 +35,8 @@
*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.
@@ -44,7 +44,7 @@ module ActionController
#
# On ActiveRecord models with no +:include+ or +:exclude+ option set,
# 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
# <tt>User.new(params[:user])</tt>), you might consider passing the model class to
@@ -106,8 +106,8 @@ def include
@include_set = true
unless super || exclude
- if m.respond_to?(:attribute_names) && m.attribute_names.any?
@vanstee
vanstee May 10, 2013

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
+ if m.respond_to?(:attribute_method_names) && m.attribute_method_names.any?
@jeremy
jeremy May 10, 2013 Member

Why do we need to check any? before assigning?

@vanstee
vanstee May 10, 2013

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.

+ self.include = m.attribute_method_names
end
end
end
@@ -155,8 +155,8 @@ def test_nested_params
end
def test_derived_wrapped_keys_from_matching_model
- User.expects(:respond_to?).with(:attribute_names).returns(true)
- User.expects(:attribute_names).twice.returns(["username"])
+ User.expects(:respond_to?).with(:attribute_method_names).returns(true)
+ User.expects(:attribute_method_names).twice.returns(["username"])
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
@@ -167,8 +167,8 @@ def test_derived_wrapped_keys_from_matching_model
def test_derived_wrapped_keys_from_specified_model
with_default_wrapper_options do
- Person.expects(:respond_to?).with(:attribute_names).returns(true)
- Person.expects(:attribute_names).twice.returns(["username"])
+ Person.expects(:respond_to?).with(:attribute_method_names).returns(true)
+ Person.expects(:attribute_method_names).twice.returns(["username"])
UsersController.wrap_parameters Person
@@ -179,8 +179,8 @@ def test_derived_wrapped_keys_from_specified_model
end
def test_not_wrapping_abstract_model
- User.expects(:respond_to?).with(:attribute_names).returns(true)
- User.expects(:attribute_names).returns([])
+ User.expects(:respond_to?).with(:attribute_method_names).returns(true)
+ User.expects(:attribute_method_names).returns([])
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
@@ -209,13 +209,13 @@ def parse
end
class SampleOne
- def self.attribute_names
+ def self.attribute_method_names
["username"]
end
end
class SampleTwo
- def self.attribute_names
+ def self.attribute_method_names
["title"]
end
end
@@ -298,7 +298,7 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase
include ParamsWrapperTestHelp
class ParamswrappernewsItem
- def self.attribute_names
+ def self.attribute_method_names
['test_attr']
end
end
@@ -7,4 +7,9 @@
*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.
@@ -72,9 +72,10 @@ module AttributeMethods
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
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_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
+ self.defined_attribute_names = []
end
module ClassMethods
@@ -276,6 +277,10 @@ def define_attribute_methods(*attr_names)
# person.name # => "Bob"
# person.name_short? # => true
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|
method_name = matcher.method_name(attr_name)
@@ -285,10 +290,11 @@ def define_attribute_method(attr_name)
if respond_to?(generate_method, true)
send(generate_method, attr_name)
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
+
attribute_method_matchers_cache.clear
end
@@ -322,6 +328,22 @@ def undefine_attribute_methods
attribute_method_matchers_cache.clear
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.
def generated_attribute_methods #:nodoc:
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
@@ -186,6 +186,15 @@ def foo
assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to
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
ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods
@@ -514,7 +514,7 @@ def index
app_file 'app/models/post.rb', <<-RUBY
class Post
- def self.attribute_names
+ def self.attribute_method_names
%w(title)
end
end