annotate vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children
rev   line source
Chris@0 1 # Copyright (c) 2005 Rick Olson
Chris@0 2 #
Chris@0 3 # Permission is hereby granted, free of charge, to any person obtaining
Chris@0 4 # a copy of this software and associated documentation files (the
Chris@0 5 # "Software"), to deal in the Software without restriction, including
Chris@0 6 # without limitation the rights to use, copy, modify, merge, publish,
Chris@0 7 # distribute, sublicense, and/or sell copies of the Software, and to
Chris@0 8 # permit persons to whom the Software is furnished to do so, subject to
Chris@0 9 # the following conditions:
Chris@0 10 #
Chris@0 11 # The above copyright notice and this permission notice shall be
Chris@0 12 # included in all copies or substantial portions of the Software.
Chris@0 13 #
Chris@0 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Chris@0 15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Chris@0 16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Chris@0 17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
Chris@0 18 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
Chris@0 19 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
Chris@0 20 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Chris@0 21
Chris@0 22 module ActiveRecord #:nodoc:
Chris@0 23 module Acts #:nodoc:
Chris@0 24 # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
Chris@0 25 # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
Chris@0 26 # column is present as well.
Chris@0 27 #
Chris@0 28 # The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart
Chris@0 29 # your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
Chris@0 30 #
Chris@0 31 # class Page < ActiveRecord::Base
Chris@0 32 # # assumes pages_versions table
Chris@0 33 # acts_as_versioned
Chris@0 34 # end
Chris@0 35 #
Chris@0 36 # Example:
Chris@0 37 #
Chris@0 38 # page = Page.create(:title => 'hello world!')
Chris@0 39 # page.version # => 1
Chris@0 40 #
Chris@0 41 # page.title = 'hello world'
Chris@0 42 # page.save
Chris@0 43 # page.version # => 2
Chris@0 44 # page.versions.size # => 2
Chris@0 45 #
Chris@0 46 # page.revert_to(1) # using version number
Chris@0 47 # page.title # => 'hello world!'
Chris@0 48 #
Chris@0 49 # page.revert_to(page.versions.last) # using versioned instance
Chris@0 50 # page.title # => 'hello world'
Chris@0 51 #
Chris@0 52 # page.versions.earliest # efficient query to find the first version
Chris@0 53 # page.versions.latest # efficient query to find the most recently created version
Chris@0 54 #
Chris@0 55 #
Chris@0 56 # Simple Queries to page between versions
Chris@0 57 #
Chris@0 58 # page.versions.before(version)
Chris@0 59 # page.versions.after(version)
Chris@0 60 #
Chris@0 61 # Access the previous/next versions from the versioned model itself
Chris@0 62 #
Chris@0 63 # version = page.versions.latest
Chris@0 64 # version.previous # go back one version
Chris@0 65 # version.next # go forward one version
Chris@0 66 #
Chris@0 67 # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
Chris@0 68 module Versioned
Chris@0 69 CALLBACKS = [:set_new_version, :save_version_on_create, :save_version?, :clear_altered_attributes]
Chris@0 70 def self.included(base) # :nodoc:
Chris@0 71 base.extend ClassMethods
Chris@0 72 end
Chris@0 73
Chris@0 74 module ClassMethods
Chris@0 75 # == Configuration options
Chris@0 76 #
Chris@0 77 # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
Chris@0 78 # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
Chris@0 79 # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
Chris@0 80 # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
Chris@0 81 # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
Chris@0 82 # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
Chris@0 83 # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
Chris@0 84 # * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
Chris@0 85 # For finer control, pass either a Proc or modify Model#version_condition_met?
Chris@0 86 #
Chris@0 87 # acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
Chris@0 88 #
Chris@0 89 # or...
Chris@0 90 #
Chris@0 91 # class Auction
Chris@0 92 # def version_condition_met? # totally bypasses the <tt>:if</tt> option
Chris@0 93 # !expired?
Chris@0 94 # end
Chris@0 95 # end
Chris@0 96 #
Chris@0 97 # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
Chris@0 98 # either a symbol or array of symbols. WARNING - This will attempt to overwrite any attribute setters you may have.
Chris@0 99 # Use this instead if you want to write your own attribute setters (and ignore if_changed):
Chris@0 100 #
Chris@0 101 # def name=(new_name)
Chris@0 102 # write_changed_attribute :name, new_name
Chris@0 103 # end
Chris@0 104 #
Chris@0 105 # * <tt>extend</tt> - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block
Chris@0 106 # to create an anonymous mixin:
Chris@0 107 #
Chris@0 108 # class Auction
Chris@0 109 # acts_as_versioned do
Chris@0 110 # def started?
Chris@0 111 # !started_at.nil?
Chris@0 112 # end
Chris@0 113 # end
Chris@0 114 # end
Chris@0 115 #
Chris@0 116 # or...
Chris@0 117 #
Chris@0 118 # module AuctionExtension
Chris@0 119 # def started?
Chris@0 120 # !started_at.nil?
Chris@0 121 # end
Chris@0 122 # end
Chris@0 123 # class Auction
Chris@0 124 # acts_as_versioned :extend => AuctionExtension
Chris@0 125 # end
Chris@0 126 #
Chris@0 127 # Example code:
Chris@0 128 #
Chris@0 129 # @auction = Auction.find(1)
Chris@0 130 # @auction.started?
Chris@0 131 # @auction.versions.first.started?
Chris@0 132 #
Chris@0 133 # == Database Schema
Chris@0 134 #
Chris@0 135 # The model that you're versioning needs to have a 'version' attribute. The model is versioned
Chris@0 136 # into a table called #{model}_versions where the model name is singlular. The _versions table should
Chris@0 137 # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
Chris@0 138 #
Chris@0 139 # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
Chris@0 140 # then that field is reflected in the versioned model as 'versioned_type' by default.
Chris@0 141 #
Chris@0 142 # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
Chris@0 143 # method, perfect for a migration. It will also create the version column if the main model does not already have it.
Chris@0 144 #
Chris@0 145 # class AddVersions < ActiveRecord::Migration
Chris@0 146 # def self.up
Chris@0 147 # # create_versioned_table takes the same options hash
Chris@0 148 # # that create_table does
Chris@0 149 # Post.create_versioned_table
Chris@0 150 # end
Chris@0 151 #
Chris@0 152 # def self.down
Chris@0 153 # Post.drop_versioned_table
Chris@0 154 # end
Chris@0 155 # end
Chris@0 156 #
Chris@0 157 # == Changing What Fields Are Versioned
Chris@0 158 #
Chris@0 159 # By default, acts_as_versioned will version all but these fields:
Chris@0 160 #
Chris@0 161 # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
Chris@0 162 #
Chris@0 163 # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
Chris@0 164 #
Chris@0 165 # class Post < ActiveRecord::Base
Chris@0 166 # acts_as_versioned
Chris@0 167 # self.non_versioned_columns << 'comments_count'
Chris@0 168 # end
Chris@0 169 #
Chris@0 170 def acts_as_versioned(options = {}, &extension)
Chris@0 171 # don't allow multiple calls
Chris@0 172 return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
Chris@0 173
Chris@0 174 send :include, ActiveRecord::Acts::Versioned::ActMethods
Chris@0 175
Chris@0 176 cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
Chris@0 177 :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
Chris@0 178 :version_association_options
Chris@0 179
Chris@0 180 # legacy
Chris@0 181 alias_method :non_versioned_fields, :non_versioned_columns
Chris@0 182 alias_method :non_versioned_fields=, :non_versioned_columns=
Chris@0 183
Chris@0 184 class << self
Chris@0 185 alias_method :non_versioned_fields, :non_versioned_columns
Chris@0 186 alias_method :non_versioned_fields=, :non_versioned_columns=
Chris@0 187 end
Chris@0 188
Chris@0 189 send :attr_accessor, :altered_attributes
Chris@0 190
Chris@0 191 self.versioned_class_name = options[:class_name] || "Version"
Chris@0 192 self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
Chris@0 193 self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
Chris@0 194 self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
Chris@0 195 self.version_column = options[:version_column] || 'version'
Chris@0 196 self.version_sequence_name = options[:sequence_name]
Chris@0 197 self.max_version_limit = options[:limit].to_i
Chris@0 198 self.version_condition = options[:if] || true
Chris@0 199 self.non_versioned_columns = [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
Chris@0 200 self.version_association_options = {
Chris@0 201 :class_name => "#{self.to_s}::#{versioned_class_name}",
Chris@0 202 :foreign_key => versioned_foreign_key,
Chris@0 203 :dependent => :delete_all
Chris@0 204 }.merge(options[:association_options] || {})
Chris@0 205
Chris@0 206 if block_given?
Chris@0 207 extension_module_name = "#{versioned_class_name}Extension"
Chris@0 208 silence_warnings do
Chris@0 209 self.const_set(extension_module_name, Module.new(&extension))
Chris@0 210 end
Chris@0 211
Chris@0 212 options[:extend] = self.const_get(extension_module_name)
Chris@0 213 end
Chris@0 214
Chris@0 215 class_eval do
Chris@0 216 has_many :versions, version_association_options do
Chris@0 217 # finds earliest version of this record
Chris@0 218 def earliest
Chris@0 219 @earliest ||= find(:first, :order => 'version')
Chris@0 220 end
Chris@0 221
Chris@0 222 # find latest version of this record
Chris@0 223 def latest
Chris@0 224 @latest ||= find(:first, :order => 'version desc')
Chris@0 225 end
Chris@0 226 end
Chris@0 227 before_save :set_new_version
Chris@0 228 after_create :save_version_on_create
Chris@0 229 after_update :save_version
Chris@0 230 after_save :clear_old_versions
Chris@0 231 after_save :clear_altered_attributes
Chris@0 232
Chris@0 233 unless options[:if_changed].nil?
Chris@0 234 self.track_altered_attributes = true
Chris@0 235 options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
Chris@0 236 options[:if_changed].each do |attr_name|
Chris@0 237 define_method("#{attr_name}=") do |value|
Chris@0 238 write_changed_attribute attr_name, value
Chris@0 239 end
Chris@0 240 end
Chris@0 241 end
Chris@0 242
Chris@0 243 include options[:extend] if options[:extend].is_a?(Module)
Chris@0 244 end
Chris@0 245
Chris@0 246 # create the dynamic versioned model
Chris@0 247 const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
Chris@0 248 def self.reloadable? ; false ; end
Chris@0 249 # find first version before the given version
Chris@0 250 def self.before(version)
Chris@0 251 find :first, :order => 'version desc',
Chris@0 252 :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]
Chris@0 253 end
Chris@0 254
Chris@0 255 # find first version after the given version.
Chris@0 256 def self.after(version)
Chris@0 257 find :first, :order => 'version',
Chris@0 258 :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]
Chris@0 259 end
Chris@0 260
Chris@0 261 def previous
Chris@0 262 self.class.before(self)
Chris@0 263 end
Chris@0 264
Chris@0 265 def next
Chris@0 266 self.class.after(self)
Chris@0 267 end
Chris@0 268
Chris@0 269 def versions_count
Chris@0 270 page.version
Chris@0 271 end
Chris@0 272 end
Chris@0 273
Chris@0 274 versioned_class.cattr_accessor :original_class
Chris@0 275 versioned_class.original_class = self
Chris@0 276 versioned_class.set_table_name versioned_table_name
Chris@0 277 versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
Chris@0 278 :class_name => "::#{self.to_s}",
Chris@0 279 :foreign_key => versioned_foreign_key
Chris@0 280 versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
Chris@0 281 versioned_class.set_sequence_name version_sequence_name if version_sequence_name
Chris@0 282 end
Chris@0 283 end
Chris@0 284
Chris@0 285 module ActMethods
Chris@0 286 def self.included(base) # :nodoc:
Chris@0 287 base.extend ClassMethods
Chris@0 288 end
Chris@0 289
Chris@0 290 # Finds a specific version of this record
Chris@0 291 def find_version(version = nil)
Chris@0 292 self.class.find_version(id, version)
Chris@0 293 end
Chris@0 294
Chris@0 295 # Saves a version of the model if applicable
Chris@0 296 def save_version
Chris@0 297 save_version_on_create if save_version?
Chris@0 298 end
Chris@0 299
Chris@0 300 # Saves a version of the model in the versioned table. This is called in the after_save callback by default
Chris@0 301 def save_version_on_create
Chris@0 302 rev = self.class.versioned_class.new
Chris@0 303 self.clone_versioned_model(self, rev)
Chris@0 304 rev.version = send(self.class.version_column)
Chris@0 305 rev.send("#{self.class.versioned_foreign_key}=", self.id)
Chris@0 306 rev.save
Chris@0 307 end
Chris@0 308
Chris@0 309 # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
Chris@0 310 # Override this method to set your own criteria for clearing old versions.
Chris@0 311 def clear_old_versions
Chris@0 312 return if self.class.max_version_limit == 0
Chris@0 313 excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
Chris@0 314 if excess_baggage > 0
Chris@0 315 sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}"
Chris@0 316 self.class.versioned_class.connection.execute sql
Chris@0 317 end
Chris@0 318 end
Chris@0 319
Chris@0 320 def versions_count
Chris@0 321 version
Chris@0 322 end
Chris@0 323
Chris@0 324 # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
Chris@0 325 def revert_to(version)
Chris@0 326 if version.is_a?(self.class.versioned_class)
Chris@0 327 return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record?
Chris@0 328 else
Chris@0 329 return false unless version = versions.find_by_version(version)
Chris@0 330 end
Chris@0 331 self.clone_versioned_model(version, self)
Chris@0 332 self.send("#{self.class.version_column}=", version.version)
Chris@0 333 true
Chris@0 334 end
Chris@0 335
Chris@0 336 # Reverts a model to a given version and saves the model.
Chris@0 337 # Takes either a version number or an instance of the versioned model
Chris@0 338 def revert_to!(version)
Chris@0 339 revert_to(version) ? save_without_revision : false
Chris@0 340 end
Chris@0 341
Chris@0 342 # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
Chris@0 343 def save_without_revision
Chris@0 344 save_without_revision!
Chris@0 345 true
Chris@0 346 rescue
Chris@0 347 false
Chris@0 348 end
Chris@0 349
Chris@0 350 def save_without_revision!
Chris@0 351 without_locking do
Chris@0 352 without_revision do
Chris@0 353 save!
Chris@0 354 end
Chris@0 355 end
Chris@0 356 end
Chris@0 357
Chris@0 358 # Returns an array of attribute keys that are versioned. See non_versioned_columns
Chris@0 359 def versioned_attributes
Chris@0 360 self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) }
Chris@0 361 end
Chris@0 362
Chris@0 363 # If called with no parameters, gets whether the current model has changed and needs to be versioned.
Chris@0 364 # If called with a single parameter, gets whether the parameter has changed.
Chris@0 365 def changed?(attr_name = nil)
Chris@0 366 attr_name.nil? ?
Chris@0 367 (!self.class.track_altered_attributes || (altered_attributes && altered_attributes.length > 0)) :
Chris@0 368 (altered_attributes && altered_attributes.include?(attr_name.to_s))
Chris@0 369 end
Chris@0 370
Chris@0 371 # keep old dirty? method
Chris@0 372 alias_method :dirty?, :changed?
Chris@0 373
Chris@0 374 # Clones a model. Used when saving a new version or reverting a model's version.
Chris@0 375 def clone_versioned_model(orig_model, new_model)
Chris@0 376 self.versioned_attributes.each do |key|
Chris@0 377 new_model.send("#{key}=", orig_model.send(key)) if orig_model.has_attribute?(key)
Chris@0 378 end
Chris@0 379
Chris@0 380 if orig_model.is_a?(self.class.versioned_class)
Chris@0 381 new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
Chris@0 382 elsif new_model.is_a?(self.class.versioned_class)
Chris@0 383 new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
Chris@0 384 end
Chris@0 385 end
Chris@0 386
Chris@0 387 # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
Chris@0 388 def save_version?
Chris@0 389 version_condition_met? && changed?
Chris@0 390 end
Chris@0 391
Chris@0 392 # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
Chris@0 393 # custom version condition checking.
Chris@0 394 def version_condition_met?
Chris@0 395 case
Chris@0 396 when version_condition.is_a?(Symbol)
Chris@0 397 send(version_condition)
Chris@0 398 when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
Chris@0 399 version_condition.call(self)
Chris@0 400 else
Chris@0 401 version_condition
Chris@0 402 end
Chris@0 403 end
Chris@0 404
Chris@0 405 # Executes the block with the versioning callbacks disabled.
Chris@0 406 #
Chris@0 407 # @foo.without_revision do
Chris@0 408 # @foo.save
Chris@0 409 # end
Chris@0 410 #
Chris@0 411 def without_revision(&block)
Chris@0 412 self.class.without_revision(&block)
Chris@0 413 end
Chris@0 414
Chris@0 415 # Turns off optimistic locking for the duration of the block
Chris@0 416 #
Chris@0 417 # @foo.without_locking do
Chris@0 418 # @foo.save
Chris@0 419 # end
Chris@0 420 #
Chris@0 421 def without_locking(&block)
Chris@0 422 self.class.without_locking(&block)
Chris@0 423 end
Chris@0 424
Chris@0 425 def empty_callback() end #:nodoc:
Chris@0 426
Chris@0 427 protected
Chris@0 428 # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
Chris@0 429 def set_new_version
Chris@0 430 self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?)
Chris@0 431 end
Chris@0 432
Chris@0 433 # Gets the next available version for the current record, or 1 for a new record
Chris@0 434 def next_version
Chris@0 435 return 1 if new_record?
Chris@0 436 (versions.calculate(:max, :version) || 0) + 1
Chris@0 437 end
Chris@0 438
Chris@0 439 # clears current changed attributes. Called after save.
Chris@0 440 def clear_altered_attributes
Chris@0 441 self.altered_attributes = []
Chris@0 442 end
Chris@0 443
Chris@0 444 def write_changed_attribute(attr_name, attr_value)
Chris@0 445 # Convert to db type for comparison. Avoids failing Float<=>String comparisons.
Chris@0 446 attr_value_for_db = self.class.columns_hash[attr_name.to_s].type_cast(attr_value)
Chris@0 447 (self.altered_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) || self.send(attr_name) == attr_value_for_db
Chris@0 448 write_attribute(attr_name, attr_value_for_db)
Chris@0 449 end
Chris@0 450
Chris@0 451 module ClassMethods
Chris@0 452 # Finds a specific version of a specific row of this model
Chris@0 453 def find_version(id, version = nil)
Chris@0 454 return find(id) unless version
Chris@0 455
Chris@0 456 conditions = ["#{versioned_foreign_key} = ? AND version = ?", id, version]
Chris@0 457 options = { :conditions => conditions, :limit => 1 }
Chris@0 458
Chris@0 459 if result = find_versions(id, options).first
Chris@0 460 result
Chris@0 461 else
Chris@0 462 raise RecordNotFound, "Couldn't find #{name} with ID=#{id} and VERSION=#{version}"
Chris@0 463 end
Chris@0 464 end
Chris@0 465
Chris@0 466 # Finds versions of a specific model. Takes an options hash like <tt>find</tt>
Chris@0 467 def find_versions(id, options = {})
Chris@0 468 versioned_class.find :all, {
Chris@0 469 :conditions => ["#{versioned_foreign_key} = ?", id],
Chris@0 470 :order => 'version' }.merge(options)
Chris@0 471 end
Chris@0 472
Chris@0 473 # Returns an array of columns that are versioned. See non_versioned_columns
Chris@0 474 def versioned_columns
Chris@0 475 self.columns.select { |c| !non_versioned_columns.include?(c.name) }
Chris@0 476 end
Chris@0 477
Chris@0 478 # Returns an instance of the dynamic versioned model
Chris@0 479 def versioned_class
Chris@0 480 const_get versioned_class_name
Chris@0 481 end
Chris@0 482
Chris@0 483 # Rake migration task to create the versioned table using options passed to acts_as_versioned
Chris@0 484 def create_versioned_table(create_table_options = {})
Chris@0 485 # create version column in main table if it does not exist
Chris@0 486 if !self.content_columns.find { |c| %w(version lock_version).include? c.name }
Chris@0 487 self.connection.add_column table_name, :version, :integer
Chris@0 488 end
Chris@0 489
Chris@0 490 self.connection.create_table(versioned_table_name, create_table_options) do |t|
Chris@0 491 t.column versioned_foreign_key, :integer
Chris@0 492 t.column :version, :integer
Chris@0 493 end
Chris@0 494
Chris@0 495 updated_col = nil
Chris@0 496 self.versioned_columns.each do |col|
Chris@0 497 updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name)
Chris@0 498 self.connection.add_column versioned_table_name, col.name, col.type,
Chris@0 499 :limit => col.limit,
Chris@0 500 :default => col.default,
Chris@0 501 :scale => col.scale,
Chris@0 502 :precision => col.precision
Chris@0 503 end
Chris@0 504
Chris@0 505 if type_col = self.columns_hash[inheritance_column]
Chris@0 506 self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
Chris@0 507 :limit => type_col.limit,
Chris@0 508 :default => type_col.default,
Chris@0 509 :scale => type_col.scale,
Chris@0 510 :precision => type_col.precision
Chris@0 511 end
Chris@0 512
Chris@0 513 if updated_col.nil?
Chris@0 514 self.connection.add_column versioned_table_name, :updated_at, :timestamp
Chris@0 515 end
Chris@0 516 end
Chris@0 517
Chris@0 518 # Rake migration task to drop the versioned table
Chris@0 519 def drop_versioned_table
Chris@0 520 self.connection.drop_table versioned_table_name
Chris@0 521 end
Chris@0 522
Chris@0 523 # Executes the block with the versioning callbacks disabled.
Chris@0 524 #
Chris@0 525 # Foo.without_revision do
Chris@0 526 # @foo.save
Chris@0 527 # end
Chris@0 528 #
Chris@0 529 def without_revision(&block)
Chris@0 530 class_eval do
Chris@0 531 CALLBACKS.each do |attr_name|
Chris@0 532 alias_method "orig_#{attr_name}".to_sym, attr_name
Chris@0 533 alias_method attr_name, :empty_callback
Chris@0 534 end
Chris@0 535 end
Chris@0 536 block.call
Chris@0 537 ensure
Chris@0 538 class_eval do
Chris@0 539 CALLBACKS.each do |attr_name|
Chris@0 540 alias_method attr_name, "orig_#{attr_name}".to_sym
Chris@0 541 end
Chris@0 542 end
Chris@0 543 end
Chris@0 544
Chris@0 545 # Turns off optimistic locking for the duration of the block
Chris@0 546 #
Chris@0 547 # Foo.without_locking do
Chris@0 548 # @foo.save
Chris@0 549 # end
Chris@0 550 #
Chris@0 551 def without_locking(&block)
Chris@0 552 current = ActiveRecord::Base.lock_optimistically
Chris@0 553 ActiveRecord::Base.lock_optimistically = false if current
Chris@0 554 result = block.call
Chris@0 555 ActiveRecord::Base.lock_optimistically = true if current
Chris@0 556 result
Chris@0 557 end
Chris@0 558 end
Chris@0 559 end
Chris@0 560 end
Chris@0 561 end
Chris@0 562 end
Chris@0 563
Chris@0 564 ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned