Module: Cms::Behaviors::Versioning::InstanceMethods

Defined in:
lib/cms/behaviors/versioning.rb

Instance Method Summary collapse

Instance Method Details

#as_of_draft_versionObject



306
307
308
# File 'lib/cms/behaviors/versioning.rb', line 306

def as_of_draft_version
  as_of_version(draft.version)
end

#as_of_version(version) ⇒ ContentBlock

Find a Content Block as of a specific version.

Parameters:

  • version (Integer)

    The specific version of the block to look up

Returns:

  • (ContentBlock)

    The block as of the state it existed at ‘version’.

Raises:

  • (ActiveRecord::RecordNotFound)


314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/cms/behaviors/versioning.rb', line 314

def as_of_version(version)
  v = find_version(version)
  raise ActiveRecord::RecordNotFound.new("version #{version.inspect} does not exist for <#{self.class}:#{id}>") unless v
  obj = self.class.new

  (self.class.versioned_columns + [:version, :created_at, :created_by_id, :updated_at, :updated_by_id]).each do |a|
    obj.send("#{a}=", v.send(a))
  end
  obj.id = id
  obj.lock_version = lock_version

  # Need to do this so associations can be loaded
  obj.instance_variable_set("@persisted", true)
  obj.instance_variable_set("@new_record", false)

  # Callback to allow us to load other data when an older version is loaded
  obj.after_as_of_version if obj.respond_to?(:after_as_of_version)

  # Last but not least, clear the changed attributes
  if changed_attrs = obj.send(:changed_attributes)
    changed_attrs.clear
  end

  obj
end

#build_new_versionObject

Build a new version of this record and associate it with this record.

Called as a before_create in order to correctly allow any other associations to be saved correctly. Called explicitly during update, where it will just define the new_version to be saved.



199
200
201
202
# File 'lib/cms/behaviors/versioning.rb', line 199

def build_new_version
  @new_version = build_new_version_and_add_to_versions_list_for_saving
  logger.debug {"New version of #{self.class}::Version is #{@new_version.attributes}"}
end

#build_new_version_and_add_to_versions_list_for_savingObject



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/cms/behaviors/versioning.rb', line 105

def build_new_version_and_add_to_versions_list_for_saving
  # First get the values from the draft
  attrs = draft_attributes

  # Now overwrite all values
  (self.class.versioned_columns - %w(  version  )).each do |col|
    attrs[col] = send(col)
  end

  attrs[:version_comment] = @version_comment || default_version_comment
  @version_comment = nil
  new_version = versions.build(attrs)
  new_version.version = new_record? ? 1 : (draft.version.to_i + 1)
  after_build_new_version(new_version) if respond_to?(:after_build_new_version)
  new_version
end

#create_or_updateObject

ActiveRecord 3.0.0 call chain ActiveRecord 3 now uses basic inheritence rather than alias_method_chain. The order in which ActiveRecord::Base includes methods (at the bottom of activerecord) repeatedly overrides save/save! with chains of ‘super’

Callstack order as observed

  1. ActiveRecord::Base#save - The original method called by client

AR::Transactions#save
AR::Dirty#save
AR::Validations#save
ActiveRecord::Persistence#save
ActiveRecord::Persistence#create_or_update
AR::Callbacks#create_or_update (runs :save callbacks)

This aliases the original ActiveRecord::Base.save method, in order to change how calling save works. It should do the following things:

  1. If the record is unchanged, no save is performed, but true is returned. (Skipping after_save callbacks)

  2. If its an update, a new version is created and that is saved.

  3. If new record, its version is set to 1, and its published if needed.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/cms/behaviors/versioning.rb', line 170

def create_or_update
  logger.debug {"#{self.class}#create_or_update called. Published = #{!!publish_on_save}"}
  self.skip_callbacks = false
  unless different_from_last_draft?
    logger.debug {"No difference between this version and last. Skipping save"}
    self.skip_callbacks = true
    return true
  end
  logger.debug {"Saving #{self.class} #{self.attributes}"}
  if new_record?
    self.version = 1
    # This should call ActiveRecord::Callbacks#create_or_update, which will correctly trigger the :save callback_chain
    saved_correctly = super
    changed_attributes.clear
  else
    logger.debug {"#{self.class}#update"}
    # Because we are 'skipping' the normal ActiveRecord update here, we must manually call the save callback chain.
    run_callbacks :save do
      saved_correctly = @new_version.save
    end
  end
  publish_if_needed
  return saved_correctly
end

#current_versionObject



298
299
300
# File 'lib/cms/behaviors/versioning.rb', line 298

def current_version
  find_version(self.version)
end

#default_version_commentObject



129
130
131
132
133
134
135
# File 'lib/cms/behaviors/versioning.rb', line 129

def default_version_comment
  if new_record?
    "Created"
  else
    "Changed #{(changes.keys - %w[  version created_by_id updated_by_id  ]).sort.join(', ')}"
  end
end

#different_from_last_draft?Boolean

Returns:

  • (Boolean)


370
371
372
373
374
375
376
377
378
# File 'lib/cms/behaviors/versioning.rb', line 370

