aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib')
-rw-r--r--activemodel/lib/active_model/attribute.rb7
-rw-r--r--activemodel/lib/active_model/attribute/user_provided_default.rb3
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb2
-rw-r--r--activemodel/lib/active_model/attribute_mutation_tracker.rb14
-rw-r--r--activemodel/lib/active_model/attribute_set/builder.rb4
-rw-r--r--activemodel/lib/active_model/attribute_set/yaml_encoder.rb3
-rw-r--r--activemodel/lib/active_model/attributes.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb108
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--activemodel/lib/active_model/type/binary.rb2
-rw-r--r--activemodel/lib/active_model/type/date.rb2
-rw-r--r--activemodel/lib/active_model/type/integer.rb7
-rw-r--r--activemodel/lib/active_model/type/registry.rb12
-rw-r--r--activemodel/lib/active_model/type/time.rb8
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb7
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb2
16 files changed, 96 insertions, 93 deletions
diff --git a/activemodel/lib/active_model/attribute.rb b/activemodel/lib/active_model/attribute.rb
index 27f3e38e31..3f19cda07b 100644
--- a/activemodel/lib/active_model/attribute.rb
+++ b/activemodel/lib/active_model/attribute.rb
@@ -133,10 +133,6 @@ module ActiveModel
end
protected
-
- attr_reader :original_attribute
- alias_method :assigned?, :original_attribute
-
def original_value_for_database
if assigned?
original_attribute.original_value_for_database
@@ -146,6 +142,9 @@ module ActiveModel
end
private
+ attr_reader :original_attribute
+ alias :assigned? :original_attribute
+
def initialize_dup(other)
if defined?(@value) && @value.duplicable?
@value = @value.dup
diff --git a/activemodel/lib/active_model/attribute/user_provided_default.rb b/activemodel/lib/active_model/attribute/user_provided_default.rb
index a5dc6188d2..9dc16e882d 100644
--- a/activemodel/lib/active_model/attribute/user_provided_default.rb
+++ b/activemodel/lib/active_model/attribute/user_provided_default.rb
@@ -44,8 +44,7 @@ module ActiveModel
end
end
- protected
-
+ private
attr_reader :user_provided_value
end
end
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
index aa931119ff..217bf1ac01 100644
--- a/activemodel/lib/active_model/attribute_assignment.rb
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -35,6 +35,8 @@ module ActiveModel
_assign_attributes(sanitize_for_mass_assignment(attributes))
end
+ alias attributes= assign_attributes
+
private
def _assign_attributes(attributes)
diff --git a/activemodel/lib/active_model/attribute_mutation_tracker.rb b/activemodel/lib/active_model/attribute_mutation_tracker.rb
index f55613ecd5..493be5bb88 100644
--- a/activemodel/lib/active_model/attribute_mutation_tracker.rb
+++ b/activemodel/lib/active_model/attribute_mutation_tracker.rb
@@ -35,10 +35,6 @@ module ActiveModel
end
end
- def changed_attribute_names
- attr_names.select { |attr| changed?(attr) }
- end
-
def any_changes?
attr_names.any? { |attr| changed?(attr) }
end
@@ -69,13 +65,8 @@ module ActiveModel
forced_changes << attr_name.to_s
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :attributes, :forced_changes
-
private
+ attr_reader :attributes, :forced_changes
def attr_names
attributes.keys
@@ -113,5 +104,8 @@ module ActiveModel
def original_value(*)
end
+
+ def force_change(*)
+ end
end
end
diff --git a/activemodel/lib/active_model/attribute_set/builder.rb b/activemodel/lib/active_model/attribute_set/builder.rb
index bf2d06b48a..2b1c2206ec 100644
--- a/activemodel/lib/active_model/attribute_set/builder.rb
+++ b/activemodel/lib/active_model/attribute_set/builder.rb
@@ -90,9 +90,6 @@ module ActiveModel
end
protected
-
- attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
-
def materialize
unless @materialized
values.each_key { |key| self[key] }
@@ -105,6 +102,7 @@ module ActiveModel
end
private
+ attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
def assign_default_value(name)
type = additional_types.fetch(name, types[name])
diff --git a/activemodel/lib/active_model/attribute_set/yaml_encoder.rb b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb
index 4ea945b956..ea1efc160e 100644
--- a/activemodel/lib/active_model/attribute_set/yaml_encoder.rb
+++ b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb
@@ -33,8 +33,7 @@ module ActiveModel
end
end
- protected
-
+ private
attr_reader :default_types
end
end
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
index 046ae67ad7..7d44f7f2a3 100644
--- a/activemodel/lib/active_model/attributes.rb
+++ b/activemodel/lib/active_model/attributes.rb
@@ -29,7 +29,7 @@ module ActiveModel
private
def define_method_attribute=(name)
- safe_name = name.unpack("h*".freeze).first
+ safe_name = name.unpack1("h*".freeze)
ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
@@ -66,6 +66,10 @@ module ActiveModel
super
end
+ def attributes
+ @attributes.to_hash
+ end
+
private
def write_attribute(attr_name, value)
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 0044fde6c5..eaf8dfb223 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -3,7 +3,6 @@
require "active_support/hash_with_indifferent_access"
require "active_support/core_ext/object/duplicable"
require "active_model/attribute_mutation_tracker"
-require "active_model/attribute_set"
module ActiveModel
# == Active \Model \Dirty
@@ -143,8 +142,11 @@ module ActiveModel
end
def changes_applied # :nodoc:
- _prepare_changes
+ unless defined?(@attributes)
+ @previously_changed = changes
+ end
@mutations_before_last_save = mutations_from_database
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
@@ -155,7 +157,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed? # => true
def changed?
- mutations_from_database.any_changes?
+ changed_attributes.present?
end
# Returns an array with the name of the attributes with unsaved changes.
@@ -164,24 +166,24 @@ module ActiveModel
# person.name = 'bob'
# person.changed # => ["name"]
def changed
- mutations_from_database.changed_attribute_names
+ changed_attributes.keys
end
# Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
- !!mutations_from_database.changed?(attr) &&
+ !!changes_include?(attr) &&
(to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
- (from == OPTION_NOT_GIVEN || from == attribute_was(attr))
+ (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
end
# Handles <tt>*_was</tt> for +method_missing+.
def attribute_was(attr) # :nodoc:
- mutations_from_database.original_value(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
end
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
def attribute_previously_changed?(attr) #:nodoc:
- mutations_before_last_save.changed?(attr)
+ previous_changes_include?(attr)
end
# Restore all previous data of the provided attributes.
@@ -191,12 +193,15 @@ module ActiveModel
# Clears all dirty data: current changes and previous changes.
def clear_changes_information
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
def clear_attribute_changes(attr_names)
+ attributes_changed_by_setter.except!(*attr_names)
attr_names.each do |attr_name|
clear_attribute_change(attr_name)
end
@@ -209,7 +214,13 @@ module ActiveModel
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def changed_attributes
- mutations_from_database.changed_values.freeze
+ # This should only be set by methods which will call changed_attributes
+ # multiple times when it is known that the computed value cannot change.
+ if defined?(@cached_changed_attributes)
+ @cached_changed_attributes
+ else
+ attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
+ end
end
# Returns a hash of changed attributes indicating their original
@@ -219,8 +230,9 @@ module ActiveModel
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
def changes
- _prepare_changes
- mutations_from_database.changes
+ cache_changed_attributes do
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
+ end
end
# Returns a hash of attributes that were changed before the model was saved.
@@ -230,7 +242,8 @@ module ActiveModel
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
- mutations_before_last_save.changes
+ @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
+ @previously_changed.merge(mutations_before_last_save.changes)
end
def attribute_changed_in_place?(attr_name) # :nodoc:
@@ -246,17 +259,11 @@ module ActiveModel
unless defined?(@mutations_from_database)
@mutations_from_database = nil
end
-
- unless defined?(@attributes)
- @_pseudo_attributes = true
- @attributes = AttributeSet.new(
- Hash.new { |h, attr|
- h[attr] = Attribute.with_cast_value(attr, _clone_attribute(attr), Type.default_value)
- }
- )
+ @mutations_from_database ||= if defined?(@attributes)
+ ActiveModel::AttributeMutationTracker.new(@attributes)
+ else
+ NullMutationTracker.instance
end
-
- @mutations_from_database ||= ActiveModel::AttributeMutationTracker.new(@attributes)
end
def forget_attribute_assignments
@@ -267,45 +274,68 @@ module ActiveModel
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
end
+ def cache_changed_attributes
+ @cached_changed_attributes = changed_attributes
+ yield
+ ensure
+ clear_changed_attributes_cache
+ end
+
+ def clear_changed_attributes_cache
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
+ end
+
+ # Returns +true+ if attr_name is changed, +false+ otherwise.
+ def changes_include?(attr_name)
+ attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
+ end
+ alias attribute_changed_by_setter? changes_include?
+
+ # Returns +true+ if attr_name were changed before the model was saved,
+ # +false+ otherwise.
+ def previous_changes_include?(attr_name)
+ previous_changes.include?(attr_name)
+ end
+
# Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
- [attribute_was(attr), _read_attribute(attr)] if attribute_changed?(attr)
+ [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
end
# Handles <tt>*_previous_change</tt> for +method_missing+.
def attribute_previous_change(attr)
- mutations_before_last_save.change_to_attribute(attr)
+ previous_changes[attr] if attribute_previously_changed?(attr)
end
# Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
- attr = attr.to_s
- mutations_from_database.force_change(attr).tap do
- @attributes[attr] if defined?(@_pseudo_attributes)
+ unless attribute_changed?(attr)
+ begin
+ value = _read_attribute(attr)
+ value = value.duplicable? ? value.clone : value
+ rescue TypeError, NoMethodError
+ end
+
+ set_attribute_was(attr, value)
end
+ mutations_from_database.force_change(attr)
end
# Handles <tt>restore_*!</tt> for +method_missing+.
def restore_attribute!(attr)
if attribute_changed?(attr)
- __send__("#{attr}=", attribute_was(attr))
+ __send__("#{attr}=", changed_attributes[attr])
clear_attribute_changes([attr])
end
end
- def _prepare_changes
- if defined?(@_pseudo_attributes)
- changed.each do |attr|
- @attributes.write_from_user(attr, _read_attribute(attr))
- end
- end
+ def attributes_changed_by_setter
+ @attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
end
- def _clone_attribute(attr)
- value = _read_attribute(attr)
- value.duplicable? ? value.clone : value
- rescue TypeError, NoMethodError
- value
+ # Force an attribute to have a particular "before" value
+ def set_attribute_was(attr, old_value)
+ attributes_changed_by_setter[attr] = old_value
end
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 47cb81bee5..c4b7b32291 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -179,7 +179,7 @@ module ActiveModel
return unless includes = options[:include]
unless includes.is_a?(Hash)
- includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
+ includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }]
end
includes.each do |association, opts|
diff --git a/activemodel/lib/active_model/type/binary.rb b/activemodel/lib/active_model/type/binary.rb
index dc2eca18be..76203c5a88 100644
--- a/activemodel/lib/active_model/type/binary.rb
+++ b/activemodel/lib/active_model/type/binary.rb
@@ -40,7 +40,7 @@ module ActiveModel
alias_method :to_str, :to_s
def hex
- @value.unpack("H*")[0]
+ @value.unpack1("H*")
end
def ==(other)
diff --git a/activemodel/lib/active_model/type/date.rb b/activemodel/lib/active_model/type/date.rb
index 8cecc16d0f..8ec5deedc4 100644
--- a/activemodel/lib/active_model/type/date.rb
+++ b/activemodel/lib/active_model/type/date.rb
@@ -42,7 +42,7 @@ module ActiveModel
end
def new_date(year, mon, mday)
- if year && year != 0
+ unless year.nil? || (year == 0 && mon == 0 && mday == 0)
::Date.new(year, mon, mday) rescue nil
end
end
diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb
index fe396998a3..da74aaa3c5 100644
--- a/activemodel/lib/active_model/type/integer.rb
+++ b/activemodel/lib/active_model/type/integer.rb
@@ -31,13 +31,8 @@ module ActiveModel
result
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :range
-
private
+ attr_reader :range
def cast_value(value)
case value
diff --git a/activemodel/lib/active_model/type/registry.rb b/activemodel/lib/active_model/type/registry.rb
index 7272d7b0c5..a19dc0f011 100644
--- a/activemodel/lib/active_model/type/registry.rb
+++ b/activemodel/lib/active_model/type/registry.rb
@@ -23,13 +23,8 @@ module ActiveModel
end
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :registrations
-
private
+ attr_reader :registrations
def registration_klass
Registration
@@ -59,10 +54,7 @@ module ActiveModel
type_name == name
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
+ private
attr_reader :name, :block
end
end
diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb
index ad7ba0351a..c094ee0013 100644
--- a/activemodel/lib/active_model/type/time.rb
+++ b/activemodel/lib/active_model/type/time.rb
@@ -28,14 +28,10 @@ module ActiveModel
private
def cast_value(value)
- return value unless value.is_a?(::String)
+ return apply_seconds_precision(value) unless value.is_a?(::String)
return if value.empty?
- if value.start_with?("2000-01-01")
- dummy_time_value = value
- else
- dummy_time_value = "2000-01-01 #{value}"
- end
+ dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
fast_string_to_time(dummy_time_value) || begin
time_hash = ::Date._parse(dummy_time_value)
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index f35e4dec7f..ea3a6b52ab 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -58,13 +58,8 @@ module ActiveModel
klass.send(:attr_writer, *attr_writers)
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :attributes
-
private
+ attr_reader :attributes
def convert_to_reader_name(method_name)
method_name.to_s.chomp("=")
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
index 0b9b5ce6a1..bafb8e2106 100644
--- a/activemodel/lib/active_model/validations/clusivity.rb
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -32,7 +32,7 @@ module ActiveModel
@delimiter ||= options[:in] || options[:within]
end
- # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
+ # After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
# possible values in the range for equality, which is slower but more accurate.
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
# endpoints, which is fast but is only accurate on Numeric, Time, Date,