Module: DraftApprove::DraftChangesProxy

Included in:
Serialization::Json::DraftChangesProxy
Defined in:
lib/draft_approve/draft_changes_proxy.rb

Overview

Mixin wrapper for Draft and acts_as_draftable objects, such that both have a consistent API to get current and new values within the context of a specific DraftTransaction.

References to other objects returned by methods from this class are also wrapped in a DraftChangesProxy, meaning it is relatively easy to chain and navigate complex association trees within the context of a DraftTransaction.

This can be useful, for example, to display all changes that will occur on an object, including changes to all it’s associated ‘child’ objects.

It is often most convenient to use the DraftTransaction#draft_proxy_for method to construct a DraftApproveProxy instance. This will ensure the correct implementation of DraftApproveProxy is used.

Classes which include this module must implement the instance methods new_value, association_changed?, associations_added, associations_updated, associations_removed.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#draftObject (readonly)

Returns the value of attribute draft.



26
27
28
# File 'lib/draft_approve/draft_changes_proxy.rb', line 26

def draft
  @draft
end

#draft_transactionObject (readonly)

Returns the value of attribute draft_transaction.



26
27
28
# File 'lib/draft_approve/draft_changes_proxy.rb', line 26

def draft_transaction
  @draft_transaction
end

#draftableObject (readonly)

Returns the value of attribute draftable.



26
27
28
# File 'lib/draft_approve/draft_changes_proxy.rb', line 26

def draftable
  @draftable
end

#draftable_classObject (readonly)

Returns the value of attribute draftable_class.



26
27
28
# File 'lib/draft_approve/draft_changes_proxy.rb', line 26

def draftable_class
  @draftable_class
end

Instance Method Details

#association_changed?(association_name) ⇒ Boolean

Whether any changes will occur to the given association of the proxied Draft or draftable object.

Parameters:

  • association_name (String)

Returns:

  • (Boolean)

    true if any objects will be added to this association, removed from this association, or existing associations changed in any way. false otherwise.



190
191
192
# File 'lib/draft_approve/draft_changes_proxy.rb', line 190

def association_changed?(association_name)
  raise "#association_changed? has not been implemented in #{self.class.name}"
end

#associations_added(association_name) ⇒ Array<DraftChangesProxy>

All associated objects which will be added to the given association of the proxied Draft or draftable object.

Parameters:

  • association_name (String)

Returns:

  • (Array<DraftChangesProxy>)

    DraftChangesProxy objects for each object which will be added to the given association



201
202
203
# File 'lib/draft_approve/draft_changes_proxy.rb', line 201

def associations_added(association_name)
  raise "#associations_added has not been implemented in #{self.class.name}"
end

#associations_removed(association_name) ⇒ Array<DraftChangesProxy>

All associated objects which will be removed from the given association of the proxied Draft or draftable object.

Parameters:

  • association_name (String)

Returns:

  • (Array<DraftChangesProxy>)

    DraftChangesProxy objects for each object which will be removed from the given association



223
224
225
# File 'lib/draft_approve/draft_changes_proxy.rb', line 223

def associations_removed(association_name)
  raise "#associations_removed has not been implemented in #{self.class.name}"
end

#associations_updated(association_name) ⇒ Array<DraftChangesProxy>

All associated objects which have been updated, but remain the proxied Draft or draftable object.

Parameters:

  • association_name (String)

Returns:

  • (Array<DraftChangesProxy>)

    DraftChangesProxy objects for each object which will be added to the given association



212
213
214
# File 'lib/draft_approve/draft_changes_proxy.rb', line 212

def associations_updated(association_name)
  raise "#associations_updated has not been implemented in #{self.class.name}"
end

#changedArray<String>

List of attributes on the proxied Draft or draftable object which have changes.

Note, this method only considers changes to attributes and changes to any belongs_to references. Any added / changed / deleted has_many or has_one associations are not considered.

Returns:

  • (Array<String>)

    array of the attributes which have changed on the proxied object



108
109
110
111
112
113
114
# File 'lib/draft_approve/draft_changes_proxy.rb', line 108

def changed
  if @draft.blank?
    [] # No draft for this object, so no attributes have changed
  else
    @draft.draft_changes.keys
  end
end

#changed?Boolean

Whether or not the proxied Draft or draftable object has any changes.

Note, this method only considers changes to attributes and changes to any belongs_to references. Any added / changed / deleted has_many or has_one associations are not considered.

Returns:

  • (Boolean)

    whether or not the proxied object has changes



91
92
93
94
95
96
97
# File 'lib/draft_approve/draft_changes_proxy.rb', line 91

def changed?
  if @draft.blank?
    false # No draft for this object, so nothing changed
  else
    @draft.draft_changes.present?
  end
end

#changesHash<String, Array>

Hash of changes on the proxied Draft or draftable object which have changes.

Note, this method only considers changes to attributes and changes to any belongs_to references. Any added / changed / deleted has_many or has_one associations are not considered.

Returns:

  • (Hash<String, Array>)

    hash of the changes on the proxied object, eg. { "name" => ["old_name", "new_name"] }



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/draft_approve/draft_changes_proxy.rb', line 125

def changes
  @changes_memo ||= begin  # Memoize result
    if @draft.blank?
      {} # No draft for this object, so no attributes have changed
    else
      @draft.draft_changes.each_with_object({}) do |(k,v), new_hash|
        new_hash[k] = [current_value(k), new_value(k)]
      end
    end
  end
