msales-acts_as_versioned 0.5.2

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,4 @@
1
+ ---
2
+ :patch: 2
3
+ :major: 0
4
+ :minor: 5
@@ -0,0 +1,486 @@
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
+
22
+ module ActiveRecord #:nodoc:
23
+ module Acts #:nodoc:
24
+ # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
25
+ # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
26
+ # column is present as well.
27
+ #
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
29
+ # your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
30
+ #
31
+ # class Page < ActiveRecord::Base
32
+ # # assumes pages_versions table
33
+ # acts_as_versioned
34
+ # end
35
+ #
36
+ # Example:
37
+ #
38
+ # page = Page.create(:title => 'hello world!')
39
+ # page.version # => 1
40
+ #
41
+ # page.title = 'hello world'
42
+ # page.save
43
+ # page.version # => 2
44
+ # page.versions.size # => 2
45
+ #
46
+ # page.revert_to(1) # using version number
47
+ # page.title # => 'hello world!'
48
+ #
49
+ # page.revert_to(page.versions.last) # using versioned instance
50
+ # page.title # => 'hello world'
51
+ #
52
+ # page.versions.earliest # efficient query to find the first version
53
+ # page.versions.latest # efficient query to find the most recently created version
54
+ #
55
+ #
56
+ # Simple Queries to page between versions
57
+ #
58
+ # page.versions.before(version)
59
+ # page.versions.after(version)
60
+ #
61
+ # Access the previous/next versions from the versioned model itself
62
+ #
63
+ # version = page.versions.latest
64
+ # version.previous # go back one version
65
+ # version.next # go forward one version
66
+ #
67
+ # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
68
+ module Versioned
69
+ CALLBACKS = [:set_new_version, :save_version, :save_version?]
70
+ def self.included(base) # :nodoc:
71
+ base.extend ClassMethods
72
+ end
73
+
74
+ module ClassMethods
75
+ # == Configuration options
76
+ #
77
+ # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
78
+ # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
79
+ # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
80
+ # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
81
+ # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
82
+ # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
83
+ # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
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.
85
+ # For finer control, pass either a Proc or modify Model#version_condition_met?
86
+ #
87
+ # acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
88
+ #
89
+ # or...
90
+ #
91
+ # class Auction
92
+ # def version_condition_met? # totally bypasses the <tt>:if</tt> option
93
+ # !expired?
94
+ # end
95
+ # end
96
+ #
97
+ # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
98
+ # either a symbol or array of symbols.
99
+ #
100
+ # * <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
101
+ # to create an anonymous mixin:
102
+ #
103
+ # class Auction
104
+ # acts_as_versioned do
105
+ # def started?
106
+ # !started_at.nil?
107
+ # end
108
+ # end
109
+ # end
110
+ #
111
+ # or...
112
+ #
113
+ # module AuctionExtension
114
+ # def started?
115
+ # !started_at.nil?
116
+ # end
117
+ # end
118
+ # class Auction
119
+ # acts_as_versioned :extend => AuctionExtension
120
+ # end
121
+ #
122
+ # Example code:
123
+ #
124
+ # @auction = Auction.find(1)
125
+ # @auction.started?
126
+ # @auction.versions.first.started?
127
+ #
128
+ # == Database Schema
129
+ #
130
+ # The model that you're versioning needs to have a 'version' attribute. The model is versioned
131
+ # into a table called #{model}_versions where the model name is singlular. The _versions table should
132
+ # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
133
+ #
134
+ # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
135
+ # then that field is reflected in the versioned model as 'versioned_type' by default.
136
+ #
137
+ # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
138
+ # method, perfect for a migration. It will also create the version column if the main model does not already have it.
139
+ #
140
+ # class AddVersions < ActiveRecord::Migration
141
+ # def self.up
142
+ # # create_versioned_table takes the same options hash
143
+ # # that create_table does
144
+ # Post.create_versioned_table
145
+ # end
146
+ #
147
+ # def self.down
148
+ # Post.drop_versioned_table
149
+ # end
150
+ # end
151
+ #
152
+ # == Changing What Fields Are Versioned
153
+ #
154
+ # By default, acts_as_versioned will version all but these fields:
155
+ #
156
+ # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
157
+ #
158
+ # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
159
+ #
160
+ # class Post < ActiveRecord::Base
161
+ # acts_as_versioned
162
+ # self.non_versioned_columns << 'comments_count'
163
+ # end
164
+ #
165
+ def acts_as_versioned(options = {}, &extension)
166
+ # don't allow multiple calls
167
+ return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
168
+
169
+ send :include, ActiveRecord::Acts::Versioned::ActMethods
170
+
171
+ cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
172
+ :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
173
+ :version_association_options, :version_if_changed
174
+
175
+ self.versioned_class_name = options[:class_name] || "Version"
176
+ self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
177
+ self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
178
+ self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
179
+ self.version_column = options[:version_column] || 'version'
180
+ self.version_sequence_name = options[:sequence_name]
181
+ self.max_version_limit = options[:limit].to_i
182
+ self.version_condition = options[:if] || true
183
+ 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)
184
+ self.version_association_options = {
185
+ :class_name => "#{self.to_s}::#{versioned_class_name}",
186
+ :foreign_key => versioned_foreign_key,
187
+ :dependent => :delete_all
188
+ }.merge(options[:association_options] || {})
189
+
190
+ if block_given?
191
+ extension_module_name = "#{versioned_class_name}Extension"
192
+ silence_warnings do
193
+ self.const_set(extension_module_name, Module.new(&extension))
194
+ end
195
+
196
+ options[:extend] = self.const_get(extension_module_name)
197
+ end
198
+
199
+ class_eval <<-CLASS_METHODS
200
+ has_many :versions, version_association_options do
201
+ # finds earliest version of this record
202
+ def earliest
203
+ @earliest ||= find(:first, :order => '#{version_column}')
204
+ end
205
+
206
+ # find latest version of this record
207
+ def latest
208
+ @latest ||= find(:first, :order => '#{version_column} desc')
209
+ end
210
+ end
211
+ before_save :set_new_version
212
+ after_save :save_version
213
+ after_save :clear_old_versions
214
+
215
+ unless options[:if_changed].nil?
216
+ self.track_altered_attributes = true
217
+ options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
218
+ self.version_if_changed = options[:if_changed].map(&:to_s)
219
+ end
220
+
221
+ include options[:extend] if options[:extend].is_a?(Module)
222
+ CLASS_METHODS
223
+
224
+ # create the dynamic versioned model
225
+ const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
226
+ def self.reloadable? ; false ; end
227
+ # find first version before the given version
228
+ def self.before(version)
229
+ find :first, :order => 'version desc',
230
+ :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]
231
+ end
232
+
233
+ # find first version after the given version.
234
+ def self.after(version)
235
+ find :first, :order => 'version',
236
+ :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]
237
+ end
238
+
239
+ def previous
240
+ self.class.before(self)
241
+ end
242
+
243
+ def next
244
+ self.class.after(self)
245
+ end
246
+
247
+ def versions_count
248
+ page.version
249
+ end
250
+ end
251
+
252
+ versioned_class.cattr_accessor :original_class
253
+ versioned_class.original_class = self
254
+ versioned_class.set_table_name versioned_table_name
255
+ versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
256
+ :class_name => "::#{self.to_s}",
257
+ :foreign_key => versioned_foreign_key
258
+ versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
259
+ versioned_class.set_sequence_name version_sequence_name if version_sequence_name
260
+ end
261
+ end
262
+
263
+ module ActMethods
264
+ def self.included(base) # :nodoc:
265
+ base.extend ClassMethods
266
+ end
267
+
268
+ # Saves a version of the model in the versioned table. This is called in the after_save callback by default
269
+ def save_version
270
+ if @saving_version
271
+ @saving_version = nil
272
+ rev = self.class.versioned_class.new
273
+ clone_versioned_model(self, rev)
274
+ rev.send("#{self.class.version_column}=", send(self.class.version_column))
275
+ rev.send("#{self.class.versioned_foreign_key}=", id)
276
+ rev.save
277
+ end
278
+ end
279
+
280
+ # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
281
+ # Override this method to set your own criteria for clearing old versions.
282
+ def clear_old_versions
283
+ return if self.class.max_version_limit == 0
284
+ excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
285
+ if excess_baggage > 0
286
+ self.class.versioned_class.delete_all ["#{self.class.version_column} <= ? and #{self.class.versioned_foreign_key} = ?", excess_baggage, id]
287
+ end
288
+ end
289
+
290
+ # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
291
+ def revert_to(version)
292
+ if version.is_a?(self.class.versioned_class)
293
+ return false unless version.send(self.class.versioned_foreign_key) == id and !version.new_record?
294
+ else
295
+ return false unless version = versions.send("find_by_#{self.class.version_column}", version)
296
+ end
297
+ self.clone_versioned_model(version, self)
298
+ send("#{self.class.version_column}=", version.send(self.class.version_column))
299
+ true
300
+ end
301
+
302
+ # Reverts a model to a given version and saves the model.
303
+ # Takes either a version number or an instance of the versioned model
304
+ def revert_to!(version)
305
+ revert_to(version) ? save_without_revision : false
306
+ end
307
+
308
+ # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
309
+ def save_without_revision
310
+ save_without_revision!
311
+ true
312
+ rescue
313
+ false
314
+ end
315
+
316
+ def save_without_revision!
317
+ without_locking do
318
+ without_revision do
319
+ save!
320
+ end
321
+ end
322
+ end
323
+
324
+ def altered?
325
+ track_altered_attributes ? (version_if_changed - changed).length < version_if_changed.length : changed?
326
+ end
327
+
328
+ # Clones a model. Used when saving a new version or reverting a model's version.
329
+ def clone_versioned_model(orig_model, new_model)
330
+ self.class.versioned_columns.each do |col|
331
+ new_model.send("#{col.name}=", orig_model.send(col.name)) if orig_model.has_attribute?(col.name)
332
+ end
333
+
334
+ if orig_model.is_a?(self.class.versioned_class)
335
+ new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
336
+ elsif new_model.is_a?(self.class.versioned_class)
337
+ new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
338
+ end
339
+ end
340
+
341
+ # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
342
+ def save_version?
343
+ version_condition_met? && altered?
344
+ end
345
+
346
+ # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
347
+ # custom version condition checking.
348
+ def version_condition_met?
349
+ case
350
+ when version_condition.is_a?(Symbol)
351
+ send(version_condition)
352
+ when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
353
+ version_condition.call(self)
354
+ else
355
+ version_condition
356
+ end
357
+ end
358
+
359
+ # Executes the block with the versioning callbacks disabled.
360
+ #
361
+ # @foo.without_revision do
362
+ # @foo.save
363
+ # end
364
+ #
365
+ def without_revision(&block)
366
+ self.class.without_revision(&block)
367
+ end
368
+
369
+ # Turns off optimistic locking for the duration of the block
370
+ #
371
+ # @foo.without_locking do
372
+ # @foo.save
373
+ # end
374
+ #
375
+ def without_locking(&block)
376
+ self.class.without_locking(&block)
377
+ end
378
+
379
+ def empty_callback() end #:nodoc:
380
+
381
+ protected
382
+ # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
383
+ def set_new_version
384
+ @saving_version = new_record? || save_version?
385
+ self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?)
386
+ end
387
+
388
+ # Gets the next available version for the current record, or 1 for a new record
389
+ def next_version
390
+ (new_record? ? 0 : versions.calculate(:max, version_column).to_i) + 1
391
+ end
392
+
393
+ module ClassMethods
394
+ # Returns an array of columns that are versioned. See non_versioned_columns
395
+ def versioned_columns
396
+ @versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) }
397
+ end
398
+
399
+ # Returns an instance of the dynamic versioned model
400
+ def versioned_class
401
+ const_get versioned_class_name
402
+ end
403
+
404
+ # Rake migration task to create the versioned table using options passed to acts_as_versioned
405
+ def create_versioned_table(create_table_options = {})
406
+ # create version column in main table if it does not exist
407
+ if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name }
408
+ self.connection.add_column table_name, version_column, :integer
409
+ self.reset_column_information
410
+ end
411
+
412
+ return if connection.table_exists?(versioned_table_name)
413
+
414
+ self.connection.create_table(versioned_table_name, create_table_options) do |t|
415
+ t.column versioned_foreign_key, :integer
416
+ t.column version_column, :integer
417
+ end
418
+
419
+ self.versioned_columns.each do |col|
420
+ self.connection.add_column versioned_table_name, col.name, col.type,
421
+ :limit => col.limit,
422
+ :default => col.default,
423
+ :scale => col.scale,
424
+ :precision => col.precision
425
+ end
426
+
427
+ if type_col = self.columns_hash[inheritance_column]
428
+ self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
429
+ :limit => type_col.limit,
430
+ :default => type_col.default,
431
+ :scale => type_col.scale,
432
+ :precision => type_col.precision
433
+ end
434
+
435
+ self.connection.add_index versioned_table_name, versioned_foreign_key
436
+ end
437
+
438
+ # Rake migration task to drop the versioned table
439
+ def drop_versioned_table
440
+ self.connection.drop_table versioned_table_name
441
+ end
442
+
443
+ # Executes the block with the versioning callbacks disabled.
444
+ #
445
+ # Foo.without_revision do
446
+ # @foo.save
447
+ # end
448
+ #
449
+ def without_revision(&block)
450
+ class_eval do
451
+ CALLBACKS.each do |attr_name|
452
+ alias_method "orig_#{attr_name}".to_sym, attr_name
453
+ alias_method attr_name, :empty_callback
454
+ end
455
+ end
456
+ block.call
457
+ ensure
458
+ class_eval do
459
+ CALLBACKS.each do |attr_name|
460
+ alias_method attr_name, "orig_#{attr_name}".to_sym
461
+ end
462
+ end
463
+ end
464
+
465
+ # Turns off optimistic locking for the duration of the block
466
+ #
467
+ # Foo.without_locking do
468
+ # @foo.save
469
+ # end
470
+ #
471
+ def without_locking(&block)
472
+ current = ActiveRecord::Base.lock_optimistically
473
+ ActiveRecord::Base.lock_optimistically = false if current
474
+ begin
475
+ block.call
476
+ ensure
477
+ ActiveRecord::Base.lock_optimistically = true if current
478
+ end
479
+ end
480
+ end
481
+ end
482
+ end
483
+ end
484
+ end
485
+
486
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned
@@ -0,0 +1,48 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../../../rails/activesupport/lib')
2
+ $:.unshift(File.dirname(__FILE__) + '/../../../rails/activerecord/lib')
3
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
4
+ require 'test/unit'
5
+ begin
6
+ require 'active_support'
7
+ require 'active_record'
8
+ require 'active_record/fixtures'
9
+ rescue LoadError
10
+ require 'rubygems'
11
+ retry
12
+ end
13
+
14
+ begin
15
+ require 'ruby-debug'
16
+ Debugger.start
17
+ rescue LoadError
18
+ end
19
+
20
+ require 'acts_as_versioned'
21
+
22
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
23
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
24
+ ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
25
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
26
+
27
+ load(File.dirname(__FILE__) + "/schema.rb")
28
+
29
+ # set up custom sequence on widget_versions for DBs that support sequences
30
+ if ENV['DB'] == 'postgresql'
31
+ ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil
32
+ ActiveRecord::Base.connection.remove_column :widget_versions, :id
33
+ ActiveRecord::Base.connection.execute "CREATE SEQUENCE widgets_seq START 101;"
34
+ ActiveRecord::Base.connection.execute "ALTER TABLE widget_versions ADD COLUMN id INTEGER PRIMARY KEY DEFAULT nextval('widgets_seq');"
35
+ end
36
+
37
+ Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
38
+ $:.unshift(Test::Unit::TestCase.fixture_path)
39
+
40
+ class Test::Unit::TestCase #:nodoc:
41
+ # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
42
+ self.use_transactional_fixtures = true
43
+
44
+ # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
45
+ self.use_instantiated_fixtures = false
46
+
47
+ # Add more helper methods to be used by all tests here...
48
+ end
@@ -0,0 +1,18 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: acts_as_versioned_plugin.sqlite.db
4
+ sqlite3:
5
+ :adapter: sqlite3
6
+ :dbfile: 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
@@ -0,0 +1,16 @@
1
+ welcome_2:
2
+ id: 1
3
+ page_id: 1
4
+ title: Welcome to the weblog
5
+ body: Such a lovely day
6
+ version: 24
7
+ author_id: 1
8
+ revisor_id: 1
9
+ welcome_1:
10
+ id: 2
11
+ page_id: 1
12
+ title: Welcome to the weblg
13
+ body: Such a lovely day
14
+ version: 23
15
+ author_id: 2
16
+ revisor_id: 2
@@ -0,0 +1,8 @@
1
+ welcome:
2
+ id: 1
3
+ title: Welcome to the weblog
4
+ body: Such a lovely day
5
+ version: 24
6
+ author_id: 1
7
+ revisor_id: 1
8
+ created_on: "2008-01-01 00:00:00"
@@ -0,0 +1,6 @@
1
+ class Widget < ActiveRecord::Base
2
+ acts_as_versioned :sequence_name => 'widgets_seq', :association_options => {
3
+ :dependent => :nullify, :order => 'version desc'
4
+ }
5
+ non_versioned_columns << 'foo'
6
+ end
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_unit')
2
+
3
+ if ActiveRecord::Base.connection.supports_migrations?
4
+ class Thing < ActiveRecord::Base
5
+ attr_accessor :version
6
+ acts_as_versioned
7
+ end
8
+
9
+ class MigrationTest < Test::Unit::TestCase
10
+ self.use_transactional_fixtures = false
11
+ def teardown
12
+ if ActiveRecord::Base.connection.respond_to?(:initialize_schema_information)
13
+ ActiveRecord::Base.connection.initialize_schema_information
14
+ ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0"
15
+ else
16
+ ActiveRecord::Base.connection.initialize_schema_migrations_table
17
+ ActiveRecord::Base.connection.assume_migrated_upto_version(0)
18
+ end
19
+
20
+ Thing.connection.drop_table "things" rescue nil
21
+ Thing.connection.drop_table "thing_versions" rescue nil
22
+ Thing.reset_column_information
23
+ end
24
+
25
+ def test_versioned_migration
26
+ assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
27
+ # take 'er up
28
+ ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
29
+ t = Thing.create :title => 'blah blah', :price => 123.45, :type => 'Thing'
30
+ assert_equal 1, t.versions.size
31
+
32
+ # check that the price column has remembered its value correctly
33
+ assert_equal t.price, t.versions.first.price
34
+ assert_equal t.title, t.versions.first.title
35
+ assert_equal t[:type], t.versions.first[:type]
36
+
37
+ # make sure that the precision of the price column has been preserved
38
+ assert_equal 7, Thing::Version.columns.find{|c| c.name == "price"}.precision
39
+ assert_equal 2, Thing::Version.columns.find{|c| c.name == "price"}.scale
40
+
41
+ # now lets take 'er back down
42
+ ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/')
43
+ assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,82 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :pages, :force => true do |t|
3
+ t.column :version, :integer
4
+ t.column :title, :string, :limit => 255
5
+ t.column :body, :text
6
+ t.column :created_on, :datetime
7
+ t.column :updated_on, :datetime
8
+ t.column :author_id, :integer
9
+ t.column :revisor_id, :integer
10
+ end
11
+
12
+ create_table :page_versions, :force => true do |t|
13
+ t.column :page_id, :integer
14
+ t.column :version, :integer
15
+ t.column :title, :string, :limit => 255
16
+ t.column :body, :text
17
+ t.column :created_on, :datetime
18
+ t.column :updated_on, :datetime
19
+ t.column :author_id, :integer
20
+ t.column :revisor_id, :integer
21
+ end
22
+
23
+ add_index :page_versions, [:page_id, :version], :unique => true
24
+
25
+ create_table :authors, :force => true do |t|
26
+ t.column :page_id, :integer
27
+ t.column :name, :string
28
+ end
29
+
30
+ create_table :locked_pages, :force => true do |t|
31
+ t.column :lock_version, :integer
32
+ t.column :title, :string, :limit => 255
33
+ t.column :body, :text
34
+ t.column :type, :string, :limit => 255
35
+ end
36
+
37
+ create_table :locked_pages_revisions, :force => true do |t|
38
+ t.column :page_id, :integer
39
+ t.column :lock_version, :integer
40
+ t.column :title, :string, :limit => 255
41
+ t.column :body, :text
42
+ t.column :version_type, :string, :limit => 255
43
+ t.column :updated_at, :datetime
44
+ end
45
+
46
+ add_index :locked_pages_revisions, [:page_id, :lock_version], :unique => true
47
+
48
+ create_table :widgets, :force => true do |t|
49
+ t.column :name, :string, :limit => 50
50
+ t.column :foo, :string
51
+ t.column :version, :integer
52
+ t.column :updated_at, :datetime
53
+ end
54
+
55
+ create_table :widget_versions, :force => true do |t|
56
+ t.column :widget_id, :integer
57
+ t.column :name, :string, :limit => 50
58
+ t.column :version, :integer
59
+ t.column :updated_at, :datetime
60
+ end
61
+
62
+ add_index :widget_versions, [:widget_id, :version], :unique => true
63
+
64
+ create_table :landmarks, :force => true do |t|
65
+ t.column :name, :string
66
+ t.column :latitude, :float
67
+ t.column :longitude, :float
68
+ t.column :doesnt_trigger_version,:string
69
+ t.column :version, :integer
70
+ end
71
+
72
+ create_table :landmark_versions, :force => true do |t|
73
+ t.column :landmark_id, :integer
74
+ t.column :name, :string
75
+ t.column :latitude, :float
76
+ t.column :longitude, :float
77
+ t.column :doesnt_trigger_version,:string
78
+ t.column :version, :integer
79
+ end
80
+
81
+ add_index :landmark_versions, [:landmark_id, :version], :unique => true
82
+ end
@@ -0,0 +1,370 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_unit')
2
+ require File.join(File.dirname(__FILE__), 'fixtures/page')
3
+ require File.join(File.dirname(__FILE__), 'fixtures/widget')
4
+
5
+ class VersionedTest < Test::Unit::TestCase
6
+ fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
7
+ set_fixture_class :page_versions => Page::Version
8
+
9
+ def test_saves_versioned_copy
10
+ p = Page.create! :title => 'first title', :body => 'first body'
11
+ assert !p.new_record?
12
+ assert_equal 1, p.versions.size
13
+ assert_equal 1, p.version
14
+ assert_instance_of Page.versioned_class, p.versions.first
15
+ end
16
+
17
+ def test_saves_without_revision
18
+ p = pages(:welcome)
19
+ old_versions = p.versions.count
20
+
21
+ p.save_without_revision
22
+
23
+ p.without_revision do
24
+ p.update_attributes :title => 'changed'
25
+ end
26
+
27
+ assert_equal old_versions, p.versions.count
28
+ end
29
+
30
+ def test_rollback_with_version_number
31
+ p = pages(:welcome)
32
+ assert_equal 24, p.version
33
+ assert_equal 'Welcome to the weblog', p.title
34
+
35
+ assert p.revert_to!(23), "Couldn't revert to 23"
36
+ assert_equal 23, p.version
37
+ assert_equal 'Welcome to the weblg', p.title
38
+ end
39
+
40
+ def test_versioned_class_name
41
+ assert_equal 'Version', Page.versioned_class_name
42
+ assert_equal 'LockedPageRevision', LockedPage.versioned_class_name
43
+ end
44
+
45
+ def test_versioned_class
46
+ assert_equal Page::Version, Page.versioned_class
47
+ assert_equal LockedPage::LockedPageRevision, LockedPage.versioned_class
48
+ end
49
+
50
+ def test_special_methods
51
+ assert_nothing_raised { pages(:welcome).feeling_good? }
52
+ assert_nothing_raised { pages(:welcome).versions.first.feeling_good? }
53
+ assert_nothing_raised { locked_pages(:welcome).hello_world }
54
+ assert_nothing_raised { locked_pages(:welcome).versions.first.hello_world }
55
+ end
56
+
57
+ def test_rollback_with_version_class
58
+ p = pages(:welcome)
59
+ assert_equal 24, p.version
60
+ assert_equal 'Welcome to the weblog', p.title
61
+
62
+ assert p.revert_to!(p.versions.find_by_version(23)), "Couldn't revert to 23"
63
+ assert_equal 23, p.version
64
+ assert_equal 'Welcome to the weblg', p.title
65
+ end
66
+
67
+ def test_rollback_fails_with_invalid_revision
68
+ p = locked_pages(:welcome)
69
+ assert !p.revert_to!(locked_pages(:thinking))
70
+ end
71
+
72
+ def test_saves_versioned_copy_with_options
73
+ p = LockedPage.create! :title => 'first title'
74
+ assert !p.new_record?
75
+ assert_equal 1, p.versions.size
76
+ assert_instance_of LockedPage.versioned_class, p.versions.first
77
+ end
78
+
79
+ def test_rollback_with_version_number_with_options
80
+ p = locked_pages(:welcome)
81
+ assert_equal 'Welcome to the weblog', p.title
82
+ assert_equal 'LockedPage', p.versions.first.version_type
83
+
84
+ assert p.revert_to!(p.versions.first.lock_version), "Couldn't revert to 23"
85
+ assert_equal 'Welcome to the weblg', p.title
86
+ assert_equal 'LockedPage', p.versions.first.version_type
87
+ end
88
+
89
+ def test_rollback_with_version_class_with_options
90
+ p = locked_pages(:welcome)
91
+ assert_equal 'Welcome to the weblog', p.title
92
+ assert_equal 'LockedPage', p.versions.first.version_type
93
+
94
+ assert p.revert_to!(p.versions.first), "Couldn't revert to 1"
95
+ assert_equal 'Welcome to the weblg', p.title
96
+ assert_equal 'LockedPage', p.versions.first.version_type
97
+ end
98
+
99
+ def test_saves_versioned_copy_with_sti
100
+ p = SpecialLockedPage.create! :title => 'first title'
101
+ assert !p.new_record?
102
+ assert_equal 1, p.versions.size
103
+ assert_instance_of LockedPage.versioned_class, p.versions.first
104
+ assert_equal 'SpecialLockedPage', p.versions.first.version_type
105
+ end
106
+
107
+ def test_rollback_with_version_number_with_sti
108
+ p = locked_pages(:thinking)
109
+ assert_equal 'So I was thinking', p.title
110
+
111
+ assert p.revert_to!(p.versions.first.lock_version), "Couldn't revert to 1"
112
+ assert_equal 'So I was thinking!!!', p.title
113
+ assert_equal 'SpecialLockedPage', p.versions.first.version_type
114
+ end
115
+
116
+ def test_lock_version_works_with_versioning
117
+ p = locked_pages(:thinking)
118
+ p2 = LockedPage.find(p.id)
119
+
120
+ p.title = 'fresh title'
121
+ p.save
122
+ assert_equal 2, p.versions.size # limit!
123
+
124
+ assert_raises(ActiveRecord::StaleObjectError) do
125
+ p2.title = 'stale title'
126
+ p2.save
127
+ end
128
+ end
129
+
130
+ def test_version_if_condition
131
+ p = Page.create! :title => "title"
132
+ assert_equal 1, p.version
133
+
134
+ Page.feeling_good = false
135
+ p.save
136
+ assert_equal 1, p.version
137
+ Page.feeling_good = true
138
+ end
139
+
140
+ def test_version_if_condition2
141
+ # set new if condition
142
+ Page.class_eval do
143
+ def new_feeling_good() title[0..0] == 'a'; end
144
+ alias_method :old_feeling_good, :feeling_good?
145
+ alias_method :feeling_good?, :new_feeling_good
146
+ end
147
+
148
+ p = Page.create! :title => "title"
149
+ assert_equal 1, p.version # version does not increment
150
+ assert_equal 1, p.versions.count
151
+
152
+ p.update_attributes(:title => 'new title')
153
+ assert_equal 1, p.version # version does not increment
154
+ assert_equal 1, p.versions.count
155
+
156
+ p.update_attributes(:title => 'a title')
157
+ assert_equal 2, p.version
158
+ assert_equal 2, p.versions.count
159
+
160
+ # reset original if condition
161
+ Page.class_eval { alias_method :feeling_good?, :old_feeling_good }
162
+ end
163
+
164
+ def test_version_if_condition_with_block
165
+ # set new if condition
166
+ old_condition = Page.version_condition
167
+ Page.version_condition = Proc.new { |page| page.title[0..0] == 'b' }
168
+
169
+ p = Page.create! :title => "title"
170
+ assert_equal 1, p.version # version does not increment
171
+ assert_equal 1, p.versions.count
172
+
173
+ p.update_attributes(:title => 'a title')
174
+ assert_equal 1, p.version # version does not increment
175
+ assert_equal 1, p.versions.count
176
+
177
+ p.update_attributes(:title => 'b title')
178
+ assert_equal 2, p.version
179
+ assert_equal 2, p.versions.count
180
+
181
+ # reset original if condition
182
+ Page.version_condition = old_condition
183
+ end
184
+
185
+ def test_version_no_limit
186
+ p = Page.create! :title => "title", :body => 'first body'
187
+ p.save
188
+ p.save
189
+ 5.times do |i|
190
+ p.title = "title#{i}"
191
+ p.save
192
+ assert_equal "title#{i}", p.title
193
+ assert_equal (i+2), p.version
194
+ end
195
+ end
196
+
197
+ def test_version_max_limit
198
+ p = LockedPage.create! :title => "title"
199
+ p.update_attributes(:title => "title1")
200
+ p.update_attributes(:title => "title2")
201
+ 5.times do |i|
202
+ p.title = "title#{i}"
203
+ p.save
204
+ assert_equal "title#{i}", p.title
205
+ assert_equal (i+4), p.lock_version
206
+ assert p.versions(true).size <= 2, "locked version can only store 2 versions"
207
+ end
208
+ end
209
+
210
+ def test_track_altered_attributes_default_value
211
+ assert !Page.track_altered_attributes
212
+ assert LockedPage.track_altered_attributes
213
+ assert SpecialLockedPage.track_altered_attributes
214
+ end
215
+
216
+ def test_track_altered_attributes
217
+ p = LockedPage.create! :title => "title"
218
+ assert_equal 1, p.lock_version
219
+ assert_equal 1, p.versions(true).size
220
+
221
+ p.body = 'whoa'
222
+ assert !p.save_version?
223
+ p.save
224
+ assert_equal 2, p.lock_version # still increments version because of optimistic locking
225
+ assert_equal 1, p.versions(true).size
226
+
227
+ p.title = 'updated title'
228
+ assert p.save_version?
229
+ p.save
230
+ assert_equal 3, p.lock_version
231
+ assert_equal 1, p.versions(true).size # version 1 deleted
232
+
233
+ p.title = 'updated title!'
234
+ assert p.save_version?
235
+ p.save
236
+ assert_equal 4, p.lock_version
237
+ assert_equal 2, p.versions(true).size # version 1 deleted
238
+ end
239
+
240
+ def test_find_versions
241
+ assert_equal 1, locked_pages(:welcome).versions.find(:all, :conditions => ['title LIKE ?', '%weblog%']).size
242
+ end
243
+
244
+ def test_find_version
245
+ assert_equal page_versions(:welcome_1), pages(:welcome).versions.find_by_version(23)
246
+ end
247
+
248
+ def test_with_sequence
249
+ assert_equal 'widgets_seq', Widget.versioned_class.sequence_name
250
+ 3.times { Widget.create! :name => 'new widget' }
251
+ assert_equal 3, Widget.count
252
+ assert_equal 3, Widget.versioned_class.count
253
+ end
254
+
255
+ def test_has_many_through
256
+ assert_equal [authors(:caged), authors(:mly)], pages(:welcome).authors
257
+ end
258
+
259
+ def test_has_many_through_with_custom_association
260
+ assert_equal [authors(:caged), authors(:mly)], pages(:welcome).revisors
261
+ end
262
+
263
+ def test_referential_integrity
264
+ pages(:welcome).destroy
265
+ assert_equal 0, Page.count
266
+ assert_equal 0, Page::Version.count
267
+ end
268
+
269
+ def test_association_options
270
+ association = Page.reflect_on_association(:versions)
271
+ options = association.options
272
+ assert_equal :delete_all, options[:dependent]
273
+
274
+ association = Widget.reflect_on_association(:versions)
275
+ options = association.options
276
+ assert_equal :nullify, options[:dependent]
277
+ assert_equal 'version desc', options[:order]
278
+ assert_equal 'widget_id', options[:foreign_key]
279
+
280
+ widget = Widget.create! :name => 'new widget'
281
+ assert_equal 1, Widget.count
282
+ assert_equal 1, Widget.versioned_class.count
283
+ widget.destroy
284
+ assert_equal 0, Widget.count
285
+ assert_equal 1, Widget.versioned_class.count
286
+ end
287
+
288
+ def test_versioned_records_should_belong_to_parent
289
+ page = pages(:welcome)
290
+ page_version = page.versions.last
291
+ assert_equal page, page_version.page
292
+ end
293
+
294
+ def test_unaltered_attributes
295
+ landmarks(:washington).attributes = landmarks(:washington).attributes.except("id")
296
+ assert !landmarks(:washington).changed?
297
+ end
298
+
299
+ def test_unchanged_string_attributes
300
+ landmarks(:washington).attributes = landmarks(:washington).attributes.except("id").inject({}) { |params, (key, value)| params.update(key => value.to_s) }
301
+ assert !landmarks(:washington).changed?
302
+ end
303
+
304
+ def test_should_find_earliest_version
305
+ assert_equal page_versions(:welcome_1), pages(:welcome).versions.earliest
306
+ end
307
+
308
+ def test_should_find_latest_version
309
+ assert_equal page_versions(:welcome_2), pages(:welcome).versions.latest
310
+ end
311
+
312
+ def test_should_find_previous_version
313
+ assert_equal page_versions(:welcome_1), page_versions(:welcome_2).previous
314
+ assert_equal page_versions(:welcome_1), pages(:welcome).versions.before(page_versions(:welcome_2))
315
+ end
316
+
317
+ def test_should_find_next_version
318
+ assert_equal page_versions(:welcome_2), page_versions(:welcome_1).next
319
+ assert_equal page_versions(:welcome_2), pages(:welcome).versions.after(page_versions(:welcome_1))
320
+ end
321
+
322
+ def test_should_find_version_count
323
+ assert_equal 2, pages(:welcome).versions.size
324
+ end
325
+
326
+ def test_if_changed_creates_version_if_a_listed_column_is_changed
327
+ landmarks(:washington).name = "Washington"
328
+ assert landmarks(:washington).changed?
329
+ assert landmarks(:washington).altered?
330
+ end
331
+
332
+ def test_if_changed_creates_version_if_all_listed_columns_are_changed
333
+ landmarks(:washington).name = "Washington"
334
+ landmarks(:washington).latitude = 1.0
335
+ landmarks(:washington).longitude = 1.0
336
+ assert landmarks(:washington).changed?
337
+ assert landmarks(:washington).altered?
338
+ end
339
+
340
+ def test_if_changed_does_not_create_new_version_if_unlisted_column_is_changed
341
+ landmarks(:washington).doesnt_trigger_version = "This should not trigger version"
342
+ assert landmarks(:washington).changed?
343
+ assert !landmarks(:washington).altered?
344
+ end
345
+
346
+ def test_without_locking_temporarily_disables_optimistic_locking
347
+ enabled1 = false
348
+ block_called = false
349
+
350
+ ActiveRecord::Base.lock_optimistically = true
351
+ LockedPage.without_locking do
352
+ enabled1 = ActiveRecord::Base.lock_optimistically
353
+ block_called = true
354
+ end
355
+ enabled2 = ActiveRecord::Base.lock_optimistically
356
+
357
+ assert block_called
358
+ assert !enabled1
359
+ assert enabled2
360
+ end
361
+
362
+ def test_without_locking_reverts_optimistic_locking_settings_if_block_raises_exception
363
+ assert_raises(RuntimeError) do
364
+ LockedPage.without_locking do
365
+ raise RuntimeError, "oh noes"
366
+ end
367
+ end
368
+ assert ActiveRecord::Base.lock_optimistically
369
+ end
370
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: msales-acts_as_versioned
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.2
5
+ platform: ruby
6
+ authors:
7
+ - technoweenie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-20 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: TODO
17
+ email: technoweenie@bidwell.textdrive.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - VERSION.yml
26
+ - lib/acts_as_versioned.rb
27
+ - test/abstract_unit.rb
28
+ - test/database.yml
29
+ - test/fixtures
30
+ - test/fixtures/authors.yml
31
+ - test/fixtures/landmark.rb
32
+ - test/fixtures/landmark_versions.yml
33
+ - test/fixtures/landmarks.yml
34
+ - test/fixtures/locked_pages.yml
35
+ - test/fixtures/locked_pages_revisions.yml
36
+ - test/fixtures/migrations
37
+ - test/fixtures/migrations/1_add_versioned_tables.rb
38
+ - test/fixtures/page.rb
39
+ - test/fixtures/page_versions.yml
40
+ - test/fixtures/pages.yml
41
+ - test/fixtures/widget.rb
42
+ - test/migration_test.rb
43
+ - test/schema.rb
44
+ - test/versioned_test.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/technoweenie/acts_as_versioned
47
+ licenses:
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --inline-source
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: TODO
73
+ test_files: []
74
+