mongo_mapper_acts_as_versioned 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Gigamo <gigamo@gmail.com>
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.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ = MongoMapper::ActsAsVersioned
2
+
3
+ Basic MongoMapper port of technoweenie's [acts_as_versioned](http://github.com/technoweenie/acts_as_versioned).
4
+
5
+ == Basic Usage
6
+
7
+ class Page
8
+ include MongoMapper::Document
9
+
10
+ plugin ActsAsVersioned
11
+
12
+ key :title, String
13
+ end
14
+
15
+ page = Page.create(:title => 'title')
16
+ page.version # => 1
17
+ page.versions.size # => 1
18
+
19
+ page.title = 'new title'
20
+ page.save
21
+
22
+ page = page.reload
23
+ page.version # => 2
24
+ page.versions.size # => 2
25
+ page.title # => 'new title'
26
+
27
+ page.revert_to!(1)
28
+ page = page.reload
29
+ page.version # => 1
30
+ page.versions.size # => 2
31
+ page.title # => 'title'
32
+
33
+ == Tested with
34
+
35
+ * MongoMapper 0.8.3
36
+ * Ruby 1.9.2
37
+
38
+ == TODO
39
+
40
+ * Add loads more options
41
+ * Properly document those options
42
+
43
+ == Copyright
44
+
45
+ Copyright (c) 2010 Gigamo @lt;gigamo@gmail.com&gt;. See LICENSE for details.
@@ -0,0 +1,178 @@
1
+ module ActsAsVersioned
2
+ CALLBACKS = [:set_new_version, :save_version, :save_version?]
3
+
4
+ def self.configure(model)
5
+ model.class_eval do
6
+ const_set(:Version, Class.new).class_eval do
7
+ include MongoMapper::Document
8
+
9
+ key :version, Integer
10
+ key :changed_attributes, Hash
11
+
12
+ def self.before(version)
13
+ where(
14
+ super_foreign_key => version[super_foreign_key],
15
+ :version.lt => version.version
16
+ ).sort(:version.desc).first
17
+ end
18
+
19
+ def self.after(version)
20
+ where(
21
+ super_foreign_key => version[super_foreign_key],
22
+ :version.gt => version.version
23
+ ).sort(:version.asc).first
24
+ end
25
+
26
+ def previous
27
+ self.class.before(self)
28
+ end
29
+
30
+ def next
31
+ self.class.after(self)
32
+ end
33
+
34
+ def self.super_foreign_key
35
+ original_class.to_s.foreign_key
36
+ end
37
+
38
+ class << self
39
+ protected :super_foreign_key
40
+ end
41
+ end
42
+
43
+ versioned_class.cattr_accessor :original_class
44
+ versioned_class.original_class = self
45
+ versioned_class.set_collection_name "#{self.collection_name.singularize}_versions"
46
+ versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
47
+ :class_name => "::#{self}",
48
+ :foreign_key => self.to_s.foreign_key
49
+ end
50
+
51
+ model.key :version, Integer
52
+ model.many :versions, :class_name => "#{model}::Version",
53
+ :foreign_key => model.to_s.foreign_key, :dependent => :destroy do
54
+ def earliest
55
+ query.sort(:version).first
56
+ end
57
+
58
+ def latest
59
+ query.sort(:version.desc).first
60
+ end
61
+ end
62
+ model.before_save :set_new_version
63
+ model.after_save :save_version
64
+ end
65
+
66
+ module InstanceMethods
67
+ def save_version
68
+ if @saving_version
69
+ @saving_version = nil
70
+
71
+ rev = self.class.versioned_class.new
72
+ clone_versioned_model(self, rev)
73
+ rev.version = version
74
+ rev[self.class.to_s.foreign_key] = id
75
+ rev.save!
76
+ end
77
+ end
78
+
79
+ def revert_to(version)
80
+ if version.is_a?(self.class.versioned_class)
81
+ return false unless version[self.class.to_s.foreign_key] == id and !version.new_record?
82
+ else
83
+ return false unless version = versions.where(:version => version).first
84
+ end
85
+
86
+ clone_versioned_model(version, self)
87
+ self.version = version.version
88
+
89
+ true
90
+ end
91
+
92
+ def revert_to!(version)
93
+ revert_to(version) ? save_without_revision : false
94
+ end
95
+
96
+ def save_without_revision
97
+ save_without_revision!
98
+ true
99
+ rescue
100
+ false
101
+ end
102
+
103
+ def save_without_revision!
104
+ without_revision do
105
+ save!
106
+ end
107
+ end
108
+
109
+ def clone_versioned_model(orig_model, new_model)
110
+ if orig_model.is_a?(self.class.versioned_class)
111
+ orig_model = orig_model.changed_attributes
112
+ end
113
+
114
+ if new_model.is_a?(self.class.versioned_class)
115
+ new_model = new_model.changed_attributes
116
+ end
117
+
118
+ self.class.versioned_keys.each do |col|
119
+ new_model[col] = orig_model[col]
120
+ end
121
+ end
122
+
123
+ def save_version?
124
+ (self.class.versioned_keys & changed).any?
125
+ end
126
+
127
+ def without_revision(&block)
128
+ self.class.without_revision(&block)
129
+ end
130
+
131
+ def empty_callback
132
+ end
133
+
134
+ protected
135
+
136
+ def set_new_version
137
+ @saving_version = new_record? || save_version?
138
+ self.version = next_version if @saving_version
139
+ end
140
+
141
+ def next_version
142
+ (new_record? ? 0 : versions.map(&:version).max) + 1
143
+ end
144
+ end
145
+
146
+ module ClassMethods
147
+ def versioned_class
148
+ const_get(:Version)
149
+ end
150
+
151
+ def versioned_keys
152
+ keys.keys - skipped_keys
153
+ end
154
+
155
+ def without_revision
156
+ class_eval do
157
+ CALLBACKS.each do |attr_name|
158
+ alias_method :"orig_#{attr_name}", attr_name
159
+ alias_method attr_name, :empty_callback
160
+ end
161
+ end
162
+ yield
163
+ ensure
164
+ class_eval do
165
+ CALLBACKS.each do |attr_name|
166
+ alias_method attr_name, :"orig_#{attr_name}"
167
+ end
168
+ end
169
+ end
170
+
171
+ def skipped_keys
172
+ @skipped_keys ||= [
173
+ '_id', 'created_at', 'updated_at', 'creator_id',
174
+ 'updater_id', 'version', self.class.to_s.foreign_key
175
+ ]
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsVersioned
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActsAsVersioned do
4
+ before :all do
5
+ class Landmark
6
+ include MongoMapper::Document
7
+
8
+ plugin ActsAsVersioned
9
+
10
+ self.skipped_keys << 'depth'
11
+
12
+ key :title, String
13
+ key :depth, Integer
14
+ timestamps!
15
+ end
16
+ end
17
+
18
+ it 'should set the correct properties on the version class' do
19
+ Landmark::Version.original_class.should == Landmark
20
+ Landmark::Version.collection_name.should == 'landmark_versions'
21
+ Landmark.versioned_class.should == Landmark::Version
22
+ end
23
+
24
+ it 'should save a versioned copy' do
25
+ l = Landmark.create(:title => 'title')
26
+ l.new_record?.should be_false
27
+ l.versions.size.should == 1
28
+ l.version.should == 1
29
+ l.versions.first.should be_a(Landmark.versioned_class)
30
+ end
31
+
32
+ it 'should save without revision' do
33
+ l = Landmark.create(:title => 'title')
34
+ l.version.should == 1
35
+
36
+ l.update_attributes(:title => 'changed')
37
+ l = l.reload
38
+ l.version.should == 2
39
+
40
+ old_versions = l.versions.size
41
+
42
+ l.save_without_revision
43
+
44
+ l.without_revision do
45
+ l.update_attributes :title => 'changed again'
46
+ end
47
+
48
+ l.reload.versions.size.should == old_versions
49
+ end
50
+
51
+ it 'should rollback with version number' do
52
+ l = Landmark.create(:title => 'title')
53
+ (2..10).each do |i|
54
+ l = Landmark.first
55
+ l.update_attributes(:title => "title#{i}")
56
+ l.version.should == i
57
+ end
58
+
59
+ l = l.reload
60
+ l.version.should == 10
61
+ l.versions.size.should == 10
62
+ l.title.should == 'title10'
63
+
64
+ l.revert_to!(7).should be_true
65
+ l = l.reload
66
+ l.version.should == 7
67
+ l.versions.size.should == 10
68
+ l.title.should == 'title7'
69
+ end
70
+
71
+ it 'should rollback with version class' do
72
+ l = Landmark.create(:title => 'title')
73
+ (2..10).each do |i|
74
+ l = Landmark.first
75
+ l.update_attributes(:title => "title#{i}")
76
+ l.version.should == i
77
+ end
78
+
79
+ l = l.reload
80
+ l.version.should == 10
81
+ l.versions.size.should == 10
82
+ l.title.should == 'title10'
83
+
84
+ l.revert_to!(l.versions.find_by_version(7)).should be_true
85
+ l = l.reload
86
+ l.version.should == 7
87
+ l.versions.size.should == 10
88
+ l.title.should == 'title7'
89
+ end
90
+
91
+ it 'should have versioned records belong to its parent' do
92
+ l = Landmark.create(:title => 'title')
93
+ (2..10).each do |i|
94
+ l = Landmark.first
95
+ l.update_attributes(:title => "title#{i}")
96
+ l.version.should == i
97
+ end
98
+
99
+ l_version = l.reload.versions.last
100
+ l_version.landmark.should == l.reload
101
+ end
102
+
103
+ it 'should not create new versions for skipped keys' do
104
+ l = Landmark.create(:title => 'title')
105
+ l.update_attributes(:depth => 1)
106
+ l = l.reload
107
+ l.version.should == 1
108
+ l.versions.size.should == 1
109
+ end
110
+
111
+ it 'should create a new version even if a skipped key is added' do
112
+ l = Landmark.create(:title => 'title')
113
+ l.update_attributes(:title => 'new title', :depth => 1)
114
+ l = l.reload
115
+ l.version.should == 2
116
+ l.versions.size.should == 2
117
+ end
118
+
119
+ it 'should remember skipped keys through versions' do
120
+ l = Landmark.create(:title => 'title')
121
+ l.update_attributes(:title => 'new title')
122
+ l = l.reload
123
+ l.version.should == 2
124
+ l.versions.size.should == 2
125
+
126
+ l.update_attributes(:depth => 1)
127
+ l = l.reload
128
+ l.version.should == 2
129
+ l.versions.size.should == 2
130
+ l.depth.should == 1
131
+ l.title.should == 'new title'
132
+
133
+ l.revert_to!(1)
134
+ l = l.reload
135
+ l.version.should == 1
136
+ l.versions.size.should == 2
137
+ l.depth.should == 1
138
+ l.title.should == 'title'
139
+ end
140
+
141
+ it 'should store changes in a hash' do
142
+ l = Landmark.create(:title => 'title')
143
+ l.versions[0].changed_attributes.should == {'title' => 'title'}
144
+
145
+ l.update_attributes(:title => 'changed title', :depth => 1)
146
+ l.reload.versions[1].changed_attributes.should == {'title' => 'changed title'}
147
+ end
148
+
149
+ context 'finders' do
150
+ before :each do
151
+ @l = Landmark.create(:title => 'title')
152
+ (2..5).each do |i|
153
+ Landmark.first.update_attributes(:title => "title#{i}")
154
+ end
155
+ @l = @l.reload
156
+ end
157
+
158
+ it 'should find the earliest version' do
159
+ @l.versions.earliest.should == @l.versions.find_by_version(1)
160
+ end
161
+
162
+ it 'should find the latest version' do
163
+ @l.versions.latest.should == @l.versions.find_by_version(5)
164
+ end
165
+
166
+ it 'should find the previous version' do
167
+ @l.versions[1].previous.should == @l.versions[0]
168
+ end
169
+
170
+ it 'should find the next version' do
171
+ @l.versions[0].next.should == @l.versions[1]
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,16 @@
1
+ require 'mongo_mapper'
2
+ require 'rspec'
3
+ require File.expand_path('../../lib/acts_as_versioned', __FILE__)
4
+
5
+ MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017)
6
+ MongoMapper.database = 'test'
7
+ MongoMapper.database.collections.each {|c| c.drop_indexes }
8
+
9
+ Rspec.configure do |config|
10
+ config.after :each do
11
+ MongoMapper.database.collections.each do |collection|
12
+ collection.remove unless collection.name =~ /(.*\.)?system\..*/
13
+ collection.drop_indexes
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongo_mapper_acts_as_versioned
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Gigamo
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-27 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mongo_mapper
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: rspec
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ description: Basic MongoMapper port of technoweenie's acts_as_versioned
47
+ email:
48
+ - gigamo@gmail.com
49
+ executables: []
50
+
51
+ extensions: []
52
+
53
+ extra_rdoc_files: []
54
+
55
+ files:
56
+ - lib/acts_as_versioned/version.rb
57
+ - lib/acts_as_versioned.rb
58
+ - spec/acts_as_versioned_spec.rb
59
+ - spec/spec_helper.rb
60
+ - LICENSE
61
+ - README.md
62
+ has_rdoc: true
63
+ homepage: http://github.com/gigamo/mongo_mapper_acts_as_versioned
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ segments:
85
+ - 1
86
+ - 3
87
+ - 6
88
+ version: 1.3.6
89
+ requirements: []
90
+
91
+ rubyforge_project: mongo_mapper_acts_as_versioned
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Basic MongoMapper port of technoweenie's acts_as_versioned
96
+ test_files: []
97
+