diff options
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods/dirty.rb')
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 122 |
1 files changed, 88 insertions, 34 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 99070f127b..6a5c057384 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -38,12 +38,39 @@ module ActiveRecord end end - def initialize_dup(other) # :nodoc: - super - init_changed_attributes - end + def initialize_dup(other) # :nodoc: + super + init_changed_attributes + end + + def changed? + super || changed_in_place.any? + end + + def changed + super | changed_in_place + end + + def attribute_changed?(attr_name, options = {}) + result = super + # We can't change "from" something in place. Only setters can define + # "from" and "to" + result ||= changed_in_place?(attr_name) unless options.key?(:from) + result + end + + def changes_applied + super + store_original_raw_attributes + end + + def reset_changes + super + original_raw_attributes.clear + end + + private - private def initialize_internals_callback super init_changed_attributes @@ -55,7 +82,7 @@ module ActiveRecord # optimistic locking) won't get written unless they get marked as changed self.class.columns.each do |c| attr, orig_value = c.name, c.default - changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr]) + changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value) end end @@ -63,19 +90,35 @@ module ActiveRecord def write_attribute(attr, value) attr = attr.to_s - save_changed_attribute(attr, value) + old_value = old_attribute_value(attr) - super(attr, value) + result = super + store_original_raw_attribute(attr) + save_changed_attribute(attr, old_value) + result end - def save_changed_attribute(attr, value) - # The attribute already has an unsaved change. + def raw_write_attribute(attr, value) + attr = attr.to_s + + result = super + original_raw_attributes[attr] = value + result + end + + def save_changed_attribute(attr, old_value) + if attribute_changed?(attr) + changed_attributes.delete(attr) unless _field_changed?(attr, old_value) + else + changed_attributes[attr] = old_value if _field_changed?(attr, old_value) + end + end + + def old_attribute_value(attr) if attribute_changed?(attr) - old = changed_attributes[attr] - changed_attributes.delete(attr) unless _field_changed?(attr, old, value) + changed_attributes[attr] else - old = clone_attribute_value(:read_attribute, attr) - changed_attributes[attr] = old if _field_changed?(attr, old, value) + clone_attribute_value(:read_attribute, attr) end end @@ -93,34 +136,45 @@ module ActiveRecord changed end - def _field_changed?(attr, old, value) - if column = column_for_attribute(attr) - if column.number? && (changes_from_nil_to_empty_string?(column, old, value) || - changes_from_zero_to_string?(old, value)) - value = nil - else - value = column.type_cast(value) - end + def _field_changed?(attr, old_value) + new_value = read_attribute(attr) + raw_value = read_attribute_before_type_cast(attr) + column_for_attribute(attr).changed?(old_value, new_value, raw_value) + end + + def changed_in_place + self.class.attribute_names.select do |attr_name| + changed_in_place?(attr_name) end + end - old != value + def changed_in_place?(attr_name) + type = type_for_attribute(attr_name) + old_value = original_raw_attribute(attr_name) + value = read_attribute(attr_name) + type.changed_in_place?(old_value, value) end - def changes_from_nil_to_empty_string?(column, old, value) - # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. - # Hence we don't record it as a change if the value changes from nil to ''. - # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll - # be typecast back to 0 (''.to_i => 0) - column.null && (old.nil? || old == 0) && value.blank? + def original_raw_attribute(attr_name) + original_raw_attributes.fetch(attr_name) do + read_attribute_before_type_cast(attr_name) + end + end + + def original_raw_attributes + @original_raw_attributes ||= {} end - def changes_from_zero_to_string?(old, value) - # For columns with old 0 and value non-empty string - old == 0 && value.is_a?(String) && value.present? && non_zero?(value) + def store_original_raw_attribute(attr_name) + type = type_for_attribute(attr_name) + value = type.type_cast_for_database(read_attribute(attr_name)) + original_raw_attributes[attr_name] = value end - def non_zero?(value) - value !~ /\A0+(\.0+)?\z/ + def store_original_raw_attributes + attribute_names.each do |attr| + store_original_raw_attribute(attr) + end end end end |