From 7cc145ec65167084e8b05d9462eaf94534fa0168 Mon Sep 17 00:00:00 2001 From: claudiob Date: Fri, 2 Jan 2015 14:19:21 -0800 Subject: Use Active Model, not ActiveModel in plain English Also prevents the word "Model" from linking to the documentation of ActiveModel::Model because that's not intended. [ci skip] --- activemodel/lib/active_model/conversion.rb | 6 +++--- activemodel/lib/active_model/dirty.rb | 2 +- activemodel/lib/active_model/gem_version.rb | 2 +- activemodel/lib/active_model/naming.rb | 2 +- activemodel/lib/active_model/serializers/xml.rb | 2 +- activemodel/lib/active_model/validations/absence.rb | 2 +- activemodel/lib/active_model/validator.rb | 2 +- activemodel/lib/active_model/version.rb | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 9c9b6f4a77..9de6ea65be 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -22,7 +22,7 @@ module ActiveModel module Conversion extend ActiveSupport::Concern - # If your object is already designed to implement all of the Active Model + # If your object is already designed to implement all of the \Active \Model # you can use the default :to_model implementation, which simply # returns +self+. # @@ -33,9 +33,9 @@ module ActiveModel # person = Person.new # person.to_model == person # => true # - # If your model does not act like an Active Model object, then you should + # If your model does not act like an \Active \Model object, then you should # define :to_model yourself returning a proxy object that wraps - # your object with Active Model compliant methods. + # your object with \Active \Model compliant methods. def to_model self end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 60af31cca7..2f34208dd3 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -102,7 +102,7 @@ module ActiveModel # # If an attribute is modified in-place then make use of # +[attribute_name]_will_change!+ to mark that the attribute is changing. - # Otherwise Active Model can't track changes to in-place attributes. Note + # Otherwise \Active \Model can't track changes to in-place attributes. Note # that Active Record can detect in-place modifications automatically. You do # not need to call +[attribute_name]_will_change!+ on Active Record models. # diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb index 2403242ce6..762f4fe939 100644 --- a/activemodel/lib/active_model/gem_version.rb +++ b/activemodel/lib/active_model/gem_version.rb @@ -1,5 +1,5 @@ module ActiveModel - # Returns the version of the currently loaded Active Model as a Gem::Version + # Returns the version of the currently loaded \Active \Model as a Gem::Version def self.gem_version Gem::Version.new VERSION::STRING end diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 4e6b02c246..1819d2403f 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -211,7 +211,7 @@ module ActiveModel # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover" # # Providing the functionality that ActiveModel::Naming provides in your object - # is required to pass the Active Model Lint test. So either extending the + # is required to pass the \Active \Model Lint test. So either extending the # provided method below, or rolling your own is required. module Naming def self.extended(base) #:nodoc: diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 3ad3bf30ad..e33c766627 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -6,7 +6,7 @@ require 'active_support/core_ext/time/acts_like' module ActiveModel module Serializers - # == Active Model XML Serializer + # == \Active \Model XML Serializer module Xml extend ActiveSupport::Concern include ActiveModel::Serialization diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb index 9b5416fb1d..75bf655578 100644 --- a/activemodel/lib/active_model/validations/absence.rb +++ b/activemodel/lib/active_model/validations/absence.rb @@ -1,6 +1,6 @@ module ActiveModel module Validations - # == Active Model Absence Validator + # == \Active \Model Absence Validator class AbsenceValidator < EachValidator #:nodoc: def validate_each(record, attr_name, value) record.errors.add(attr_name, :present, options) if value.present? diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 0116de68ab..ac32750946 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -127,7 +127,7 @@ module ActiveModel # in the options hash invoking the validate_each method passing in the # record, attribute and value. # - # All Active Model validations are built on top of this validator. + # All \Active \Model validations are built on top of this validator. class EachValidator < Validator #:nodoc: attr_reader :attributes diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index b1f9082ea7..6da3b4117b 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,7 +1,7 @@ require_relative 'gem_version' module ActiveModel - # Returns the version of the currently loaded ActiveModel as a Gem::Version + # Returns the version of the currently loaded \Active \Model as a Gem::Version def self.version gem_version end -- cgit v1.2.3 From 2386daabe7f8c979b453010dc0de3e1f6bbf859d Mon Sep 17 00:00:00 2001 From: claudiob Date: Thu, 16 Oct 2014 16:21:24 -0700 Subject: Throw :abort halts default CallbackChains This commit changes arguments and default value of CallbackChain's :terminator option. After this commit, Chains of callbacks defined **without** an explicit `:terminator` option will be halted as soon as a `before_` callback throws `:abort`. Chains of callbacks defined **with** a `:terminator` option will maintain their existing behavior of halting as soon as a `before_` callback matches the terminator's expectation. For instance, ActiveModel's callbacks will still halt the chain when a `before_` callback returns `false`. --- activemodel/lib/active_model/callbacks.rb | 2 +- activemodel/lib/active_model/validations/callbacks.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index b3d70dc515..061d96a80b 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -103,7 +103,7 @@ module ActiveModel def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - terminator: ->(_,result) { result == false }, + terminator: ->(_,result_lambda) { result_lambda.call == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name], only: [:before, :around, :after] diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 25ccabd66b..449f1a0299 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -23,7 +23,7 @@ module ActiveModel included do include ActiveSupport::Callbacks define_callbacks :validation, - terminator: ->(_,result) { result == false }, + terminator: ->(_,result_lambda) { result_lambda.call == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name] end -- cgit v1.2.3 From f767981286b4c7dcb96e061a6f3edcc334008ea8 Mon Sep 17 00:00:00 2001 From: claudiob Date: Mon, 8 Dec 2014 06:35:25 -0800 Subject: Deprecate `false` as the way to halt AM validation callbacks Before this commit, returning `false` in an ActiveModel validation callback such as `before_validation` would halt the callback chain. After this commit, the behavior is deprecated: will still work until the next release of Rails but will also display a deprecation warning. The preferred way to halt a callback chain is to explicitly `throw(:abort)`. --- activemodel/CHANGELOG.md | 9 +++++++++ .../lib/active_model/validations/callbacks.rb | 5 ++--- activemodel/test/cases/validations/callbacks_test.rb | 20 +++++++++++++++++--- 3 files changed, 28 insertions(+), 6 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index b86e988841..562d49d069 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1 +1,10 @@ +* Deprecate returning `false` as a way to halt callback chains. + + Returning `false` in a `before_` validation callback will display a + deprecation warning explaining that the preferred method to halt a callback + chain is to explicitly `throw(:abort)`. + + *claudiob* + + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 449f1a0299..4b58ef66e3 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -15,15 +15,14 @@ module ActiveModel # after_validation :do_stuff_after_validation # end # - # Like other before_* callbacks if +before_validation+ returns - # +false+ then valid? will not be called. + # Like other before_* callbacks if +before_validation+ throws + # +:abort+ then valid? will not be called. module Callbacks extend ActiveSupport::Concern included do include ActiveSupport::Callbacks define_callbacks :validation, - terminator: ->(_,result_lambda) { result_lambda.call == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name] end diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 5d6d48b824..4b0dd58efb 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -30,11 +30,16 @@ class DogWithTwoValidators < Dog before_validation { self.history << 'before_validation_marker2' } end -class DogBeforeValidatorReturningFalse < Dog +class DogDeprecatedBeforeValidatorReturningFalse < Dog before_validation { false } before_validation { self.history << 'before_validation_marker2' } end +class DogBeforeValidatorThrowingAbort < Dog + before_validation { throw :abort } + before_validation { self.history << 'before_validation_marker2' } +end + class DogAfterValidatorReturningFalse < Dog after_validation { false } after_validation { self.history << 'after_validation_marker' } @@ -86,13 +91,22 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history end - def test_further_callbacks_should_not_be_called_if_before_validation_returns_false - d = DogBeforeValidatorReturningFalse.new + def test_further_callbacks_should_not_be_called_if_before_validation_throws_abort + d = DogBeforeValidatorThrowingAbort.new output = d.valid? assert_equal [], d.history assert_equal false, output end + def test_deprecated_further_callbacks_should_not_be_called_if_before_validation_returns_false + d = DogDeprecatedBeforeValidatorReturningFalse.new + assert_deprecated do + output = d.valid? + assert_equal [], d.history + assert_equal false, output + end + end + def test_further_callbacks_should_be_called_if_after_validation_returns_false d = DogAfterValidatorReturningFalse.new d.valid? -- cgit v1.2.3 From 91b8129320522f664801f122daea4a7628db90a7 Mon Sep 17 00:00:00 2001 From: claudiob Date: Mon, 8 Dec 2014 06:53:24 -0800 Subject: Deprecate `false` as the way to halt AM callbacks Before this commit, returning `false` in an ActiveModel `before_` callback such as `before_create` would halt the callback chain. After this commit, the behavior is deprecated: will still work until the next release of Rails but will also display a deprecation warning. The preferred way to halt a callback chain is to explicitly `throw(:abort)`. --- activemodel/CHANGELOG.md | 2 +- activemodel/lib/active_model/callbacks.rb | 3 +-- activemodel/test/cases/callbacks_test.rb | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 562d49d069..b52ec8a67f 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,6 +1,6 @@ * Deprecate returning `false` as a way to halt callback chains. - Returning `false` in a `before_` validation callback will display a + Returning `false` in a `before_` callback will display a deprecation warning explaining that the preferred method to halt a callback chain is to explicitly `throw(:abort)`. diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 061d96a80b..6214802074 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -6,7 +6,7 @@ module ActiveModel # Provides an interface for any class to have Active Record like callbacks. # # Like the Active Record methods, the callback chain is aborted as soon as - # one of the methods in the chain returns +false+. + # one of the methods throws +:abort+. # # First, extend ActiveModel::Callbacks from the class you are creating: # @@ -103,7 +103,6 @@ module ActiveModel def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - terminator: ->(_,result_lambda) { result_lambda.call == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name], only: [:before, :around, :after] diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb index 2ac681b8d8..85455c112c 100644 --- a/activemodel/test/cases/callbacks_test.rb +++ b/activemodel/test/cases/callbacks_test.rb @@ -33,11 +33,13 @@ class CallbacksTest < ActiveModel::TestCase def initialize(options = {}) @callbacks = [] @valid = options[:valid] - @before_create_returns = options[:before_create_returns] + @before_create_returns = options.fetch(:before_create_returns, true) + @before_create_throws = options[:before_create_throws] end def before_create @callbacks << :before_create + throw(@before_create_throws) if @before_create_throws @before_create_returns end @@ -62,10 +64,18 @@ class CallbacksTest < ActiveModel::TestCase assert_equal model.callbacks.last, :final_callback end - test "the callback chain is halted when a before callback returns false" do + test "the callback chain is halted when a before callback returns false (deprecated)" do model = ModelCallbacks.new(before_create_returns: false) + assert_deprecated do + model.create + assert_equal model.callbacks.last, :before_create + end + end + + test "the callback chain is halted when a callback throws :abort" do + model = ModelCallbacks.new(before_create_throws: :abort) model.create - assert_equal model.callbacks.last, :before_create + assert_equal model.callbacks, [:before_create] end test "after callbacks are not executed if the block returns false" do -- cgit v1.2.3 From 9c65c539e2caa4590aded1975aead008f8135da4 Mon Sep 17 00:00:00 2001 From: claudiob Date: Wed, 24 Dec 2014 09:58:19 +0100 Subject: Add config to halt callback chain on return false This stems from [a comment](rails#17227 (comment)) by @dhh. In summary: * New Rails 5.0 apps will not accept `return false` as a way to halt callback chains, and will not display a deprecation warning. * Existing apps ported to Rails 5.0 will still accept `return false` as a way to halt callback chains, albeit with a deprecation warning. For this purpose, this commit introduces a Rails configuration option: ```ruby config.active_support.halt_callback_chains_on_return_false ``` For new Rails 5.0 apps, this option will be set to `false` by a new initializer `config/initializers/callback_terminator.rb`: ```ruby Rails.application.config.active_support.halt_callback_chains_on_return_false = false ``` For existing apps ported to Rails 5.0, the initializers above will not exist. Even running `rake rails:update` will not create this initializer. Since the default value of `halt_callback_chains_on_return_false` is set to `true`, these apps will still accept `return true` as a way to halt callback chains, displaying a deprecation warning. Developers will be able to switch to the new behavior (and stop the warning) by manually adding the line above to their `config/application.rb`. A gist with the suggested release notes to add to Rails 5.0 after this commit is available at https://gist.github.com/claudiob/614c59409fb7d11f2931 --- activemodel/CHANGELOG.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index b52ec8a67f..d643a08235 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,10 +1,12 @@ -* Deprecate returning `false` as a way to halt callback chains. - - Returning `false` in a `before_` callback will display a - deprecation warning explaining that the preferred method to halt a callback - chain is to explicitly `throw(:abort)`. - - *claudiob* +* Change the way in which callback chains can be halted. + + The preferred method to halt a callback chain from now on is to explicitly + `throw(:abort)`. + In the past, returning `false` in an ActiveModel or ActiveModel::Validations + `before_` callback had the side effect of halting the callback chain. + This is not recommended anymore and, depending on the value of the + `config.active_support.halt_callback_chains_on_return_false` option, will + either not work at all or display a deprecation warning. Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes. -- cgit v1.2.3 From 37175a24bd508e2983247ec5d011d57df836c743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 3 Jan 2015 18:20:30 -0300 Subject: Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and `ActiveModel::Dirty#reset_changes`. --- activemodel/CHANGELOG.md | 5 +++++ activemodel/lib/active_model/dirty.rb | 20 -------------------- activemodel/test/cases/dirty_test.rb | 17 ----------------- 3 files changed, 5 insertions(+), 37 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index d643a08235..f86b4804c8 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,8 @@ +* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and + `ActiveModel::Dirty#reset_changes`. + + *Rafael Mendonça França* + * Change the way in which callback chains can be halted. The preferred method to halt a callback chain from now on is to explicitly diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 094424e4fd..9c8ca5d93f 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -116,7 +116,6 @@ module ActiveModel included do attribute_method_suffix '_changed?', '_change', '_will_change!', '_was' - attribute_method_affix prefix: 'reset_', suffix: '!' attribute_method_affix prefix: 'restore_', suffix: '!' end @@ -204,15 +203,6 @@ module ActiveModel @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end - def reset_changes - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#reset_changes` is deprecated and will be removed on Rails 5. - Please use `#clear_changes_information` instead. - MSG - - clear_changes_information - end - # Handle *_change for +method_missing+. def attribute_change(attr) [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) @@ -231,16 +221,6 @@ module ActiveModel set_attribute_was(attr, value) end - # Handle reset_*! for +method_missing+. - def reset_attribute!(attr) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#reset_#{attr}!` is deprecated and will be removed on Rails 5. - Please use `#restore_#{attr}!` instead. - MSG - - restore_attribute!(attr) - end - # Handle restore_*! for +method_missing+. def restore_attribute!(attr) if attribute_changed?(attr) diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index db2cd885e2..8ffd62fd86 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -181,23 +181,6 @@ class DirtyTest < ActiveModel::TestCase assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes end - test "reset_changes is deprecated" do - @model.name = 'Dmitry' - @model.name_changed? - @model.save - @model.name = 'Bob' - - assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] - assert_equal 'Dmitry', @model.changed_attributes['name'] - - assert_deprecated do - @model.deprecated_reload - end - - assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes - assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes - end - test "restore_attributes should restore all previous data" do @model.name = 'Dmitry' @model.color = 'Red' -- cgit v1.2.3 From bf7b8c193ffe2d6a05272a6ed763d87cfe743bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 12:08:51 -0300 Subject: Remove unneeded requires These requires were added only to change deprecation message --- activemodel/lib/active_model/dirty.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 9c8ca5d93f..afba9bab0d 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -1,6 +1,5 @@ require 'active_support/hash_with_indifferent_access' require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/string/filters' module ActiveModel # == Active \Model \Dirty -- cgit v1.2.3 From fc933fd4abd7e2b4709e7ec17fc15a1e17266179 Mon Sep 17 00:00:00 2001 From: George Millo Date: Tue, 6 Jan 2015 00:10:56 +0000 Subject: removing unecessary parameter in private method '_singularize' only ever gets called with one argument --- activemodel/lib/active_model/naming.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 1819d2403f..ada1f9a4f3 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -189,8 +189,8 @@ module ActiveModel private - def _singularize(string, replacement='_') - ActiveSupport::Inflector.underscore(string).tr('/', replacement) + def _singularize(string) + ActiveSupport::Inflector.underscore(string).tr('/', '_') end end -- cgit v1.2.3 From e0213f45eacd587fb7c0630dc9eb6288b4337e46 Mon Sep 17 00:00:00 2001 From: robertomiranda Date: Sun, 19 May 2013 22:37:46 -0500 Subject: Remove attributes_protected_by_default reference, since MassAssignmentSecurity was removed from ActiveModel f8c9a4d3e88181 --- activemodel/lib/active_model/secure_password.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 96e88f1b6c..871031ece4 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -77,13 +77,6 @@ module ActiveModel validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED validates_confirmation_of :password, allow_blank: true end - - # This code is necessary as long as the protected_attributes gem is supported. - if respond_to?(:attributes_protected_by_default) - def self.attributes_protected_by_default #:nodoc: - super + ['password_digest'] - end - end end end -- cgit v1.2.3 From 140557e85fc43ef5cff81a3af748605a6870f45b Mon Sep 17 00:00:00 2001 From: mo khan Date: Sat, 10 Jan 2015 09:35:58 -0700 Subject: allow '1' or true for acceptance validation. --- activemodel/CHANGELOG.md | 6 ++++++ activemodel/lib/active_model/validations/acceptance.rb | 8 ++++++-- activemodel/test/cases/validations/acceptance_validation_test.rb | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index f86b4804c8..fb4b972282 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,9 @@ +* Change validates_acceptance_of to accept true by default. + + The default for validates_acceptance_of is now "1" and true. + In the past, only "1" was the default and you were required to add + accept: true. + * Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and `ActiveModel::Dirty#reset_changes`. diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index ac5e79859b..ee160fb483 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -3,12 +3,12 @@ module ActiveModel module Validations class AcceptanceValidator < EachValidator # :nodoc: def initialize(options) - super({ allow_nil: true, accept: "1" }.merge!(options)) + super({ allow_nil: true, accept: ["1", true] }.merge!(options)) setup!(options[:class]) end def validate_each(record, attribute, value) - unless value == options[:accept] + unless acceptable_option?(value) record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil)) end end @@ -20,6 +20,10 @@ module ActiveModel klass.send(:attr_reader, *attr_readers) klass.send(:attr_writer, *attr_writers) end + + def acceptable_option?(value) + Array(options[:accept]).include?(value) + end end module HelperMethods diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb index e78aa1adaf..b7872ea6bf 100644 --- a/activemodel/test/cases/validations/acceptance_validation_test.rb +++ b/activemodel/test/cases/validations/acceptance_validation_test.rb @@ -65,4 +65,10 @@ class AcceptanceValidationTest < ActiveModel::TestCase ensure Person.clear_validators! end + + def test_validates_acceptance_of_true + Topic.validates_acceptance_of(:terms_of_service) + + assert Topic.new(terms_of_service: true).valid? + end end -- cgit v1.2.3 From 684cbee4733d47fe242b5637d6bdc48ec16ac536 Mon Sep 17 00:00:00 2001 From: claudiob Date: Sun, 11 Jan 2015 12:25:53 -0800 Subject: Add test for AM::Validation::Callbacks with :on `before_validation` and `after_validation` from ActiveModel::Validation::Callbacks accept an optional `:on` parameter that was not previously documented or tested. For instance given before_validation :do_something, on: :create then `object.valid?(:create)` will invoke `:do_something` while `object.valid?` or `object.valid?(:anything_else)` will not. --- .../test/cases/validations/callbacks_test.rb | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'activemodel') diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 4b0dd58efb..cc50ffbbef 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -50,6 +50,14 @@ class DogWithMissingName < Dog validates_presence_of :name end +class DogValidatorWithOnCondition < Dog + before_validation :set_before_validation_marker, on: :create + after_validation :set_after_validation_marker, on: :create + + def set_before_validation_marker; self.history << 'before_validation_marker'; end + def set_after_validation_marker; self.history << 'after_validation_marker' ; end +end + class DogValidatorWithIfCondition < Dog before_validation :set_before_validation_marker1, if: -> { true } before_validation :set_before_validation_marker2, if: -> { false } @@ -73,6 +81,24 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase assert_equal ["before_validation_marker1", "after_validation_marker1"], d.history end + def test_on_condition_is_respected_for_validation_with_matching_context + d = DogValidatorWithOnCondition.new + d.valid?(:create) + assert_equal ["before_validation_marker", "after_validation_marker"], d.history + end + + def test_on_condition_is_respected_for_validation_without_matching_context + d = DogValidatorWithOnCondition.new + d.valid?(:save) + assert_equal [], d.history + end + + def test_on_condition_is_respected_for_validation_without_context + d = DogValidatorWithOnCondition.new + d.valid? + assert_equal [], d.history + end + def test_before_validation_and_after_validation_callbacks_should_be_called d = DogWithMethodCallbacks.new d.valid? -- cgit v1.2.3 From d3927c8d180574657b50dfc3ca40b07d48b3545c Mon Sep 17 00:00:00 2001 From: claudiob Date: Sun, 11 Jan 2015 12:32:26 -0800 Subject: Remove unused "deprecated_reload" method The method was introduced in https://github.com/rails/rails/commit/66d0a0153578ce760d822580c5b8c0b726042ac2#diff-8cec05860729a3851ceb756f4dd90370R49 for the "reset_changes is deprecated" test, but this test was successively removed in https://github.com/rails/rails/commit/37175a24bd508e2983247ec5d011d57df836c743 --- activemodel/test/cases/dirty_test.rb | 4 ---- 1 file changed, 4 deletions(-) (limited to 'activemodel') diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index 8ffd62fd86..66ed8a350a 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -45,10 +45,6 @@ class DirtyTest < ActiveModel::TestCase def reload clear_changes_information end - - def deprecated_reload - reset_changes - end end setup do -- cgit v1.2.3 From 095b92af6c38db0e6a87ed7eef3f09203d99dd17 Mon Sep 17 00:00:00 2001 From: Anton Davydov Date: Mon, 12 Jan 2015 18:21:56 +0300 Subject: Fix error messages scope [skip ci] --- activemodel/lib/active_model/errors.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 55687cb3c7..477edbd120 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -388,8 +388,8 @@ module ActiveModel # Translates an error message in its default scope # (activemodel.errors.messages). # - # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, - # if it's not there, it's looked up in models.MODEL.MESSAGE and if + # Error messages are first looked up in activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE, + # if it's not there, it's looked up in activemodel.errors.models.MODEL.MESSAGE and if # that is not there also, it returns the translation of the default message # (e.g. activemodel.errors.messages.MESSAGE). The translated model # name, translated attribute name and the value are available for -- cgit v1.2.3 From ea721d7027c16f64c47395546c67858060a273e3 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 18 Jan 2015 13:43:31 -0700 Subject: Don't calculate in-place changes on attribute assignment When an attribute is assigned, we determine if it was already marked as changed so we can determine if we need to clear the changes, or mark it as changed. Since this only affects the `attributes_changed_by_setter` hash, in-place changes are irrelevant to this process. Since calculating in-place changes can be expensive, we can just skip it here. I also added a test for the only edge case I could think of that would be affected by this change. --- activemodel/lib/active_model/dirty.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index afba9bab0d..614bc6a5d8 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -189,6 +189,7 @@ module ActiveModel def changes_include?(attr_name) attributes_changed_by_setter.include?(attr_name) end + alias attribute_changed_by_setter? changes_include? # Removes current changes and makes them accessible through +previous_changes+. def changes_applied # :doc: -- cgit v1.2.3 From cb74473db68900d336844d840dda6e10dc03fde1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Wn=C4=99trzak?= Date: Sun, 4 Jan 2015 09:18:03 +0100 Subject: Add ActiveModel::Errors#details To be able to return type of validator, one can now call `details` on Errors instance: ```ruby class User < ActiveRecord::Base validates :name, presence: true end ``` ```ruby user = User.new; user.valid?; user.errors.details => {name: [{error: :blank}]} ``` --- activemodel/CHANGELOG.md | 20 +++++++++++- activemodel/lib/active_model/errors.rb | 57 ++++++++++++++++++++++------------ activemodel/test/cases/errors_test.rb | 42 +++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 21 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index fb4b972282..175912b240 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,7 +1,25 @@ +* Add `ActiveModel::Errors#details` + + To be able to return type of used validator, one can now call `details` + on Errors instance: + + ```ruby + class User < ActiveRecord::Base + validates :name, presence: true + end + ``` + + ```ruby + user = User.new; user.valid?; user.errors.details + => {name: [{error: :blank}]} + ``` + + *Wojciech Wnętrzak* + * Change validates_acceptance_of to accept true by default. The default for validates_acceptance_of is now "1" and true. - In the past, only "1" was the default and you were required to add + In the past, only "1" was the default and you were required to add accept: true. * Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 477edbd120..912448e43c 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -23,7 +23,7 @@ module ActiveModel # attr_reader :errors # # def validate! - # errors.add(:name, "cannot be nil") if name.nil? + # errors.add(:name, :blank, message: "cannot be nil") if name.nil? # end # # # The following methods are needed to be minimally implemented @@ -58,8 +58,9 @@ module ActiveModel include Enumerable CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict] + MESSAGE_OPTIONS = [:message] - attr_reader :messages + attr_reader :messages, :details # Pass in the instance of the object that is using the errors object. # @@ -71,10 +72,12 @@ module ActiveModel def initialize(base) @base = base @messages = {} + @details = Hash.new { |details, attribute| details[attribute] = [] } end def initialize_dup(other) # :nodoc: @messages = other.messages.dup + @details = other.details.dup super end @@ -85,6 +88,7 @@ module ActiveModel # person.errors.full_messages # => [] def clear messages.clear + details.clear end # Returns +true+ if the error messages include an error for the given key @@ -126,6 +130,7 @@ module ActiveModel # person.errors.get(:name) # => nil def delete(key) messages.delete(key) + details.delete(key) end # When passed a symbol or a name of a method, returns an array of errors @@ -149,12 +154,12 @@ module ActiveModel # Yields the attribute and the error for that attribute. If the attribute # has more than one error message, yields once for each error message. # - # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, :blank, message: "can't be blank") # person.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # end # - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # # then yield :name and "must be specified" @@ -167,9 +172,9 @@ module ActiveModel # Returns the number of error messages. # - # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, :blank, message: "can't be blank") # person.errors.size # => 1 - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.size # => 2 def size values.flatten.size @@ -193,8 +198,8 @@ module ActiveModel # Returns an array of error messages, with the attribute name included. # - # person.errors.add(:name, "can't be blank") - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.to_a # => ["name can't be blank", "name must be specified"] def to_a full_messages @@ -202,9 +207,9 @@ module ActiveModel # Returns the number of error messages. # - # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, :blank, message: "can't be blank") # person.errors.count # => 1 - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.count # => 2 def count to_a.size @@ -223,8 +228,8 @@ module ActiveModel # Returns an xml formatted representation of the Errors hash. # - # person.errors.add(:name, "can't be blank") - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.to_xml # # => # # @@ -261,17 +266,20 @@ module ActiveModel end end - # Adds +message+ to the error messages on +attribute+. More than one error - # can be added to the same +attribute+. If no +message+ is supplied, - # :invalid is assumed. + # Adds +message+ to the error messages and used validator type to +details+ on +attribute+. + # More than one error can be added to the same +attribute+. + # If no +message+ is supplied, :invalid is assumed. # # person.errors.add(:name) # # => ["is invalid"] - # person.errors.add(:name, 'must be implemented') + # person.errors.add(:name, :not_implemented, message: "must be implemented") # # => ["is invalid", "must be implemented"] # # person.errors.messages - # # => {:name=>["must be implemented", "is invalid"]} + # # => {:name=>["is invalid", "must be implemented"]} + # + # person.errors.details + # # => {:name=>[{error: :not_implemented}, {error: :invalid}]} # # If +message+ is a symbol, it will be translated using the appropriate # scope (see +generate_message+). @@ -293,16 +301,22 @@ module ActiveModel # +attribute+ should be set to :base if the error is not # directly associated with a single attribute. # - # person.errors.add(:base, "either name or email must be present") + # person.errors.add(:base, :name_or_email_blank, + # message: "either name or email must be present") # person.errors.messages # # => {:base=>["either name or email must be present"]} + # person.errors.details + # # => {:base=>[{error: :name_or_email_blank}]} def add(attribute, message = :invalid, options = {}) + message = message.call if message.respond_to?(:call) + detail = normalize_detail(attribute, message, options) message = normalize_message(attribute, message, options) if exception = options[:strict] exception = ActiveModel::StrictValidationFailed if exception == true raise exception, full_message(attribute, message) end + details[attribute.to_sym] << detail self[attribute] << message end @@ -339,6 +353,7 @@ module ActiveModel # person.errors.add :name, :blank # person.errors.added? :name, :blank # => true def added?(attribute, message = :invalid, options = {}) + message = message.call if message.respond_to?(:call) message = normalize_message(attribute, message, options) self[attribute].include? message end @@ -447,12 +462,14 @@ module ActiveModel case message when Symbol generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) - when Proc - message.call else message end end + + def normalize_detail(attribute, message, options) + { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)) + end end # Raised when a validation cannot be corrected by end users and are considered diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index efedd9055f..9cfba9812b 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -323,4 +323,46 @@ class ErrorsTest < ActiveModel::TestCase person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' }) person.errors.add_on_blank :name, message: 'custom' end + + test "details returns added error detail" do + person = Person.new + person.errors.add(:name, :invalid) + assert_equal({ name: [{ error: :invalid }] }, person.errors.details) + end + + test "details returns added error detail with custom option" do + person = Person.new + person.errors.add(:name, :greater_than, count: 5) + assert_equal({ name: [{ error: :greater_than, count: 5 }] }, person.errors.details) + end + + test "details do not include message option" do + person = Person.new + person.errors.add(:name, :invalid, message: "is bad") + assert_equal({ name: [{ error: :invalid }] }, person.errors.details) + end + + test "dup duplicates details" do + errors = ActiveModel::Errors.new(Person.new) + errors.add(:name, :invalid) + errors_dup = errors.dup + errors_dup.add(:name, :taken) + assert_not_same errors_dup.details, errors.details + end + + test "delete removes details on given attribute" do + errors = ActiveModel::Errors.new(Person.new) + errors.add(:name, :invalid) + errors.delete(:name) + assert_empty errors.details[:name] + end + + test "clear removes details" do + person = Person.new + person.errors.add(:name, :invalid) + + assert_equal 1, person.errors.details.count + person.errors.clear + assert person.errors.details.empty? + end end -- cgit v1.2.3 From 2ee6ed69fc5c6ac9da109df33ba249f1f762e323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Wn=C4=99trzak?= Date: Wed, 21 Jan 2015 08:04:40 +0100 Subject: Add missing AS core extension dependency --- activemodel/lib/active_model/naming.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index ada1f9a4f3..2bc3eeaa19 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/introspection' +require 'active_support/core_ext/module/remove_method' module ActiveModel class Name -- cgit v1.2.3 From 2606fb339797a99c50e531105fc92071cef3db01 Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Fri, 23 Jan 2015 13:57:34 +0200 Subject: Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributesAssignment` Allows to use it for any object as an includable module. --- activemodel/CHANGELOG.md | 20 ++++ activemodel/lib/active_model.rb | 1 + .../lib/active_model/attribute_assignment.rb | 64 +++++++++++++ .../test/cases/attribute_assignment_test.rb | 106 +++++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 activemodel/lib/active_model/attribute_assignment.rb create mode 100644 activemodel/test/cases/attribute_assignment_test.rb (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 175912b240..e20ebde8b1 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,23 @@ +* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment` + allowing to use it for any object as an includable module + + ``` ruby + class Cat + include ActiveModel::AttributeAssignment + attr_accessor :name, :status + end + + cat = Cat.new + cat.assign_attributes(name: "Gorby", status: "yawning") + cat.name # => 'Gorby' + cat.status => 'yawning' + cat.assign_attributes(status: "sleeping") + cat.name # => 'Gorby' + cat.status => 'sleeping' + ``` + + *Bogdan Gusiev* + * Add `ActiveModel::Errors#details` To be able to return type of used validator, one can now call `details` diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 46d60db756..58fe08cc11 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -28,6 +28,7 @@ require 'active_model/version' module ActiveModel extend ActiveSupport::Autoload + autoload :AttributeAssignment autoload :AttributeMethods autoload :BlockValidator, 'active_model/validator' autoload :Callbacks diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb new file mode 100644 index 0000000000..36ddbad826 --- /dev/null +++ b/activemodel/lib/active_model/attribute_assignment.rb @@ -0,0 +1,64 @@ +require 'active_support/core_ext/hash/keys' + +module ActiveModel + module AttributeAssignment + include ActiveModel::ForbiddenAttributesProtection + + # Allows you to set all the attributes by passing in a hash of attributes with + # keys matching the attribute names. + # + # If the passed hash responds to permitted? method and the return value + # of this method is +false+ an ActiveModel::ForbiddenAttributesError + # exception is raised. + # + # class Cat + # include ActiveModel::AttributeAssignment + # attr_accessor :name, :status + # end + # + # cat = Cat.new + # cat.assign_attributes(name: "Gorby", status: "yawning") + # cat.name # => 'Gorby' + # cat.status => 'yawning' + # cat.assign_attributes(status: "sleeping") + # cat.name # => 'Gorby' + # cat.status => 'sleeping' + def assign_attributes(new_attributes) + if !new_attributes.respond_to?(:stringify_keys) + raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." + end + return if new_attributes.blank? + + attributes = new_attributes.stringify_keys + _assign_attributes(sanitize_for_mass_assignment(attributes)) + end + + private + def _assign_attributes(attributes) + attributes.each do |k, v| + _assign_attribute(k, v) + end + end + + def _assign_attribute(k, v) + public_send("#{k}=", v) + rescue NoMethodError + if respond_to?("#{k}=") + raise + else + raise UnknownAttributeError.new(self, k) + end + end + + # Raised when unknown attributes are supplied via mass assignment. + class UnknownAttributeError < NoMethodError + attr_reader :record, :attribute + + def initialize(record, attribute) + @record = record + @attribute = attribute + super("unknown attribute '#{attribute}' for #{@record.class}.") + end + end + end +end diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb new file mode 100644 index 0000000000..5c1be6a456 --- /dev/null +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -0,0 +1,106 @@ +require 'cases/helper' +require 'active_support/hash_with_indifferent_access' + +class AttributeAssignmentTest < ActiveModel::TestCase + + class Model + include ActiveModel::AttributeAssignment + + attr_accessor :name, :description + + def initialize(attributes = {}) + assign_attributes(attributes) + end + + def broken_attribute=(value) + non_existing_method(value) + end + + private + def metadata=(data) + @metadata = data + end + end + + class ProtectedParams < ActiveSupport::HashWithIndifferentAccess + def permit! + @permitted = true + end + + def permitted? + @permitted ||= false + end + + def dup + super.tap do |duplicate| + duplicate.instance_variable_set :@permitted, permitted? + end + end + end + + test "simple assignment" do + model = Model.new + + model.assign_attributes(name: 'hello', description: 'world') + assert_equal 'hello', model.name + assert_equal 'world', model.description + end + + test "assign non-existing attribute" do + model = Model.new + error = assert_raises ActiveModel::AttributeAssignment::UnknownAttributeError do + model.assign_attributes(hz: 1) + end + + assert_equal model, error.record + assert_equal "hz", error.attribute + end + + test "assign private attribute" do + model = Model.new + assert_raises ActiveModel::AttributeAssignment::UnknownAttributeError do + model.assign_attributes(metadata: { a: 1 }) + end + end + + test "raises NoMethodError if raised in attribute writer" do + assert_raises NoMethodError do + Model.new(broken_attribute: 1) + end + end + + test "raises ArgumentError if non-hash object passed" do + assert_raises ArgumentError do + Model.new(1) + end + end + + test 'forbidden attributes cannot be used for mass assignment' do + params = ProtectedParams.new(name: 'Guille', description: 'm') + assert_raises(ActiveModel::ForbiddenAttributesError) do + Model.new(params) + end + end + + test 'permitted attributes can be used for mass assignment' do + params = ProtectedParams.new(name: 'Guille', description: 'desc') + params.permit! + model = Model.new(params) + + assert_equal 'Guille', model.name + assert_equal 'desc', model.description + end + + test 'regular hash should still be used for mass assignment' do + model = Model.new(name: 'Guille', description: 'm') + + assert_equal 'Guille', model.name + assert_equal 'm', model.description + end + + test 'blank attributes should not raise' do + model = Model.new + assert_nil model.assign_attributes(ProtectedParams.new({})) + end + +end -- cgit v1.2.3 From a225d4bec51778d99ccba5f0d6700dd00d2474f4 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 23 Jan 2015 14:51:59 -0700 Subject: =?UTF-8?q?=E2=9C=82=EF=B8=8F=20and=20=F0=9F=92=85=20for=20#10776?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor style changes across the board. Changed an alias to an explicit method declaration, since the alias will not be documented otherwise. --- activemodel/CHANGELOG.md | 2 +- .../lib/active_model/attribute_assignment.rb | 5 +- .../test/cases/attribute_assignment_test.rb | 59 +++++++++++----------- 3 files changed, 33 insertions(+), 33 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index e20ebde8b1..9cebb680fc 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -6,7 +6,7 @@ include ActiveModel::AttributeAssignment attr_accessor :name, :status end - + cat = Cat.new cat.assign_attributes(name: "Gorby", status: "yawning") cat.name # => 'Gorby' diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb index 36ddbad826..356421476c 100644 --- a/activemodel/lib/active_model/attribute_assignment.rb +++ b/activemodel/lib/active_model/attribute_assignment.rb @@ -34,6 +34,7 @@ module ActiveModel end private + def _assign_attributes(attributes) attributes.each do |k, v| _assign_attribute(k, v) @@ -41,10 +42,8 @@ module ActiveModel end def _assign_attribute(k, v) - public_send("#{k}=", v) - rescue NoMethodError if respond_to?("#{k}=") - raise + public_send("#{k}=", v) else raise UnknownAttributeError.new(self, k) end diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb index 5c1be6a456..402caf21f7 100644 --- a/activemodel/test/cases/attribute_assignment_test.rb +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -1,8 +1,7 @@ -require 'cases/helper' -require 'active_support/hash_with_indifferent_access' +require "cases/helper" +require "active_support/hash_with_indifferent_access" class AttributeAssignmentTest < ActiveModel::TestCase - class Model include ActiveModel::AttributeAssignment @@ -13,13 +12,15 @@ class AttributeAssignmentTest < ActiveModel::TestCase end def broken_attribute=(value) - non_existing_method(value) + raise ErrorFromAttributeWriter end - private - def metadata=(data) - @metadata = data - end + protected + + attr_writer :metadata + end + + class ErrorFromAttributeWriter < StandardError end class ProtectedParams < ActiveSupport::HashWithIndifferentAccess @@ -41,14 +42,14 @@ class AttributeAssignmentTest < ActiveModel::TestCase test "simple assignment" do model = Model.new - model.assign_attributes(name: 'hello', description: 'world') - assert_equal 'hello', model.name - assert_equal 'world', model.description + model.assign_attributes(name: "hello", description: "world") + assert_equal "hello", model.name + assert_equal "world", model.description end test "assign non-existing attribute" do model = Model.new - error = assert_raises ActiveModel::AttributeAssignment::UnknownAttributeError do + error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do model.assign_attributes(hz: 1) end @@ -58,49 +59,49 @@ class AttributeAssignmentTest < ActiveModel::TestCase test "assign private attribute" do model = Model.new - assert_raises ActiveModel::AttributeAssignment::UnknownAttributeError do + assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do model.assign_attributes(metadata: { a: 1 }) end end - test "raises NoMethodError if raised in attribute writer" do - assert_raises NoMethodError do + test "does not swallow errors raised in an attribute writer" do + assert_raises(ErrorFromAttributeWriter) do Model.new(broken_attribute: 1) end end - test "raises ArgumentError if non-hash object passed" do - assert_raises ArgumentError do + test "an ArgumentError is raised if a non-hash-like obejct is passed" do + assert_raises(ArgumentError) do Model.new(1) end end - test 'forbidden attributes cannot be used for mass assignment' do - params = ProtectedParams.new(name: 'Guille', description: 'm') + test "forbidden attributes cannot be used for mass assignment" do + params = ProtectedParams.new(name: "Guille", description: "m") + assert_raises(ActiveModel::ForbiddenAttributesError) do Model.new(params) end end - test 'permitted attributes can be used for mass assignment' do - params = ProtectedParams.new(name: 'Guille', description: 'desc') + test "permitted attributes can be used for mass assignment" do + params = ProtectedParams.new(name: "Guille", description: "desc") params.permit! model = Model.new(params) - assert_equal 'Guille', model.name - assert_equal 'desc', model.description + assert_equal "Guille", model.name + assert_equal "desc", model.description end - test 'regular hash should still be used for mass assignment' do - model = Model.new(name: 'Guille', description: 'm') + test "regular hash should still be used for mass assignment" do + model = Model.new(name: "Guille", description: "m") - assert_equal 'Guille', model.name - assert_equal 'm', model.description + assert_equal "Guille", model.name + assert_equal "m", model.description end - test 'blank attributes should not raise' do + test "assigning no attributes should not raise, even if the hash is un-permitted" do model = Model.new assert_nil model.assign_attributes(ProtectedParams.new({})) end - end -- cgit v1.2.3 From 5bdb42159ec461d678652319da14b4a59bfafd27 Mon Sep 17 00:00:00 2001 From: Eugene Gilburg Date: Fri, 23 Jan 2015 14:36:43 -0800 Subject: use attribute assignment module logic during active model initialization --- activemodel/CHANGELOG.md | 11 +++++++++++ activemodel/lib/active_model/model.rb | 7 +++---- activemodel/test/cases/model_test.rb | 4 +++- 3 files changed, 17 insertions(+), 5 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 9cebb680fc..77386e5e41 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,14 @@ +* Assigning an unknown attribute key to an `ActiveModel` instance during initialization + will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of + `NoMethodError` + + ```ruby + User.new(foo: 'some value') + # => ActiveModel::AttributeAssignment::UnknownAttributeError: unknown attribute 'foo' for User. + ``` + + *Eugene Gilburg* + * Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment` allowing to use it for any object as an includable module diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index d51d6ddcc9..dac8d549a7 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -57,6 +57,7 @@ module ActiveModel # (see below). module Model extend ActiveSupport::Concern + include ActiveModel::AttributeAssignment include ActiveModel::Validations include ActiveModel::Conversion @@ -75,10 +76,8 @@ module ActiveModel # person = Person.new(name: 'bob', age: '18') # person.name # => "bob" # person.age # => "18" - def initialize(params={}) - params.each do |attr, value| - self.public_send("#{attr}=", value) - end if params + def initialize(attributes={}) + assign_attributes(attributes) if attributes super() end diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index ee0fa26546..9a8d873ec9 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -70,6 +70,8 @@ class ModelTest < ActiveModel::TestCase end def test_mixin_initializer_when_args_dont_exist - assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') } + assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + SimpleModel.new(hello: 'world') + end end end -- cgit v1.2.3 From fb7d95b21222f762eb4dded727a5999fb198fdfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Wn=C4=99trzak?= Date: Sat, 24 Jan 2015 11:58:52 +0100 Subject: Fixed duplicating ActiveModel::Errors#details --- activemodel/lib/active_model/errors.rb | 3 ++- activemodel/test/cases/errors_test.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 912448e43c..a809c72ccd 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/object/deep_dup' module ActiveModel # == Active \Model \Errors @@ -77,7 +78,7 @@ module ActiveModel def initialize_dup(other) # :nodoc: @messages = other.messages.dup - @details = other.details.dup + @details = other.details.deep_dup super end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 9cfba9812b..17dbb817d0 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -347,7 +347,7 @@ class ErrorsTest < ActiveModel::TestCase errors.add(:name, :invalid) errors_dup = errors.dup errors_dup.add(:name, :taken) - assert_not_same errors_dup.details, errors.details + assert_not_equal errors_dup.details, errors.details end test "delete removes details on given attribute" do -- cgit v1.2.3 From 9a6c6c6f094ce965cc251865bdc1828bc4f38039 Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Tue, 27 Jan 2015 15:52:52 +0200 Subject: Provide a better error message on :required association Fixes #18696. --- activemodel/CHANGELOG.md | 5 +++++ activemodel/lib/active_model/locale/en.yml | 1 + 2 files changed, 6 insertions(+) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 77386e5e41..ca1018d399 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,8 @@ +* Change the default error message from `can't be blank` to `must exist` for + the presence validator of the `:required` option on `belongs_to`/`has_one` associations. + + *Henrik Nygren* + * Assigning an unknown attribute key to an `ActiveModel` instance during initialization will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of `NoMethodError` diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index bf07945fe1..b0b20ee0bf 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -14,6 +14,7 @@ en: empty: "can't be empty" blank: "can't be blank" present: "must be blank" + required: "must exist" too_long: one: "is too long (maximum is 1 character)" other: "is too long (maximum is %{count} characters)" -- cgit v1.2.3 From afe402dac7da9e2e9d0f3e29582a502d8596d0e0 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Sat, 31 Jan 2015 11:54:00 +0100 Subject: unify CHANGELOG format. [ci skip] --- activemodel/CHANGELOG.md | 62 +++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 77386e5e41..5830a00bc5 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -2,48 +2,46 @@ will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of `NoMethodError` - ```ruby - User.new(foo: 'some value') - # => ActiveModel::AttributeAssignment::UnknownAttributeError: unknown attribute 'foo' for User. - ``` + Example: + + User.new(foo: 'some value') + # => ActiveModel::AttributeAssignment::UnknownAttributeError: unknown attribute 'foo' for User. *Eugene Gilburg* * Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment` - allowing to use it for any object as an includable module - - ``` ruby - class Cat - include ActiveModel::AttributeAssignment - attr_accessor :name, :status - end - - cat = Cat.new - cat.assign_attributes(name: "Gorby", status: "yawning") - cat.name # => 'Gorby' - cat.status => 'yawning' - cat.assign_attributes(status: "sleeping") - cat.name # => 'Gorby' - cat.status => 'sleeping' - ``` + allowing to use it for any object as an includable module. + + Example: + + class Cat + include ActiveModel::AttributeAssignment + attr_accessor :name, :status + end + + cat = Cat.new + cat.assign_attributes(name: "Gorby", status: "yawning") + cat.name # => 'Gorby' + cat.status => 'yawning' + cat.assign_attributes(status: "sleeping") + cat.name # => 'Gorby' + cat.status => 'sleeping' *Bogdan Gusiev* * Add `ActiveModel::Errors#details` To be able to return type of used validator, one can now call `details` - on Errors instance: - - ```ruby - class User < ActiveRecord::Base - validates :name, presence: true - end - ``` - - ```ruby - user = User.new; user.valid?; user.errors.details - => {name: [{error: :blank}]} - ``` + on errors instance. + + Example: + + class User < ActiveRecord::Base + validates :name, presence: true + end + + user = User.new; user.valid?; user.errors.details + => {name: [{error: :blank}]} *Wojciech Wnętrzak* -- cgit v1.2.3