dm-is-versioned 0.9.7
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/History.txt +1 -0
- data/LICENSE +20 -0
- data/Manifest.txt +12 -0
- data/README.txt +3 -0
- data/Rakefile +58 -0
- data/TODO +0 -0
- data/lib/dm-is-versioned/is/version.rb +7 -0
- data/lib/dm-is-versioned/is/versioned.rb +127 -0
- data/lib/dm-is-versioned.rb +16 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/versioned_spec.rb +155 -0
- metadata +87 -0
data/History.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Timothy Bennett
|
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/Manifest.txt
ADDED
data/README.txt
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
7
|
+
require ROOT + 'lib/dm-is-versioned/is/version'
|
8
|
+
|
9
|
+
AUTHOR = "Bernerd Schaefer"
|
10
|
+
EMAIL = "bj.schaefer@gmail.com"
|
11
|
+
GEM_NAME = "dm-is-versioned"
|
12
|
+
GEM_VERSION = DataMapper::Is::Versioned::VERSION
|
13
|
+
GEM_DEPENDENCIES = [["dm-core", GEM_VERSION]]
|
14
|
+
GEM_CLEAN = ["log", "pkg"]
|
15
|
+
GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO ] }
|
16
|
+
|
17
|
+
PROJECT_NAME = "datamapper"
|
18
|
+
PROJECT_URL = "http://github.com/sam/dm-more/tree/master/dm-is-versioned"
|
19
|
+
PROJECT_DESCRIPTION = PROJECT_SUMMARY = "DataMapper plugin enabling simple versioning of models"
|
20
|
+
|
21
|
+
require ROOT.parent + 'tasks/hoe'
|
22
|
+
|
23
|
+
task :default => [ :spec ]
|
24
|
+
|
25
|
+
WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
|
26
|
+
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
27
|
+
|
28
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION} (default ruby)"
|
29
|
+
task :install => [ :package ] do
|
30
|
+
sh "#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources", :verbose => false
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
|
34
|
+
task :uninstall => [ :clobber ] do
|
35
|
+
sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
|
36
|
+
end
|
37
|
+
|
38
|
+
namespace :jruby do
|
39
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION} with JRuby"
|
40
|
+
task :install => [ :package ] do
|
41
|
+
sh %{#{SUDO} jruby -S gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'Run specifications'
|
46
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
47
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
48
|
+
t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
|
49
|
+
|
50
|
+
begin
|
51
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
52
|
+
t.rcov_opts << '--exclude' << 'spec'
|
53
|
+
t.rcov_opts << '--text-summary'
|
54
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
55
|
+
rescue Exception
|
56
|
+
# rcov not installed
|
57
|
+
end
|
58
|
+
end
|
data/TODO
ADDED
File without changes
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
##
|
4
|
+
# = Is Versioned
|
5
|
+
# The Versioned module will configure a model to be versioned.
|
6
|
+
#
|
7
|
+
# The is-versioned plugin functions differently from other versioning
|
8
|
+
# solutions (such as acts_as_versioned), but can be configured to
|
9
|
+
# function like it if you so desire.
|
10
|
+
#
|
11
|
+
# The biggest difference is that there is not an incrementing 'version'
|
12
|
+
# field, but rather, any field of your choosing which will be unique
|
13
|
+
# on update.
|
14
|
+
#
|
15
|
+
# == Setup
|
16
|
+
# For simplicity, I will assume that you have loaded dm-timestamps to
|
17
|
+
# automatically update your :updated_at field. See versioned_spec for
|
18
|
+
# and example of updating the versioned field yourself.
|
19
|
+
#
|
20
|
+
# class Story
|
21
|
+
# include DataMapper::Resource
|
22
|
+
# property :id, Serial
|
23
|
+
# property :title, String
|
24
|
+
# property :updated_at, DateTime
|
25
|
+
#
|
26
|
+
# is_versioned :on => [:updated_at]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# == Auto Upgrading and Auto Migrating
|
30
|
+
#
|
31
|
+
# Story.auto_migrate! # => will run auto_migrate! on Story::Version, too
|
32
|
+
# Story.auto_upgrade! # => will run auto_upgrade! on Story::Version, too
|
33
|
+
#
|
34
|
+
# == Usage
|
35
|
+
#
|
36
|
+
# story = Story.get(1)
|
37
|
+
# story.title = "New Title"
|
38
|
+
# story.save # => Saves this story and creates a new version with the
|
39
|
+
# # original values.
|
40
|
+
# story.versions.size # => 1
|
41
|
+
#
|
42
|
+
# story.title = "A Different New Title"
|
43
|
+
# story.save
|
44
|
+
# story.versions.size # => 2
|
45
|
+
#
|
46
|
+
# TODO: enable replacing a current version with an old version.
|
47
|
+
module Versioned
|
48
|
+
|
49
|
+
def is_versioned(options = {})
|
50
|
+
on = options[:on]
|
51
|
+
|
52
|
+
class << self; self end.class_eval do
|
53
|
+
define_method :const_missing do |name|
|
54
|
+
storage_name = Extlib::Inflection.tableize(self.name + "Version")
|
55
|
+
model = DataMapper::Model.new(storage_name)
|
56
|
+
|
57
|
+
if name == :Version
|
58
|
+
properties.each do |property|
|
59
|
+
options = property.options
|
60
|
+
options[:key] = true if property.name == on || options[:serial] == true
|
61
|
+
options[:serial] = false
|
62
|
+
model.property property.name, property.type, options
|
63
|
+
end
|
64
|
+
|
65
|
+
self.const_set("Version", model)
|
66
|
+
else
|
67
|
+
super(name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
self.after_class_method :auto_migrate! do
|
73
|
+
self::Version.auto_migrate!
|
74
|
+
end
|
75
|
+
|
76
|
+
self.after_class_method :auto_upgrade! do
|
77
|
+
self::Version.auto_upgrade!
|
78
|
+
end
|
79
|
+
|
80
|
+
self.before :attribute_set do |property, value|
|
81
|
+
pending_version_attributes[property] ||= self.attribute_get(property)
|
82
|
+
end
|
83
|
+
|
84
|
+
self.after :update do |result|
|
85
|
+
if result && dirty_attributes.has_key?(properties[on])
|
86
|
+
self.class::Version.create(self.attributes.merge(pending_version_attributes))
|
87
|
+
self.pending_version_attributes.clear
|
88
|
+
end
|
89
|
+
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
include DataMapper::Is::Versioned::InstanceMethods
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
module InstanceMethods
|
98
|
+
##
|
99
|
+
# Returns a hash of original values to be stored in the
|
100
|
+
# versions table when a new version is created. It is
|
101
|
+
# cleared after a version model is created.
|
102
|
+
#
|
103
|
+
# --
|
104
|
+
# @return <Hash>
|
105
|
+
def pending_version_attributes
|
106
|
+
@pending_version_attributes ||= {}
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Returns a collection of other versions of this resource.
|
111
|
+
# The versions are related on the models keys, and ordered
|
112
|
+
# by the version field.
|
113
|
+
#
|
114
|
+
# --
|
115
|
+
# @return <Collection>
|
116
|
+
def versions
|
117
|
+
query = {}
|
118
|
+
version = self.class.const_get("Version")
|
119
|
+
self.class.key.zip(self.key) { |property, value| query[property.name] = value }
|
120
|
+
query.merge(:order => version.key.collect { |key| key.name.desc })
|
121
|
+
version.all(query)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end # Versioned
|
126
|
+
end # Is
|
127
|
+
end # DataMapper
|
@@ -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-versioned' / 'is' / 'versioned.rb'
|
8
|
+
|
9
|
+
# Include the plugin in Resource
|
10
|
+
module DataMapper
|
11
|
+
module Resource
|
12
|
+
module ClassMethods
|
13
|
+
include DataMapper::Is::Versioned
|
14
|
+
end # module ClassMethods
|
15
|
+
end # module Resource
|
16
|
+
end # module DataMapper
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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-versioned'
|
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')
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
class Story
|
4
|
+
include DataMapper::Resource
|
5
|
+
|
6
|
+
property :id, Integer, :serial => true
|
7
|
+
property :title, String
|
8
|
+
property :updated_at, DateTime
|
9
|
+
|
10
|
+
before :save do
|
11
|
+
# For the sake of testing, make sure the updated_at is always unique
|
12
|
+
time = self.updated_at ? self.updated_at + 1 : Time.now
|
13
|
+
self.updated_at = time if self.dirty?
|
14
|
+
end
|
15
|
+
|
16
|
+
is_versioned :on => :updated_at
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
21
|
+
describe 'DataMapper::Is::Versioned' do
|
22
|
+
describe "inner class" do
|
23
|
+
it "should be present" do
|
24
|
+
Story::Version.should be_a_kind_of(Class)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a default storage name" do
|
28
|
+
Story::Version.storage_name.should == "story_versions"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have its parent's properties" do
|
32
|
+
Story.properties.each do |property|
|
33
|
+
Story::Version.properties.should have_property(property.name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end # inner class
|
37
|
+
|
38
|
+
describe "#auto_migrate!" do
|
39
|
+
before do
|
40
|
+
Story::Version.should_receive(:auto_migrate!)
|
41
|
+
end
|
42
|
+
it "should get called on the inner class" do
|
43
|
+
Story.auto_migrate!
|
44
|
+
end
|
45
|
+
end # #auto_migrate!
|
46
|
+
|
47
|
+
describe "#auto_upgrade!" do
|
48
|
+
before do
|
49
|
+
Story::Version.should_receive(:auto_upgrade!)
|
50
|
+
end
|
51
|
+
it "should get called on the inner class" do
|
52
|
+
Story.auto_upgrade!
|
53
|
+
end
|
54
|
+
end # #auto_upgrade!
|
55
|
+
|
56
|
+
describe "#create" do
|
57
|
+
before do
|
58
|
+
Story.auto_migrate!
|
59
|
+
Story.create(:title => "A Very Interesting Article")
|
60
|
+
end
|
61
|
+
it "should not create a versioned copy" do
|
62
|
+
Story::Version.all.size.should == 0
|
63
|
+
end
|
64
|
+
end # #create
|
65
|
+
|
66
|
+
describe "#save" do
|
67
|
+
before do
|
68
|
+
Story.auto_migrate!
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "(with new resource)" do
|
72
|
+
before do
|
73
|
+
@story = Story.new(:title => "A Story")
|
74
|
+
@story.save
|
75
|
+
end
|
76
|
+
it "should not create a versioned copy" do
|
77
|
+
Story::Version.all.size.should == 0
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "(with a clean existing resource)" do
|
82
|
+
before do
|
83
|
+
@story = Story.create(:title => "A Story")
|
84
|
+
@story.save
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should not create a versioned copy" do
|
88
|
+
Story::Version.all.size.should == 0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "(with a dirty existing resource)" do
|
93
|
+
before do
|
94
|
+
@story = Story.create(:title => "A Story")
|
95
|
+
@story.title = "An Inner Update"
|
96
|
+
@story.title = "An Updated Story"
|
97
|
+
@story.save
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should create a versioned copy" do
|
101
|
+
Story::Version.all.size.should == 1
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should not have the same value for the versioned field" do
|
105
|
+
@story.updated_at.should_not == Story::Version.first.updated_at
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should save the original value, not the inner update" do
|
109
|
+
# changes to the story between saves shouldn't be updated.
|
110
|
+
@story.versions.last.title.should == "A Story"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end # #save
|
115
|
+
|
116
|
+
describe "#pending_version_attributes" do
|
117
|
+
before do
|
118
|
+
@story = Story.create(:title => "A Story")
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should be updated when a property changes" do
|
122
|
+
@story.title = "A New Title"
|
123
|
+
@story.pending_version_attributes[:title].should == "A Story"
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should be cleared when a resource is saved" do
|
127
|
+
@story.title = "A New Title"
|
128
|
+
@story.save
|
129
|
+
@story.pending_version_attributes.should be_empty
|
130
|
+
end
|
131
|
+
end # #pending_version_attributes
|
132
|
+
|
133
|
+
describe "#versions" do
|
134
|
+
before do
|
135
|
+
Story.auto_migrate!
|
136
|
+
@story = Story.create(:title => "A Story")
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should return an empty array when there are no versions" do
|
140
|
+
@story.versions.should == []
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should return a collection when there are versions" do
|
144
|
+
@story.versions.should == Story::Version.all(:id => @story.id)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should not return another object's versions" do
|
148
|
+
@story2 = Story.create(:title => "A Different Story")
|
149
|
+
@story2.title = "A Different Title"
|
150
|
+
@story2.save
|
151
|
+
@story.versions.should == Story::Version.all(:id => @story.id)
|
152
|
+
end
|
153
|
+
end # #versions
|
154
|
+
end
|
155
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-is-versioned
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bernerd Schaefer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-18 00:00:00 -08: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 simple versioning of models
|
36
|
+
email:
|
37
|
+
- bj.schaefer@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-versioned.rb
|
54
|
+
- lib/dm-is-versioned/is/version.rb
|
55
|
+
- lib/dm-is-versioned/is/versioned.rb
|
56
|
+
- spec/spec.opts
|
57
|
+
- spec/spec_helper.rb
|
58
|
+
- spec/versioned_spec.rb
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://github.com/sam/dm-more/tree/master/dm-is-versioned
|
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: datamapper
|
82
|
+
rubygems_version: 1.3.1
|
83
|
+
signing_key:
|
84
|
+
specification_version: 2
|
85
|
+
summary: DataMapper plugin enabling simple versioning of models
|
86
|
+
test_files: []
|
87
|
+
|