Module: ActiveRecord::Acts::Versioned::ActMethods

Defined in:
vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary

Instance Method Summary

Class Method Details

+ (Object) included(base)

:nodoc:



286
287
288
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 286

def self.included(base) # :nodoc:
  base.extend ClassMethods
end

Instance Method Details

- (Boolean) changed?(attr_name = nil) Also known as: dirty?

If called with no parameters, gets whether the current model has changed and needs to be versioned. If called with a single parameter, gets whether the parameter has changed.

Returns:

  • (Boolean)


365
366
367
368
369
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 365

def changed?(attr_name = nil)
  attr_name.nil? ?
    (!self.class.track_altered_attributes || (altered_attributes && altered_attributes.length > 0)) :
    (altered_attributes && altered_attributes.include?(attr_name.to_s))
end

- (Object) clear_old_versions

Clears old revisions if a limit is set with the :limit option in acts_as_versioned. Override this method to set your own criteria for clearing old versions.



311
312
313
314
315
316
317
318
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 311

def clear_old_versions
  return if self.class.max_version_limit == 0
  excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
  if excess_baggage > 0
    sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}"
    self.class.versioned_class.connection.execute sql
  end
end

- (Object) clone_versioned_model(orig_model, new_model)

Clones a model. Used when saving a new version or reverting a model’s version.



375
376
377
378
379
380
381
382
383
384
385
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 375

def clone_versioned_model(orig_model, new_model)
  self.versioned_attributes.each do |key|
    new_model.send("#{key}=", orig_model.send(key)) if orig_model.has_attribute?(key)
  end

  if orig_model.is_a?(self.class.versioned_class)
    new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
  elsif new_model.is_a?(self.class.versioned_class)
    new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
  end
end

- (Object) empty_callback

:nodoc:



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 425

def empty_callback() end 

protected
  # sets the new version before saving, unless you're using optimistic locking.  In that case, let it take care of the version.
  def set_new_version
    self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?)
  end

  # Gets the next available version for the current record, or 1 for a new record
  def next_version
    return 1 if new_record?
    (versions.calculate(:max, :version) || 0) + 1
  end

  # clears current changed attributes.  Called after save.
  def clear_altered_attributes
    self.altered_attributes = []
  end

  def write_changed_attribute(attr_name, attr_value)
    # Convert to db type for comparison. Avoids failing Float<=>String comparisons.
    attr_value_for_db = self.class.columns_hash[attr_name.to_s].type_cast(attr_value)
    (self.altered_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) || self.send(attr_name) == attr_value_for_db
    write_attribute(attr_name, attr_value_for_db)
  end

module ClassMethods
  # Finds a specific version of a specific row of this model
  def find_version(id, version = nil)
    return find(id) unless version

    conditions = ["#{versioned_foreign_key} = ? AND version = ?", id, version]
    options = { :conditions => conditions, :limit => 1 }

    if result = find_versions(id, options).first
      result
    else
      raise RecordNotFound, "Couldn't find #{name} with ID=#{id} and VERSION=#{version}"
    end
  end

  # Finds versions of a specific model.  Takes an options hash like <tt>find</tt>
  def find_versions(id, options = {})
    versioned_class.find :all, {
      :conditions => ["#{versioned_foreign_key} = ?", id],
      :order      => 'version' }.merge(options)
  end

  # Returns an array of columns that are versioned.  See non_versioned_columns
  def versioned_columns
    self.columns.select { |c| !non_versioned_columns.include?(c.name) }
  end

  # Returns an instance of the dynamic versioned model
  def versioned_class
    const_get versioned_class_name
  end

  # Rake migration task to create the versioned table using options passed to acts_as_versioned
  def create_versioned_table(create_table_options = {})
    # create version column in main table if it does not exist
    if !self.content_columns.find { |c| %w(version lock_version).include? c.name }
      self.connection.add_column table_name, :version, :integer
    end

    self.connection.create_table(versioned_table_name, create_table_options) do |t|
      t.column versioned_foreign_key, :integer
      t.column :version, :integer
    end

    updated_col = nil
    self.versioned_columns.each do |col| 
      updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name)
      self.connection.add_column versioned_table_name, col.name, col.type, 
        :limit => col.limit, 
        :default => col.default,
        :scale => col.scale,
        :precision => col.precision
    end

    if type_col = self.columns_hash[inheritance_column]
      self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type, 
        :limit => type_col.limit, 
        :default => type_col.default,
        :scale => type_col.scale,
        :precision => type_col.precision
    end

    if updated_col.nil?
      self.connection.add_column versioned_table_name, :updated_at, :timestamp
    end
  end

  # Rake migration task to drop the versioned table
  def drop_versioned_table
    self.connection.drop_table versioned_table_name
  end

  # Executes the block with the versioning callbacks disabled.
  #
  #   Foo.without_revision do
  #     @foo.save
  #   end
  #
  def without_revision(&block)
    class_eval do 
      CALLBACKS.each do |attr_name|
        alias_method "orig_#{attr_name}".to_sym, attr_name
        alias_method attr_name, :empty_callback
      end
    end
    block.call
  ensure
    class_eval do 
      CALLBACKS.each do |attr_name|
        alias_method attr_name, "orig_#{attr_name}".to_sym
      end
    end
  end

  # Turns off optimistic locking for the duration of the block
  #
  #   Foo.without_locking do
  #     @foo.save
  #   end
  #
  def without_locking(&block)
    current = ActiveRecord::Base.lock_optimistically
    ActiveRecord::Base.lock_optimistically = false if current
    result = block.call
    ActiveRecord::Base.lock_optimistically = true if current
    result
  end