end

#create?Boolean

Returns true if this Draft is to create a new record, false otherwise.

Returns:

  • (Boolean)

    true if this Draft is to create a new record, false otherwise



73
74
75
# File 'lib/draft_approve/draft_changes_proxy.rb', line 73

def create?
  @draft.present? && @draft.create?
end

#current_to_s(include_class_and_id: false) ⇒ String

Returns a string representing the current value of the proxied object.

Examples:

# When draft_changes_proxy is for a new Person
draft_changes_proxy.current_to_s
#=> "New Person"
# When draft_changes_proxy is for an existing Person
draft_changes_proxy.current_to_s
#=> "Joe Blogs"
# When draft_changes_proxy is for an existing Person
draft_changes_proxy.current_to_s(include_class_and_id: true)
#=> "Joe Blogs <Person #1>"

Parameters:

  • include_class_and_id (Boolean) (defaults to: false)

    if true and the proxied object already exists (ie. this isn’t a proxy for a new draft), then append “<[classname] #[id]>” to the end of the returned string

Returns:

  • (String)

    the to_s of the current value of the proxied object (ie. the value before any changes would take effect). If there is no current value (ie. this is a proxy for a new draft) then simply returns “New [classname]”.



252
253
254
255
256
257
258
259
260
261
# File 'lib/draft_approve/draft_changes_proxy.rb', line 252

def current_to_s(include_class_and_id: false)
  if @draftable.present? && include_class_and_id
    return "#{@draftable.to_s} <#{@draftable_class} ##{@draftable.id}>"
  elsif @draftable.present?
    return @draftable.to_s
  else
    # No current draftable
    return "New #{@draftable_class}"
  end
end

#current_value(attribute_name) ⇒ Object?

The currently persisted value for the given attribute on the proxied Draft or draftable object.

Parameters:

  • attribute_name (String)

Returns:

  • (Object, nil)

    the old value of the given attribute, or nil if there was no previous value



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/draft_approve/draft_changes_proxy.rb', line 144

def current_value(attribute_name)
  # Create hash with default block for auto-memoization
  @current_values_memo ||= Hash.new do |hash, attribute|
    hash[attribute] = begin
      if @draftable.present?
        # Current value is what's on the draftable object
        draft_proxy_for(@draftable.public_send(attribute))
      else
        # No draftable exists, so this must be a CREATE draft, meaning
        # there's no 'old' value...
        association = @draftable_class.reflect_on_association(attribute)
        if (association.blank? || association.belongs_to? || association.has_one?)
          nil # Not an association, or links to a single object
        else
          []  # Is a has_many association
        end
      end
    end
  end

  # Get memoized value, or calculate and store it
  @current_values_memo[attribute_name.to_s]
end

#delete?Boolean

Returns true if this Draft is to delete an existing record, false otherwise.

Returns:

  • (Boolean)

    true if this Draft is to delete an existing record, false otherwise



79
80
81
# File 'lib/draft_approve/draft_changes_proxy.rb', line 79

def delete?
  @draft.present? && @draft.delete?
end

#initialize(object, transaction = nil) ⇒ Object

Creates a new DraftChangesProxy

Parameters:

  • object (Object)

    the Draft object, or the instance of an acts_as_draftable class, which is being proxied to get changes

  • transaction (DraftTransaction) (defaults to: nil)

    the DraftTransaction within which to look for changes. If object is a Draft, this parameter is optional and if not provided will use the DraftTransaction associated with the given Draft. If object is not a Draft, this parameter is required.



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
# File 'lib/draft_approve/draft_changes_proxy.rb', line 37

def initialize(object, transaction = nil)
  if object.blank?
    raise(ArgumentError, "object is required")
  end

  if object.new_record?
    raise(ArgumentError, "object #{object} must already be persisted")
  end

  if object.is_a? Draft
    if transaction.present? && object.draft_transaction != transaction
      raise(ArgumentError, "draft_transaction for #{object} is inconsistent with given draft_transaction #{transaction}")
    end

    # Construct DraftableProxy from a draft
    # Note that @draftable may be nil (if this is a CREATE draft)
    @draft = object
    @draftable = (object.draftable.present? && object.draftable.persisted?) ? object.draftable : nil
    @draftable_class = Object.const_get(object.draftable_type)
    @draft_transaction = object.draft_transaction
  else
    if transaction.blank?
      raise(ArgumentError, "draft_transaction is required when object is a draftable")
    end

    # Construct DraftableProxy from a draftable
    # Note that @draft may be nil (if the draftable has no changes within the scope of this transaction)
    @draft = transaction.drafts.find_by(draftable: object)
    @draftable = object
    @draftable_class = object.class
    @draft_transaction = transaction
  end
end

#new_value(attribute_name) ⇒ Object?

The new, drafted value for the given attribute on the proxied Draft or draftable object. If no changes have been drafted for the given attribute, then returns the currently persisted value for the attribute.

Parameters:

  • attribute_name (String)

Returns:

  • (Object, nil)

    the new value of the given attribute, or the currently persisted value if there are no draft changes for the attribute



178
179
180
# File 'lib/draft_approve/draft_changes_proxy.rb', line 178

def new_value(attribute_name)
  raise "#new_value has not been implemented in #{self.class.name}"
end