aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model')
-rw-r--r--activemodel/lib/active_model/attribute.rb1
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb40
-rw-r--r--activemodel/lib/active_model/attribute_set.rb12
-rw-r--r--activemodel/lib/active_model/attributes.rb32
-rw-r--r--activemodel/lib/active_model/conversion.rb2
-rw-r--r--activemodel/lib/active_model/dirty.rb6
-rw-r--r--activemodel/lib/active_model/errors.rb17
-rw-r--r--activemodel/lib/active_model/naming.rb4
-rw-r--r--activemodel/lib/active_model/type/date_time.rb6
-rw-r--r--activemodel/lib/active_model/type/decimal.rb4
-rw-r--r--activemodel/lib/active_model/type/string.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb5
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb4
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb36
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/lib/active_model/validator.rb2
18 files changed, 116 insertions, 65 deletions
diff --git a/activemodel/lib/active_model/attribute.rb b/activemodel/lib/active_model/attribute.rb
index 3f19cda07b..75f60d205e 100644
--- a/activemodel/lib/active_model/attribute.rb
+++ b/activemodel/lib/active_model/attribute.rb
@@ -206,6 +206,7 @@ module ActiveModel
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
end
alias_method :with_value_from_user, :with_value_from_database
+ alias_method :with_cast_value, :with_value_from_database
end
class Uninitialized < Attribute # :nodoc:
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
index 217bf1ac01..f0e3458f51 100644
--- a/activemodel/lib/active_model/attribute_assignment.rb
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -27,7 +27,7 @@ module ActiveModel
# 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."
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
end
return if new_attributes.empty?
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 888a431e5f..5c4670f393 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -369,7 +369,7 @@ module ActiveModel
"define_method(:'#{name}') do |*args|"
end
- extra = (extra.map!(&:inspect) << "*args").join(", ".freeze)
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
target = if CALL_COMPILABLE_REGEXP.match?(send)
"#{"self." unless include_private}#{send}(#{extra})"
@@ -474,5 +474,43 @@ module ActiveModel
def _read_attribute(attr)
__send__(attr)
end
+
+ module AttrNames # :nodoc:
+ DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
+
+ # We want to generate the methods via module_eval rather than
+ # define_method, because define_method is slower on dispatch.
+ # Evaluating many similar methods may use more memory as the instruction
+ # sequences are duplicated and cached (in MRI). define_method may
+ # be slower on dispatch, but if you're careful about the closure
+ # created, then define_method will consume much less memory.
+ #
+ # But sometimes the database might return columns with
+ # characters that are not allowed in normal method names (like
+ # 'my_column(omg)'. So to work around this we first define with
+ # the __temp__ identifier, and then use alias method to rename
+ # it to what we want.
+ #
+ # We are also defining a constant to hold the frozen string of
+ # the attribute name. Using a constant means that we do not have
+ # to allocate an object on each call to the attribute method.
+ # Making it frozen means that it doesn't get duped when used to
+ # key the @attributes in read_attribute.
+ def self.define_attribute_accessor_method(mod, attr_name, writer: false)
+ method_name = "#{attr_name}#{'=' if writer}"
+ if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
+ yield method_name, "'#{attr_name}'.freeze"
+ else
+ safe_name = attr_name.unpack1("h*")
+ const_name = "ATTR_#{safe_name}"
+ const_set(const_name, attr_name) unless const_defined?(const_name)
+ temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
+ attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
+ yield temp_method_name, attr_name_expr
+ mod.alias_method method_name, temp_method_name
+ mod.undef_method temp_method_name
+ end
+ end
+ end
end
end
diff --git a/activemodel/lib/active_model/attribute_set.rb b/activemodel/lib/active_model/attribute_set.rb
index a890ee3932..4679b33852 100644
--- a/activemodel/lib/active_model/attribute_set.rb
+++ b/activemodel/lib/active_model/attribute_set.rb
@@ -37,16 +37,8 @@ module ActiveModel
attributes.each_key.select { |name| self[name].initialized? }
end
- if defined?(JRUBY_VERSION)
- # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
- # https://github.com/jruby/jruby/pull/2562
- def fetch_value(name, &block)
- self[name].value(&block)
- end
- else
- def fetch_value(name)
- self[name].value { |n| yield n if block_given? }
- end
+ def fetch_value(name, &block)
+ self[name].value(&block)
end
def write_from_database(name, value)
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
index 5bf213d593..c3a446098c 100644
--- a/activemodel/lib/active_model/attributes.rb
+++ b/activemodel/lib/active_model/attributes.rb
@@ -29,17 +29,16 @@ module ActiveModel
private
def define_method_attribute=(name)
- safe_name = name.unpack1("h*".freeze)
- ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
-
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}=(value)
- name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
- write_attribute(name, value)
- end
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
- undef_method :__temp__#{safe_name}=
- STR
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
+ generated_attribute_methods, name, writer: true,
+ ) do |temp_method_name, attr_name_expr|
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{temp_method_name}(value)
+ name = #{attr_name_expr}
+ write_attribute(name, value)
+ end
+ RUBY
+ end
end
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
@@ -97,15 +96,4 @@ module ActiveModel
write_attribute(attribute_name, value)
end
end
-
- module AttributeMethods #:nodoc:
- AttrNames = Module.new {
- def self.set_name_cache(name, value)
- const_name = "ATTR_#{name}"
- unless const_defined? const_name
- const_set const_name, -value
- end
- end
- }
- end
end
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index cdc1282817..82713ddc81 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -103,7 +103,7 @@ module ActiveModel
@_to_partial_path ||= begin
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
collection = ActiveSupport::Inflector.tableize(name)
- "#{collection}/#{element}".freeze
+ "#{collection}/#{element}"
end
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 093052a70c..0d9e761b1e 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -141,7 +141,9 @@ module ActiveModel
@mutations_from_database = nil
end
- def changes_applied # :nodoc:
+ # Clears dirty data and moves +changes+ to +previously_changed+ and
+ # +mutations_from_database+ to +mutations_before_last_save+ respectively.
+ def changes_applied
unless defined?(@attributes)
@previously_changed = changes
end
@@ -151,7 +153,7 @@ module ActiveModel
@mutations_from_database = nil
end
- # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
#
# person.changed? # => false
# person.name = 'bob'
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index af94d52d45..969effdc20 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -112,6 +112,17 @@ module ActiveModel
@details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }
end
+ # Removes all errors except the given keys. Returns a hash containing the removed errors.
+ #
+ # person.errors.keys # => [:name, :age, :gender, :city]
+ # person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
+ # person.errors.keys # => [:age, :gender]
+ def slice!(*keys)
+ keys = keys.map(&:to_sym)
+ @details.slice!(*keys)
+ @messages.slice!(*keys)
+ end
+
# Clear the error messages.
#
# person.errors.full_messages # => ["name cannot be nil"]
@@ -327,11 +338,11 @@ module ActiveModel
# person.errors.added? :name, :too_long # => false
# person.errors.added? :name, "is too long" # => false
def added?(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
+
if message.is_a? Symbol
- self.details[attribute.to_sym].map { |e| e[:error] }.include? message
+ details[attribute.to_sym].include? normalize_detail(message, options)
else
- message = message.call if message.respond_to?(:call)
- message = normalize_message(attribute, message, options)
self[attribute].include? message
end
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 983401801f..bf23fa3c05 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -209,7 +209,7 @@ module ActiveModel
private
def _singularize(string)
- ActiveSupport::Inflector.underscore(string).tr("/".freeze, "_".freeze)
+ ActiveSupport::Inflector.underscore(string).tr("/", "_")
end
end
@@ -252,7 +252,7 @@ module ActiveModel
# Person.model_name.plural # => "people"
def model_name
@_model_name ||= begin
- namespace = parents.detect do |n|
+ namespace = module_parents.detect do |n|
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
end
ActiveModel::Name.new(self, namespace)
diff --git a/activemodel/lib/active_model/type/date_time.rb b/activemodel/lib/active_model/type/date_time.rb
index 9641bf45ee..d48598376e 100644
--- a/activemodel/lib/active_model/type/date_time.rb
+++ b/activemodel/lib/active_model/type/date_time.rb
@@ -39,9 +39,9 @@ module ActiveModel
end
def value_from_multiparameter_assignment(values_hash)
- missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
- if missing_parameter
- raise ArgumentError, missing_parameter
+ missing_parameters = (1..3).select { |key| !values_hash.key?(key) }
+ if missing_parameters.any?
+ raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
end
super
end
diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb
index e8ee18c00e..b37dad1c41 100644
--- a/activemodel/lib/active_model/type/decimal.rb
+++ b/activemodel/lib/active_model/type/decimal.rb
@@ -12,6 +12,10 @@ module ActiveModel
:decimal
end
+ def serialize(value)
+ cast(value)
+ end
+
def type_cast_for_schema(value)
value.to_s.inspect
end
diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb
index 36f13945b1..a9c9bfadb6 100644
--- a/activemodel/lib/active_model/type/string.rb
+++ b/activemodel/lib/active_model/type/string.rb
@@ -16,8 +16,8 @@ module ActiveModel
def cast_value(value)
case value
when ::String then ::String.new(value)
- when true then "t".freeze
- when false then "f".freeze
+ when true then "t"
+ when false then "f"
else value.to_s
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 7f14d102dd..f18f9a601a 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/hash/keys"
-require "active_support/core_ext/hash/except"
module ActiveModel
# == Active \Model \Validations
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index ea3a6b52ab..6fd54270f2 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -54,8 +54,9 @@ module ActiveModel
def define_on(klass)
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
- klass.send(:attr_reader, *attr_readers)
- klass.send(:attr_writer, *attr_writers)
+ klass.define_attribute_methods
+ klass.attr_reader(*attr_readers)
+ klass.attr_writer(*attr_writers)
end
private
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 1b5d5b09ab..b549755ba4 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -19,11 +19,11 @@ module ActiveModel
private
def setup!(klass)
- klass.send(:attr_reader, *attributes.map do |attribute|
+ klass.attr_reader(*attributes.map do |attribute|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
end.compact)
- klass.send(:attr_writer, *attributes.map do |attribute|
+ klass.attr_writer(*attributes.map do |attribute|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
end.compact)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 126a87ac6e..c5997283ea 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -9,6 +9,9 @@ module ActiveModel
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
+ INTEGER_REGEX = /\A[+-]?\d+\z/
+ DECIMAL_REGEX = /\A[+-]?\d+\.?\d*(e|e[+-])?\d+\z/
+
def check_validity!
keys = CHECKS.keys - [:odd, :even]
options.slice(*keys).each do |option, value|
@@ -49,11 +52,7 @@ module ActiveModel
return
end
- if raw_value.is_a?(Numeric)
- value = raw_value
- else
- value = parse_raw_value_as_a_number(raw_value)
- end
+ value = parse_as_number(raw_value)
options.slice(*CHECKS.keys).each do |option, option_value|
case option
@@ -69,6 +68,8 @@ module ActiveModel
option_value = record.send(option_value)
end
+ option_value = parse_as_number(option_value)
+
unless value.send(CHECKS[option], option_value)
record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value))
end
@@ -79,18 +80,33 @@ module ActiveModel
private
def is_number?(raw_value)
- !parse_raw_value_as_a_number(raw_value).nil?
+ !parse_as_number(raw_value).nil?
rescue ArgumentError, TypeError
false
end
- def parse_raw_value_as_a_number(raw_value)
- return raw_value.to_i if is_integer?(raw_value)
- Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
+ def parse_as_number(raw_value)
+ if raw_value.is_a?(Float)
+ raw_value.to_d
+ elsif raw_value.is_a?(Numeric)
+ raw_value
+ elsif is_integer?(raw_value)
+ raw_value.to_i
+ elsif is_decimal?(raw_value) && !is_hexadecimal_literal?(raw_value)
+ BigDecimal(raw_value)
+ end
end
def is_integer?(raw_value)
- /\A[+-]?\d+\z/ === raw_value.to_s
+ INTEGER_REGEX.match?(raw_value.to_s)
+ end
+
+ def is_decimal?(raw_value)
+ DECIMAL_REGEX.match?(raw_value.to_s)
+ end
+
+ def is_hexadecimal_literal?(raw_value)
+ /\A0[xX]/.match?(raw_value)
end
def filtered_options(value)
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 88cca318ef..21c4ce0dfe 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -116,7 +116,7 @@ module ActiveModel
key = "#{key.to_s.camelize}Validator"
begin
- validator = key.include?("::".freeze) ? key.constantize : const_get(key)
+ validator = key.include?("::") ? key.constantize : const_get(key)
rescue NameError
raise ArgumentError, "Unknown validator: '#{key}'"
end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index e17c3ca7b3..94d53b8dd1 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -90,7 +90,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def initialize(options={})
# super
- # options[:class].send :attr_accessor, :custom_attribute
+ # options[:class].attr_accessor :custom_attribute
# end
# end
class Validator