end

- (Object) find_version(version = nil)

Finds a specific version of this record



291
292
293
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 291

def find_version(version = nil)
  self.class.find_version(id, version)
end

- (Object) revert_to(version)

Reverts a model to a given version. Takes either a version number or an instance of the versioned model



325
326
327
328
329
330
331
332
333
334
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 325

def revert_to(version)
  if version.is_a?(self.class.versioned_class)
    return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record?
  else
    return false unless version = versions.find_by_version(version)
  end
  self.clone_versioned_model(version, self)
  self.send("#{self.class.version_column}=", version.version)
  true
end

- (Object) revert_to!(version)

Reverts a model to a given version and saves the model. Takes either a version number or an instance of the versioned model



338
339
340
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 338

def revert_to!(version)
  revert_to(version) ? save_without_revision : false
end

- (Object) save_version

Saves a version of the model if applicable



296
297
298
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 296

def save_version
  save_version_on_create if save_version?
end

- (Boolean) save_version?

Checks whether a new version shall be saved or not. Calls version_condition_met? and changed?.

Returns:

  • (Boolean)


388
389
390
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 388

def save_version?
  version_condition_met? && changed?
end

- (Object) save_version_on_create

Saves a version of the model in the versioned table. This is called in the after_save callback by default



301
302
303
304
305
306
307
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 301

def save_version_on_create
  rev = self.class.versioned_class.new
  self.clone_versioned_model(self, rev)
  rev.version = send(self.class.version_column)
  rev.send("#{self.class.versioned_foreign_key}=", self.id)
  rev.save
end

- (Object) save_without_revision

Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.



343
344
345
346
347
348
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 343

def save_without_revision
  save_without_revision!
  true
rescue
  false
end

- (Object) save_without_revision!



350
351
352
353
354
355
356
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 350

def save_without_revision!
  without_locking do
    without_revision do
      save!
    end
  end
end

- (Boolean) version_condition_met?

Checks condition set in the :if option to check whether a revision should be created or not. Override this for custom version condition checking.

Returns:

  • (Boolean)


394
395
396
397
398
399
400
401
402
403
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 394

def version_condition_met?
  case
  when version_condition.is_a?(Symbol)
    send(version_condition)
  when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
    version_condition.call(self)
  else
    version_condition
  end
end

- (Object) versioned_attributes

Returns an array of attribute keys that are versioned. See non_versioned_columns



359
360
361
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 359

def versioned_attributes
  self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) }
end

- (Object) versions_count



320
321
322
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 320

def versions_count
  version
end

- (Object) without_locking(&block)

Turns off optimistic locking for the duration of the block

  @foo.without_locking do
    @foo.save
  end


421
422
423
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 421

def without_locking(&block)
  self.class.without_locking(&block)
end

- (Object) without_revision(&block)

Executes the block with the versioning callbacks disabled.

  @foo.without_revision do
    @foo.save
  end


411
412
413
# File 'vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb', line 411

def without_revision(&block)
  self.class.without_revision(&block)
end