acts_as_versioned_rails3 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,496 @@
1
+ # Copyright (c) 2005 Rick Olson
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ require 'active_support/concern'
22
+
23
+ module ActiveRecord #:nodoc:
24
+ module Acts #:nodoc:
25
+ # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
26
+ # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
27
+ # column is present as well.
28
+ #
29
+ # 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
30
+ # your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
31
+ #
32
+ # class Page < ActiveRecord::Base
33
+ # # assumes pages_versions table
34
+ # acts_as_versioned
35
+ # end
36
+ #
37
+ # Example:
38
+ #
39
+ # page = Page.create(:title => 'hello world!')
40
+ # page.version # => 1
41
+ #
42
+ # page.title = 'hello world'
43
+ # page.save
44
+ # page.version # => 2
45
+ # page.versions.size # => 2
46
+ #
47
+ # page.revert_to(1) # using version number
48
+ # page.title # => 'hello world!'
49
+ #
50
+ # page.revert_to(page.versions.last) # using versioned instance
51
+ # page.title # => 'hello world'
52
+ #
53
+ # page.versions.earliest # efficient query to find the first version
54
+ # page.versions.latest # efficient query to find the most recently created version
55
+ #
56
+ #
57
+ # Simple Queries to page between versions
58
+ #
59
+ # page.versions.before(version)
60
+ # page.versions.after(version)
61
+ #
62
+ # Access the previous/next versions from the versioned model itself
63
+ #
64
+ # version = page.versions.latest
65
+ # version.previous # go back one version
66
+ # version.next # go forward one version
67
+ #
68
+ # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
69
+ module Versioned
70
+ VERSION = "0.6.0"
71
+ CALLBACKS = [:set_new_version, :save_version, :save_version?]
72
+
73
+ # == Configuration options
74
+ #
75
+ # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
76
+ # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
77
+ # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
78
+ # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
79
+ # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
80
+ # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
81
+ # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
82
+ # * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
83
+ # For finer control, pass either a Proc or modify Model#version_condition_met?
84
+ #
85
+ # acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
86
+ #
87
+ # or...
88
+ #
89
+ # class Auction
90
+ # def version_condition_met? # totally bypasses the <tt>:if</tt> option
91
+ # !expired?
92
+ # end
93
+ # end
94
+ #
95
+ # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
96
+ # either a symbol or array of symbols.
97
+ #
98
+ # * <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
99
+ # to create an anonymous mixin:
100
+ #
101
+ # class Auction
102
+ # acts_as_versioned do
103
+ # def started?
104
+ # !started_at.nil?
105
+ # end
106
+ # end
107
+ # end
108
+ #
109
+ # or...
110
+ #
111
+ # module AuctionExtension
112
+ # def started?
113
+ # !started_at.nil?
114
+ # end
115
+ # end
116
+ # class Auction
117
+ # acts_as_versioned :extend => AuctionExtension
118
+ # end
119
+ #
120
+ # Example code:
121
+ #
122
+ # @auction = Auction.find(1)
123
+ # @auction.started?
124
+ # @auction.versions.first.started?
125
+ #
126
+ # == Database Schema
127
+ #
128
+ # The model that you're versioning needs to have a 'version' attribute. The model is versioned
129
+ # into a table called #{model}_versions where the model name is singlular. The _versions table should
130
+ # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
131
+ #
132
+ # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
133
+ # then that field is reflected in the versioned model as 'versioned_type' by default.
134
+ #
135
+ # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
136
+ # method, perfect for a migration. It will also create the version column if the main model does not already have it.
137
+ #
138
+ # class AddVersions < ActiveRecord::Migration
139
+ # def self.up
140
+ # # create_versioned_table takes the same options hash
141
+ # # that create_table does
142
+ # Post.create_versioned_table
143
+ # end
144
+ #
145
+ # def self.down
146
+ # Post.drop_versioned_table
147
+ # end
148
+ # end
149
+ #
150
+ # == Changing What Fields Are Versioned
151
+ #
152
+ # By default, acts_as_versioned will version all but these fields:
153
+ #
154
+ # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
155
+ #
156
+ # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
157
+ #
158
+ # class Post < ActiveRecord::Base
159
+ # acts_as_versioned
160
+ # self.non_versioned_columns << 'comments_count'
161
+ # end
162
+ #
163
+ def acts_as_versioned(options = {}, &extension)
164
+ # don't allow multiple calls
165
+ return if self.included_modules.include?(ActiveRecord::Acts::Versioned::Behaviors)
166
+
167
+ cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
168
+ :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
169
+ :version_association_options, :version_if_changed
170
+
171
+ self.versioned_class_name = options[:class_name] || "Version"
172
+ self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
173
+ self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
174
+ self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
175
+ self.version_column = options[:version_column] || 'version'
176
+ self.version_sequence_name = options[:sequence_name]
177
+ self.max_version_limit = options[:limit].to_i
178
+ self.version_condition = options[:if] || true
179
+ self.non_versioned_columns = [self.primary_key, inheritance_column, self.version_column, 'lock_version', versioned_inheritance_column] + options[:non_versioned_columns].to_a.map(&:to_s)
180
+ self.version_association_options = {
181
+ :class_name => "#{self.to_s}::#{versioned_class_name}",
182
+ :foreign_key => versioned_foreign_key,
183
+ :dependent => :delete_all
184
+ }.merge(options[:association_options] || {})
185
+
186
+ if block_given?
187
+ extension_module_name = "#{versioned_class_name}Extension"
188
+ silence_warnings do
189
+ self.const_set(extension_module_name, Module.new(&extension))
190
+ end
191
+
192
+ options[:extend] = self.const_get(extension_module_name)
193
+ end
194
+
195
+ unless options[:if_changed].nil?
196
+ self.track_altered_attributes = true
197
+ options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
198
+ self.version_if_changed = options[:if_changed].map(&:to_s)
199
+ end
200
+
201
+ include options[:extend] if options[:extend].is_a?(Module)
202
+
203
+ include ActiveRecord::Acts::Versioned::Behaviors
204
+
205
+ #
206
+ # Create the dynamic versioned model
207
+ #
208
+ const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
209
+ def self.reloadable?;
210
+ false;
211
+ end
212
+
213
+ # find first version before the given version
214
+ def self.before(version)
215
+ where(["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]).
216
+ order('version DESC').
217
+ first
218
+ end
219
+
220
+ # find first version after the given version.
221
+ def self.after(version)
222
+ where(["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]).
223
+ order('version ASC').
224
+ first
225
+ end
226
+
227
+ # finds earliest version of this record
228
+ def self.earliest
229
+ order("#{original_class.version_column}").first
230
+ end
231
+
232
+ # find latest version of this record
233
+ def self.latest
234
+ order("#{original_class.version_column} desc").first
235
+ end
236
+
237
+ def previous
238
+ self.class.before(self)
239
+ end
240
+
241
+ def next
242
+ self.class.after(self)
243
+ end
244
+
245
+ def versions_count
246
+ page.version
247
+ end
248
+ end
249
+
250
+ versioned_class.cattr_accessor :original_class
251
+ versioned_class.original_class = self
252
+ versioned_class.table_name = versioned_table_name
253
+ versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
254
+ :class_name => "::#{self.to_s}",
255
+ :foreign_key => versioned_foreign_key
256
+ versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
257
+ versioned_class.sequence_name = version_sequence_name if version_sequence_name
258
+ end
259
+
260
+ module Behaviors
261
+ extend ActiveSupport::Concern
262
+
263
+ included do
264
+ has_many :versions, self.version_association_options
265
+
266
+ before_save :set_new_version
267
+ after_save :save_version
268
+ after_save :clear_old_versions
269
+ end
270
+
271
+ # Saves a version of the model in the versioned table. This is called in the after_save callback by default
272
+ def save_version
273
+ if @saving_version
274
+ @saving_version = nil
275
+ rev = self.class.versioned_class.new
276
+ clone_versioned_model(self, rev)
277
+ rev.send("#{self.class.version_column}=", send(self.class.version_column))
278
+ rev.send("#{self.class.versioned_foreign_key}=", id)
279
+ rev.save
280
+ end
281
+ end
282
+
283
+ # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
284
+ # Override this method to set your own criteria for clearing old versions.
285
+ def clear_old_versions
286
+ return if self.class.max_version_limit == 0
287
+ excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
288
+ if excess_baggage > 0
289
+ self.class.versioned_class.delete_all ["#{self.class.version_column} <= ? and #{self.class.versioned_foreign_key} = ?", excess_baggage, id]
290
+ end
291
+ end
292
+
293
+ # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
294
+ def revert_to(version)
295
+ if version.is_a?(self.class.versioned_class)
296
+ return false unless version.send(self.class.versioned_foreign_key) == id and !version.new_record?
297
+ else
298
+ return false unless version = versions.where(self.class.version_column => version).first
299
+ end
300
+ self.clone_versioned_model(version, self)
301
+ send("#{self.class.version_column}=", version.send(self.class.version_column))
302
+ true
303
+ end
304
+
305
+ # Reverts a model to a given version and saves the model.
306
+ # Takes either a version number or an instance of the versioned model
307
+ def revert_to!(version)
308
+ revert_to(version) ? save_without_revision : false
309
+ end
310
+
311
+ # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
312
+ def save_without_revision
313
+ save_without_revision!
314
+ true
315
+ rescue
316
+ false
317
+ end
318
+
319
+ def save_without_revision!
320
+ without_locking do
321
+ without_revision do
322
+ save!
323
+ end
324
+ end
325
+ end
326
+
327
+ def altered?
328
+ track_altered_attributes ? (version_if_changed - changed).length < version_if_changed.length : changed?
329
+ end
330
+
331
+ # Clones a model. Used when saving a new version or reverting a model's version.
332
+ def clone_versioned_model(orig_model, new_model)
333
+ self.class.versioned_columns.each do |col|
334
+ new_model[col.name] = orig_model.send(col.name) if orig_model.has_attribute?(col.name)
335
+ end
336
+
337
+ clone_inheritance_column(orig_model, new_model)
338
+ end
339
+
340
+ def clone_inheritance_column(orig_model, new_model)
341
+ if orig_model.is_a?(self.class.versioned_class) && new_model.class.column_names.include?(new_model.class.inheritance_column.to_s)
342
+ new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
343
+ elsif new_model.is_a?(self.class.versioned_class) && new_model.class.column_names.include?(self.class.versioned_inheritance_column.to_s)
344
+ new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
345
+ end
346
+ end
347
+
348
+ # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
349
+ def save_version?
350
+ version_condition_met? && altered?
351
+ end
352
+
353
+ # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
354
+ # custom version condition checking.
355
+ def version_condition_met?
356
+ case
357
+ when version_condition.is_a?(Symbol)
358
+ send(version_condition)
359
+ when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
360
+ version_condition.call(self)
361
+ else
362
+ version_condition
363
+ end
364
+ end
365
+
366
+ # Executes the block with the versioning callbacks disabled.
367
+ #
368
+ # @foo.without_revision do
369
+ # @foo.save
370
+ # end
371
+ #
372
+ def without_revision(&block)
373
+ self.class.without_revision(&block)
374
+ end
375
+
376
+ # Turns off optimistic locking for the duration of the block
377
+ #
378
+ # @foo.without_locking do
379
+ # @foo.save
380
+ # end
381
+ #
382
+ def without_locking(&block)
383
+ self.class.without_locking(&block)
384
+ end
385
+
386
+ def empty_callback()
387
+ end
388
+
389
+ #:nodoc:
390
+
391
+ protected
392
+ # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
393
+ def set_new_version
394
+ @saving_version = new_record? || save_version?
395
+ self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?)
396
+ end
397
+
398
+ # Gets the next available version for the current record, or 1 for a new record
399
+ def next_version
400
+ (new_record? ? 0 : versions.calculate(:maximum, version_column).to_i) + 1
401
+ end
402
+
403
+ module ClassMethods
404
+ # Returns an array of columns that are versioned. See non_versioned_columns
405
+ def versioned_columns
406
+ @versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) }
407
+ end
408
+
409
+ # Returns an instance of the dynamic versioned model
410
+ def versioned_class
411
+ const_get versioned_class_name
412
+ end
413
+
414
+ # Rake migration task to create the versioned table using options passed to acts_as_versioned
415
+ def create_versioned_table(create_table_options = {})
416
+ # create version column in main table if it does not exist
417
+ if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name }
418
+ self.connection.add_column table_name, version_column, :integer
419
+ self.reset_column_information
420
+ end
421
+
422
+ return if connection.table_exists?(versioned_table_name)
423
+
424
+ self.connection.create_table(versioned_table_name, create_table_options) do |t|
425
+ t.column versioned_foreign_key, :integer
426
+ t.column version_column, :integer
427
+ end
428
+
429
+ self.versioned_columns.each do |col|
430
+ self.connection.add_column versioned_table_name, col.name, col.type,
431
+ :limit => col.limit,
432
+ :default => col.default,
433
+ :scale => col.scale,
434
+ :precision => col.precision
435
+ end
436
+
437
+ if type_col = self.columns_hash[inheritance_column]
438
+ self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
439
+ :limit => type_col.limit,
440
+ :default => type_col.default,
441
+ :scale => type_col.scale,
442
+ :precision => type_col.precision
443
+ end
444
+
445
+ self.connection.add_index versioned_table_name, versioned_foreign_key
446
+ end
447
+
448
+ # Rake migration task to drop the versioned table
449
+ def drop_versioned_table
450
+ self.connection.drop_table versioned_table_name
451
+ end
452
+
453
+ # Executes the block with the versioning callbacks disabled.
454
+ #
455
+ # Foo.without_revision do
456
+ # @foo.save
457
+ # end
458
+ #
459
+ def without_revision(&block)
460
+ class_eval do
461
+ CALLBACKS.each do |attr_name|
462
+ alias_method "orig_#{attr_name}".to_sym, attr_name
463
+ alias_method attr_name, :empty_callback
464
+ end
465
+ end
466
+ block.call
467
+ ensure
468
+ class_eval do
469
+ CALLBACKS.each do |attr_name|
470
+ alias_method attr_name, "orig_#{attr_name}".to_sym
471
+ end
472
+ end
473
+ end
474
+
475
+ # Turns off optimistic locking for the duration of the block
476
+ #
477
+ # Foo.without_locking do
478
+ # @foo.save
479
+ # end
480
+ #
481
+ def without_locking(&block)
482
+ current = ActiveRecord::Base.lock_optimistically
483
+ ActiveRecord::Base.lock_optimistically = false if current
484
+ begin
485
+ block.call
486
+ ensure
487
+ ActiveRecord::Base.lock_optimistically = true if current
488
+ end
489
+ end
490
+ end
491
+ end
492
+ end
493
+ end
494
+ end
495
+
496
+ ActiveRecord::Base.extend ActiveRecord::Acts::Versioned
@@ -0,0 +1,49 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup(:default, :development)
4
+
5
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
6
+ require 'test/unit'
7
+ require 'active_support'
8
+ require 'active_record'
9
+ require 'active_record/fixtures'
10
+ require 'active_record/test_case'
11
+
12
+ begin
13
+ require 'ruby-debug'
14
+ Debugger.start
15
+ rescue LoadError
16
+ end
17
+
18
+ require 'acts_as_versioned'
19
+
20
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
21
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
22
+ ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
23
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
24
+
25
+ load(File.dirname(__FILE__) + "/schema.rb")
26
+
27
+ # set up custom sequence on widget_versions for DBs that support sequences
28
+ if ENV['DB'] == 'postgresql'
29
+ ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil
30
+ ActiveRecord::Base.connection.remove_column :widget_versions, :id
31
+ ActiveRecord::Base.connection.execute "CREATE SEQUENCE widgets_seq START 101;"
32
+ ActiveRecord::Base.connection.execute "ALTER TABLE widget_versions ADD COLUMN id INTEGER PRIMARY KEY DEFAULT nextval('widgets_seq');"
33
+ end
34
+
35
+ class ActiveSupport::TestCase #:nodoc:
36
+ include ActiveRecord::TestFixtures
37
+
38
+ self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
39
+
40
+ # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
41
+ self.use_transactional_fixtures = true
42
+
43
+ # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
44
+ self.use_instantiated_fixtures = false
45
+
46
+ # Add more helper methods to be used by all tests here...
47
+ end
48
+
49
+ $:.unshift(ActiveSupport::TestCase.fixture_path)
data/test/database.yml ADDED
@@ -0,0 +1,18 @@
1
+ sqlite:
2
+ adapter: sqlite
3
+ dbfile: acts_as_versioned_plugin.sqlite.db
4
+ sqlite3:
5
+ adapter: sqlite3
6
+ database: acts_as_versioned_plugin.sqlite3.db
7
+ postgresql:
8
+ adapter: postgresql
9
+ username: postgres
10
+ password: postgres
11
+ database: acts_as_versioned_plugin_test
12
+ min_messages: ERROR
13
+ mysql:
14
+ adapter: mysql
15
+ host: localhost
16
+ username: rails
17
+ password:
18
+ database: acts_as_versioned_plugin_test
@@ -0,0 +1,6 @@
1
+ caged:
2
+ id: 1
3
+ name: caged
4
+ mly:
5
+ id: 2
6
+ name: mly
@@ -0,0 +1,3 @@
1
+ class Landmark < ActiveRecord::Base
2
+ acts_as_versioned :if_changed => [ :name, :longitude, :latitude ]
3
+ end
@@ -0,0 +1,7 @@
1
+ washington:
2
+ id: 1
3
+ landmark_id: 1
4
+ version: 1
5
+ name: Washington, D.C.
6
+ latitude: 38.895
7
+ longitude: -77.036667
@@ -0,0 +1,7 @@
1
+ washington:
2
+ id: 1
3
+ name: Washington, D.C.
4
+ latitude: 38.895
5
+ longitude: -77.036667
6
+ doesnt_trigger_version: This is not important
7
+ version: 1
@@ -0,0 +1,10 @@
1
+ welcome:
2
+ id: 1
3
+ title: Welcome to the weblog
4
+ lock_version: 24
5
+ type: LockedPage
6
+ thinking:
7
+ id: 2
8
+ title: So I was thinking
9
+ lock_version: 24
10
+ type: SpecialLockedPage
@@ -0,0 +1,27 @@
1
+ welcome_1:
2
+ id: 1
3
+ page_id: 1
4
+ title: Welcome to the weblg
5
+ lock_version: 23
6
+ version_type: LockedPage
7
+
8
+ welcome_2:
9
+ id: 2
10
+ page_id: 1
11
+ title: Welcome to the weblog
12
+ lock_version: 24
13
+ version_type: LockedPage
14
+
15
+ thinking_1:
16
+ id: 3
17
+ page_id: 2
18
+ title: So I was thinking!!!
19
+ lock_version: 23
20
+ version_type: SpecialLockedPage
21
+
22
+ thinking_2:
23
+ id: 4
24
+ page_id: 2
25
+ title: So I was thinking
26
+ lock_version: 24
27
+ version_type: SpecialLockedPage
@@ -0,0 +1,15 @@
1
+ class AddVersionedTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table("things") do |t|
4
+ t.column :title, :text
5
+ t.column :price, :decimal, :precision => 7, :scale => 2
6
+ t.column :type, :string
7
+ end
8
+ Thing.create_versioned_table
9
+ end
10
+
11
+ def self.down
12
+ Thing.drop_versioned_table
13
+ drop_table "things" rescue nil
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ class Page < ActiveRecord::Base
2
+ belongs_to :author
3
+ has_many :authors, :through => :versions, :order => 'name'
4
+ belongs_to :revisor, :class_name => 'Author'
5
+ has_many :revisors, :class_name => 'Author', :through => :versions, :order => 'name'
6
+ acts_as_versioned :if => :feeling_good? do
7
+ def self.included(base)
8
+ base.cattr_accessor :feeling_good
9
+ base.feeling_good = true
10
+ base.belongs_to :author
11
+ base.belongs_to :revisor, :class_name => 'Author'
12
+ end
13
+
14
+ def feeling_good?
15
+ @@feeling_good == true
16
+ end
17
+ end
18
+ end
19
+
20
+ module LockedPageExtension
21
+ def hello_world
22
+ 'hello_world'
23
+ end
24
+ end
25
+
26
+ class LockedPage < ActiveRecord::Base
27
+ acts_as_versioned \
28
+ :inheritance_column => :version_type,
29
+ :foreign_key => :page_id,
30
+ :table_name => :locked_pages_revisions,
31
+ :class_name => 'LockedPageRevision',
32
+ :version_column => :lock_version,
33
+ :limit => 2,
34
+ :if_changed => :title,
35
+ :extend => LockedPageExtension
36
+ end
37
+
38
+ class SpecialLockedPage < LockedPage
39
+ end
40
+
41
+ class Author < ActiveRecord::Base
42
+ has_many :pages
43
+ end