Dirty

Attribute and object level dirty checking with rollback support

This is an area of functionality that you might not ever need. If you do need it, you will be endlessly thankful that it is here.

You can't go back and change the beginning, but you can start where you are and change the ending.

C.S. Lewis

All Futures provides an obsessively complete API for inspecting, manipulating and reversing changes to your model's attributes.

If history is written by the winner, there are three high-level concepts that you need to 💡 so that you can win:

  1. 3-stage timeline: previously, was and [future] changes

  2. Changes are tracked, but you can mess with the tapes

  3. Single-attribute and record-level methods are available

Timeline

Imagine that you create a new instance of your Example model:

class Example < AllFutures::Base
  attribute :name, :string
  attribute :age, :integer, default: 21
end

example = Example.new
puts example.name # => nil
puts example.age  # => 21

Now, we're going to change the name to "Steve", save it, and then change the name to "Fred".

puts example.name        # Previously nil
example.name = "Steve"
example.save
puts example.name        # Was Steve
example.name = "Fred"
puts example.name        # Changed to Fred

All Futures is able to keep track of the previous value before an attribute was saved, the value it became when it was saved, and the value it changed to after it was saved. Only the most recent current value is stored, and if nothing changes, it's possible two or all three [of the previous/was/changed] values could be the same.

Tracking

Internally, the changes_applied method is called when the current state of the attributes is saved to Redis. This is done for you with the standard CRUD methods like save and update.

You will find methods for interrogating the values for each attribute at every stage, as well as tools for rolling back to previous versions. There are also methods which direct All Futures to forget that changes happened at all.

Forest or Trees

It might seem like there's a lot of methods in this module, but for maximum developer silkiness there are often several methods per concept. Take the fictional method pound:

  • pound :name

  • pound! :name

  • pound_all

  • pound_all!

  • pound_name

  • pound_name!

As you see, we have generic methods, collection methods and dynamic methods. The convention is that if it ends with a !, it gets saved to Redis.

Instance methods

Generally, you use the generic version eg. attribute_will_change?(:name) when you are going to iterate over your attributes Hash, while you use the specific version eg. name_will_change? when you are writing custom business rules.

attribute_change(attribute), attribute_changed?(attribute)

If attribute has changed since the last save, returns an Array where the first element is the saved value and the second element is the value it will change to if saved. Otherwise, returns nil.

? form interrogates dirty tracking for attribute and returns true or false.

attribute_present?(attribute)

Returns true if the specified attribute exists and has been set by the user and is neither nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings). Otherwise, it returns false. Note that it always returns true with Boolean attributes.

attribute_previous_change(attribute), attribute_previously_changed?(attribute)

Returns an Array where the first element is the value of the attribute before it was saved, and the second element is the value after it was saved.

? form returns true or false depending on whether attribute was changed to a new value at the time of the last save operation.

attribute_previously_was(attribute)

Returns the value of attribute before the last save operation.

attribute_was(attribute)

Returns the value of attribute after the last save operation, before it was changed.

attribute_will_change!(attribute), attribute_will_change?(attribute)

Forces dirty tracking to report that the value of attribute has changed, even if it has not. ? form is an alias for attribute_changed?

changed

Returns an Array of Strings, showing all attributes that have changed after the most recent save operation.

changed_attributes, changed_attributes?

Returns a Hash of all changes after the most recent save operation, where the key is the attribute name as a String, and the value is the former value, which the attribute was changed from.

For example, if you change the search attribute from nil to "foo" and call changed_attributes, it will return {"search"=>nil}

changed_attributes? is an alias for dirty?

changes

Returns a Hash of all changes after the most recent save operation, where the attribute is the String key and the value is is an Array containing the value before and the value after it was saved.

For example, if you change the search attribute from nil to "foo" and call changes, it will return {"search"=>[nil, "foo"]}

changes_applied

Clears dirty data and moves changes to previous changes.

clear_attribute_change(attribute), clear_attribute_changes(Array)

Remove dirty tracking data for an attribute or an Array of attributes, without modifying the value itself. This has the effect of fooling All Futures into forgetting that data was changed.

Singluar form returns nil while plural form returns an Array of attributes that had their dirty tracking data removed.

clear_changes_information

Clears all dirty data: current changes and previous changes.

dirty?

Reports true or false depending on whether there any attributes with data that have changed since the last save operation.

previous_attributes

Returns a Hash of the attributes on your All Futures model instance, with the values reflecting their state before the last save operation.

previous_changes, previous_changes?

Returns a Hash of all changes that were persisted with the most recent save operation, where the key is the name of the attribute in String form and the value is is an Array containing the value before and the value after it was saved.

For example, if you change the search attribute from nil to "foo", call save and then previous_changes, it will return {"search"=>[nil, "foo"]}

previous_changes? is an alias for saved_changes?

restore_attribute(attribute)

Change an attribute to the value it was after the last save operation. It will return an Array of Symbols for the attributes that were restored.

restore_attributes(Array = changed)

Change attributes to the value that they were after the last save operation. You can either specify an Array of attributes to restore or it will default to all of the attributes with values that have changed since the last save operation. It will return an Array of Symbols for the attributes that were restored.

rollback_attribute(attribute), rollback_attribute!(attribute)

Change an attribute to the value it was before the last save operation. It will return the restored value that the attribute was returned to.

If you use the ! version, it will save after rolling back the value, and return true or false.

rollback_attributes(Array = changed), rollback_attributes!(Array = changed)

Change attributes to the value that they were before the last save operation. You can either specify an Array of attributes to rollback, or it will default to all of the attributes with values that have changed since the last save operation. It will return an Array of Symbols for the attributes that were rolled back.

If you use the ! version, it will save after rolling back the values, and return true or false.

saved_changes, saved_changes?

Returns a Hash of all changes after the most recent save operation, where the key is the attribute name as a String, and the value is the new value, which the attribute was changed to.

For example, if you change the search attribute from nil to "foo" and call saved_changes, it will return {"search"=>"foo"}

saved_changes? returns true or false depending on whether any attribute value changes have been persisted with a save call.

Per-attribute instance methods

When you see an attribute that contains the upper-case word ATTR, this means that there is a separate version of the method available for every attribute defined on your model.

For example, if you have a search attribute, you will have methods such as search_will_change? and rollback_search! available.

ATTR?

Returns true or false depending on standard Ruby evaluation of attribute value. This is most useful for Boolean type attributes.

ATTR_change, ATTR_changed?

Attribute method version of attribute_change and attribute_changed?

ATTR_previous_change, ATTR_previously_changed?

Attribute method versions of attribute_previous_change and attribute_previously_changed?

ATTR_previously_was

Attribute method version of attribute_previously_was

ATTR_was

Attribute method version of attribute_was

ATTR_will_change!, ATTR_will_change?

Attribute method version of attribute_will_change! and attribute_will_change?

clear_ATTR_change

Attribute method version of clear_attribute_change

restore_ATTR

Attribute method version of restore_attribute

rollback_ATTR, rollback_ATTR!

Attribute method version of rollback_attribute and rollback_attribute!

Last updated