X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=vendor%2Frails%2Factiverecord%2Flib%2Factive_record%2Fdirty.rb;fp=vendor%2Frails%2Factiverecord%2Flib%2Factive_record%2Fdirty.rb;h=ae573799ae02a6cb82fb7eb4f02c42245d59fdf5;hb=d115f2e23823271635bad69229a42cd8ac68debe;hp=0000000000000000000000000000000000000000;hpb=37cb670bf3ddde90b214e591f100ed4446469484;p=depot.git diff --git a/vendor/rails/activerecord/lib/active_record/dirty.rb b/vendor/rails/activerecord/lib/active_record/dirty.rb new file mode 100644 index 0000000..ae57379 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/dirty.rb @@ -0,0 +1,183 @@ +module ActiveRecord + # Track unsaved attribute changes. + # + # A newly instantiated object is unchanged: + # person = Person.find_by_name('uncle bob') + # person.changed? # => false + # + # Change the name: + # person.name = 'Bob' + # person.changed? # => true + # person.name_changed? # => true + # person.name_was # => 'uncle bob' + # person.name_change # => ['uncle bob', 'Bob'] + # person.name = 'Bill' + # person.name_change # => ['uncle bob', 'Bill'] + # + # Save the changes: + # person.save + # person.changed? # => false + # person.name_changed? # => false + # + # Assigning the same value leaves the attribute unchanged: + # person.name = 'Bill' + # person.name_changed? # => false + # person.name_change # => nil + # + # Which attributes have changed? + # person.name = 'bob' + # person.changed # => ['name'] + # person.changes # => { 'name' => ['Bill', 'bob'] } + # + # Before modifying an attribute in-place: + # person.name_will_change! + # person.name << 'by' + # person.name_change # => ['uncle bob', 'uncle bobby'] + module Dirty + DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was'] + + def self.included(base) + base.attribute_method_suffix *DIRTY_SUFFIXES + base.alias_method_chain :write_attribute, :dirty + base.alias_method_chain :save, :dirty + base.alias_method_chain :save!, :dirty + base.alias_method_chain :update, :dirty + base.alias_method_chain :reload, :dirty + + base.superclass_delegating_accessor :partial_updates + base.partial_updates = true + + base.send(:extend, ClassMethods) + end + + # Do any attributes have unsaved changes? + # person.changed? # => false + # person.name = 'bob' + # person.changed? # => true + def changed? + !changed_attributes.empty? + end + + # List of attributes with unsaved changes. + # person.changed # => [] + # person.name = 'bob' + # person.changed # => ['name'] + def changed + changed_attributes.keys + end + + # Map of changed attrs => [original value, new value]. + # person.changes # => {} + # person.name = 'bob' + # person.changes # => { 'name' => ['bill', 'bob'] } + def changes + changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h } + end + + # Attempts to +save+ the record and clears changed attributes if successful. + def save_with_dirty(*args) #:nodoc: + if status = save_without_dirty(*args) + changed_attributes.clear + end + status + end + + # Attempts to save! the record and clears changed attributes if successful. + def save_with_dirty!(*args) #:nodoc: + status = save_without_dirty!(*args) + changed_attributes.clear + status + end + + # reload the record and clears changed attributes. + def reload_with_dirty(*args) #:nodoc: + record = reload_without_dirty(*args) + changed_attributes.clear + record + end + + private + # Map of change attr => original value. + def changed_attributes + @changed_attributes ||= {} + end + + # Handle *_changed? for +method_missing+. + def attribute_changed?(attr) + changed_attributes.include?(attr) + end + + # Handle *_change for +method_missing+. + def attribute_change(attr) + [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) + end + + # Handle *_was for +method_missing+. + def attribute_was(attr) + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + + # Handle *_will_change! for +method_missing+. + def attribute_will_change!(attr) + changed_attributes[attr] = clone_attribute_value(:read_attribute, attr) + end + + # Wrap write_attribute to remember original attribute value. + def write_attribute_with_dirty(attr, value) + attr = attr.to_s + + # The attribute already has an unsaved change. + if changed_attributes.include?(attr) + old = changed_attributes[attr] + changed_attributes.delete(attr) unless field_changed?(attr, old, value) + else + old = clone_attribute_value(:read_attribute, attr) + changed_attributes[attr] = old if field_changed?(attr, old, value) + end + + # Carry on. + write_attribute_without_dirty(attr, value) + end + + def update_with_dirty + if partial_updates? + # Serialized attributes should always be written in case they've been + # changed in place. + update_without_dirty(changed | self.class.serialized_attributes.keys) + else + update_without_dirty + end + end + + def field_changed?(attr, old, value) + if column = column_for_attribute(attr) + if column.type == :integer && column.null && (old.nil? || old == 0) + # For nullable integer 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) + value = nil if value.blank? + else + value = column.type_cast(value) + end + end + + old != value + end + + module ClassMethods + def self.extended(base) + base.metaclass.alias_method_chain(:alias_attribute, :dirty) + end + + def alias_attribute_with_dirty(new_name, old_name) + alias_attribute_without_dirty(new_name, old_name) + DIRTY_SUFFIXES.each do |suffix| + module_eval <<-STR, __FILE__, __LINE__+1 + def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end + STR + end + end + end + end +end