dm-is-temporal 0.3.0 → 0.4.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.
- data/Gemfile +20 -0
- data/Gemfile.lock +43 -0
- data/README.md +3 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/dm-is-temporal/is/temporal.rb +230 -11
- data/spec/assoc_spec.rb +263 -0
- data/spec/hooks_spec.rb +20 -16
- data/spec/temporal_spec.rb +9 -6
- metadata +55 -14
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source :rubygems
|
2
|
+
|
3
|
+
# there is a bug in 1.1.0 that is preventing up from using it. Its already fixed though, and should be available
|
4
|
+
# in 1.1.1. We'll upgrade then
|
5
|
+
|
6
|
+
#gem 'dm-core', '~> 1.1.1'
|
7
|
+
gem 'dm-core', '~> 1.0.2'
|
8
|
+
|
9
|
+
group :development do
|
10
|
+
gem 'jeweler', '1.5.2'
|
11
|
+
gem 'rake', '0.8.7'
|
12
|
+
gem 'rspec', '1.3.0'
|
13
|
+
end
|
14
|
+
|
15
|
+
group :datamapper do
|
16
|
+
# gem 'dm-migrations', '~> 1.1.1'
|
17
|
+
# gem 'dm-sqlite-adapter', '~> 1.1.1'
|
18
|
+
gem 'dm-migrations', '~> 1.0.2'
|
19
|
+
gem 'dm-sqlite-adapter', '~> 1.0.2'
|
20
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.2.5)
|
5
|
+
data_objects (0.10.5)
|
6
|
+
addressable (~> 2.1)
|
7
|
+
dm-core (1.0.2)
|
8
|
+
addressable (~> 2.2)
|
9
|
+
extlib (~> 0.9.15)
|
10
|
+
dm-do-adapter (1.0.2)
|
11
|
+
data_objects (~> 0.10.2)
|
12
|
+
dm-core (~> 1.0.2)
|
13
|
+
dm-migrations (1.0.2)
|
14
|
+
dm-core (~> 1.0.2)
|
15
|
+
dm-sqlite-adapter (1.0.2)
|
16
|
+
dm-do-adapter (~> 1.0.2)
|
17
|
+
do_sqlite3 (~> 0.10.2)
|
18
|
+
do_jdbc (0.10.5-java)
|
19
|
+
data_objects (= 0.10.5)
|
20
|
+
do_sqlite3 (0.10.5-java)
|
21
|
+
data_objects (= 0.10.5)
|
22
|
+
do_jdbc (= 0.10.5)
|
23
|
+
jdbc-sqlite3 (>= 3.5.8)
|
24
|
+
extlib (0.9.15)
|
25
|
+
git (1.2.5)
|
26
|
+
jdbc-sqlite3 (3.6.14.2.056-java)
|
27
|
+
jeweler (1.5.2)
|
28
|
+
bundler (~> 1.0.0)
|
29
|
+
git (>= 1.2.5)
|
30
|
+
rake
|
31
|
+
rake (0.8.7)
|
32
|
+
rspec (1.3.0)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
java
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
dm-core (~> 1.0.2)
|
39
|
+
dm-migrations (~> 1.0.2)
|
40
|
+
dm-sqlite-adapter (~> 1.0.2)
|
41
|
+
jeweler (= 1.5.2)
|
42
|
+
rake (= 0.8.7)
|
43
|
+
rspec (= 1.3.0)
|
data/README.md
CHANGED
@@ -105,7 +105,8 @@ How it works
|
|
105
105
|
-------------
|
106
106
|
Temporal patterns differ from versioning patterns (such as [dm-is-versioned](https://github.com/datamapper/dm-is-versioned))
|
107
107
|
in that every version of the temporal properties is a peer (even the most recent). Accessing temporal properties without the `at(time)` method
|
108
|
-
is just a convinience for `at(DateTime.now)`.
|
108
|
+
is just a convinience for `at(DateTime.now)`. Furthermore, a reference to a temporal object doesn't have to change when
|
109
|
+
the state of the temporal object changes. This is due to the continuity table (my_models) illustrated below.
|
109
110
|
|
110
111
|
In addition, you have the ability inject versions at previous time-states (modifying history).
|
111
112
|
|
@@ -136,7 +137,7 @@ TODO
|
|
136
137
|
------
|
137
138
|
|
138
139
|
+ MyClass.update (update all records for a model) doesn't work
|
139
|
-
+ Temporal Associations
|
140
|
+
+ Bi-directional Temporal Associations
|
140
141
|
+ Temporal Property pattern (i.e. multiple independent temporal properties per class)
|
141
142
|
+ Bi-temporality
|
142
143
|
+ Add a config flag that enables an error to be raised when attempting to rewrite existing versions
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
@@ -6,6 +6,7 @@ module DataMapper
|
|
6
6
|
|
7
7
|
def initialize(base)
|
8
8
|
@__properties__ = []
|
9
|
+
@__has__ = []
|
9
10
|
@__base__ = base
|
10
11
|
@__before__ = []
|
11
12
|
@__after__ = []
|
@@ -23,24 +24,46 @@ module DataMapper
|
|
23
24
|
@__after__ << [args, block]
|
24
25
|
end
|
25
26
|
|
26
|
-
def has(*args)
|
27
|
-
|
27
|
+
def has(multiplicity, *args)
|
28
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
29
|
+
name = args.shift
|
30
|
+
raise "Associations :through option is not yet supported!" if opts[:through]
|
31
|
+
@__has__ << [multiplicity, name, args, opts]
|
28
32
|
end
|
29
33
|
|
30
34
|
def belongs_to(*args)
|
31
35
|
raise 'Temporal associations are not supported yet!'
|
32
36
|
end
|
33
37
|
|
38
|
+
def n
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
34
42
|
def __properties__
|
35
43
|
@__properties__
|
36
44
|
end
|
45
|
+
|
46
|
+
def __has__
|
47
|
+
@__has__
|
48
|
+
end
|
37
49
|
end
|
38
50
|
|
51
|
+
@@__temporal_classes__ = []
|
52
|
+
|
53
|
+
def __temporal_classes__
|
54
|
+
@@__temporal_classes__
|
55
|
+
end
|
56
|
+
protected :__temporal_classes__
|
57
|
+
|
39
58
|
def is_temporal(*args, &block)
|
40
59
|
|
41
60
|
extend(Migration) if respond_to?(:auto_migrate!)
|
42
61
|
|
43
|
-
version_model = DataMapper::Model.new
|
62
|
+
version_model = DataMapper::Model.new do
|
63
|
+
def has(*args)
|
64
|
+
#ignore all
|
65
|
+
end
|
66
|
+
end
|
44
67
|
|
45
68
|
if block_given?
|
46
69
|
version_model.instance_eval <<-RUBY
|
@@ -55,11 +78,12 @@ module DataMapper
|
|
55
78
|
version_model.instance_eval(&block)
|
56
79
|
|
57
80
|
const_set(:TemporalVersion, version_model)
|
81
|
+
@@__temporal_classes__ << self::TemporalVersion
|
58
82
|
|
59
83
|
h = PropertyHelper.new(self)
|
60
84
|
h.instance_eval(&block)
|
61
85
|
|
62
|
-
has n, :temporal_versions
|
86
|
+
has n, :temporal_versions #, :accessor => :protected
|
63
87
|
|
64
88
|
create_methods
|
65
89
|
|
@@ -67,8 +91,48 @@ module DataMapper
|
|
67
91
|
create_temporal_reader(a[0])
|
68
92
|
create_temporal_writer(a[0])
|
69
93
|
end
|
94
|
+
|
95
|
+
|
96
|
+
h.__has__.each do |a|
|
97
|
+
has_args = a[2]
|
98
|
+
plural_name = a[1]
|
99
|
+
|
100
|
+
if has_args.any? and has_args[0].is_a?(String)
|
101
|
+
clazz = has_args[0]
|
102
|
+
table = DataMapper::Inflector.tableize(clazz)
|
103
|
+
singular_name = DataMapper::Inflector.singularize(table)
|
104
|
+
else
|
105
|
+
singular_name = DataMapper::Inflector.singularize(plural_name.to_s)
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
list_model = DataMapper::Model.new
|
110
|
+
|
111
|
+
list_model.instance_eval <<-RUBY
|
112
|
+
def self.default_repository_name
|
113
|
+
:#{default_repository_name}
|
114
|
+
end
|
115
|
+
RUBY
|
116
|
+
|
117
|
+
list_model.property(:id, DataMapper::Property::Serial)
|
118
|
+
list_model.property(:updated_at, DataMapper::Property::DateTime)
|
119
|
+
list_model.property(:deleted_at, DataMapper::Property::DateTime)
|
120
|
+
list_model.before(:save) { self.updated_at ||= DateTime.now }
|
121
|
+
list_model.belongs_to singular_name #, :accessor => :protected
|
122
|
+
|
123
|
+
temporal_list_name = "temporal_#{plural_name}".to_sym
|
124
|
+
|
125
|
+
list_model_name = Inflector.classify("temporal_#{singular_name}")
|
126
|
+
|
127
|
+
const_set(list_model_name.to_sym, list_model)
|
128
|
+
@@__temporal_classes__ << eval("self::#{list_model_name}")
|
129
|
+
|
130
|
+
has n, temporal_list_name, list_model_name
|
131
|
+
|
132
|
+
create_temporal_list_reader(plural_name, singular_name, temporal_list_name, list_model_name)
|
133
|
+
# create_temporal_list_writer(a[1], temporal_list)
|
134
|
+
end
|
70
135
|
else
|
71
|
-
# const_set(:TemporalVersion + args[0], version_model)
|
72
136
|
raise "Temporal Property pattern not supported yet"
|
73
137
|
end
|
74
138
|
|
@@ -146,6 +210,15 @@ module DataMapper
|
|
146
210
|
@__at__ = nil
|
147
211
|
t
|
148
212
|
end
|
213
|
+
|
214
|
+
def __versions_for_context__(temporal_list_name, context=DateTime.now)
|
215
|
+
@__at__ ||= context
|
216
|
+
t = self.__send__(temporal_list_name).select do |n|
|
217
|
+
(t.nil? or n.updated_at > t.updated_at) and n.updated_at <= @__at__ and (n.deleted_at.nil? or (n.deleted_at > @__at__))
|
218
|
+
end
|
219
|
+
@__at__ = nil
|
220
|
+
t
|
221
|
+
end
|
149
222
|
RUBY
|
150
223
|
end
|
151
224
|
|
@@ -172,7 +245,24 @@ module DataMapper
|
|
172
245
|
end
|
173
246
|
t.#{name} = x
|
174
247
|
self.save
|
175
|
-
|
248
|
+
x
|
249
|
+
end
|
250
|
+
RUBY
|
251
|
+
end
|
252
|
+
|
253
|
+
def create_temporal_list_reader(plural_name, singular_name, temporal_list_name, temporal_list_model)
|
254
|
+
class_eval <<-RUBY
|
255
|
+
def #{plural_name}(context=DateTime.now)
|
256
|
+
at = @__at__
|
257
|
+
at ||= context
|
258
|
+
versions = __versions_for_context__(#{temporal_list_name.inspect}, context)
|
259
|
+
TemporalListProxy.new(
|
260
|
+
self,
|
261
|
+
versions.map {|v| v.#{singular_name}},
|
262
|
+
#{temporal_list_model},
|
263
|
+
#{temporal_list_name.to_sym.inspect},
|
264
|
+
#{singular_name.to_sym.inspect},
|
265
|
+
at)
|
176
266
|
end
|
177
267
|
RUBY
|
178
268
|
end
|
@@ -191,19 +281,148 @@ module DataMapper
|
|
191
281
|
end
|
192
282
|
end
|
193
283
|
|
284
|
+
class TemporalListProxy
|
285
|
+
# make this a blank slate
|
286
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
287
|
+
|
288
|
+
def initialize(base_object, list, temporal_list_model, temporal_list_name, name, context)
|
289
|
+
@base_object = base_object
|
290
|
+
@list = list
|
291
|
+
@temporal_list_name = temporal_list_name
|
292
|
+
@temporal_list_model = temporal_list_model
|
293
|
+
@name = name
|
294
|
+
@context = context
|
295
|
+
end
|
296
|
+
|
297
|
+
def <<(x)
|
298
|
+
new_model = @temporal_list_model.create(:updated_at => @context, @name => x)
|
299
|
+
@base_object.send(@temporal_list_name) << new_model
|
300
|
+
end
|
301
|
+
|
302
|
+
def clear
|
303
|
+
@base_object.send(@temporal_list_name).each do |temporal|
|
304
|
+
temporal.deleted_at = @context
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def []=(x,y)
|
309
|
+
raise "Unsupported method"
|
310
|
+
end
|
311
|
+
|
312
|
+
def map!
|
313
|
+
raise "Unsupported method"
|
314
|
+
end
|
315
|
+
|
316
|
+
def collect!
|
317
|
+
raise "Unsupported method"
|
318
|
+
end
|
319
|
+
|
320
|
+
def compact!
|
321
|
+
raise "Unsupported method"
|
322
|
+
end
|
323
|
+
|
324
|
+
def delete(obj)
|
325
|
+
@base_object.send(@temporal_list_name).each do |temporal|
|
326
|
+
if temporal.send(@name) == obj
|
327
|
+
temporal.deleted_at = @context
|
328
|
+
return true
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def delete_at(i)
|
334
|
+
# probably won't ever support this - not really doing order
|
335
|
+
raise "Unsupported method"
|
336
|
+
end
|
337
|
+
|
338
|
+
def delete_if
|
339
|
+
@base_object.send(@temporal_list_name).each do |temporal|
|
340
|
+
if yield(temporal.send(@name))
|
341
|
+
temporal.deleted_at = @context
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def drop(i)
|
347
|
+
raise "Unsupported method"
|
348
|
+
end
|
349
|
+
|
350
|
+
def drop_while(i)
|
351
|
+
raise "Unsupported method"
|
352
|
+
end
|
353
|
+
|
354
|
+
def fill(*args)
|
355
|
+
raise "Unsupported method"
|
356
|
+
end
|
357
|
+
|
358
|
+
def flatten!(level=nil)
|
359
|
+
raise "Unsupported method"
|
360
|
+
end
|
361
|
+
|
362
|
+
def replace(other_array=nil)
|
363
|
+
raise "Unsupported method"
|
364
|
+
end
|
365
|
+
|
366
|
+
def insert(index, *args)
|
367
|
+
raise "Unsupported method"
|
368
|
+
end
|
369
|
+
|
370
|
+
def pop(n=nil)
|
371
|
+
temporal = @base_object.send(@temporal_list_name).last
|
372
|
+
temporal.deleted_at = @context
|
373
|
+
temporal.send(@name)
|
374
|
+
end
|
375
|
+
|
376
|
+
def push(*obj)
|
377
|
+
raise "Unsupported method"
|
378
|
+
end
|
379
|
+
|
380
|
+
def rehject!
|
381
|
+
raise "Unsupported method"
|
382
|
+
end
|
383
|
+
|
384
|
+
def reverse!
|
385
|
+
raise "Unsupported method"
|
386
|
+
end
|
387
|
+
|
388
|
+
def shuffle!
|
389
|
+
raise "Unsupported method"
|
390
|
+
end
|
391
|
+
|
392
|
+
def slice(*args)
|
393
|
+
raise "Unsupported method"
|
394
|
+
end
|
395
|
+
|
396
|
+
def sort!
|
397
|
+
raise "Unsupported method"
|
398
|
+
end
|
399
|
+
|
400
|
+
def uniq!
|
401
|
+
raise "Unsupported method"
|
402
|
+
end
|
403
|
+
|
404
|
+
def method_missing(sym, *args, &block)
|
405
|
+
@list.__send__(sym, *args, &block)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
194
409
|
module Migration
|
195
410
|
|
196
411
|
def auto_migrate!(repository_name = self.repository_name)
|
197
|
-
super
|
198
|
-
self::
|
412
|
+
super(repository_name)
|
413
|
+
self::__temporal_classes__.each do |t|
|
414
|
+
t.auto_migrate!
|
415
|
+
end
|
199
416
|
end
|
200
417
|
|
201
418
|
def auto_upgrade!(repository_name = self.repository_name)
|
202
|
-
super
|
203
|
-
self::
|
419
|
+
super(repository_name)
|
420
|
+
self::__temporal_classes__.each do |t|
|
421
|
+
t.auto_upgrade!
|
422
|
+
end
|
204
423
|
end
|
205
424
|
|
206
|
-
end
|
425
|
+
end
|
207
426
|
end
|
208
427
|
end
|
209
428
|
end
|
data/spec/assoc_spec.rb
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Assoc
|
4
|
+
class Foobar
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Serial
|
8
|
+
property :baz, String
|
9
|
+
|
10
|
+
# todo bi-direction temporal relationships
|
11
|
+
# belongs_to :my_model
|
12
|
+
end
|
13
|
+
|
14
|
+
class MyModel
|
15
|
+
include DataMapper::Resource
|
16
|
+
|
17
|
+
property :id, Serial
|
18
|
+
property :name, String
|
19
|
+
|
20
|
+
is_temporal do
|
21
|
+
has n, :foobars
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class MyModelTwo
|
26
|
+
include DataMapper::Resource
|
27
|
+
|
28
|
+
property :id, Serial
|
29
|
+
property :name, String
|
30
|
+
|
31
|
+
is_temporal do
|
32
|
+
has n, :foobazes, 'Foobar'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe DataMapper::Is::Temporal do
|
38
|
+
|
39
|
+
before(:all) do
|
40
|
+
DataMapper.setup(:default, "sqlite3::memory:")
|
41
|
+
DataMapper.setup(:test, "sqlite3::memory:")
|
42
|
+
end
|
43
|
+
|
44
|
+
before(:each) do
|
45
|
+
DataMapper.auto_migrate!
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#has" do
|
49
|
+
|
50
|
+
context "with defaults" do
|
51
|
+
subject do
|
52
|
+
Assoc::MyModel.create
|
53
|
+
end
|
54
|
+
|
55
|
+
it "adds a Foobar at the current time" do
|
56
|
+
f = Assoc::Foobar.create(:baz => "hello")
|
57
|
+
subject.foobars << f
|
58
|
+
subject.save
|
59
|
+
subject.foobars.size.should == 1
|
60
|
+
subject.foobars.should include(f)
|
61
|
+
|
62
|
+
subject.instance_eval { self.temporal_foobars.size.should == 1}
|
63
|
+
end
|
64
|
+
|
65
|
+
it "adds a couple Foobars at different times" do
|
66
|
+
bot = DateTime.parse('-4712-01-01T00:00:00+00:00')
|
67
|
+
old = DateTime.parse('-2011-01-01T00:00:00+00:00')
|
68
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
69
|
+
|
70
|
+
subject.at(old).foobars << f1 = Assoc::Foobar.create(:baz => "hello")
|
71
|
+
subject.save
|
72
|
+
subject.at(old).foobars.size.should == 1
|
73
|
+
|
74
|
+
subject.instance_eval { self.temporal_foobars.size.should == 1}
|
75
|
+
|
76
|
+
subject.at(now).foobars << f2 = Assoc::Foobar.create(:baz => "goodbye")
|
77
|
+
subject.save
|
78
|
+
|
79
|
+
subject.at(bot).foobars.size.should == 0
|
80
|
+
subject.at(old).foobars.size.should == 1
|
81
|
+
subject.at(now).foobars.size.should == 2
|
82
|
+
|
83
|
+
subject.instance_eval { self.temporal_foobars.size.should == 2}
|
84
|
+
|
85
|
+
subject.foobars.should include(f1, f2)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "pops" do
|
89
|
+
old = DateTime.parse('-4712-01-01T00:00:00+00:00')
|
90
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
91
|
+
|
92
|
+
subject.at(old).foobars << f1 = Assoc::Foobar.create(:baz => "hello")
|
93
|
+
subject.at(old).foobars << f2 = Assoc::Foobar.create(:baz => "goodbye")
|
94
|
+
subject.save
|
95
|
+
|
96
|
+
subject.at(old).foobars.should include(f1, f2)
|
97
|
+
subject.at(now).foobars.should include(f1, f2)
|
98
|
+
|
99
|
+
subject.at(old).foobars.pop
|
100
|
+
|
101
|
+
subject.at(old).foobars.size.should == 1
|
102
|
+
subject.at(now).foobars.size.should == 1
|
103
|
+
subject.foobars.size.should == 1
|
104
|
+
end
|
105
|
+
|
106
|
+
it "pops and has proper state before and after" do
|
107
|
+
bot = DateTime.parse('-4712-01-01T00:00:00+00:00')
|
108
|
+
old = DateTime.parse('-2011-01-01T00:00:00+00:00')
|
109
|
+
mid = DateTime.parse('0000-01-01T00:00:00+00:00')
|
110
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
111
|
+
|
112
|
+
subject.at(old).foobars << f1 = Assoc::Foobar.create(:baz => "hello")
|
113
|
+
subject.at(old).foobars << f2 = Assoc::Foobar.create(:baz => "goodbye")
|
114
|
+
subject.save
|
115
|
+
|
116
|
+
subject.at(bot).foobars.size.should == 0
|
117
|
+
subject.at(old).foobars.should include(f1, f2)
|
118
|
+
subject.at(mid).foobars.should include(f1, f2)
|
119
|
+
subject.at(now).foobars.should include(f1, f2)
|
120
|
+
|
121
|
+
subject.at(mid).foobars.pop
|
122
|
+
|
123
|
+
subject.at(bot).foobars.size.should == 0
|
124
|
+
subject.at(old).foobars.size.should == 2
|
125
|
+
subject.at(mid).foobars.size.should == 1
|
126
|
+
subject.at(now).foobars.size.should == 1
|
127
|
+
subject.foobars.size.should == 1
|
128
|
+
end
|
129
|
+
|
130
|
+
it "deletes" do
|
131
|
+
old = DateTime.parse('-4712-01-01T00:00:00+00:00')
|
132
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
133
|
+
|
134
|
+
subject.at(old).foobars << f1 = Assoc::Foobar.create(:baz => "hello")
|
135
|
+
subject.at(old).foobars << f2 = Assoc::Foobar.create(:baz => "goodbye")
|
136
|
+
subject.save
|
137
|
+
|
138
|
+
subject.at(old).foobars.should include(f1, f2)
|
139
|
+
|
140
|
+
subject.at(old).foobars.delete(f1)
|
141
|
+
|
142
|
+
subject.at(old).foobars.size.should == 1
|
143
|
+
subject.at(now).foobars.size.should == 1
|
144
|
+
subject.foobars.size.should == 1
|
145
|
+
subject.at(old).foobars.should include(f2)
|
146
|
+
subject.at(now).foobars.should include(f2)
|
147
|
+
subject.foobars.should include(f2)
|
148
|
+
|
149
|
+
subject.at(old).foobars.delete(f2)
|
150
|
+
|
151
|
+
subject.at(old).foobars.size.should == 0
|
152
|
+
subject.at(now).foobars.size.should == 0
|
153
|
+
subject.foobars.size.should == 0
|
154
|
+
end
|
155
|
+
|
156
|
+
it "delete_if" do
|
157
|
+
old = DateTime.parse('-4712-01-01T00:00:00+00:00')
|
158
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
159
|
+
|
160
|
+
subject.at(old).foobars << f1 = Assoc::Foobar.create(:baz => "hello")
|
161
|
+
subject.at(old).foobars << f2 = Assoc::Foobar.create(:baz => "goodbye")
|
162
|
+
subject.save
|
163
|
+
|
164
|
+
subject.at(old).foobars.should include(f1, f2)
|
165
|
+
|
166
|
+
subject.at(old).foobars.delete_if do |f|
|
167
|
+
f.baz == 'hello'
|
168
|
+
end
|
169
|
+
subject.save
|
170
|
+
|
171
|
+
subject.at(old).foobars.size.should == 1
|
172
|
+
subject.at(now).foobars.size.should == 1
|
173
|
+
subject.foobars.size.should == 1
|
174
|
+
subject.at(old).foobars.should include(f2)
|
175
|
+
subject.at(now).foobars.should include(f2)
|
176
|
+
subject.foobars.should include(f2)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "clears" do
|
180
|
+
bot = DateTime.parse('-4712-01-01T00:00:00+00:00')
|
181
|
+
old = DateTime.parse('-2011-01-01T00:00:00+00:00')
|
182
|
+
mid = DateTime.parse('0000-01-01T00:00:00+00:00')
|
183
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
184
|
+
|
185
|
+
subject.at(old).foobars << f1 = Assoc::Foobar.create(:baz => "hello")
|
186
|
+
subject.at(old).foobars << f2 = Assoc::Foobar.create(:baz => "goodbye")
|
187
|
+
subject.save
|
188
|
+
|
189
|
+
subject.at(old).foobars.should include(f1, f2)
|
190
|
+
|
191
|
+
subject.at(mid).foobars.clear
|
192
|
+
subject.save
|
193
|
+
|
194
|
+
subject.at(bot).foobars.size.should == 0
|
195
|
+
subject.at(old).foobars.size.should == 2
|
196
|
+
subject.at(mid).foobars.size.should == 0
|
197
|
+
subject.at(now).foobars.size.should == 0
|
198
|
+
subject.foobars.size.should == 0
|
199
|
+
end
|
200
|
+
|
201
|
+
it "adds a couple Foobars at the same time" do
|
202
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
203
|
+
|
204
|
+
subject.at(now) do |m|
|
205
|
+
m.foobars << Assoc::Foobar.create(:baz => "hello")
|
206
|
+
m.foobars << Assoc::Foobar.create(:baz => "goodbye")
|
207
|
+
end
|
208
|
+
subject.save
|
209
|
+
|
210
|
+
subject.at(now).foobars.size.should == 2
|
211
|
+
subject.instance_eval { self.temporal_foobars.size.should == 2}
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "with explicit class" do
|
216
|
+
subject do
|
217
|
+
Assoc::MyModelTwo.create
|
218
|
+
end
|
219
|
+
|
220
|
+
it "adds a Foobar at the current time" do
|
221
|
+
f = Assoc::Foobar.create(:baz => "hello")
|
222
|
+
subject.foobazes << f
|
223
|
+
subject.save
|
224
|
+
subject.foobazes.size.should == 1
|
225
|
+
subject.foobazes.should include(f)
|
226
|
+
|
227
|
+
subject.instance_eval { self.temporal_foobazes.size.should == 1}
|
228
|
+
end
|
229
|
+
|
230
|
+
it "adds a couple Foobars at different times" do
|
231
|
+
old = DateTime.parse('-4712-01-01T00:00:00+00:00')
|
232
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
233
|
+
|
234
|
+
subject.at(old).foobazes << f1 = Assoc::Foobar.create(:baz => "hello")
|
235
|
+
subject.save
|
236
|
+
subject.at(old).foobazes.size.should == 1
|
237
|
+
|
238
|
+
subject.instance_eval { self.temporal_foobazes.size.should == 1}
|
239
|
+
|
240
|
+
subject.at(now).foobazes << f2 = Assoc::Foobar.create(:baz => "goodbye")
|
241
|
+
subject.save
|
242
|
+
subject.at(now).foobazes.size.should == 2
|
243
|
+
|
244
|
+
subject.instance_eval { self.temporal_foobazes.size.should == 2}
|
245
|
+
|
246
|
+
subject.foobazes.should include(f1, f2)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "adds a couple Foobars at the same time" do
|
250
|
+
now = DateTime.parse('2011-03-01T00:00:00+00:00')
|
251
|
+
|
252
|
+
subject.at(now) do |m|
|
253
|
+
m.foobazes << Assoc::Foobar.create(:baz => "hello")
|
254
|
+
m.foobazes << Assoc::Foobar.create(:baz => "goodbye")
|
255
|
+
end
|
256
|
+
subject.save
|
257
|
+
|
258
|
+
subject.at(now).foobazes.size.should == 2
|
259
|
+
subject.instance_eval { self.temporal_foobazes.size.should == 2}
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
data/spec/hooks_spec.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
module Hooks
|
4
|
+
class MyModel
|
5
|
+
include DataMapper::Resource
|
3
6
|
|
4
|
-
|
5
|
-
|
7
|
+
property :id, Serial
|
8
|
+
property :name, String
|
6
9
|
|
7
|
-
|
8
|
-
|
10
|
+
is_temporal do
|
11
|
+
property :foo, Integer
|
12
|
+
property :bar, String
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
before(:save) do |m|
|
15
|
+
m.foo = 42
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
after(:create) do |m|
|
19
|
-
m.bar = 'hello'
|
18
|
+
after(:create) do |m|
|
19
|
+
m.bar = 'hello'
|
20
|
+
end
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -26,13 +27,16 @@ describe DataMapper::Is::Temporal do
|
|
26
27
|
before(:all) do
|
27
28
|
DataMapper.setup(:default, "sqlite3::memory:")
|
28
29
|
DataMapper.setup(:test, "sqlite3::memory:")
|
30
|
+
end
|
31
|
+
|
32
|
+
before (:each) do
|
29
33
|
DataMapper.auto_migrate!
|
30
34
|
end
|
31
35
|
|
32
36
|
describe "#before" do
|
33
37
|
|
34
38
|
subject do
|
35
|
-
MyModel.create
|
39
|
+
Hooks::MyModel.create
|
36
40
|
end
|
37
41
|
|
38
42
|
it "before hook sets foo to 42" do
|
@@ -56,14 +60,14 @@ describe DataMapper::Is::Temporal do
|
|
56
60
|
subject.save
|
57
61
|
subject.at(oldish).bar.should == 'hello'
|
58
62
|
|
59
|
-
subject.instance_eval {
|
63
|
+
subject.instance_eval { self.temporal_versions.size.should == 1}
|
60
64
|
|
61
65
|
subject.at(nowish).bar = 'quack'
|
62
66
|
subject.save
|
63
67
|
subject.at(nowish).bar.should == 'quack'
|
64
68
|
subject.at(oldish).bar.should == 'hello'
|
65
69
|
|
66
|
-
subject.instance_eval {
|
70
|
+
subject.instance_eval { self.temporal_versions.size.should == 2}
|
67
71
|
end
|
68
72
|
end
|
69
73
|
end
|
data/spec/temporal_spec.rb
CHANGED
@@ -33,6 +33,9 @@ describe DataMapper::Is::Temporal do
|
|
33
33
|
before(:all) do
|
34
34
|
DataMapper.setup(:default, "sqlite3::memory:")
|
35
35
|
DataMapper.setup(:test, "sqlite3::memory:")
|
36
|
+
end
|
37
|
+
|
38
|
+
before(:each) do
|
36
39
|
DataMapper.auto_migrate!(:default)
|
37
40
|
DataMapper.auto_migrate!(:test)
|
38
41
|
end
|
@@ -45,7 +48,7 @@ describe DataMapper::Is::Temporal do
|
|
45
48
|
|
46
49
|
it "version has the right parent" do
|
47
50
|
subject.foo = 42
|
48
|
-
subject.instance_eval {
|
51
|
+
subject.instance_eval { self.temporal_versions[0].my_model_id.should == self.id}
|
49
52
|
end
|
50
53
|
|
51
54
|
it "update all still works for non-temporal properties" do
|
@@ -121,7 +124,7 @@ describe DataMapper::Is::Temporal do
|
|
121
124
|
subject.at(nowish).foo.should == 1024
|
122
125
|
subject.at(future).foo.should == 3
|
123
126
|
|
124
|
-
subject.instance_eval {
|
127
|
+
subject.instance_eval { self.temporal_versions.size.should == 3}
|
125
128
|
end
|
126
129
|
|
127
130
|
it "and rewriting works" do
|
@@ -315,7 +318,7 @@ describe DataMapper::Is::Temporal do
|
|
315
318
|
subject.at(nowish).bar.should == "dog"
|
316
319
|
subject.at(future).bar.should == "rat"
|
317
320
|
|
318
|
-
subject.instance_eval {
|
321
|
+
subject.instance_eval { self.temporal_versions.size.should == 3}
|
319
322
|
end
|
320
323
|
|
321
324
|
it "works with different times and non-temporal properties" do
|
@@ -377,7 +380,7 @@ describe DataMapper::Is::Temporal do
|
|
377
380
|
subject.at(nowish).name.should == "time"
|
378
381
|
subject.at(future).name.should == "time"
|
379
382
|
|
380
|
-
subject.instance_eval {
|
383
|
+
subject.instance_eval { self.temporal_versions.size.should == 3}
|
381
384
|
end
|
382
385
|
|
383
386
|
it "works with no time (i.e. now)" do
|
@@ -389,7 +392,7 @@ describe DataMapper::Is::Temporal do
|
|
389
392
|
subject.foo.should == 42
|
390
393
|
subject.bar.should == "cat"
|
391
394
|
|
392
|
-
subject.instance_eval {
|
395
|
+
subject.instance_eval { self.temporal_versions.size.should == 1}
|
393
396
|
end
|
394
397
|
|
395
398
|
it "works with no time (i.e. now)" do
|
@@ -403,7 +406,7 @@ describe DataMapper::Is::Temporal do
|
|
403
406
|
subject.bar.should == "cat"
|
404
407
|
subject.name.should == "foobar"
|
405
408
|
|
406
|
-
subject.instance_eval {
|
409
|
+
subject.instance_eval { self.temporal_versions.size.should == 1}
|
407
410
|
end
|
408
411
|
end
|
409
412
|
end
|
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dm-is-temporal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 3
|
8
|
-
- 0
|
9
|
-
version: 0.3.0
|
4
|
+
prerelease:
|
5
|
+
version: 0.4.0
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- Joe Kutner
|
@@ -14,10 +10,53 @@ autorequire:
|
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
12
|
|
17
|
-
date: 2011-
|
13
|
+
date: 2011-05-11 00:00:00 -05:00
|
18
14
|
default_executable:
|
19
|
-
dependencies:
|
20
|
-
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: dm-core
|
18
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.2
|
24
|
+
requirement: *id001
|
25
|
+
prerelease: false
|
26
|
+
type: :runtime
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jeweler
|
29
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - "="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.5.2
|
35
|
+
requirement: *id002
|
36
|
+
prerelease: false
|
37
|
+
type: :development
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rake
|
40
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - "="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.8.7
|
46
|
+
requirement: *id003
|
47
|
+
prerelease: false
|
48
|
+
type: :development
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec
|
51
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - "="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.3.0
|
57
|
+
requirement: *id004
|
58
|
+
prerelease: false
|
59
|
+
type: :development
|
21
60
|
description: DataMapper plugin implementing temporal patterns
|
22
61
|
email: jpkutner [a] gmail [d] com
|
23
62
|
executables: []
|
@@ -28,12 +67,15 @@ extra_rdoc_files:
|
|
28
67
|
- LICENSE.txt
|
29
68
|
- README.md
|
30
69
|
files:
|
70
|
+
- Gemfile
|
71
|
+
- Gemfile.lock
|
31
72
|
- LICENSE.txt
|
32
73
|
- README.md
|
33
74
|
- Rakefile
|
34
75
|
- VERSION
|
35
76
|
- lib/dm-is-temporal.rb
|
36
77
|
- lib/dm-is-temporal/is/temporal.rb
|
78
|
+
- spec/assoc_spec.rb
|
37
79
|
- spec/hooks_spec.rb
|
38
80
|
- spec/spec.opts
|
39
81
|
- spec/spec_helper.rb
|
@@ -48,27 +90,26 @@ rdoc_options: []
|
|
48
90
|
require_paths:
|
49
91
|
- lib
|
50
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
51
94
|
requirements:
|
52
95
|
- - ">="
|
53
96
|
- !ruby/object:Gem::Version
|
54
|
-
segments:
|
55
|
-
- 0
|
56
97
|
version: "0"
|
57
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
58
100
|
requirements:
|
59
101
|
- - ">="
|
60
102
|
- !ruby/object:Gem::Version
|
61
|
-
segments:
|
62
|
-
- 0
|
63
103
|
version: "0"
|
64
104
|
requirements: []
|
65
105
|
|
66
106
|
rubyforge_project:
|
67
|
-
rubygems_version: 1.
|
107
|
+
rubygems_version: 1.5.1
|
68
108
|
signing_key:
|
69
109
|
specification_version: 3
|
70
110
|
summary: DataMapper plugin implementing temporal patterns
|
71
111
|
test_files:
|
112
|
+
- spec/assoc_spec.rb
|
72
113
|
- spec/hooks_spec.rb
|
73
114
|
- spec/spec_helper.rb
|
74
115
|
- spec/temporal_spec.rb
|