module AWS::Record::DirtyTracking

Provides a way to track changes in your records.

my_book = Book['bookid']

my_book.changed? #=> false
my_book.title #=> "My Book"
my_book.title = "My Awesome Book"
my_book.changed? #=> true

my_book = Book['bookid']

You can inspect further and get a list of changed attributes

my_book.changed #=> ['title']

Or you can get a more detailed description of the changes. {#changes} returns a hash of changed attributes (keys) with their old and new values.

my_book.changes
#=> { 'title' => ['My Book', 'My Awesome Book']

For every configured attribute you also get a handful of methods for inspecting changes on that attribute. Given the following attribute:

string_attr :title

You can now call any of the following methods:

* title_changed?
* title_change
* title_was
* reset_title!
* title_will_change!

Given the title change from above:

my_book.title_changed? #=> true
my_book.title_change #=> ['My Book', 'My Awesome Book']
my_book.title_was #=> ['My Book']

my_book.reset_title!
my_book.title #=> 'My Book'

## In-Place Editing

Dirty tracking works by comparing incoming attribute values upon assignment against the value that was there previously. If you use functions against the value that modify it (like gsub!) you must notify your record about the coming change.

my_book.title #=> 'My Book'
my_book.title_will_change!
my_book.title.gsub!(/My/, 'Your')
my_book.title_change #=> ['My Book', 'Your Book']

## Partial Updates

Dirty tracking makes it possible to only persist those attributes that have changed since they were loaded. This speeds up requests against AWS when saving data.

Public Instance Methods

changed() click to toggle source

Returns an array of attribute names that have changes.

book.changed #=> []
person.title = 'New Title'
book.changed #=> ['title']

@return [Array] Returns an array of attribute names that have

unsaved changes.
# File lib/aws/record/dirty_tracking.rb, line 110
def changed
  orig_values.keys
end
changed?() click to toggle source

Returns true if this model has unsaved changes.

b = Book.new(:title => 'My Book')
b.changed?
#=> true

New objects and objects freshly loaded should not have any changes:

b = Book.new
b.changed?      #=> false

b = Book.first
b.changed?      #=> false

@return [Boolean] Returns true if any of the attributes have

unsaved changes.
# File lib/aws/record/dirty_tracking.rb, line 98
def changed?
  !orig_values.empty?
end
changes() click to toggle source

Returns the changed attributes in a hash. Keys are attribute names, values are two value arrays. The first value is the previous attribute value, the second is the current attribute value.

book.title = 'New Title'
book.changes
#=> { 'title' => ['Old Title', 'New Title'] }

@return [Hash] Returns a hash of attribute changes.

# File lib/aws/record/dirty_tracking.rb, line 123
def changes
  changed.inject({}) do |changes, attr_name|
    changes[attr_name] = attribute_change(attr_name)
    changes
  end
end

Private Instance Methods

attribute_change(attribute_name) click to toggle source

Returns an array of the old value and the new value for attributes that have unsaved changes, returns nil otherwise.

This is an attribute method. The following two expressions are equivilent:

book.title_change
book.attribute_change(:title)

@example Asking for changes on an unchanged attribute

book = Book.new
book.title_change #=> nil

@example Getting changed attributes on a new object

book = Book.new(:title => 'My Book')
book.title_change #=> [nil, 'My Book']

@example Getting changed attributes on a loaded object

book = Book.first
book.title = 'New Title'
book.title_change #=> ['Old Title', 'New Title']

@param [String] attribute_name Name of the attribute to fetch

a change for.

@return [Boolean] Returns true if the named attribute

has unsaved changes.

@api private

# File lib/aws/record/dirty_tracking.rb, line 180
def attribute_change attribute_name
  self.class.attribute_for(attribute_name) do |attribute|
    if orig_values.has_key?(attribute.name)
      [orig_values[attribute.name], __send__(attribute.name)]
    else
      nil
    end
  end
end
attribute_changed?(attribute_name) click to toggle source

Returns true if the named attribute has unsaved changes.

This is an attribute method. The following two expressions are equivilent:

book.title_changed?
book.attribute_changed?(:title)

@param [String] attribute_name Name of the attribute to check

for changes.

@return [Boolean] Returns true if the named attribute

has unsaved changes.

@api private

# File lib/aws/record/dirty_tracking.rb, line 145
def attribute_changed? attribute_name
  orig_values.keys.include?(attribute_name)
end
attribute_was(attribute_name) click to toggle source

Returns the previous value for changed attributes, or the current value for unchanged attributes.

This is an attribute method. The following two expressions are equivilent:

book.title_was
book.attribute_was(:title)

@example Returns the previous value for changed attributes:

book = Book.where(:title => 'My Book').first
book.title = 'New Title'
book.title_was #=> 'My Book'

@example Returns the current value for unchanged attributes:

book = Book.where(:title => 'My Book').first
book.title_was #=> 'My Book'

@return Returns the previous value for changed attributes

or the current value for unchanged attributes.

@api private

# File lib/aws/record/dirty_tracking.rb, line 214
def attribute_was attribute_name
  self.class.attribute_for(attribute_name) do |attribute|
    name = attribute.name
    orig_values.has_key?(name) ? orig_values[name] : __send__(name)
  end
end
attribute_will_change!(attribute_name) click to toggle source

Indicate to the record that you are about to edit an attribute in place. @param [String] attribute_name Name of the attribute that will

be changed.

@return [nil] @api private

# File lib/aws/record/dirty_tracking.rb, line 238
def attribute_will_change! attribute_name
  self.class.attribute_for(attribute_name) do |attribute|
    name = attribute.name
    unless orig_values.has_key?(name)
      was = __send__(name)
      begin
        # booleans, nil, etc all #respond_to?(:clone), but they raise
        # a TypeError when you attempt to dup them.
        orig_values[name] = was.clone
      rescue TypeError
        orig_values[name] = was
      end
    end
  end
  nil
end
clear_change!(attribute_name) click to toggle source
# File lib/aws/record/dirty_tracking.rb, line 261
def clear_change! attribute_name
  orig_values.delete(attribute_name)
end
clear_changes!() click to toggle source
# File lib/aws/record/dirty_tracking.rb, line 281
def clear_changes!
  orig_values.clear
end
if_tracking_changes() { || ... } click to toggle source
# File lib/aws/record/dirty_tracking.rb, line 276
def if_tracking_changes &block
  yield unless @_ignore_changes
end
ignore_changes() { || ... } click to toggle source
# File lib/aws/record/dirty_tracking.rb, line 266
def ignore_changes &block
  begin
    @_ignore_changes = true
    yield
  ensure
    @_ignore_changes = false
  end
end
orig_values() click to toggle source
# File lib/aws/record/dirty_tracking.rb, line 256
def orig_values
  @_orig_values ||= {}
end
reset_attribute!(attribute_name) click to toggle source

Reverts any changes to the attribute, restoring its original value. @param [String] attribute_name Name of the attribute to reset. @return [nil] @api private

# File lib/aws/record/dirty_tracking.rb, line 226
def reset_attribute! attribute_name
  __send__("#{attribute_name}=", attribute_was(attribute_name))
  nil
end