dm-is-revisioned 0.1.0

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 @@
1
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Timothy Bennett and David Leal
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.
@@ -0,0 +1,12 @@
1
+ History.txt
2
+ LICENSE
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ TODO
7
+ lib/dm-is-revisioned.rb
8
+ lib/dm-is-revisioned/is/version.rb
9
+ lib/dm-is-revisioned/is/revisioned.rb
10
+ spec/spec.opts
11
+ spec/spec_helper.rb
12
+ spec/revisioned_spec.rb
@@ -0,0 +1,3 @@
1
+ = dm-is-revisioned
2
+
3
+ DataMapper plugin enabling more flexible versioning of models.
@@ -0,0 +1,75 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'spec/rake/spectask'
4
+ require 'pathname'
5
+ require 'hoe'
6
+
7
+ ROOT = Pathname(__FILE__).dirname.expand_path
8
+ require ROOT + 'lib/dm-is-revisioned/is/version'
9
+
10
+ AUTHOR = "David Leal"
11
+ EMAIL = "dgleal@gmail.com"
12
+ GEM_NAME = "dm-is-revisioned"
13
+ GEM_VERSION = DataMapper::Is::Revisioned::VERSION
14
+ GEM_DEPENDENCIES = [['dm-core', "~> 0.9.7"]]
15
+ GEM_CLEAN = ["log", "pkg"]
16
+ GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO ] }
17
+
18
+ PROJECT_NAME = "dm-revisioned"
19
+ PROJECT_URL = "http://github.com/david/dm-is-revisioned"
20
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY = "DataMapper plugin enabling more flexible versioning of models"
21
+
22
+ hoe = Hoe.new(GEM_NAME, GEM_VERSION) do |p|
23
+
24
+ p.developer(AUTHOR, EMAIL)
25
+
26
+ p.description = PROJECT_DESCRIPTION
27
+ p.summary = PROJECT_SUMMARY
28
+ p.url = PROJECT_URL
29
+
30
+ p.rubyforge_name = PROJECT_NAME if PROJECT_NAME
31
+
32
+ p.clean_globs |= GEM_CLEAN
33
+ p.spec_extras = GEM_EXTRAS if GEM_EXTRAS
34
+
35
+ GEM_DEPENDENCIES.each do |dep|
36
+ p.extra_deps << dep
37
+ end
38
+ end
39
+
40
+ task :default => [ :spec ]
41
+
42
+ WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
43
+ SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
44
+
45
+ desc "Install #{GEM_NAME} #{GEM_VERSION} (default ruby)"
46
+ task :install => [ :package ] do
47
+ sh "#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources", :verbose => false
48
+ end
49
+
50
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
51
+ task :uninstall => [ :clobber ] do
52
+ sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
53
+ end
54
+
55
+ namespace :jruby do
56
+ desc "Install #{GEM_NAME} #{GEM_VERSION} with JRuby"
57
+ task :install => [ :package ] do
58
+ sh %{#{SUDO} jruby -S gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
59
+ end
60
+ end
61
+
62
+ desc 'Run specifications'
63
+ Spec::Rake::SpecTask.new(:spec) do |t|
64
+ t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
65
+ t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
66
+
67
+ begin
68
+ t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
69
+ t.rcov_opts << '--exclude' << 'spec'
70
+ t.rcov_opts << '--text-summary'
71
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
72
+ rescue Exception
73
+ # rcov not installed
74
+ end
75
+ end
data/TODO ADDED
File without changes
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'pathname'
3
+
4
+ gem 'dm-core', '~>0.9.7'
5
+ require 'dm-core'
6
+
7
+ require Pathname(__FILE__).dirname.expand_path / 'dm-is-revisioned' / 'is' / 'revisioned'
8
+
9
+ # Include the plugin in Resource
10
+ module DataMapper
11
+ module Resource
12
+ module ClassMethods
13
+ include DataMapper::Is::Revisioned
14
+ end # module ClassMethods
15
+ end # module Resource
16
+ end # module DataMapper
@@ -0,0 +1,142 @@
1
+ module DataMapper
2
+ module Is
3
+ ##
4
+ # = Is Revisioned
5
+ # The Revisioned module is similar to Versioned but is sufficiently
6
+ # different that it justified a fork.
7
+ #
8
+ # Just like with Versioned, there is not an incrementing 'version'
9
+ # field, but rather, any field of your choosing which will be unique
10
+ # on update. However, Revisioned will let you specify exactly when a
11
+ # new version should be created. By default, this happens when the record
12
+ # is created or whenever the field you specified changes.
13
+ #
14
+ # == Setup
15
+ # For simplicity, I will assume that you have loaded dm-timestamps to
16
+ # automatically update your :updated_at field. See versioned_spec for
17
+ # and example of updating the versioned field yourself.
18
+ #
19
+ # class Story
20
+ # include DataMapper::Resource
21
+ # property :id, Serial
22
+ # property :title, String
23
+ # property :updated_at, DateTime
24
+ #
25
+ # is_revisioned :on => :updated_at
26
+ # end
27
+ #
28
+ # == Auto Upgrading and Auto Migrating
29
+ #
30
+ # Story.auto_migrate! # => will run auto_migrate! on Story::Version, too
31
+ # Story.auto_upgrade! # => will run auto_upgrade! on Story::Version, too
32
+ #
33
+ # == Usage
34
+ #
35
+ # story = Story.crreate(:title => "A Title") # Creates a new version
36
+ # story.versions.size # => 1
37
+ # story = Story.get(1)
38
+ # story.title = "New Title"
39
+ # story.save # Saves this story and creates a new version
40
+ # story.versions.size # => 2
41
+ #
42
+ # story.title = "A Different New Title"
43
+ # story.save
44
+ # story.versions.size # => 3
45
+ #
46
+ # == Specifying when a new version should occur
47
+ # You can override the wants_new_version? method to determine exactly when
48
+ # a new version should be created. The method versioned_attribute_changed?
49
+ # is provided for your convenience, and can be used inside wants_new_version?
50
+ #
51
+ # TODO: enable replacing a current version with an old version.
52
+ module Revisioned
53
+ attr_reader :versioned_property_name
54
+
55
+ def is_revisioned(options = {})
56
+ include DataMapper::Is::Revisioned::InstanceMethods
57
+
58
+ @versioned_property_name = on = options[:on]
59
+
60
+ class << self; self end.class_eval do
61
+ define_method :const_missing do |name|
62
+ storage_name = Extlib::Inflection.tableize(self.name + "Version")
63
+ model = DataMapper::Model.new(storage_name)
64
+
65
+ if name == :Version
66
+ properties.each do |property|
67
+ options = property.options
68
+ options[:key] = true if property.name == on || options[:serial] == true
69
+ options[:serial] = false
70
+ model.property property.name, property.type, options
71
+ end
72
+
73
+ self.const_set("Version", model)
74
+ else
75
+ super(name)
76
+ end
77
+ end
78
+ end
79
+
80
+ after_class_method :auto_migrate! do
81
+ self::Version.auto_migrate!
82
+ end
83
+
84
+ after_class_method :auto_upgrade! do
85
+ self::Version.auto_upgrade!
86
+ end
87
+
88
+ before :create, :check_if_wants_new_version
89
+ after :create, :save_new_version
90
+
91
+ before :update, :check_if_wants_new_version
92
+ after :update, :save_new_version
93
+ end
94
+
95
+
96
+ module InstanceMethods
97
+ ##
98
+ # Returns a hash of original values to be stored in the
99
+ # versions table when a new version is created. It is
100
+ # cleared after a version model is created.
101
+ #
102
+ # --
103
+ # @return <Hash>
104
+ def pending_version_attributes
105
+ @pending_version_attributes ||= {}
106
+ end
107
+
108
+ ##
109
+ # Returns a collection of other versions of this resource.
110
+ # The versions are related on the models keys, and ordered
111
+ # by the version field.
112
+ #
113
+ # --
114
+ # @return <Collection>
115
+ def versions(include_current = false)
116
+ query = {}
117
+ version = self.class.const_get("Version")
118
+ self.class.key.zip(self.key) { |property, value| query[property.name] = value }
119
+ query.merge(:order => version.key.collect { |key| key.name.desc })
120
+ result = version.all(query)
121
+ end
122
+
123
+ def versioned_attribute_changed?
124
+ dirty_attributes.has_key?(self.class.properties[self.class.versioned_property_name])
125
+ end
126
+
127
+ alias_method :wants_new_version?, :versioned_attribute_changed?
128
+
129
+ def check_if_wants_new_version
130
+ @should_save_version = wants_new_version?
131
+ end
132
+
133
+ def save_new_version(result, *args)
134
+ if result && @should_save_version
135
+ self.class::Version.create(self.attributes)
136
+ @should_save_version = false
137
+ end
138
+ end
139
+ end
140
+ end # Revisioned
141
+ end # Is
142
+ end # DataMapper
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module Is
3
+ module Revisioned
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,199 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
4
+ describe 'DataMapper::Is::Revisioned', "default behavior" do
5
+ before :all do
6
+ class Story
7
+ include DataMapper::Resource
8
+
9
+ property :id, Integer, :serial => true
10
+ property :title, String
11
+ property :updated_at, DateTime
12
+
13
+ before :save do
14
+ # For the sake of testing, make sure the updated_at is always unique
15
+ time = self.updated_at ? self.updated_at + 1 : Time.now
16
+ self.updated_at = time if self.dirty?
17
+ end
18
+
19
+ is_revisioned :on => :updated_at
20
+ end
21
+ end
22
+
23
+ after :all do
24
+ if defined?(:Story) then Object.send(:remove_const, :Story) end
25
+ end
26
+
27
+ describe "inner class" do
28
+ it "should be present" do
29
+ Story::Version.should be_a_kind_of(Class)
30
+ end
31
+
32
+ it "should have a default storage name" do
33
+ Story::Version.storage_name.should == "story_versions"
34
+ end
35
+
36
+ it "should have its parent's properties" do
37
+ Story.properties.each do |property|
38
+ Story::Version.properties.should have_property(property.name)
39
+ end
40
+ end
41
+ end # inner class
42
+
43
+ describe "#auto_migrate!" do
44
+ before do
45
+ Story::Version.should_receive(:auto_migrate!)
46
+ end
47
+ it "should get called on the inner class" do
48
+ Story.auto_migrate!
49
+ end
50
+ end # #auto_migrate!
51
+
52
+ describe "#auto_upgrade!" do
53
+ before do
54
+ Story::Version.should_receive(:auto_upgrade!)
55
+ end
56
+ it "should get called on the inner class" do
57
+ Story.auto_upgrade!
58
+ end
59
+ end # #auto_upgrade!
60
+
61
+ describe "#create" do
62
+ before do
63
+ Story.auto_migrate!
64
+ Story.create(:title => "A Very Interesting Article")
65
+ end
66
+
67
+ it "should create 1 versioned copy" do
68
+ Story::Version.all.size.should == 1
69
+ end
70
+ end # #create
71
+
72
+ describe "#save" do
73
+ before do
74
+ Story.auto_migrate!
75
+ end
76
+
77
+ describe "(with new resource)" do
78
+ before do
79
+ @story = Story.new(:title => "A Story")
80
+ @story.save
81
+ end
82
+ it "should create 1 versioned copy" do
83
+ Story::Version.all.size.should == 1
84
+ end
85
+ end
86
+
87
+ describe "(with a clean existing resource)" do
88
+ before do
89
+ @story = Story.create(:title => "A Story")
90
+ @story.save
91
+ end
92
+
93
+ it "should have only 1 versioned copy" do
94
+ Story::Version.all.size.should == 1
95
+ end
96
+ end
97
+
98
+ describe "(with a dirty existing resource)" do
99
+ before do
100
+ @story = Story.create(:title => "A Story")
101
+ @story.title = "An Inner Update"
102
+ @story.title = "An Updated Story"
103
+ @story.save
104
+ end
105
+
106
+ it "should have 2 versioned copies" do
107
+ Story::Version.all.size.should == 2
108
+ end
109
+
110
+ it "should not have the same value for the versioned field" do
111
+ @story.updated_at.should_not == Story::Version.first.updated_at
112
+ end
113
+ end
114
+ end # #save
115
+
116
+ describe "#versions" do
117
+ before do
118
+ Story.auto_migrate!
119
+ @story = Story.create(:title => "A Story")
120
+ end
121
+
122
+ it "should return a collection when there are versions" do
123
+ @story.versions.should == Story::Version.all(:id => @story.id)
124
+ end
125
+
126
+ it "should not return another object's versions" do
127
+ @story2 = Story.create(:title => "A Different Story")
128
+ @story2.title = "A Different Title"
129
+ @story2.save
130
+ @story.versions.should == Story::Version.all(:id => @story.id)
131
+ end
132
+ end # #versions
133
+ end
134
+
135
+ describe "DataMapper::Is::Versioned", "overriding wants_new_version?" do
136
+ before :all do
137
+ class Story
138
+ include DataMapper::Resource
139
+
140
+ property :id, Integer, :serial => true
141
+ property :title, String
142
+ property :updated_at, DateTime
143
+
144
+ before :save do
145
+ # For the sake of testing, make sure the updated_at is always unique
146
+ time = self.updated_at ? self.updated_at + 1 : Time.now
147
+ self.updated_at = time if self.dirty?
148
+ end
149
+
150
+ is_revisioned :on => :updated_at
151
+
152
+ end
153
+ end
154
+
155
+ describe "wants a new version" do
156
+ before do
157
+ Story.class_eval do
158
+ def wants_new_version?
159
+ true # this is dangerous, the key will be the same on 2 consecutive saves, even with no changes
160
+ end
161
+ end
162
+
163
+ Story.auto_migrate!
164
+ @story = Story.create(:title => "A Story")
165
+ end
166
+
167
+ it "should create 1 new version" do
168
+ Story::Version.all.size.should == 1
169
+ end
170
+ end
171
+
172
+ describe "does not want a new version" do
173
+ before do
174
+ Story.class_eval do
175
+ def wants_new_version?
176
+ false
177
+ end
178
+ end
179
+
180
+ Story.auto_migrate!
181
+ @story = Story.create(:title => "A Story")
182
+ end
183
+
184
+ it "should not create a new version" do
185
+ Story::Version.all.should be_empty
186
+ end
187
+
188
+ it "should not create a new version on update" do
189
+ @story.title = "A New Hope"
190
+ @story.save
191
+ Story::Version.all.should be_empty
192
+ end
193
+ end
194
+
195
+ after :all do
196
+ if defined?(:Story) then Object.send(:remove_const, :Story) end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,2 @@
1
+ --format specdoc
2
+ --colour
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ gem 'rspec', '>=1.1.3'
3
+ require 'spec'
4
+ require 'pathname'
5
+ require Pathname(__FILE__).dirname.expand_path.parent + 'lib/dm-is-revisioned'
6
+
7
+ def load_driver(name, default_uri)
8
+ return false if ENV['ADAPTER'] != name.to_s
9
+
10
+ lib = "do_#{name}"
11
+
12
+ begin
13
+ gem lib, '~>0.9.7'
14
+ require lib
15
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
16
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
17
+ true
18
+ rescue Gem::LoadError => e
19
+ warn "Could not load #{lib}: #{e}"
20
+ false
21
+ end
22
+ end
23
+
24
+ ENV['ADAPTER'] ||= 'sqlite3'
25
+
26
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
27
+ HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
28
+ HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-is-revisioned
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Leal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-05 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: dm-core
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 0.9.7
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.2
34
+ version:
35
+ description: DataMapper plugin enabling more flexible versioning of models
36
+ email:
37
+ - dgleal@gmail.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - README.txt
44
+ - LICENSE
45
+ - TODO
46
+ files:
47
+ - History.txt
48
+ - LICENSE
49
+ - Manifest.txt
50
+ - README.txt
51
+ - Rakefile
52
+ - TODO
53
+ - lib/dm-is-revisioned.rb
54
+ - lib/dm-is-revisioned/is/version.rb
55
+ - lib/dm-is-revisioned/is/revisioned.rb
56
+ - spec/spec.opts
57
+ - spec/spec_helper.rb
58
+ - spec/revisioned_spec.rb
59
+ has_rdoc: true
60
+ homepage: http://github.com/david/dm-is-revisioned
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --main
64
+ - README.txt
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project: dm-revisioned
82
+ rubygems_version: 1.3.1
83
+ signing_key:
84
+ specification_version: 2
85
+ summary: DataMapper plugin enabling more flexible versioning of models
86
+ test_files: []
87
+