Class: Draftsman::Draft

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
lib/draftsman/draft.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.createsObject



17
18
19
# File 'lib/draftsman/draft.rb', line 17

def self.creates
  where :event => 'create'
end

.destroysObject



21
22
23
# File 'lib/draftsman/draft.rb', line 21

def self.destroys
  where :event => 'destroy'
end

.object_changes_col_is_json?Boolean

Returns whether the ‘object_changes` column is using the `json` type supported by PostgreSQL.

Returns:

  • (Boolean)


31
32
33
# File 'lib/draftsman/draft.rb', line 31

def self.object_changes_col_is_json?
  @object_changes_col_is_json ||= columns_hash['object_changes'].type == :json
end

.object_col_is_json?Boolean

Returns whether the ‘object` column is using the `json` type supported by PostgreSQL.

Returns:

  • (Boolean)


26
27
28
# File 'lib/draftsman/draft.rb', line 26

def self.object_col_is_json?
  @object_col_is_json ||= columns_hash['object'].type == :json
end

.previous_draft_col_is_json?Boolean

Returns whether the ‘previous_draft` column is using the `json` type supported by PostgreSQL.

Returns:

  • (Boolean)


36
37
38
# File 'lib/draftsman/draft.rb', line 36

def self.previous_draft_col_is_json?
  @previous_draft_col_is_json ||= columns_hash['previous_draft'].type == :json
end

.updatesObject



40
41
42
# File 'lib/draftsman/draft.rb', line 40

def self.updates
  where :event => 'update'
end

.with_item_keys(item_type, item_id) ⇒ Object



13
14
15
# File 'lib/draftsman/draft.rb', line 13

def self.with_item_keys(item_type, item_id)
  scoped :conditions => { :item_type => item_type, :item_id => item_id }
end

Instance Method Details

#changesetObject

Returns what changed in this draft. Similar to ‘ActiveModel::Dirty#changes`. Returns `nil` if your `drafts` table does not have an `object_changes` text column.



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/draftsman/draft.rb', line 46

def changeset
  return nil unless self.class.column_names.include? 'object_changes'

  _changes = self.class.object_changes_col_is_json? ? self.object_changes : Draftsman.serializer.load(self.object_changes)

  @changeset ||= HashWithIndifferentAccess.new(_changes).tap do |changes|
    item_type.constantize.unserialize_draft_attribute_changes(changes)
  end
rescue
  {}
end

#create?Boolean

Returns whether or not this is a ‘create` event.

Returns:

  • (Boolean)


59
60
61
# File 'lib/draftsman/draft.rb', line 59

def create?
  self.event == 'create'
end

#destroy?Boolean

Returns whether or not this is a ‘destroy` event.

Returns:

  • (Boolean)


64
65
66
# File 'lib/draftsman/draft.rb', line 64

def destroy?
  self.event == 'destroy'
end

#draft_publication_dependenciesObject

Returns related draft dependencies that would be along for the ride for a ‘publish!` action.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/draftsman/draft.rb', line 69

def draft_publication_dependencies
  dependencies = []

  my_item = self.item.draft? ? self.item.draft.reify : self.item

  case self.event
  when 'create', 'update'
    associations = my_item.class.reflect_on_all_associations(:belongs_to)

    associations.each do |association|
      association_class =
        if association.options.key?(:polymorphic)
          my_item.send(association.foreign_key.sub('_id', '_type')).constantize
        else
          association.klass
        end

      if association_class.draftable? && association.name != association_class.draft_association_name.to_sym
        dependency = my_item.send(association.name)
        dependencies << dependency.draft if dependency.present? && dependency.draft? && dependency.draft.create?
      end
    end
  when 'destroy'
    associations = my_item.class.reflect_on_all_associations(:has_one) + my_item.class.reflect_on_all_associations(:has_many)

    associations.each do |association|
      if association.klass.draftable?
        # Reconcile different association types into an array, even if `has_one` produces a single-item
        associated_dependencies =
          case association.macro
          when :has_one
            my_item.send(association.name).present? ? [my_item.send(association.name)] : []
          when :has_many
            my_item.send(association.name)
          end

        associated_dependencies.each do |dependency|
          dependencies << dependency.draft if dependency.draft?
        end
      end
    end
  end

  dependencies
end

#draft_reversion_dependenciesObject

Returns related draft dependencies that would be along for the ride for a ‘revert!` action.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/draftsman/draft.rb', line 116