def different_from_last_draft?
  return true if self.changed?
  last_draft = self.draft
  return true unless last_draft
  (self.class.versioned_columns - %w(  version  )).each do |col|
    return true if self.send(col) != last_draft.send(col)
  end
  return false
end

#draftObject



282
283
284
# File 'lib/cms/behaviors/versioning.rb', line 282

def draft
  versions.first(:order => "version desc")
end

#draft_attributesObject



122
123
124
125
126
127
# File 'lib/cms/behaviors/versioning.rb', line 122

def draft_attributes
  # When there is no draft, we'll just copy the attributes from this object
  # Otherwise we need to use the draft
  d = new_record? ? self : draft
  self.class.versioned_columns.inject({}) { |attrs, col| attrs[col] = d.send(col); attrs }
end

#draft_version?Boolean

Returns:

  • (Boolean)


286
287
288
# File 'lib/cms/behaviors/versioning.rb', line 286

def draft_version?
  version == draft.version
end

#find_version(number) ⇒ Object



302
303
304
# File 'lib/cms/behaviors/versioning.rb', line 302

def find_version(number)
  versions.first(:conditions => {:version => number})
end

#initialize_versionObject



100
101
102
# File 'lib/cms/behaviors/versioning.rb', line 100

def initialize_version
  self.version = 1 if new_record?
end

#live_versionObject



290
291
292
# File 'lib/cms/behaviors/versioning.rb', line 290

def live_version
  find_version(self.class.find(id).version)
end

#live_version?Boolean

Returns:

  • (Boolean)


294
295
296
# File 'lib/cms/behaviors/versioning.rb', line 294

def live_version?
  version == self.class.find(id).version
end

#publish_if_neededObject



137
138
139
140
141
142
143
144
# File 'lib/cms/behaviors/versioning.rb', line 137

def publish_if_needed
  logger.debug { "#{self.class}#publish_if_needed. publish? = '#{!!@publish_on_save}'"}

  if @publish_on_save
    publish
    @publish_on_save = nil
  end
end

#revertObject



340
341
342
343
# File 'lib/cms/behaviors/versioning.rb', line 340

def revert
  draft_version = draft.version
  revert_to(draft_version - 1) unless draft_version == 1
end

#revert_to(version) ⇒ Object



356
357
358
359
# File 'lib/cms/behaviors/versioning.rb', line 356

def revert_to(version)
  revert_to_without_save(version)
  save
end

#revert_to_without_save(version) ⇒ Object



345
346
347
348
349
350
351
352
353
354
# File 'lib/cms/behaviors/versioning.rb', line 345

def revert_to_without_save(version)
  raise "Version parameter missing" if version.blank?
  self.revert_to_version = find_version(version)
  raise "Could not find version #{version}" unless revert_to_version
  (self.class.versioned_columns - ["version"]).each do |a|
    send("#{a}=", revert_to_version.send(a))
  end
  self.version_comment = "Reverted to version #{version}"
  self
end

#save!(perform_validations = true) ⇒ Object

def save(perform_validations=true)

  transaction do
    #logger.info "..... Calling valid?"
    return false unless !perform_validations || valid?

    if different_from_last_draft?
      #logger.info "..... Changes => #{changes.inspect}"
    else
      #logger.info "..... No Changes"
      return true
    end

    #logger.info "..... Calling before_save"
    return false if callback(:before_save) == false

    if new_record?
      #logger.info "..... Calling before_create"
      return false if callback(:before_create) == false
    else
      #logger.info "..... Calling before_update"
      return false if callback(:before_update) == false
    end

    #logger.info "..... Calling build_new_version"
    new_version = build_new_version
    #logger.info "..... Is new version valid? #{new_version.valid?}"
    if new_record?
      self.version = 1
      #logger.info "..... Calling create_without_callbacks"
      if result = create_without_callbacks
        #logger.info "..... Calling after_create"
        if callback(:after_create) != false
          #logger.info "..... Calling after_save"
          callback(:after_save)
        end

        if @publish_on_save
          publish
          @publish_on_save = nil
        end
        changed_attributes.clear
      end
      result
    elsif new_version
      #logger.info "..... Calling save"
      if result = new_version.save
        #logger.info "..... Calling after_save"
        if callback(:after_update) != false
          #logger.info "..... Calling after_update"
          callback(:after_save)
        end

        if @publish_on_save
          publish
          @publish_on_save = nil
        end
        changed_attributes.clear
      end
      result
    end
    true
  end
end


278
279
280
# File 'lib/cms/behaviors/versioning.rb', line 278

def save!(perform_validations=true)
  save(:validate=>perform_validations) || raise(ActiveRecord::RecordNotSaved.new(errors.full_messages))
end

#version_commentObject



361
362
363
# File 'lib/cms/behaviors/versioning.rb', line 361

def version_comment
  @version_comment
end

#version_comment=(version_comment) ⇒ Object



365
366
367
368
# File 'lib/cms/behaviors/versioning.rb', line 365

def version_comment=(version_comment)
  @version_comment = version_comment
  send(:changed_attributes)["version_comment"] = @version_comment
end