mongo_mapper_acts_as_versioned 0.0.1

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/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
+