1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
module AttributeMethods
module Dirty # :nodoc:
extend ActiveSupport::Concern
include ActiveModel::Dirty
included do
if self < ::ActiveRecord::Timestamp
raise "You cannot include Dirty after Timestamp"
end
class_attribute :partial_writes, instance_writer: false
self.partial_writes = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
def save(*)
if status = super
changes_applied
end
status
end
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
def save!(*)
super.tap do
changes_applied
end
end
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
clear_changes_information
end
end
def initialize_dup(other) # :nodoc:
super
@original_raw_attributes = nil
calculate_changes_from_defaults
end
def changes_applied
super
store_original_raw_attributes
end
def clear_changes_information
super
original_raw_attributes.clear
end
def changed_attributes
# 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
super.reverse_merge(attributes_changed_in_place).freeze
end
end
def changes
cache_changed_attributes do
super
end
end
def attribute_changed_in_place?(attr_name)
old_value = original_raw_attribute(attr_name)
@attributes[attr_name].changed_in_place_from?(old_value)
end
private
def changes_include?(attr_name)
super || attribute_changed_in_place?(attr_name)
end
def calculate_changes_from_defaults
@changed_attributes = nil
self.class.column_defaults.each do |attr, orig_value|
set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
end
end
# Wrap write_attribute to remember original attribute value.
def write_attribute(attr, value)
attr = attr.to_s
old_value = old_attribute_value(attr)
result = super
store_original_raw_attribute(attr)
save_changed_attribute(attr, old_value)
result
end
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)
clear_changed_attributes_cache
if attribute_changed_by_setter?(attr)
clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
else
set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
end
end
def old_attribute_value(attr)
if attribute_changed?(attr)
changed_attributes[attr]
else
clone_attribute_value(:_read_attribute, attr)
end
end
def _update_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
def _create_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
# Serialized attributes should always be written in case they've been
# changed in place.
def keys_for_partial_write
changed & persistable_attribute_names
end
def _field_changed?(attr, old_value)
@attributes[attr].changed_from?(old_value)
end
def attributes_changed_in_place
changed_in_place.each_with_object({}) do |attr_name, h|
orig = @attributes[attr_name].original_value
h[attr_name] = orig
end
end
def changed_in_place
self.class.attribute_names.select do |attr_name|
attribute_changed_in_place?(attr_name)
end
end
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 store_original_raw_attribute(attr_name)
original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
end
def store_original_raw_attributes
attribute_names.each do |attr|
store_original_raw_attribute(attr)
end
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
end
end
end
|