diff options
Diffstat (limited to 'activemodel/lib/active_model')
17 files changed, 80 insertions, 64 deletions
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 6b0a752566..374265f0d8 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -62,7 +62,7 @@ module ActiveModel # person = Person.create # person.to_param # => "1" def to_param - persisted? ? to_key.join('-') : nil + (persisted? && key = to_key) ? key.join('-') : nil end # Returns a +string+ identifying the path associated with the object. @@ -83,8 +83,8 @@ module ActiveModel # internal method and should not be accessed directly. def _to_partial_path #:nodoc: @_to_partial_path ||= begin - element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)) - collection = ActiveSupport::Inflector.tableize(self) + element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name)) + collection = ActiveSupport::Inflector.tableize(name) "#{collection}/#{element}".freeze end end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 58d87e6f7f..98ffffeb10 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -136,7 +136,7 @@ module ActiveModel # person.save # person.previous_changes # => {"name" => ["bob", "robert"]} def previous_changes - @previously_changed ||= {} + @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new end # Returns a hash of the attributes with unsaved changes indicating their original @@ -167,13 +167,13 @@ module ActiveModel # Removes current changes and makes them accessible through +previous_changes+. def changes_applied @previously_changed = changes - @changed_attributes = {} + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end # Removes all dirty data: current changes and previous changes def reset_changes - @previously_changed = {} - @changed_attributes = {} + @previously_changed = ActiveSupport::HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end # Handle <tt>*_change</tt> for +method_missing+. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 010c4bb6f9..917d3b9142 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -94,7 +94,7 @@ module ActiveModel # person.errors.include?(:name) # => true # person.errors.include?(:age) # => false def include?(attribute) - (v = messages[attribute]) && v.any? + messages[attribute].present? end # aliases include? alias :has_key? :include? @@ -289,6 +289,13 @@ module ActiveModel # # => NameIsInvalid: name is invalid # # person.errors.messages # => {} + # + # +attribute+ should be set to <tt>:base</tt> if the error is not + # directly associated with a single attribute. + # + # person.errors.add(:base, "either name or email must be present") + # person.errors.messages + # # => {:base=>["either name or email must be present"]} def add(attribute, message = :invalid, options = {}) message = normalize_message(attribute, message, options) if exception = options[:strict] diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb new file mode 100644 index 0000000000..964b24398d --- /dev/null +++ b/activemodel/lib/active_model/gem_version.rb @@ -0,0 +1,15 @@ +module ActiveModel + # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt> + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 4 + MINOR = 2 + TINY = 0 + PRE = "alpha" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index d824a66784..826e89bf9d 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -20,9 +20,9 @@ module ActiveModel # value to the password_confirmation attribute and the validation # will not be triggered. # - # You need to add bcrypt-ruby (~> 3.1.2) to Gemfile to use #has_secure_password: + # You need to add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password: # - # gem 'bcrypt-ruby', '~> 3.1.2' + # gem 'bcrypt', '~> 3.1.7' # # Example using Active Record (which automatically includes ActiveModel::SecurePassword): # @@ -42,13 +42,13 @@ module ActiveModel # User.find_by(name: 'david').try(:authenticate, 'notright') # => false # User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user def has_secure_password(options = {}) - # Load bcrypt-ruby only when has_secure_password is used. + # Load bcrypt gem only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) # being dependent on a binary library. begin require 'bcrypt' rescue LoadError - $stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install" + $stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install" raise end @@ -57,11 +57,15 @@ module ActiveModel include InstanceMethodsOnActivation if options.fetch(:validations, true) - validates_confirmation_of :password, if: :password_confirmation_required? - validates_presence_of :password, on: :create - validates_presence_of :password_confirmation, if: :password_confirmation_required? + # This ensures the model has a password by checking whether the password_digest + # is present, so that this works with both new and existing records. However, + # when there is an error, the message is added to the password attribute instead + # so that the error message will make sense to the end-user. + validate do |record| + record.errors.add(:password, :blank) unless record.password_digest.present? + end - before_create { raise "Password digest missing on new record" if password_digest.blank? } + validates_confirmation_of :password, if: ->{ password.present? } end if respond_to?(:attributes_protected_by_default) @@ -100,7 +104,9 @@ module ActiveModel # user.password = 'mUc3m00RsqyRe' # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4." def password=(unencrypted_password) - unless unencrypted_password.blank? + if unencrypted_password.nil? + self.password_digest = nil + elsif unencrypted_password.present? @password = unencrypted_password cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost) @@ -110,12 +116,6 @@ module ActiveModel def password_confirmation=(unencrypted_password) @password_confirmation = unencrypted_password end - - private - - def password_confirmation_required? - password_confirmation && password.present? - end end end end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 6be44b5d63..cf97f45dba 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -66,8 +66,10 @@ module ActiveModel # end # # Options: - # * <tt>:on</tt> - Specifies the context where this validation is active - # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>) + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # You can pass a symbol or an array of symbols. + # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. # * <tt>:allow_blank</tt> - Skip validation if attribute is blank. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine @@ -124,10 +126,10 @@ module ActiveModel # end # # Options: - # * <tt>:on</tt> - Specifies the context where this validation is active - # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>) - # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. - # * <tt>:allow_blank</tt> - Skip validation if attribute is blank. + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # You can pass a symbol or an array of symbols. + # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:if</tt> - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. <tt>if: :allow_validation</tt>, # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, @@ -143,7 +145,7 @@ module ActiveModel options = options.dup options[:if] = Array(options[:if]) options[:if].unshift lambda { |o| - o.validation_context == options[:on] + Array(options[:on]).include?(o.validation_context) } end args << options @@ -199,12 +201,12 @@ module ActiveModel # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}> # # ] # - # If one runs Person.clear_validators! and then checks to see what + # If one runs <tt>Person.clear_validators!</tt> and then checks to see what # validators this class has, you would obtain: # # Person.validators # => [] # - # Also, the callback set by +validate :cannot_be_robot+ will be erased + # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased # so that: # # Person._validate_callbacks.empty? # => true @@ -283,6 +285,8 @@ module ActiveModel # Runs all the specified validations and returns +true+ if no errors were # added otherwise +false+. # + # Aliased as validate. + # # class Person # include ActiveModel::Validations # @@ -317,6 +321,8 @@ module ActiveModel self.validation_context = current_context end + alias_method :validate, :valid? + # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were # added, +false+ otherwise. # diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb index 1a1863370b..9b5416fb1d 100644 --- a/activemodel/lib/active_model/validations/absence.rb +++ b/activemodel/lib/active_model/validations/absence.rb @@ -21,7 +21,7 @@ module ActiveModel # * <tt>:message</tt> - A custom error message (default is: "must be blank"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_absence_of(*attr_names) validates_with AbsenceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 139de16326..ac5e79859b 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -38,8 +38,6 @@ module ActiveModel # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "must be # accepted"). - # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default - # is +true+). # * <tt>:accept</tt> - Specifies value that is considered accepted. # The default value is a string "1", which makes it easy to relate to # an HTML checkbox. This should be set to +true+ if you are validating @@ -47,8 +45,8 @@ module ActiveModel # before validation. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. - # See <tt>ActiveModel::Validation#validates</tt> for more information + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information. def validates_acceptance_of(*attr_names) validates_with AcceptanceValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index b0542661af..a51523912f 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -57,7 +57,7 @@ module ActiveModel # confirmation"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_confirmation_of(*attr_names) validates_with ConfirmationValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index 48bf5cd802..f342d27275 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -34,13 +34,9 @@ module ActiveModel # <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. # * <tt>:message</tt> - Specifies a custom error message (default is: "is # reserved"). - # * <tt>:allow_nil</tt> - If set to true, skips this validation if the - # attribute is +nil+ (default is +false+). - # * <tt>:allow_blank</tt> - If set to true, skips this validation if the - # attribute is blank(default is +false+). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_exclusion_of(*attr_names) validates_with ExclusionValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index f0fe22438f..ff3e95da34 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -91,10 +91,6 @@ module ActiveModel # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "is invalid"). - # * <tt>:allow_nil</tt> - If set to true, skips this validation if the - # attribute is +nil+ (default is +false+). - # * <tt>:allow_blank</tt> - If set to true, skips this validation if the - # attribute is blank (default is +false+). # * <tt>:with</tt> - Regular expression that if the attribute matches will # result in a successful validation. This can be provided as a proc or # lambda returning regular expression which will be called at runtime. @@ -107,7 +103,7 @@ module ActiveModel # beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_format_of(*attr_names) validates_with FormatValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 24337614c5..c84025f083 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -29,17 +29,14 @@ module ActiveModel # * <tt>:in</tt> - An enumerable object of available items. This can be # supplied as a proc, lambda or symbol which returns an enumerable. If the # enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>, - # otherwise with <tt>include?</tt>. + # otherwise with <tt>include?</tt>. When using a proc or lambda the instance + # under validation is passed as an argument. # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt> # * <tt>:message</tt> - Specifies a custom error message (default is: "is # not included in the list"). - # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the - # attribute is +nil+ (default is +false+). - # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the - # attribute is blank (default is +false+). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_inclusion_of(*attr_names) validates_with InclusionValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index c8d3236463..a9fb9804d4 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -110,7 +110,7 @@ module ActiveModel # * <tt>:even</tt> - Specifies the value must be an even number. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+ . + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . # See <tt>ActiveModel::Validation#validates</tt> for more information # # The following checks can also be supplied with a proc or a symbol which diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index ab8c8359fc..5d593274eb 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -29,7 +29,7 @@ module ActiveModel # * <tt>:message</tt> - A custom error message (default is: "can't be blank"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index bf588b7bd0..ae8d377fdf 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -83,7 +83,9 @@ module ActiveModel # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a +true+ or # +false+ value. - # * <tt>:strict</tt> - if the <tt>:strict</tt> option is set to true + # * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+. + # * <tt>:allow_blank</tt> - Skip validation if the attribute is blank. + # * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true # will raise ActiveModel::StrictValidationFailed instead of adding the error. # <tt>:strict</tt> option can also be set to any other exception. # diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 16bd6670d1..7022f9bee5 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -139,6 +139,8 @@ module ActiveModel # class version of this method for more information. def validates_with(*args, &block) options = args.extract_options! + options[:class] = self.class + args.each do |klass| validator = klass.new(options, &block) validator.validate(self) diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index 58ba3ab9b2..b1f9082ea7 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,11 +1,8 @@ +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 ActiveModel as a <tt>Gem::Version</tt> def self.version - Gem::Version.new "4.1.0.beta1" - end - - module VERSION #:nodoc: - MAJOR, MINOR, TINY, PRE = ActiveModel.version.segments - STRING = ActiveModel.version.to_s + gem_version end end |