def draft_reversion_dependencies
  dependencies = []

  case self.event
  when 'create'
    associations = self.item.class.reflect_on_all_associations(:has_one) + self.item.class.reflect_on_all_associations(:has_many)

    associations.each do |association|
      if association.klass.draftable?
        # Reconcile different association types into an array, even if `has_one` produces a single-item
        associated_dependencies =
          case association.macro
          when :has_one
            self.item.send(association.name).present? ? [self.item.send(association.name)] : []
          when :has_many
            self.item.send(association.name)
          end

        associated_dependencies.each do |dependency|
          dependencies << dependency.draft if dependency.draft?
        end
      end
    end
  when 'destroy'
    associations = self.item.class.reflect_on_all_associations(:belongs_to)

    associations.each do |association|
      association_class =
        if association.options.key?(:polymorphic)
          self.item.send(association.foreign_key.sub('_id', '_type')).constantize
        else
          association.klass
        end

      if association_class.draftable? && association_class.trashable? && association.name != association_class.draft_association_name.to_sym
        dependency = self.item.send(association.name)
        dependencies << dependency.draft if dependency.present? && dependency.draft? && dependency.draft.destroy?
      end
    end
  end

  dependencies
end

#publish!Object

Publishes this draft’s associated ‘item`, publishes its `item`’s dependencies, and destroys itself.

  • For ‘create` drafts, adds a value for the `published_at` timestamp on the item and destroys the draft.

  • For ‘update` drafts, applies the drafted changes to the item and destroys the draft.

  • For ‘destroy` drafts, destroys the item and the draft.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/draftsman/draft.rb', line 164

def publish!
  ActiveRecord::Base.transaction do
    case self.event
    when 'create', 'update'
      # Parents must be published too
      self.draft_publication_dependencies.each { |dependency| dependency.publish! }

      # Update drafts need to copy over data to main record
      self.item.attributes = self.reify.attributes if self.update?

      # Write `published_at` attribute
      self.item.send "#{self.item.class.published_at_attribute_name}=", Time.now

      # Clear out draft
      self.item.send "#{self.item.class.draft_association_name}_id=", nil

      # Determine which columns should be updated
      only   = self.item.class.draftsman_options[:only]
      ignore = self.item.class.draftsman_options[:ignore]
      skip   = self.item.class.draftsman_options[:skip]
      attributes_to_change = only.any? ? only : self.item.attribute_names
      attributes_to_change = attributes_to_change - ignore + ['published_at', "#{self.item.class.draft_association_name}_id"] - skip

      # Save without validations or callbacks
      self.item.attributes.slice(*attributes_to_change).each do |key, value|
        self.item.send("#{key}=", value)
      end
      self.item.save(:validate => false)
      
      self.item.reload

      # Destroy draft
      self.destroy
    when 'destroy'
      self.item.destroy
    end
  end
end

#reifyObject

Returns instance of item converted to its drafted state.

Example usage:

`@category = @category.reify if @category.draft?`


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/draftsman/draft.rb', line 208

def reify
  without_identity_map do
    if !self.previous_draft.nil?
      reify_previous_draft.reify
    elsif !self.object.nil?
      # This appears to be necessary if for some reason the draft's model hasn't been loaded (such as when done in the console).
      require self.item_type.underscore

      model = item.reload

      attrs = self.class.object_col_is_json? ? self.object : Draftsman.serializer.load(object)
      model.class.unserialize_attributes_for_draftsman attrs

      attrs.each do |key, value|
        # Skip counter_cache columns
        if model.respond_to?("#{key}=") && !key.end_with?('_count')
          model.send "#{key}=", value
        elsif !key.end_with?('_count')
          logger.warn "Attribute #{key} does not exist on #{item_type} (Draft ID: #{id})."
        end
      end

      model.send "#{model.class.draft_association_name}=", self
      model
    end
  end
end

#revert!Object

Reverts this draft.

  • For create drafts, destroys the draft and the item.

  • For update drafts, destroys the draft only.

  • For destroy drafts, destroys the draft and undoes the ‘trashed_at` timestamp on the item. If a previous draft was drafted for destroy, restores the draft.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/draftsman/draft.rb', line 241

def revert!
  ActiveRecord::Base.transaction do
    case self.event
    when 'create'
      self.item.destroy
      self.destroy
    when 'update'
      self.item.class.where(:id => self.item).update_all("#{self.item.class.draft_association_name}_id".to_sym => nil)
      self.destroy
    when 'destroy'
      # Parents must be restored too
      self.draft_reversion_dependencies.each { |dependency| dependency.revert! }

      # Restore previous draft if one was stashed away
      if self.previous_draft.present?
        prev_draft = reify_previous_draft
        prev_draft.save!

        self.item.class.where(:id => self.item).update_all "#{self.item.class.draft_association_name}_id".to_sym => prev_draft.id,
                                                           self.item.class.trashed_at_attribute_name => nil
      else
        self.item.class.where(:id => self.item).update_all "#{self.item.class.draft_association_name}_id".to_sym => nil,
                                                           self.item.class.trashed_at_attribute_name => nil
      end
      
      self.destroy
    end
  end
end

#update?Boolean

Returns whether or not this is an ‘update` event.

Returns:

  • (Boolean)


272
273
274
# File 'lib/draftsman/draft.rb', line 272

def update?
  self.event == 'update'
end