dm-is-temporal 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
File without changes
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Joe Kutner
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,73 @@
1
+ dm-is-temporal
2
+ ==================================
3
+
4
+ DataMapper plugin implementing temporal patterns on DataMapper models.
5
+
6
+ These patterns are based on research by Martin Fowler, Richard Snodgrass and others. For more information follow these links:
7
+
8
+ + [Temporal Patterns](http://martinfowler.com/eaaDev/timeNarrative.html)
9
+
10
+ + [Developing Time-Oriented Database Applications in SQL](http://www.cs.arizona.edu/people/rts/publications.html)
11
+
12
+ Examples
13
+ ---------
14
+
15
+ require 'rubygems'
16
+ require 'dm-core'
17
+ require 'dm-migrations'
18
+ require 'dm-is-temporal'
19
+
20
+ DataMapper.setup(:default, "sqlite3::memory:")
21
+
22
+ class MyModel
23
+ include DataMapper::Resource
24
+
25
+ property :id, Serial
26
+ property :name, String
27
+
28
+ is_temporal do
29
+ property :foo, Integer
30
+ property :bar, String
31
+ end
32
+ end
33
+
34
+ DataMapper.auto_migrate!
35
+
36
+ m = MyModel.create(:name => 'start', :foo => 1)
37
+
38
+ m.foo
39
+ #= 1
40
+ m.name
41
+ #=> 'start'
42
+
43
+ m.foo = 2
44
+
45
+ m.name = 'hello'
46
+ m.name
47
+ #=> 'hello'
48
+
49
+ m.foo
50
+ #= 2
51
+
52
+ m.foo = 42
53
+
54
+ old = DateTime.parse('-4712-01-01T00:00:00+00:00')
55
+ m.at(old).foo
56
+ #=> nil
57
+ m.foo
58
+ #=> 42
59
+
60
+ m.update(:foo => 100, :name => 'goodbye')
61
+
62
+ m.foo
63
+ #=> 100
64
+ m.name
65
+ #=> 'goodbye'
66
+
67
+
68
+ Copyright
69
+ ----------
70
+
71
+ Copyright © 2011 Joe Kutner. Released under the MIT License.
72
+
73
+ See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ begin
2
+ gem 'jeweler', '~> 1.5.2'
3
+ require 'jeweler'
4
+
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = 'dm-is-temporal'
7
+ gem.version = '0.0.1'
8
+ gem.summary = 'DataMapper plugin implementing temporal patterns'
9
+ gem.description = gem.summary
10
+ gem.email = 'jpkutner [a] gmail [d] com'
11
+ gem.homepage = 'http://github.com/jkutner/%s' % gem.name
12
+ gem.authors = [ 'Joe Kutner' ]
13
+ gem.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
14
+
15
+ #gem.has_rdoc = 'yard'
16
+ #gem.rubyforge_project = 'datamapper'
17
+ end
18
+
19
+ Jeweler::GemcutterTasks.new
20
+
21
+ FileList['tasks/**/*.rake'].each { |task| import task }
22
+ rescue LoadError
23
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
24
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,9 @@
1
+ require 'dm-core'
2
+ require 'dm-is-temporal/is/temporal'
3
+
4
+ # Include the plugin in Resource
5
+ module DataMapper
6
+ module Model
7
+ include DataMapper::Is::Temporal
8
+ end
9
+ end
@@ -0,0 +1,170 @@
1
+ module DataMapper
2
+ module Is
3
+ module Temporal
4
+
5
+ class PropertyHelper
6
+
7
+ def initialize(base)
8
+ @__properties__ = []
9
+ @__base__ = base
10
+ end
11
+
12
+ def property(*args)
13
+ @__properties__ << args
14
+ end
15
+
16
+ def has(*args)
17
+ raise 'Temporal associations are not supported yet!'
18
+ end
19
+
20
+ def belongs_to(*args)
21
+ raise 'Temporal associations are not supported yet!'
22
+ end
23
+
24
+ def __properties__
25
+ @__properties__
26
+ end
27
+ end
28
+
29
+ def is_temporal(*args, &block)
30
+
31
+ extend(Migration) if respond_to?(:auto_migrate!)
32
+
33
+ version_model = DataMapper::Model.new
34
+
35
+ if block_given?
36
+ version_model.instance_eval <<-RUBY
37
+ def self.default_repository_name
38
+ :#{default_repository_name}
39
+ end
40
+ RUBY
41
+
42
+ version_model.property(:id, DataMapper::Property::Serial)
43
+ version_model.property(:updated_at, DataMapper::Property::DateTime)
44
+ version_model.before(:save) { self.updated_at = DateTime.now }
45
+ version_model.instance_eval(&block)
46
+
47
+ const_set(:TemporalVersion, version_model)
48
+
49
+ h = PropertyHelper.new(self)
50
+ h.instance_eval(&block)
51
+
52
+ has n, :temporal_versions
53
+
54
+ create_methods
55
+
56
+ h.__properties__.each do |a|
57
+ create_temporal_reader(a[0])
58
+ create_temporal_writer(a[0])
59
+ end
60
+ else
61
+ # const_set(:TemporalVersion + args[0], version_model)
62
+ raise "Temporal Property pattern not supported yet"
63
+ end
64
+
65
+ end
66
+
67
+ private
68
+
69
+ def create_methods
70
+ class_eval <<-RUBY
71
+
72
+ def self.update(options={})
73
+ raise 'TODO'
74
+ end
75
+
76
+ def self.all(*args)
77
+ raise 'TODO'
78
+ end
79
+
80
+ def self.create(options={})
81
+ t_opts = __select_temporal_options__(options)
82
+ options.delete_if {|k,v| t_opts.keys.include?(k) }
83
+
84
+ base = super(options)
85
+ base.temporal_versions << TemporalVersion.create(t_opts) if t_opts.size > 0
86
+ base.save
87
+ base
88
+ end
89
+
90
+ def at(context)
91
+ @__at__ = context
92
+ self
93
+ end
94
+
95
+ def update(options={})
96
+ cur_t = __version_for_context__
97
+ attrs = cur_t.nil? ? {} : cur_t.attributes
98
+ t_opts = self.class.__select_temporal_options__(options)
99
+ options.delete_if {|k,v| t_opts.keys.include?(k) }
100
+ super(options)
101
+
102
+ self.temporal_versions <<
103
+ TemporalVersion.create(attrs.merge(:id => nil, :updated_at => nil).merge(t_opts))
104
+
105
+ self.save
106
+ end
107
+
108
+ private
109
+
110
+ def self.__select_temporal_options__(options={})
111
+ props = TemporalVersion.properties.map {|p| p.name}
112
+ temporal_opts = options.
113
+ select {|k,v| props.include?(k)}.
114
+ inject({}) {|a,b| a.merge({b[0] => b[1]}) }
115
+ return temporal_opts
116
+ end
117
+
118
+ def __version_for_context__(context=DateTime.now)
119
+ @__at__ ||= context
120
+ t = nil
121
+ temporal_versions.each do |n|
122
+ if (t.nil? or n.updated_at > t.updated_at) and n.updated_at <= @__at__
123
+ t = n
124
+ end
125
+ end
126
+ @__at__ = nil
127
+ t
128
+ end
129
+ RUBY
130
+ end
131
+
132
+ def create_temporal_reader(name)
133
+ class_eval <<-RUBY
134
+ def #{name}(context=DateTime.now)
135
+ t = __version_for_context__(context)
136
+ t.nil? ? nil : t.#{name}
137
+ end
138
+ RUBY
139
+ end
140
+
141
+ def create_temporal_writer(name)
142
+ class_eval <<-RUBY
143
+ def #{name}=(x)
144
+ t = __version_for_context__
145
+ attrs = t.nil? ? {} : t.attributes
146
+ t = TemporalVersion.create(attrs.merge(:id => nil, :updated_at => nil))
147
+ temporal_versions << t
148
+ t.#{name} = x
149
+ self.save
150
+ #{name}
151
+ end
152
+ RUBY
153
+ end
154
+
155
+ module Migration
156
+
157
+ def auto_migrate!(repository_name = self.repository_name)
158
+ super
159
+ self::TemporalVersion.auto_migrate!
160
+ end
161
+
162
+ def auto_upgrade!(repository_name = self.repository_name)
163
+ super
164
+ self::TemporalVersion.auto_upgrade!
165
+ end
166
+
167
+ end
168
+ end
169
+ end
170
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --loadby random
3
+ --format profile
4
+ --backtrace
@@ -0,0 +1,11 @@
1
+ #require 'dm-core/spec/setup'
2
+ #require 'dm-core/spec/lib/adapter_helpers'
3
+
4
+ require 'rubygems'
5
+ require 'dm-core'
6
+ require 'dm-is-temporal'
7
+ require 'dm-migrations'
8
+
9
+ #Spec::Runner.configure do |config|
10
+ # config.extend(DataMapper::Spec::Adapters::Helpers)
11
+ #end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ class MyModel
5
+ include DataMapper::Resource
6
+
7
+ # def self.default_repository_name
8
+ # :test
9
+ # end
10
+
11
+ property :id, Serial
12
+ property :name, String
13
+
14
+ is_temporal do
15
+ property :foo, Integer
16
+ property :bar, String
17
+ end
18
+ end
19
+
20
+ class MyOtherRepoModel
21
+ include DataMapper::Resource
22
+
23
+ def self.default_repository_name
24
+ :test
25
+ end
26
+
27
+ property :id, Serial
28
+ property :name, String
29
+
30
+ is_temporal do
31
+ property :foo, Integer
32
+ end
33
+ end
34
+
35
+ describe DataMapper::Is::Temporal do
36
+
37
+ before(:all) do
38
+ DataMapper.setup(:default, "sqlite3::memory:")
39
+ DataMapper.setup(:test, "sqlite3::memory:")
40
+ DataMapper.auto_migrate!(:default)
41
+ DataMapper.auto_migrate!(:test)
42
+ end
43
+
44
+ describe "#is_temporal" do
45
+
46
+ subject do
47
+ MyModel.create
48
+ end
49
+
50
+ context "when at context" do
51
+ it "returns old values" do
52
+ subject.foo.should == nil
53
+ subject.foo = 42
54
+ subject.foo.should == 42
55
+
56
+ old = DateTime.parse('-4712-01-01T00:00:00+00:00')
57
+ now = DateTime.now
58
+
59
+ subject.at(old).foo.should == nil
60
+ subject.at(now).foo.should == 42
61
+ end
62
+ end
63
+
64
+ context "when foo is 42" do
65
+ it "returns 42" do
66
+ subject.foo.should == nil
67
+ subject.foo = 42
68
+ subject.foo.should == 42
69
+ end
70
+ end
71
+
72
+ context "when bar is 'hello'" do
73
+ it "returns 'hello' and then 'goodbye'" do
74
+ subject.bar.should == nil
75
+ subject.bar = 'hello'
76
+ subject.bar.should == 'hello'
77
+ subject.bar = 'goodbye'
78
+ subject.bar.should == 'goodbye'
79
+ end
80
+ end
81
+
82
+ context "when name is 'hello'" do
83
+ it "returns 'hello' and then 'goodbye'" do
84
+ subject.name.should == nil
85
+ subject.name = 'hello'
86
+ subject.name.should == 'hello'
87
+ subject.name = 'goodbye'
88
+ subject.name.should == 'goodbye'
89
+ end
90
+ end
91
+
92
+ context "when updated" do
93
+ it "all values set and reset" do
94
+ subject.update(
95
+ :foo => 42,
96
+ :bar => 'hello',
97
+ :name => 'joe'
98
+ )
99
+
100
+ subject.foo.should == 42
101
+ subject.bar.should == 'hello'
102
+ subject.name.should == 'joe'
103
+ end
104
+ end
105
+
106
+ context "when churnned" do
107
+ it "has many versions" do
108
+ subject.foo = 1
109
+ subject.foo = 2
110
+ subject.foo = 3
111
+ subject.foo = 4
112
+
113
+ subject.instance_eval {self.temporal_versions.size.should == 4}
114
+ end
115
+ end
116
+
117
+ context "when class create with options" do
118
+ it "return all values" do
119
+ m = MyModel.create(
120
+ :foo => 42,
121
+ :bar => 'hello',
122
+ :name => 'joe'
123
+ )
124
+
125
+ m.foo.should == 42
126
+ m.bar.should == 'hello'
127
+ m.name.should == 'joe'
128
+
129
+ m.foo = 10
130
+ m.foo.should == 10
131
+ end
132
+ end
133
+
134
+ context "when repository name is set" do
135
+ it "MyModel returns :default" do
136
+ MyModel::TemporalVersion.default_repository_name.should == :default
137
+ end
138
+
139
+ it "MyOtherRepoModel returns :test" do
140
+ MyOtherRepoModel::TemporalVersion.default_repository_name.should == :test
141
+ end
142
+
143
+ context "when name is 'hello'" do
144
+ subject do
145
+ MyOtherRepoModel.create
146
+ end
147
+
148
+ it "returns 'hello' and then 'goodbye'" do
149
+ subject.name.should == nil
150
+ subject.name = 'hello'
151
+ subject.name.should == 'hello'
152
+ subject.name = 'goodbye'
153
+ subject.name.should == 'goodbye'
154
+ end
155
+ end
156
+
157
+ context "when updated" do
158
+ it "all values set and reset" do
159
+ subject.update(
160
+ :foo => 42,
161
+ :name => 'joe'
162
+ )
163
+
164
+ subject.foo.should == 42
165
+ subject.name.should == 'joe'
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-is-temporal
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
+ - Joe Kutner
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-08 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: DataMapper plugin implementing temporal patterns
22
+ email: jpkutner [a] gmail [d] com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - LICENSE.txt
29
+ - README.md
30
+ files:
31
+ - Gemfile
32
+ - LICENSE.txt
33
+ - README.md
34
+ - Rakefile
35
+ - VERSION
36
+ - lib/dm-is-temporal.rb
37
+ - lib/dm-is-temporal/is/temporal.rb
38
+ - spec/spec.opts
39
+ - spec/spec_helper.rb
40
+ - spec/temporal_spec.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/jkutner/dm-is-temporal
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.6
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: DataMapper plugin implementing temporal patterns
71
+ test_files:
72
+ - spec/spec_helper.rb
73
+ - spec/temporal_spec.rb