Module: Aversion

Defined in:
lib/aversion.rb,
lib/aversion/version.rb

Overview

Public: Aversion makes your Ruby objects versionable. It also makes them immutable, so the only way to obtain transformed copies is to explicitly mutate state in #transform calls, which will return the modified copy, leaving the original intact.

Examples

class Person
  include Aversion

  def initialize(hunger)
    @hunger = hunger
  end

  def eat
    transform do
      @hunger -= 5
    end
  end
end

# Objects are immutable. Calls to mutate state will return new modified
# copies (thanks to #transform):
john       = Person.new
new_john   = john.eat
newer_john = new_john.eat

# You can roll back to a previous state:
new_john_again = newer_john.rollback

# Calculate deltas between objects, and replay the differences to get to the
# desired state:
difference = newer_john - john
newer_john_again = john.replay(difference)

Constant Summary collapse

VERSION =
"0.0.2"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Public: When we include Aversion, we override .new with an immutable constructor and provide a .new_mutable version.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/aversion.rb', line 41

def self.included(base)
  base.class_eval do
    # Public: Initializes an immutable instance.
    def self.new(*args)
      new_mutable(*args).freeze
    end

    # Public: Initializes a mutable instance.
    def self.new_mutable(*args)
      allocate.tap do |instance|
        instance.send :initialize, *args
        instance.instance_eval do
          self.history = []
          @__initial_args__ = args
        end
      end
    end
  end
end

Instance Method Details

#-(other) ⇒ Object

Public: Returns the difference between two versioned objects, which is an array of the transformations one lacks from the other.



117
118
119
120
121
# File 'lib/aversion.rb', line 117

def -(other)
  younger, older = [history, other.history].sort { |a,b| a.length <=> b.length }
  difference     = (older.length - younger.length) - 1
  older[difference..-1]
end

#==(other) ⇒ Object

Public: Returns whether two versionable objects are equal.

They will be equal as long as they have the same initial args with they were constructed with and their history is the same.



127
128
129
# File 'lib/aversion.rb', line 127

def ==(other)
  initial_args == other.initial_args && history == other.history
end

#historyObject

Internal: Returns the history of this object.



105
106
107
# File 'lib/aversion.rb', line 105

def history
  @__transformations__
end

#history=(transformations) ⇒ Object

Internal: Sets the history of this object to a specific array fo transformations.



111
112
113
# File 'lib/aversion.rb', line 111

def history=(transformations)
  @__transformations__ = transformations
end

#initial_argsObject

Public: Exposes the initial arguments passed to the constructor, for comparison purposes.



133
134
135
# File 'lib/aversion.rb', line 133

def initial_args
  @__initial_args__
end

#mutableObject

Public: Returns a mutable version of the object, in case anyone needs it. We do need it internally to perform transformations.



63
64
65
66
67
68
69
70
# File 'lib/aversion.rb', line 63

def mutable
  self.class.new_mutable(*initial_args).tap do |mutable|
    instance_variables.each do |ivar|
      mutable.instance_variable_set(ivar, instance_variable_get(ivar))
      mutable.history = history.dup
    end
  end
end

#replay(transformations) ⇒ Object

Public: Replays an array of transformations (procs).

transformations - the Array of Procs to apply.

Returns a new, immutable copy with those transformations applied.



95
96
97
98
99
100
101
102
# File 'lib/aversion.rb', line 95

def replay(transformations)
  (frozen? ? mutable : self).tap do |object|
    transformations.each do |transformation|
      object.history << transformation
      object.instance_eval(&transformation)
    end
  end.freeze
end

#rollbackObject

Public: Rolls back to a previous version of the state.

Returns a new, immutable copy with the previous state.



84
85
86
87
88
# File 'lib/aversion.rb', line 84

def rollback
  self.class.new_mutable(*initial_args).tap do |instance|
    instance.replay(history[0..-2])
  end.freeze
end

#transform(&block) ⇒ Object

Public: The only way to transform state.

Returns a new, immutable copy with the transformation applied.



75
76
77
78
79
# File 'lib/aversion.rb', line 75

def transform(&block)
  mutable.tap do |new_instance|
    new_instance.replay([block.dup])
  end.freeze
end