Module: ActiveRecord::Acts::Versioned::ClassMethods

Defined in:
lib/acts_as_versioned.rb

Instance Method Summary collapse

Instance Method Details

#acts_as_versioned(options = {}) ⇒ Object

Configuration options

  • class_name - versioned model class name (default: PageVersion in the above example)

  • table_name - versioned model table name (default: page_versions in the above example)

  • foreign_key - foreign key used to relate the versioned model to the original model (default: page_id in the above example)

  • inheritance_column - name of the column to save the model’s inheritance_column value for STI. (default: versioned_type)

  • version_column - name of the column in the model that keeps the version number (default: version)

  • sequence_name - name of the custom sequence to be used by the versioned model.

  • limit - number of revisions to keep, defaults to unlimited

  • if - symbol of method to check before saving a new version. If this method returns false, a new version is not saved. For finer control, pass either a Proc or modify Model#version_condition_met?

    acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
    

    or…

    class Auction
      def version_condition_met? # totally bypasses the <tt>:if</tt> option
        !expired?
      end
    end
    
  • if_changed - Simple way of specifying attributes that are required to be changed before saving a model. This takes either a symbol or array of symbols.

Database Schema

The model that you’re versioning needs to have a ‘version’ attribute. The model is versioned into a table called #model_versions where the model name is singlular. The _versions table should contain all the fields you want versioned, the same version column, and a #model_id foreign key field.

A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance, then that field is reflected in the versioned model as ‘versioned_type’ by default.

Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table method, perfect for a migration. It will also create the version column if the main model does not already have it.

class AddVersions < ActiveRecord::Migration
  def self.up
    # create_versioned_table takes the same options hash
    # that create_table does
    Post.create_versioned_table
  end

  def self.down
    Post.drop_versioned_table
  end
end


104
105
106
107
108
109
110
111
112
113
114
115
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/acts_as_versioned.rb', line 104

def acts_as_versioned(options = {})
  # don't allow multiple calls
  return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)

  class_eval do
    include ActiveRecord::Acts::Versioned::ActMethods
    cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column, 
      :version_column, :max_version_limit, :track_changed_attributes, :version_condition, :version_sequence_name
    attr_accessor :changed_attributes
  end
  
  self.versioned_class_name = options[:class_name] || "#{self.to_s.demodulize}Version"
  self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
  self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{Inflector.underscore(Inflector.demodulize(class_name_of_active_record_descendant(self)))}_versions#{table_name_suffix}"            
  self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
  self.version_column = options[:version_column] || 'version'
  self.version_sequence_name = options[:sequence_name]
  self.max_version_limit = options[:limit].to_i
  self.version_condition = options[:if] || true

  class_eval do
    has_many :versions, 
      :class_name  => "ActiveRecord::Acts::Versioned::#{versioned_class_name}",
      :foreign_key => "#{versioned_foreign_key}",
      :order       => 'version'
    before_save  :set_new_version
    after_create :save_version_on_create
    after_update :save_version
    after_save   :clear_old_versions
    after_save   :clear_changed_attributes
    
    unless options[:if_changed].nil?
      self.track_changed_attributes = true
      options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
      options[:if_changed].each do |attr_name|
        define_method("#{attr_name}=") do |value|
          (self.changed_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) or self.send(attr_name) == value
          write_attribute(attr_name.to_s, value)
        end
      end
    end
  end
  
  # create the dynamic versioned model
  # maybe if i sit down long enough i can think up a better way to do this.
  dynamic_model = <<-EOV
    class ActiveRecord::Acts::Versioned::#{versioned_class_name} < ActiveRecord::Base
      set_table_name "#{versioned_table_name}"
      belongs_to :#{self.to_s.demodulize.underscore}, :class_name => "#{self.to_s}"
  EOV
  
  dynamic_model += %Q{set_sequence_name "#{version_sequence_name}"\n} if version_sequence_name
  
  eval dynamic_model + 'end'
end