mongo_mapper_acts_as_versioned 0.0.4 → 0.0.10
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.
- data/README.md +1 -1
- data/lib/acts_as_versioned.rb +29 -89
- data/spec/acts_as_versioned_spec.rb +172 -181
- metadata +3 -3
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ActsAsVersioned for [MongoMapper](http://github.com/jnunemaker/mongomapper)
|
2
2
|
|
3
|
-
Basic MongoMapper port of technoweenie's [acts_as_versioned](http://github.com/technoweenie/acts_as_versioned). Stores changed attributes in a Hash key instead of copying all keys from the original model.
|
3
|
+
Basic MongoMapper port of technoweenie's [acts_as_versioned](http://github.com/technoweenie/acts_as_versioned). Stores changed attributes in a Hash key inside an Embedded Document instead of copying all keys from the original model.
|
4
4
|
|
5
5
|
## Usage
|
6
6
|
|
data/lib/acts_as_versioned.rb
CHANGED
@@ -1,108 +1,55 @@
|
|
1
1
|
module MongoMapper
|
2
2
|
module Acts
|
3
3
|
module Versioned
|
4
|
-
VERSION = '0.0.
|
5
|
-
CALLBACKS = [:
|
4
|
+
VERSION = '0.0.10'
|
5
|
+
CALLBACKS = [:save_version, :save_version?]
|
6
6
|
|
7
7
|
def self.configure(model)
|
8
8
|
model.class_eval do
|
9
|
-
cattr_accessor :versioned_class_name, :
|
10
|
-
:versioned_collection_name, :non_versioned_keys
|
11
|
-
|
12
|
-
self.versioned_class_name = :Version
|
13
|
-
self.versioned_foreign_key = self.to_s.foreign_key
|
14
|
-
self.versioned_collection_name = "#{collection_name.singularize}_versions"
|
15
|
-
self.non_versioned_keys = [
|
16
|
-
'_id', 'created_at', 'updated_at', 'creator_id',
|
17
|
-
'updater_id', 'version', versioned_foreign_key,
|
18
|
-
'_type', '_version_type'
|
19
|
-
]
|
9
|
+
cattr_accessor :versioned_class_name, :non_versioned_keys
|
20
10
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
delegate :versioned_foreign_key, :to => :original_class
|
26
|
-
end
|
27
|
-
|
28
|
-
key :version, Integer
|
29
|
-
key :changed_attributes, Hash
|
30
|
-
|
31
|
-
if type_key = keys['_type']
|
32
|
-
key :_version_type, type_key.type, type_key.options
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.before(version)
|
36
|
-
where(
|
37
|
-
versioned_foreign_key => version[versioned_foreign_key],
|
38
|
-
:version.lt => version.version
|
39
|
-
).sort(:version.desc).first
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.after(version)
|
43
|
-
where(
|
44
|
-
versioned_foreign_key => version[versioned_foreign_key],
|
45
|
-
:version.gt => version.version
|
46
|
-
).sort(:version.asc).first
|
47
|
-
end
|
11
|
+
self.versioned_class_name = :Version
|
12
|
+
self.non_versioned_keys = %w(
|
13
|
+
_id _type created_at updated_at creator_id updater_id version
|
14
|
+
)
|
48
15
|
|
49
|
-
|
50
|
-
|
51
|
-
end
|
16
|
+
const_set(versioned_class_name, Class.new).class_eval do
|
17
|
+
include MongoMapper::EmbeddedDocument
|
52
18
|
|
53
|
-
|
54
|
-
|
55
|
-
end
|
19
|
+
key :version, Integer
|
20
|
+
key :modified, Hash
|
56
21
|
end
|
57
22
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
|
62
|
-
:class_name => self.to_s,
|
63
|
-
:foreign_key => versioned_foreign_key
|
64
|
-
|
65
|
-
key :version, Integer
|
66
|
-
|
67
|
-
many :versions,
|
68
|
-
:class_name => "#{self}::#{versioned_class_name}",
|
69
|
-
:foreign_key => versioned_foreign_key,
|
70
|
-
:dependent => :destroy do
|
71
|
-
def earliest
|
72
|
-
query.sort(:version).first
|
73
|
-
end
|
74
|
-
|
75
|
-
def latest
|
76
|
-
query.sort(:version.desc).first
|
23
|
+
many :versions, :class => "#{self}::#{versioned_class_name}".constantize do
|
24
|
+
def [](given_version)
|
25
|
+
detect {|version| version.version.to_s == given_version.to_s }
|
77
26
|
end
|
78
27
|
end
|
79
|
-
|
80
|
-
before_save :set_new_version
|
81
|
-
after_save :save_version
|
82
28
|
end
|
29
|
+
|
30
|
+
model.key :version, Integer
|
31
|
+
model.before_save :save_version
|
83
32
|
end
|
84
33
|
|
85
34
|
module InstanceMethods
|
86
35
|
def save_version
|
87
|
-
if
|
88
|
-
|
89
|
-
|
36
|
+
if new_record? || save_version?
|
37
|
+
self.version = next_version
|
90
38
|
rev = self.class.versioned_class.new
|
91
|
-
|
39
|
+
clone_attributes(self, rev)
|
92
40
|
rev.version = version
|
93
|
-
|
94
|
-
rev.save!
|
41
|
+
self.versions << rev
|
95
42
|
end
|
96
43
|
end
|
97
44
|
|
98
45
|
def revert_to(version)
|
99
46
|
if version.is_a?(self.class.versioned_class)
|
100
|
-
return false
|
47
|
+
return false if version.new_record?
|
101
48
|
else
|
102
|
-
return false unless version = versions
|
49
|
+
return false unless version = versions[version]
|
103
50
|
end
|
104
51
|
|
105
|
-
|
52
|
+
clone_attributes(version, self)
|
106
53
|
self.version = version.version
|
107
54
|
|
108
55
|
true
|
@@ -125,17 +72,15 @@ module MongoMapper
|
|
125
72
|
end
|
126
73
|
end
|
127
74
|
|
128
|
-
def
|
75
|
+
def clone_attributes(orig_model, new_model)
|
129
76
|
if orig_model.is_a?(self.class.versioned_class)
|
130
|
-
|
131
|
-
orig_model = orig_model.changed_attributes
|
77
|
+
orig_model = orig_model.modified
|
132
78
|
elsif new_model.is_a?(self.class.versioned_class)
|
133
|
-
new_model
|
134
|
-
new_model = new_model.changed_attributes
|
79
|
+
new_model = new_model.modified
|
135
80
|
end
|
136
81
|
|
137
|
-
self.class.versioned_keys.each do |
|
138
|
-
new_model[
|
82
|
+
self.class.versioned_keys.each do |attribute|
|
83
|
+
new_model[attribute] = orig_model[attribute]
|
139
84
|
end
|
140
85
|
end
|
141
86
|
|
@@ -152,11 +97,6 @@ module MongoMapper
|
|
152
97
|
|
153
98
|
protected
|
154
99
|
|
155
|
-
def set_new_version
|
156
|
-
@saving_version = new_record? || save_version?
|
157
|
-
self.version = next_version if @saving_version
|
158
|
-
end
|
159
|
-
|
160
100
|
def next_version
|
161
101
|
new_record? || versions.empty? ? 1 : versions.map(&:version).max.next
|
162
102
|
end
|
@@ -1,231 +1,222 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe MongoMapper::Acts::Versioned do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
context 'landmarks and generally' do
|
5
|
+
before :all do
|
6
|
+
class Landmark
|
7
|
+
include MongoMapper::Document
|
7
8
|
|
8
|
-
|
9
|
+
plugin MongoMapper::Acts::Versioned
|
9
10
|
|
10
|
-
|
11
|
+
self.non_versioned_keys << 'depth'
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
key :title, String
|
14
|
+
key :depth, Integer
|
15
|
+
timestamps!
|
16
|
+
end
|
17
|
+
|
18
|
+
class Sublandmark < Landmark
|
19
|
+
key :location, String
|
20
|
+
end
|
15
21
|
end
|
16
22
|
|
17
|
-
|
18
|
-
|
23
|
+
it 'should set the correct properties on the version class' do
|
24
|
+
Landmark.versioned_class.should == Landmark::Version
|
25
|
+
Sublandmark.versioned_class.should == Landmark::Version
|
19
26
|
end
|
20
|
-
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
28
|
+
it 'should save a versioned copy' do
|
29
|
+
l = Landmark.create(:title => 'title')
|
30
|
+
l.new_record?.should be_false
|
31
|
+
l.versions.size.should == 1
|
32
|
+
l.version.should == 1
|
33
|
+
l.versions.first.should be_a(Landmark.versioned_class)
|
34
|
+
end
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
l.versions.size.should == 1
|
35
|
-
l.version.should == 1
|
36
|
-
l.versions.first.should be_a(Landmark.versioned_class)
|
37
|
-
end
|
36
|
+
it 'should save without revision' do
|
37
|
+
l = Landmark.create(:title => 'title')
|
38
|
+
l.version.should == 1
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
l.update_attributes(:title => 'changed')
|
41
|
+
l = l.reload
|
42
|
+
l.version.should == 2
|
42
43
|
|
43
|
-
|
44
|
-
l = l.reload
|
45
|
-
l.version.should == 2
|
44
|
+
old_versions = l.versions.size
|
46
45
|
|
47
|
-
|
46
|
+
l.save_without_revision
|
48
47
|
|
49
|
-
|
48
|
+
l.without_revision do
|
49
|
+
l.update_attributes :title => 'changed again'
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
l.update_attributes :title => 'changed again'
|
52
|
+
l.reload.versions.size.should == old_versions
|
53
53
|
end
|
54
54
|
|
55
|
-
|
56
|
-
|
55
|
+
it 'should rollback with version number' do
|
56
|
+
l = Landmark.create(:title => 'title')
|
57
|
+
(2..10).each do |i|
|
58
|
+
l = Landmark.first
|
59
|
+
l.update_attributes(:title => "title#{i}")
|
60
|
+
end
|
57
61
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
l
|
62
|
-
|
62
|
+
l = l.reload
|
63
|
+
l.version.should == 10
|
64
|
+
l.versions.size.should == 10
|
65
|
+
l.title.should == 'title10'
|
66
|
+
|
67
|
+
l.revert_to!(7).should be_true
|
68
|
+
l = l.reload
|
69
|
+
l.version.should == 7
|
70
|
+
l.versions.size.should == 10
|
71
|
+
l.title.should == 'title7'
|
63
72
|
end
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
74
|
+
it 'should rollback with version class' do
|
75
|
+
l = Landmark.create(:title => 'title')
|
76
|
+
(2..10).each do |i|
|
77
|
+
l = Landmark.first
|
78
|
+
l.update_attributes(:title => "title#{i}")
|
79
|
+
end
|
69
80
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
l.title.should == 'title7'
|
75
|
-
end
|
81
|
+
l = l.reload
|
82
|
+
l.version.should == 10
|
83
|
+
l.versions.size.should == 10
|
84
|
+
l.title.should == 'title10'
|
76
85
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
l
|
81
|
-
l.
|
86
|
+
l.revert_to!(l.versions[7]).should be_true
|
87
|
+
l = l.reload
|
88
|
+
l.version.should == 7
|
89
|
+
l.versions.size.should == 10
|
90
|
+
l.title.should == 'title7'
|
82
91
|
end
|
83
92
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
93
|
+
it 'should have versioned records belong to its parent' do
|
94
|
+
l = Landmark.create(:title => 'title')
|
95
|
+
(2..10).each do |i|
|
96
|
+
l = Landmark.first
|
97
|
+
l.update_attributes(:title => "title#{i}")
|
98
|
+
end
|
88
99
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
l.versions.size.should == 10
|
93
|
-
l.title.should == 'title7'
|
94
|
-
end
|
100
|
+
l_version = l.reload.versions.last
|
101
|
+
l_version._root_document.should == l.reload
|
102
|
+
end
|
95
103
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
l =
|
100
|
-
l.
|
104
|
+
it 'should not create new versions for skipped keys' do
|
105
|
+
l = Landmark.create(:title => 'title')
|
106
|
+
l.update_attributes(:depth => 1)
|
107
|
+
l = l.reload
|
108
|
+
l.version.should == 1
|
109
|
+
l.versions.size.should == 1
|
101
110
|
end
|
102
111
|
|
103
|
-
|
104
|
-
|
105
|
-
|
112
|
+
it 'should create a new version even if a skipped key is added' do
|
113
|
+
l = Landmark.create(:title => 'title')
|
114
|
+
l.update_attributes(:title => 'new title', :depth => 1)
|
115
|
+
l = l.reload
|
116
|
+
l.version.should == 2
|
117
|
+
l.versions.size.should == 2
|
118
|
+
end
|
106
119
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
120
|
+
it 'should remember skipped keys through versions' do
|
121
|
+
l = Landmark.create(:title => 'title')
|
122
|
+
l.update_attributes(:title => 'new title')
|
123
|
+
l = l.reload
|
124
|
+
l.version.should == 2
|
125
|
+
l.versions.size.should == 2
|
126
|
+
|
127
|
+
l.update_attributes(:depth => 1)
|
128
|
+
l = l.reload
|
129
|
+
l.version.should == 2
|
130
|
+
l.versions.size.should == 2
|
131
|
+
l.depth.should == 1
|
132
|
+
l.title.should == 'new title'
|
133
|
+
|
134
|
+
l.revert_to!(1)
|
135
|
+
l = l.reload
|
136
|
+
l.version.should == 1
|
137
|
+
l.versions.size.should == 2
|
138
|
+
l.depth.should == 1
|
139
|
+
l.title.should == 'title'
|
140
|
+
end
|
114
141
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
l = l.reload
|
119
|
-
l.version.should == 2
|
120
|
-
l.versions.size.should == 2
|
121
|
-
end
|
142
|
+
it 'should store changes in a hash' do
|
143
|
+
l = Landmark.create(:title => 'title')
|
144
|
+
l.versions[1].modified.should == {'title' => 'title'}
|
122
145
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
l = l.reload
|
127
|
-
l.version.should == 2
|
128
|
-
l.versions.size.should == 2
|
129
|
-
|
130
|
-
l.update_attributes(:depth => 1)
|
131
|
-
l = l.reload
|
132
|
-
l.version.should == 2
|
133
|
-
l.versions.size.should == 2
|
134
|
-
l.depth.should == 1
|
135
|
-
l.title.should == 'new title'
|
136
|
-
|
137
|
-
l.revert_to!(1)
|
138
|
-
l = l.reload
|
139
|
-
l.version.should == 1
|
140
|
-
l.versions.size.should == 2
|
141
|
-
l.depth.should == 1
|
142
|
-
l.title.should == 'title'
|
143
|
-
end
|
146
|
+
l.update_attributes(:title => 'changed title', :depth => 1)
|
147
|
+
l.reload.versions[2].modified.should == {'title' => 'changed title'}
|
148
|
+
end
|
144
149
|
|
145
|
-
|
146
|
-
|
147
|
-
|
150
|
+
it 'should save a versioned class with sci' do
|
151
|
+
s = Sublandmark.create!(:title => 'first title')
|
152
|
+
s.new_record?.should be_false
|
153
|
+
s.version.should == 1
|
148
154
|
|
149
|
-
|
150
|
-
|
151
|
-
|
155
|
+
s.versions.size.should == 1
|
156
|
+
s.versions.first.should be_a(Landmark.versioned_class)
|
157
|
+
s.versions.first._root_document.should == s
|
158
|
+
end
|
152
159
|
|
153
|
-
|
154
|
-
|
155
|
-
@l = Landmark.create(:title => 'title')
|
160
|
+
it 'should rollback with sci' do
|
161
|
+
l = Landmark.create(:title => 'other title')
|
156
162
|
(2..5).each do |i|
|
157
|
-
Landmark.first
|
163
|
+
l = Landmark.first
|
164
|
+
l.update_attributes(:title => "other title#{i}")
|
158
165
|
end
|
159
|
-
@l = @l.reload
|
160
|
-
end
|
161
|
-
|
162
|
-
it 'should find the earliest version' do
|
163
|
-
@l.versions.earliest.should == @l.versions.find_by_version(1)
|
164
|
-
end
|
165
|
-
|
166
|
-
it 'should find the latest version' do
|
167
|
-
@l.versions.latest.should == @l.versions.find_by_version(5)
|
168
|
-
end
|
169
166
|
|
170
|
-
|
171
|
-
|
172
|
-
|
167
|
+
l = l.reload
|
168
|
+
l.version.should == 5
|
169
|
+
l.versions.size.should == 5
|
170
|
+
l.title.should == 'other title5'
|
171
|
+
l.revert_to!(3).should be_true
|
172
|
+
l = l.reload
|
173
|
+
l.version.should == 3
|
174
|
+
l.versions.size.should == 5
|
175
|
+
l.title.should == 'other title3'
|
176
|
+
|
177
|
+
s = Sublandmark.create(:title => 'title')
|
178
|
+
(2..5).each do |i|
|
179
|
+
s = Sublandmark.first
|
180
|
+
s.update_attributes(:title => "title#{i}")
|
181
|
+
end
|
173
182
|
|
174
|
-
|
175
|
-
|
183
|
+
s = s.reload
|
184
|
+
s.versions.should_not == l.versions
|
185
|
+
s.version.should == 5
|
186
|
+
s.versions.size.should == 5
|
187
|
+
s.title.should == 'title5'
|
188
|
+
s.revert_to!(3).should be_true
|
189
|
+
s = s.reload
|
190
|
+
s.version.should == 3
|
191
|
+
s.versions.size.should == 5
|
192
|
+
s.title.should == 'title3'
|
176
193
|
end
|
177
194
|
end
|
178
195
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
196
|
+
context 'nodes' do
|
197
|
+
before :all do
|
198
|
+
class Node
|
199
|
+
include MongoMapper::Document
|
183
200
|
|
184
|
-
|
185
|
-
|
186
|
-
s.versions.first.attributes.keys.should include('_version_type')
|
187
|
-
s.versions.first._version_type.should == 'Sublandmark'
|
188
|
-
s.versions.first.landmark.should == s
|
189
|
-
end
|
201
|
+
key :title, String
|
202
|
+
end
|
190
203
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
s = Sublandmark.first
|
195
|
-
s.update_attributes(:title => "title#{i}")
|
196
|
-
end
|
204
|
+
class Page < Node
|
205
|
+
plugin MongoMapper::Acts::Versioned
|
206
|
+
end
|
197
207
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
s.title.should == 'title5'
|
202
|
-
s.revert_to!(3).should be_true
|
203
|
-
s = s.reload
|
204
|
-
s.version.should == 3
|
205
|
-
s.versions.size.should == 5
|
206
|
-
s.title.should == 'title3'
|
207
|
-
|
208
|
-
s.versions.each do |version|
|
209
|
-
version._version_type.should == 'Sublandmark'
|
208
|
+
class Post < Node
|
209
|
+
plugin MongoMapper::Acts::Versioned
|
210
|
+
end
|
210
211
|
end
|
211
212
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
213
|
+
it 'should version only the subclass' do
|
214
|
+
page = Page.create(:title => 'page title')
|
215
|
+
post = Post.create(:title => 'post title')
|
216
|
+
page.version.should == 1
|
217
|
+
page.versions.size.should == 1
|
218
|
+
post.version.should == 1
|
219
|
+
post.versions.size.should == 1
|
216
220
|
end
|
217
|
-
|
218
|
-
l = l.reload
|
219
|
-
l.versions.should_not == s.versions
|
220
|
-
l.version.should == 5
|
221
|
-
l.versions.size.should == 5
|
222
|
-
l.title.should == 'other title5'
|
223
|
-
l.revert_to!(3).should be_true
|
224
|
-
l = l.reload
|
225
|
-
l.version.should == 3
|
226
|
-
l.versions.size.should == 5
|
227
|
-
l.title.should == 'other title3'
|
228
|
-
|
229
|
-
Landmark::Version.count.should == 10
|
230
221
|
end
|
231
222
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 10
|
9
|
+
version: 0.0.10
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Gigamo
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-09-12 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|