mongomapper-versioned 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ ## Versioned
2
+
3
+ Automatically store a version of a MongoMapper document every time it's updated. Configure a maximum number of versions you'd like to keep, or even the amount of time a particular document should keep versions around.
4
+
5
+ ### Setup
6
+
7
+ Add the gem to your Gemfile. Add the necessary Mongo indexes: run `Version.create_indexes` in your console, or if you are using Rails run the `rake versioned:create_indexes` command.
8
+
9
+ ### Basic Usage
10
+
11
+ Add the `versioned` declaration to the MongoMapper document models you want to keep versions of. When you make a change to a versioned document, a new version will be stored. Deleting a stored document will delete all the associated versions.
12
+
13
+ ````ruby
14
+ class User
15
+ versioned
16
+
17
+ key :name, String
18
+ key :email, String
19
+ end
20
+ ````
21
+
22
+ ### Querying
23
+
24
+ Every versioned document model has an association called `versions`. It's a plain old MongoMapper one-to-many association with the `Version` model, sorted in reverse chronological order. You can query for versions using MongoMapper's standard query criteria:
25
+
26
+ ````ruby
27
+ @user.versions.where(:created_at.lt > 1.day.ago)
28
+ ````
29
+
30
+ ### Pruning old versions
31
+
32
+ Use the `max` option to specifiy the maximum number of versions you want to keep. When the document is updated, the oldest versions will be pruned away to keep no more than the maximum number you specify.
33
+
34
+ ````ruby
35
+ class User
36
+ versioned max: 10
37
+
38
+ key :name, String
39
+ key :email, String
40
+ end
41
+ ````
42
+
43
+ If you'd rather specify the amount of time the versions of a doc should be kept, implement `keep_versions_for`. It must return the number of seconds to keep each revision. Every time a new version is created, versions will be purged based on their age.
44
+
45
+ This doesn't work with the `max` option. Use one or the other.
46
+
47
+ ````ruby
48
+ class User
49
+ versioned
50
+
51
+ key :name, String
52
+ key :email, String
53
+
54
+ def keep_versions_for
55
+ 90.days
56
+ end
57
+ end
58
+ ````
59
+
60
+ ### Auditing
61
+
62
+ When a versioned document is saved, you can pass the `updater` option to the save method. The associated version will keep a reference to the document passed. This allows you to keep track of who made a change to a versioned document.
63
+
64
+ ````ruby
65
+ @reginold = User.find_by_email('reggie@hotmail.com')
66
+ @user.name = "Bob"
67
+ @user.save(:updater => @reginold)
68
+ @user.versions.first.updater
69
+ => @reginold
70
+ ````
71
+
72
+ ### Rolling back
73
+
74
+ Each `Version` contains a copy of the document that was versioned. You can roll back to a particular version by calling the `rollback` method on the version you want. Reload the versioned document to pull the changes into the reference you're holding to the document.
75
+
76
+ ````ruby
77
+ @user.versions[5].rollback
78
+ @user.reload
79
+ ````
80
+
81
+ ### Version IDs
82
+
83
+ A versioned document has a `version_id` key. When you update a document, the new Version document takes on the ID of the document's current `version_id` value. The document gets a new `version_id`.
84
+
85
+ Rolling back to a previous revision will also roll back the `version_id` value of the document.
86
+
87
+ This mechanism allows you to undo changes to a document:
88
+
89
+ ````ruby
90
+ version_id = @user.version_id
91
+ @user.name
92
+ => "Roger"
93
+
94
+ @user.name = "Frank"
95
+ @user.save
96
+ @user.name = "Bob"
97
+ @user.save
98
+ @user.versions.find(version_id).rollback
99
+
100
+ @user.reload
101
+ @user.name
102
+ => "Roger"
103
+ @user.version_id == version_id
104
+ => true
105
+ ````
106
+
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ require File.join(File.dirname(__FILE__), '/lib/versioned')
5
+
6
+ require 'rake/testtask'
7
+ namespace :test do
8
+ Rake::TestTask.new(:unit) do |test|
9
+ test.libs << 'test'
10
+ test.ruby_opts << '-rubygems'
11
+ test.pattern = 'test/unit/**/test_*.rb'
12
+ test.verbose = true
13
+ end
14
+
15
+ Rake::TestTask.new(:performance) do |test|
16
+ test.libs << 'test'
17
+ test.ruby_opts << '-rubygems'
18
+ test.pattern = 'test/performance/**/*.rb'
19
+ test.verbose = true
20
+ end
21
+ end
22
+
23
+ task :default => 'test:unit'
24
+
25
+ desc 'Builds the gem'
26
+ task :build do
27
+ sh 'gem build versioned.gemspec'
28
+ Dir.mkdir('pkg') unless File.directory?('pkg')
29
+ sh "mv mongomapper-versioned-#{Versioned::VERSION}.gem pkg/mongomapper-versioned-#{Versioned::VERSION}.gem"
30
+ end
31
+
32
+ desc 'Builds and Installs the gem'
33
+ task :install => :build do
34
+ sh "gem install pkg/mongomapper-versioned-#{Versioned::VERSION}.gem"
35
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'versioned')
@@ -0,0 +1,16 @@
1
+ namespace :versioned do
2
+ desc "Check MongoMapper Versioned plugin indexes"
3
+ task :check_indexes => :environment do
4
+ missing = Version.missing_indexes
5
+ if missing.empty?
6
+ puts "Indexes have already been created."
7
+ else
8
+ puts "Indexes have not been created. Run `rake versioned:create_indexes`."
9
+ end
10
+ end
11
+
12
+ desc "Create MongoMapper Versioned plugin indexes"
13
+ task :create_indexes => :environment do
14
+ Version.create_indexes
15
+ end
16
+ end
data/lib/versioned.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'mongo_mapper'
2
+ require File.join(File.dirname(__FILE__), 'versioned', 'meta')
3
+ require File.join(File.dirname(__FILE__), 'versioned', 'version')
4
+ require File.join(File.dirname(__FILE__), 'versioned', 'versioned')
5
+
6
+ module Versioned
7
+ if Kernel.const_defined?(:Rails) && Rails.constants.include?(:Engine)
8
+ class Engine < ::Rails::Engine
9
+ engine_name :versioned
10
+ initializer "versioned.initialize" do |app|
11
+ MongoMapper::Document.plugin(Versioned)
12
+ end
13
+ initializer 'versioned.check_indexes', :after=> :disable_dependency_loading do
14
+ Version.check_indexes
15
+ end
16
+ end
17
+ else
18
+ MongoMapper::Document.plugin(Versioned)
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Versioned
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,79 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+ require 'mongomapper_id2'
3
+
4
+ class Version
5
+ include MongoMapper::Document
6
+
7
+ auto_increment!
8
+
9
+ key :doc, Hash
10
+ key :created_at, Time
11
+
12
+ before_create :set_created_at
13
+
14
+ belongs_to :versioned, polymorphic: true
15
+ belongs_to :updater, polymorphic: true
16
+
17
+ class << self
18
+ def check_indexes
19
+ unless missing_indexes.empty?
20
+ msg = "Indexes have not been created for MongoMapper versioned docs. Run `rake versioned:create_indexes`."
21
+ if Kernel.const_defined?(:IRB)
22
+ puts "Warning: #{msg}"
23
+ else
24
+ ::Rails.logger.warn(msg)
25
+ end
26
+ end
27
+ end
28
+
29
+ def missing_indexes
30
+ existing_index_names = self.collection.index_information.keys
31
+ required_index_names = required_indexes.collect do |i|
32
+ i.first.collect { |k| "#{k[0]}_#{k[1]}" }.join('_')
33
+ end
34
+ required_index_names - existing_index_names
35
+ end
36
+
37
+ def create_indexes
38
+ required_indexes.each do |index|
39
+ ensure_index *index
40
+ end
41
+ end
42
+
43
+ def required_indexes
44
+ [
45
+ [[[:versioned_id, 1], [:versioned_type, 1], [:created_at, -1]], background: true ],
46
+ [[[:versioned_id, 1], [:versioned_type, 1], [:id2, -1]], background: true],
47
+ [[[:versioned_id, 1], [:versioned_type, 1], [:id2, 1]], background: true]
48
+ ]
49
+ end
50
+ end
51
+
52
+ def rollback
53
+ versioned.rollback do
54
+ trouble = []
55
+ versioned.version_id = self.id
56
+ self.doc.each_pair do |attr, val|
57
+ mutator = "#{attr}="
58
+ if versioned.respond_to?(mutator)
59
+ versioned.send(mutator, val)
60
+ else
61
+ trouble.push(attr)
62
+ end
63
+ end
64
+ unless trouble.empty?
65
+ raise "Trying to load a #{versioned.class.name} version that has unsupported attributes: #{trouble.to_sentence}"
66
+ end
67
+ versioned.versions.destroy_all(:id2.gte => self.id2)
68
+ versioned.save
69
+ end
70
+ end
71
+
72
+ protected
73
+ # Update the created_at field on the Document to the current time. This is only called on create.
74
+ def set_created_at
75
+ unless self.created_at
76
+ self.created_at = Time.now.utc
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,100 @@
1
+ require 'active_support/concern'
2
+
3
+ module Versioned
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def versioned(opts={})
8
+ self.send(:include, Versioned::Document)
9
+ self.max_versions = opts[:max].to_i
10
+ end
11
+ end
12
+
13
+ module Document
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ key :version_id, ObjectId, default: proc { BSON::ObjectId.new }
18
+
19
+ many :versions, :as => :versioned, :sort => :id2.desc
20
+
21
+ before_update :push_version, :unless => :rolling_back?
22
+ after_update :prune_versions
23
+ after_destroy :destroy_versions
24
+
25
+ cattr_accessor :max_versions
26
+ attr_accessor :updater, :version_created_at
27
+ attr_writer :rolling_back
28
+ end
29
+
30
+ def rolling_back?
31
+ !!@rolling_back
32
+ end
33
+
34
+ def save(options={})
35
+ options.assert_valid_keys(:updater, :version_created_at)
36
+ self.updater = options.delete(:updater)
37
+ self.version_created_at = options.delete(:version_created_at)
38
+ super
39
+ end
40
+
41
+ def push_version
42
+ unless self.changes.empty?
43
+ version = self.versions.create(_id: self.version_id, doc: version_doc, updater: self.updater)
44
+ self.generate_version_id
45
+ end
46
+ ensure
47
+ self.updater = nil
48
+ self.version_created_at = nil
49
+ end
50
+
51
+ def generate_version_id
52
+ version_id = BSON::ObjectId.new
53
+ # don't use #save; that'll generate a new version
54
+ self.write_attribute(:version_id, version_id)
55
+ self.collection.update({'_id' => self.id}, { 'version_id' => version_id })
56
+ self.changed_attributes.clear
57
+ end
58
+
59
+ def prune_versions
60
+ if self.keep_versions_created_before
61
+ self.versions.destroy_all(:created_at.lt => self.keep_versions_created_before)
62
+ elsif self.class.max_versions
63
+ limit = self.versions.count - self.class.max_versions
64
+ if limit > 0
65
+ self.versions.destroy_all(sort: 'id2 asc', limit: limit)
66
+ end
67
+ end
68
+ end
69
+
70
+ def keep_versions_created_before
71
+ if self.respond_to?(:keep_versions_for)
72
+ Time.now - self.keep_versions_for
73
+ end
74
+ end
75
+
76
+ def destroy_versions
77
+ self.versions.destroy_all
78
+ end
79
+
80
+ def version_doc
81
+ {}.tap do |doc|
82
+ doc.merge!(self.attributes)
83
+ self.changes.each_pair do |attr, vals|
84
+ doc[attr] = vals.first
85
+ end
86
+ doc.delete('_id')
87
+ doc.delete('version_id')
88
+ end
89
+ end
90
+
91
+ def rollback
92
+ self.rolling_back = true
93
+ yield
94
+ ensure
95
+ self.rolling_back = false
96
+ end
97
+
98
+
99
+ end
100
+ end
@@ -0,0 +1,3 @@
1
+ require 'mongo_mapper'
2
+
3
+ MongoMapper.database = YAML.load(File.read('test/config/database.yml'))[ENV['RACK_ENV']]['database'] rescue 'versioned_test'
@@ -0,0 +1,5 @@
1
+ development:
2
+ database: versioned_dev
3
+
4
+ test:
5
+ database: versioned_test
@@ -0,0 +1,8 @@
1
+ class Post
2
+ include MongoMapper::EmbeddedDocument
3
+
4
+ key :date, Time
5
+ key :body, String
6
+ key :title, String
7
+
8
+ end
@@ -0,0 +1,10 @@
1
+ class User
2
+ include MongoMapper::Document
3
+
4
+ versioned
5
+
6
+ key :name, String
7
+ key :email, String
8
+
9
+ many :posts
10
+ end
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../lib/versioned')
2
+
3
+ require 'benchmark'
4
+
5
+ MongoMapper.database = 'versioned_performance'
6
+
7
+ class Max
8
+ include MongoMapper::Document
9
+
10
+ versioned max: 10
11
+
12
+ key :approved, Boolean
13
+ key :count, Integer
14
+ key :approved_at, Time
15
+ key :expire_on, Date
16
+ end
17
+ Max.collection.remove
18
+
19
+ class Timed
20
+ include MongoMapper::Document
21
+
22
+ versioned
23
+
24
+ key :approved, Boolean
25
+ key :count, Integer
26
+ key :approved_at, Time
27
+ key :expire_on, Date
28
+
29
+ def keep_versions_for
30
+ 1.second
31
+ end
32
+ end
33
+ Timed.collection.remove
34
+
35
+ class None
36
+ include MongoMapper::Document
37
+
38
+ key :approved, Boolean
39
+ key :count, Integer
40
+ key :approved_at, Time
41
+ key :expire_on, Date
42
+ end
43
+ None.collection.remove
44
+
45
+ Benchmark.bm(28) do |x|
46
+ max_ids, timed_ids, ids = [], [], []
47
+ x.report("write with versioning (max) ") do
48
+ 1000.times { |i| max_ids << Max.create(:count => 0, :approved => true, :approved_at => Time.now, :expire_on => Date.today).id }
49
+ end
50
+ x.report("write with versioning (timed)") do
51
+ 1000.times { |i| timed_ids << Timed.create(:count => 0, :approved => true, :approved_at => Time.now, :expire_on => Date.today).id }
52
+ end
53
+ x.report("write without versioning ") do
54
+ 1000.times { |i| ids << None.create(:count => 0, :approved => true, :approved_at => Time.now, :expire_on => Date.today).id }
55
+ end
56
+ x.report("read with versioning (max) ") do
57
+ max_ids.each { |id| Max.first(:id => id) }
58
+ end
59
+ x.report("read with versioning (timed) ") do
60
+ timed_ids.each { |id| Timed.first(:id => id) }
61
+ end
62
+ x.report("read without versioning ") do
63
+ ids.each { |id| None.first(:id => id) }
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ $LOAD_PATH.unshift('.') unless $LOAD_PATH.include?('.')
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/versioned')
4
+ require 'test/config/config'
5
+
6
+ require 'pp'
7
+ require 'shoulda'
8
+ require 'timecop'
9
+
10
+ require 'test/models/post'
11
+ require 'test/models/user'
12
+
13
+
14
+ def create_user
15
+ User.create!(
16
+ :name => 'Alex Wolfe',
17
+ :email => 'alexkwolfe@gmail.com',
18
+ :posts => []
19
+ )
20
+ end
21
+
22
+ def cleanup
23
+ User.delete_all
24
+ Version.delete_all
25
+ end
26
+
27
+
28
+
29
+
@@ -0,0 +1,229 @@
1
+ require 'test_helper'
2
+
3
+ class VersioningTest < ActiveSupport::TestCase
4
+ setup do
5
+ User.max_versions = nil
6
+ @user = create_user
7
+ end
8
+
9
+ teardown do
10
+ cleanup
11
+ end
12
+
13
+ context 'Versioned document' do
14
+ should 'have versions' do
15
+ assert @user.respond_to?(:versions)
16
+ assert_equal [], @user.versions
17
+ end
18
+
19
+ should 'have version id' do
20
+ assert @user.version_id.is_a?(BSON::ObjectId)
21
+ end
22
+
23
+ should 'not share version ids' do
24
+ assert_not_equal @user.version_id, create_user.version_id
25
+ end
26
+
27
+ should 'not push version on empty update' do
28
+ @user.save
29
+ assert @user.versions.empty?
30
+ end
31
+
32
+ should 'not get a new version id on empty update' do
33
+ version_id = @user.version_id
34
+ @user.save
35
+ assert_equal version_id, @user.version_id
36
+ end
37
+
38
+ context 'that has been updated' do
39
+ setup do
40
+ @version_id = @user.version_id
41
+ @user.name = "alex"
42
+ @user.save
43
+ end
44
+
45
+ should 'push version' do
46
+ assert_equal 1, @user.versions.size
47
+ end
48
+
49
+ should 'use version id' do
50
+ assert_equal @version_id, @user.versions.first.id
51
+ end
52
+
53
+ should 'get a new version id' do
54
+ assert_not_equal @version_id, @user.version_id
55
+ end
56
+
57
+ should 'rollback to previous version' do
58
+ @user.versions.first.rollback
59
+ @user.reload
60
+ assert_equal "Alex Wolfe", @user.name
61
+ end
62
+
63
+ should 'rollback version id' do
64
+ @user.versions.first.rollback
65
+ @user.reload
66
+ assert_equal @version_id, @user.version_id
67
+ end
68
+
69
+ should 'not store doc id in version doc' do
70
+ doc = @user.versions.first.doc
71
+ assert_nil doc['id']
72
+ assert_nil doc['_id']
73
+ end
74
+
75
+ should 'not store version_id in version doc' do
76
+ doc = @user.versions.first.doc
77
+ assert_nil doc['version_id']
78
+ end
79
+ end
80
+
81
+ context 'that has been updated many times' do
82
+ setup do
83
+ @version_id = @user.version_id
84
+ # Versions: Alex 4, Alex 3, Alex 2, Alex 1, Alex Wolfe
85
+ (1..5).each do |i|
86
+ @user.name = "Alex #{i}"
87
+ @user.save!
88
+ end
89
+ end
90
+
91
+ should 'sort versions in reverse chronological order' do
92
+ assert_equal @user.versions.to_a.sort {|x,y| y.created_at <=> x.created_at }, @user.versions.to_a
93
+ assert_equal 5, @user.versions.count
94
+ assert_equal "Alex 4", @user.versions.first.doc['name']
95
+ assert_equal "Alex Wolfe", @user.versions.last.doc['name']
96
+ end
97
+
98
+ should 'find by version id' do
99
+ version = @user.versions.find(@version_id)
100
+ assert_equal @version_id, version.id
101
+ assert_equal 'Alex Wolfe', version.doc['name']
102
+ end
103
+
104
+ context 'and has been rolled back' do
105
+ setup do
106
+ # second to oldest
107
+ @version = @user.versions[2]
108
+ @version.rollback
109
+ @user.reload
110
+ end
111
+
112
+ should 'have original data' do
113
+ assert_equal "Alex 2", @user.name
114
+ end
115
+
116
+ should 'have original version id' do
117
+ assert_equal @version.id, @user.version_id
118
+ end
119
+
120
+ should 'have correct versions' do
121
+ assert_equal 2, @user.versions.count
122
+ assert_equal 'Alex 1', @user.versions.first.doc['name']
123
+ assert_equal 'Alex Wolfe', @user.versions.last.doc['name']
124
+ end
125
+ end
126
+ end
127
+
128
+ context 'that has been updated by a user' do
129
+ setup do
130
+ @updater = User.create!(name: 'Bobby Brown', email: 'bobbeh@brown.com')
131
+ @user.name = "alex"
132
+ @user.save(updater: @updater)
133
+ end
134
+
135
+ should 'store user with version' do
136
+ assert_equal @updater, @user.versions.first.updater
137
+ end
138
+
139
+ should 'forget user after save' do
140
+ assert @user.updater.nil?
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'Destroyed versioned document' do
146
+ setup do
147
+ (1..5).each do |i|
148
+ @user.name = "#{@user.name} #{i}"
149
+ @user.save!
150
+ end
151
+ end
152
+
153
+ should 'destroy versions' do
154
+ @user.destroy
155
+ assert_equal 0, Version.count
156
+ end
157
+
158
+ should 'not destroy versions of other documents' do
159
+ assert_equal 5, Version.count
160
+ @user2 = create_user
161
+ @user2.name = "Foo"
162
+ @user2.save
163
+ @user2.name = "Bar"
164
+ @user2.save
165
+
166
+ assert_equal 7, Version.count
167
+
168
+ @user.destroy
169
+
170
+ assert_equal 2, Version.count
171
+ assert_equal 2, @user2.versions.count
172
+ assert_equal "Foo", @user2.versions.first.doc['name']
173
+ end
174
+ end
175
+
176
+ context 'Versioned document with max versions' do
177
+ setup do
178
+ User.max_versions = 5
179
+ (1..21).each do |i|
180
+ @user.name = "Alex #{i}"
181
+ @user.save!
182
+ end
183
+ @user.reload
184
+ end
185
+
186
+ should 'have no more than max' do
187
+ assert_equal 5, @user.versions.count
188
+ assert_equal ["Alex 20", "Alex 19", "Alex 18", "Alex 17", "Alex 16"], @user.versions.collect{|v| v.doc['name']}
189
+ end
190
+ end
191
+
192
+ context 'Versioned document with time limit' do
193
+ setup do
194
+ class << @user
195
+ def keep_versions_for
196
+ 5.minutes
197
+ end
198
+ end
199
+ (1..20).each do |i|
200
+ Timecop.freeze(i.minutes.ago) do
201
+ @user.name = "Alex #{i}"
202
+ @user.save!
203
+ end
204
+ end
205
+ end
206
+
207
+ should 'calculate time threshold' do
208
+ assert_equal (Time.now.utc - 5.minutes).to_i, @user.keep_versions_created_before.to_i
209
+ end
210
+
211
+ should 'prune documents older than time limit' do
212
+ @user.prune_versions
213
+ assert_equal 4, @user.versions.count
214
+ @user.versions.each do |v|
215
+ assert v.created_at >= @user.keep_versions_created_before
216
+ end
217
+ end
218
+
219
+ should "not prune other documents' versions" do
220
+ Timecop.freeze(10.minutes.ago) do
221
+ @user2 = create_user
222
+ @user2.name = "Foo Bar"
223
+ @user2.save!
224
+ end
225
+ @user.prune_versions
226
+ assert_equal 1, @user2.versions.count
227
+ end
228
+ end
229
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongomapper-versioned
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alex Wolfe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: &70092868791380 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70092868791380
25
+ - !ruby/object:Gem::Dependency
26
+ name: bson_ext
27
+ requirement: &70092868790820 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70092868790820
36
+ - !ruby/object:Gem::Dependency
37
+ name: mongo_mapper
38
+ requirement: &70092868790140 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 0.10.1
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70092868790140
47
+ - !ruby/object:Gem::Dependency
48
+ name: mongomapper_id2
49
+ requirement: &70092868789500 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70092868789500
58
+ - !ruby/object:Gem::Dependency
59
+ name: rake
60
+ requirement: &70092868788360 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70092868788360
69
+ - !ruby/object:Gem::Dependency
70
+ name: shoulda
71
+ requirement: &70092868787940 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70092868787940
80
+ - !ruby/object:Gem::Dependency
81
+ name: ruby-debug19
82
+ requirement: &70092868787380 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70092868787380
91
+ - !ruby/object:Gem::Dependency
92
+ name: timecop
93
+ requirement: &70092868786800 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *70092868786800
102
+ description:
103
+ email: alexkwolfe@gmail.com
104
+ executables: []
105
+ extensions: []
106
+ extra_rdoc_files: []
107
+ files:
108
+ - lib/mongomapper-versioned.rb
109
+ - lib/tasks/versioned.rake
110
+ - lib/versioned/meta.rb
111
+ - lib/versioned/version.rb
112
+ - lib/versioned/versioned.rb
113
+ - lib/versioned.rb
114
+ - Gemfile
115
+ - Rakefile
116
+ - README.md
117
+ - test/config/config.rb
118
+ - test/config/database.yml
119
+ - test/models/post.rb
120
+ - test/models/user.rb
121
+ - test/performance/read_write.rb
122
+ - test/test_helper.rb
123
+ - test/unit/test_versioning.rb
124
+ homepage: http://github.com/alexkwolfe/mongomapper-versioned
125
+ licenses: []
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 1.8.15
145
+ signing_key:
146
+ specification_version: 3
147
+ summary: A MongoMapper extension adding Versioning
148
+ test_files:
149
+ - test/config/config.rb
150
+ - test/config/database.yml
151
+ - test/models/post.rb
152
+ - test/models/user.rb
153
+ - test/performance/read_write.rb
154
+ - test/test_helper.rb
155
+ - test/unit/test_versioning.rb