acts_as_span 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ require 'active_support'
2
+
3
+ module ActsAsSpan
4
+ class SpanInstance
5
+ module Validations
6
+ extend ActiveSupport::Concern
7
+
8
+ module InstanceMethods
9
+ def validate
10
+ validate_start_date
11
+ validate_end_date
12
+ validate_start_date_less_than_or_equal_to_end_date
13
+ validate_overlap
14
+ end
15
+
16
+ def validate_start_date
17
+ if start_date_field_required && start_date.blank?
18
+ span_model.errors.add(start_date_field, :blank)
19
+ end
20
+ end
21
+
22
+ def validate_end_date
23
+ if end_date_field_required && end_date.blank?
24
+ span_model.errors.add(end_date_field, :blank)
25
+ end
26
+ end
27
+
28
+ def validate_start_date_less_than_or_equal_to_end_date
29
+ if start_date && end_date && end_date < start_date
30
+ span_model.errors.add(end_date_field, "Must be on or after #{start_date_field}")
31
+ end
32
+ end
33
+
34
+ def validate_overlap
35
+ if span_overlap_count && span_model.errors[start_date_field].empty? && span_model.errors[end_date_field].empty? # && ( respond_to?('archived?') ? !archived? : true )
36
+ conditions = {}
37
+
38
+ if span_overlap_scope.is_a?(Array)
39
+ span_overlap_scope.each do |symbol|
40
+ conditions[symbol] = span_model.send(symbol)
41
+ end
42
+ elsif span_overlap_scope.is_a?(Symbol)
43
+ conditions[span_overlap_scope] = span_model.send(span_overlap_scope)
44
+ end
45
+
46
+ records = span_klass.span_for(name).overlap(self).where(conditions)
47
+
48
+ if span_klass.respond_to?('not_archived')
49
+ records.not_archived
50
+ end
51
+
52
+ #TODO - This will have to be an after_save callback...
53
+ #if span_overlap_auto_close
54
+ # records.each do |record|
55
+ # record.close!(start_date)
56
+ # end
57
+ #end
58
+
59
+ if records.count > span_overlap_count
60
+ span_model.errors.add(:base, "date range overlaps with #{records.count} other record(s)")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,31 @@
1
+ require 'forwardable'
2
+
3
+ require ACTS_AS_SPAN_PATH + 'span_klass/status'
4
+ require ACTS_AS_SPAN_PATH + 'span_klass/overlap'
5
+
6
+ module ActsAsSpan
7
+ class SpanKlass
8
+ extend Forwardable
9
+
10
+ include ActsAsSpan::SpanKlass::Status
11
+ include ActsAsSpan::SpanKlass::Overlap
12
+
13
+ def_delegators :@acts_as_span_definition, :start_date_field,
14
+ :end_date_field,
15
+ :start_date_field_required,
16
+ :end_date_field_required,
17
+ :exclude_end,
18
+ :span_overlap_scope,
19
+ :span_overlap_count
20
+
21
+ def_delegators :klass, :table_name
22
+
23
+ attr_reader :name, :klass, :acts_as_span_definition
24
+
25
+ def initialize(name, klass, acts_as_span_definition)
26
+ @name = name
27
+ @klass = klass
28
+ @acts_as_span_definition = acts_as_span_definition
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_support'
2
+
3
+ module ActsAsSpan
4
+ class SpanKlass
5
+ module Overlap
6
+ extend ActiveSupport::Concern
7
+
8
+ module InstanceMethods
9
+ def overlap(test_span)
10
+ overlap_scope = klass.where( ["(#{table_name}.#{start_date_field} IS NULL OR :end_date IS NULL OR #{table_name}.#{start_date_field} <= :end_date) AND (#{table_name}.#{end_date_field} IS NULL OR :start_date IS NULL OR :start_date <= #{table_name}.#{end_date_field})", { :start_date => test_span.start_date, :end_date => test_span.end_date } ] )
11
+
12
+ if !test_span.new_record? && test_span.span_klass == klass
13
+ overlap_scope.where( ["#{table_name}.id != :id", { :id => test_span.id } ] )
14
+ end
15
+
16
+ overlap_scope
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_support'
2
+
3
+ module ActsAsSpan
4
+ class SpanKlass
5
+ module Status
6
+ extend ActiveSupport::Concern
7
+
8
+ module InstanceMethods
9
+ def current(query_date = Date.today)
10
+ klass.where(["(#{table_name}.#{start_date_field} <= :query_date OR #{table_name}.#{start_date_field} IS NULL) AND (#{table_name}.#{end_date_field} >= :query_date OR #{table_name}.#{end_date_field} IS NULL)", { :query_date => query_date } ] )
11
+ end
12
+
13
+ alias_method :current_on, :current
14
+
15
+ def future(query_date = Date.today)
16
+ klass.where(["#{table_name}.#{start_date_field} > :query_date", { :query_date => query_date } ] )
17
+ end
18
+
19
+ alias_method :future_on, :future
20
+
21
+ def expired(query_date = Date.today)
22
+ klass.where(["#{table_name}.#{end_date_field} < :query_date", { :query_date => query_date } ] )
23
+ end
24
+
25
+ alias_method :expired_on, :expired
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ module ActsAsSpan
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 5
6
+ PRE = nil
7
+
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
9
+
10
+ SUMMARY = "acts_as_span #{STRING}"
11
+ end
12
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe "acts_as_span" do
4
+ before(:each) do
5
+ build_model :span_model do
6
+ acts_as_span
7
+ end
8
+ end
9
+
10
+ context "ClassMethods" do
11
+ it "should return true for acts_as_span?" do
12
+ SpanModel.acts_as_span?.should be_true
13
+ end
14
+
15
+ it "should have 1 acts_as_span_definition" do
16
+ SpanModel.should have(1).acts_as_span_definitions
17
+ end
18
+
19
+ it "should set default options for acts_as_span_definition" do
20
+ span_definition = SpanModel.acts_as_span_definitions[:default]
21
+
22
+ span_definition.start_date_field.should == :start_date
23
+ span_definition.end_date_field.should == :end_date
24
+ span_definition.start_date_field_required.should be_false
25
+ span_definition.end_date_field_required.should be_false
26
+ span_definition.exclude_end.should be_false
27
+ span_definition.span_overlap_scope.should be_nil
28
+ span_definition.span_overlap_count.should be_nil
29
+ span_definition.name.should == :default
30
+ end
31
+
32
+ it "should return a SpanKlass w/ span" do
33
+ SpanModel.span.should be_instance_of(ActsAsSpan::SpanKlass)
34
+ end
35
+
36
+ it "should return a SpanKlass w/ span_for(:default)" do
37
+ SpanModel.span_for(:default).should be_instance_of(ActsAsSpan::SpanKlass)
38
+ end
39
+
40
+ it "should have (1) spans" do
41
+ SpanModel.spans.should have(1).span
42
+ end
43
+ end
44
+
45
+ context "InstanceMethods" do
46
+ let(:span_model) { SpanModel.new }
47
+
48
+ it "should return true for acts_as_span?" do
49
+ span_model.acts_as_span?.should be_true
50
+ end
51
+
52
+ it "should return a SpanInstance w/ span" do
53
+ span_model.span.should be_instance_of(ActsAsSpan::SpanInstance)
54
+ end
55
+
56
+ it "should return a SpanInstance w/ span_for(:default)" do
57
+ span_model.span_for(:default).should be_instance_of(ActsAsSpan::SpanInstance)
58
+ end
59
+
60
+ it "should have (1) spans" do
61
+ span_model.spans.should have(1).span
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Span" do
4
+ before(:each) do
5
+ build_model :span_model do
6
+ date :start_date
7
+ date :end_date
8
+
9
+ acts_as_span
10
+ end
11
+ end
12
+
13
+ let(:span_model) { SpanModel.new(:start_date => Date.today, :end_date => nil) }
14
+ let(:span_klass) { SpanModel.span }
15
+ let(:span_instance) { span_model.span }
16
+
17
+ context "ClassMethods" do
18
+ it "should delegate current" do
19
+ span_klass.should_receive(:current).and_return(true)
20
+
21
+ SpanModel.current
22
+ end
23
+
24
+ it "should delegate current_on" do
25
+ span_klass.should_receive(:current_on).and_return(true)
26
+
27
+ SpanModel.current_on
28
+ end
29
+
30
+ it "should delegate future" do
31
+ span_klass.should_receive(:future).and_return(true)
32
+
33
+ SpanModel.future
34
+ end
35
+
36
+ it "should delegate future_on" do
37
+ span_klass.should_receive(:future_on).and_return(true)
38
+
39
+ SpanModel.future_on
40
+ end
41
+
42
+ it "should delegate expired" do
43
+ span_klass.should_receive(:expired).and_return(true)
44
+
45
+ SpanModel.expired
46
+ end
47
+
48
+ it "should delegate expired_on" do
49
+ span_klass.should_receive(:expired_on).and_return(true)
50
+
51
+ SpanModel.expired_on
52
+ end
53
+ end
54
+
55
+ context "InstanceMethods" do
56
+ it "should delegate close!" do
57
+ span_instance.should_receive(:close!).and_return(true)
58
+
59
+ span_model.close!
60
+ end
61
+
62
+ it "should delegate close_on!" do
63
+ span_instance.should_receive(:close_on!).and_return(true)
64
+
65
+ span_model.close_on!
66
+ end
67
+
68
+ it "should delegate span_status" do
69
+ span_instance.should_receive(:span_status).and_return(true)
70
+
71
+ span_model.span_status
72
+ end
73
+
74
+ it "should delegate span_status_on" do
75
+ span_instance.should_receive(:span_status_on).and_return(true)
76
+
77
+ span_model.span_status_on
78
+ end
79
+
80
+ it "should delegate span_status_to_s" do
81
+ span_instance.should_receive(:span_status_to_s).and_return(true)
82
+
83
+ span_model.span_status_to_s
84
+ end
85
+
86
+ it "should delegate span_status_to_s_on" do
87
+ span_instance.should_receive(:span_status_to_s_on).and_return(true)
88
+
89
+ span_model.span_status_to_s_on
90
+ end
91
+
92
+ it "should delegate current?" do
93
+ span_instance.should_receive(:current?).and_return(true)
94
+
95
+ span_model.current?
96
+ end
97
+
98
+ it "should delegate current_on?" do
99
+ span_instance.should_receive(:current_on?).and_return(true)
100
+
101
+ span_model.current_on?
102
+ end
103
+
104
+ it "should delegate future?" do
105
+ span_instance.should_receive(:future?).and_return(true)
106
+
107
+ span_model.future?
108
+ end
109
+
110
+ it "should delegate future_on?" do
111
+ span_instance.should_receive(:future_on?).and_return(true)
112
+
113
+ span_model.future_on?
114
+ end
115
+
116
+ it "should delegate expired?" do
117
+ span_instance.should_receive(:expired?).and_return(true)
118
+
119
+ span_model.expired?
120
+ end
121
+
122
+ it "should delegate expired_on?" do
123
+ span_instance.should_receive(:expired_on?).and_return(true)
124
+
125
+ span_model.expired_on?
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe "acts_as_span" do
4
+ before(:each) do
5
+ build_model :negative_model do
6
+ end
7
+ end
8
+
9
+ context "NegativeModel (Class)" do
10
+ it "should respond_to acts_as_span" do
11
+ NegativeModel.should respond_to(:acts_as_span)
12
+ end
13
+
14
+ it "should return false for acts_as_span?" do
15
+ NegativeModel.acts_as_span?.should be_false
16
+ end
17
+
18
+ it "should have no acts_as_span_definitions" do
19
+ NegativeModel.acts_as_span_definitions.should be_empty
20
+ end
21
+ end
22
+
23
+ context "NegativeModel (Instance)" do
24
+ let(:negative_model) { NegativeModel.new }
25
+
26
+ it "should return false for acts_as_span?" do
27
+ negative_model.acts_as_span?.should be_false
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,304 @@
1
+ require 'spec_helper'
2
+
3
+ describe "a basic model using acts_as_span" do
4
+ before(:all) do
5
+ build_model :span_model do
6
+ string :description
7
+ date :start_date
8
+ date :end_date
9
+
10
+ acts_as_span
11
+ end
12
+
13
+ @query_date = Date.today + 1.month
14
+ end
15
+
16
+ context "named_scopes and current_on?, expired_on?, and future_on?" do
17
+ # -2 -1 Q +1 +2 C F E
18
+ # A |---| E
19
+ # B |-------| C
20
+ # C |--------------| C
21
+ # D | C
22
+ # E |------| C
23
+ # F |--| F
24
+ # G |-> C
25
+ # H |-> C
26
+ # I |-> F
27
+ # J <-| E
28
+ # K <-| C
29
+ # L <-| C
30
+ # M <-> C
31
+ context "A) start_date < query_date & end_date < query_date" do
32
+ before(:all) do
33
+ @span_model = SpanModel.create!(:start_date => @query_date - 2.days, :end_date => @query_date - 1.day)
34
+ end
35
+
36
+ it "should NOT be included in #current" do
37
+ SpanModel.current_on(@query_date).should_not include(@span_model)
38
+ @span_model.current_on?(@query_date).should be_false
39
+ end
40
+
41
+ it "should NOT be included in #future" do
42
+ SpanModel.future_on(@query_date).should_not include(@span_model)
43
+ @span_model.future_on?(@query_date).should be_false
44
+ end
45
+
46
+ it "should be included in #expired" do
47
+ SpanModel.expired_on(@query_date).should include(@span_model)
48
+ @span_model.expired_on?(@query_date).should be_true
49
+ end
50
+ end
51
+
52
+ context "B) start_date < query_date & end_date == query_date" do
53
+ before(:all) do
54
+ @span_model = SpanModel.create!(:start_date => @query_date - 2.day, :end_date => @query_date)
55
+ end
56
+
57
+ it "should be included in #current" do
58
+ SpanModel.current_on(@query_date).should include(@span_model)
59
+ @span_model.current_on?(@query_date).should be_true
60
+ end
61
+
62
+ it "should NOT be included in #future" do
63
+ SpanModel.future_on(@query_date).should_not include(@span_model)
64
+ @span_model.future_on?(@query_date).should be_false
65
+ end
66
+
67
+ it "should NOT be included in #expired" do
68
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
69
+ @span_model.expired_on?(@query_date).should be_false
70
+ end
71
+ end
72
+
73
+ context "C) start_date < query_date & end_date > query_date" do
74
+ before(:all) do
75
+ @span_model = SpanModel.create!(:start_date => @query_date - 2.day, :end_date => @query_date + 2.day)
76
+ end
77
+
78
+ it "should be included in #current" do
79
+ SpanModel.current_on(@query_date).should include(@span_model)
80
+ @span_model.current_on?(@query_date).should be_true
81
+ end
82
+
83
+ it "should NOT be included in #future" do
84
+ SpanModel.future_on(@query_date).should_not include(@span_model)
85
+ @span_model.future_on?(@query_date).should be_false
86
+ end
87
+
88
+ it "should NOT be included in #expired" do
89
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
90
+ @span_model.expired_on?(@query_date).should be_false
91
+ end
92
+ end
93
+
94
+ context "D) start_date == query_date & end_date == query_date" do
95
+ before(:all) do
96
+ @span_model = SpanModel.create!(:start_date => @query_date, :end_date => @query_date)
97
+ end
98
+
99
+ it "should be included in #current" do
100
+ SpanModel.current_on(@query_date).should include(@span_model)
101
+ @span_model.current_on?(@query_date).should be_true
102
+ end
103
+
104
+ it "should NOT be included in #future" do
105
+ SpanModel.future_on(@query_date).should_not include(@span_model)
106
+ @span_model.future_on?(@query_date).should be_false
107
+ end
108
+
109
+ it "should NOT be included in #expired" do
110
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
111
+ @span_model.expired_on?(@query_date).should be_false
112
+ end
113
+ end
114
+
115
+ context "E) start_date == query_date & end_date > query_date" do
116
+ before(:all) do
117
+ @span_model = SpanModel.create!(:start_date => @query_date, :end_date => @query_date + 2.day)
118
+ end
119
+
120
+ it "should be included in #current" do
121
+ SpanModel.current_on(@query_date).should include(@span_model)
122
+ @span_model.current_on?(@query_date).should be_true
123
+ end
124
+
125
+ it "should NOT be included in #future" do
126
+ SpanModel.future_on(@query_date).should_not include(@span_model)
127
+ @span_model.future_on?(@query_date).should be_false
128
+ end
129
+
130
+ it "should NOT be included in #expired" do
131
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
132
+ @span_model.expired_on?(@query_date).should be_false
133
+ end
134
+ end
135
+
136
+ context "F) start_date > query_date & end_date > query_date" do
137
+ before(:all) do
138
+ @span_model = SpanModel.create!(:start_date => @query_date + 1.day, :end_date => @query_date + 2.days)
139
+ end
140
+
141
+ it "should NOT be included in #current" do
142
+ SpanModel.current_on(@query_date).should_not include(@span_model)
143
+ @span_model.current_on?(@query_date).should be_false
144
+ end
145
+
146
+ it "should be included in #future" do
147
+ SpanModel.future_on(@query_date).should include(@span_model)
148
+ @span_model.future_on?(@query_date).should be_true
149
+ end
150
+
151
+ it "should NOT be included in #expired" do
152
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
153
+ @span_model.expired_on?(@query_date).should be_false
154
+ end
155
+ end
156
+
157
+ context "G) start_date < query_date & end_date == nil" do
158
+ before(:all) do
159
+ @span_model = SpanModel.create!(:start_date => @query_date - 2.day, :end_date => nil)
160
+ end
161
+
162
+ it "should be included in #current" do
163
+ SpanModel.current_on(@query_date).should include(@span_model)
164
+ @span_model.current_on?(@query_date).should be_true
165
+ end
166
+
167
+ it "should NOT be included in #future" do
168
+ SpanModel.future_on(@query_date).should_not include(@span_model)
169
+ @span_model.future_on?(@query_date).should be_false
170
+ end
171
+
172
+ it "should NOT be included in #expired" do
173
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
174
+ @span_model.expired_on?(@query_date).should be_false
175
+ end
176
+ end
177
+
178
+ context "H) start_date == query_date & end_date == nil" do
179
+ before(:all) do
180
+ @span_model = SpanModel.create!(:start_date => @query_date, :end_date => nil)
181
+ end
182
+
183
+ it "should be included in #current" do
184
+ SpanModel.current_on(@query_date).should include(@span_model)
185
+ @span_model.current_on?(@query_date).should be_true
186
+ end
187
+
188
+ it "should NOT be included in #future" do
189
+ SpanModel.future_on(@query_date).should_not include(@span_model)
190
+ @span_model.future_on?(@query_date).should be_false
191
+ end
192
+
193
+ it "should NOT be included in #expired" do
194
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
195
+ @span_model.expired_on?(@query_date).should be_false
196
+ end
197
+ end
198
+
199
+ context "I) start_date > query_date & end_date == nil" do
200
+ before(:all) do
201
+ @span_model = SpanModel.create!(:start_date => @query_date + 1.day, :end_date => nil)
202
+ end
203
+
204
+ it "should NOT be included in #current" do
205
+ SpanModel.current_on(@query_date).should_not include(@span_model)
206
+ @span_model.current_on?(@query_date).should be_false
207
+ end
208
+
209
+ it "should be included in #future" do
210
+ SpanModel.future_on(@query_date).should include(@span_model)
211
+ @span_model.future_on?(@query_date).should be_true
212
+ end
213
+
214
+ it "should NOT be included in #expired" do
215
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
216
+ @span_model.expired_on?(@query_date).should be_false
217
+ end
218
+ end
219
+
220
+ context "J) start_date == nil & end_date < query_date" do
221
+ before(:all) do
222
+ @span_model = SpanModel.create!(:start_date => nil, :end_date => @query_date - 1.day)
223
+ end
224
+
225
+ it "should NOT be included in #current" do
226
+ SpanModel.current_on(@query_date).should_not include(@span_model)
227
+ @span_model.current_on?(@query_date).should be_false
228
+ end
229
+
230
+ it "should NOT be included in #future" do
231
+ SpanModel.future_on(@query_date).should_not include(@span_model)
232
+ @span_model.future_on?(@query_date).should be_false
233
+ end
234
+
235
+ it "should be included in #expired" do
236
+ SpanModel.expired_on(@query_date).should include(@span_model)
237
+ @span_model.expired_on?(@query_date).should be_true
238
+ end
239
+ end
240
+
241
+ context "K) start_date == nil & end_date == query_date" do
242
+ before(:all) do
243
+ @span_model = SpanModel.create!(:start_date => nil, :end_date => @query_date)
244
+ end
245
+
246
+ it "should be included in #current" do
247
+ SpanModel.current_on(@query_date).should include(@span_model)
248
+ @span_model.current_on?(@query_date).should be_true
249
+ end
250
+
251
+ it "should NOT be included in #future" do
252
+ SpanModel.future_on(@query_date).should_not include(@span_model)
253
+ @span_model.future_on?(@query_date).should be_false
254
+ end
255
+
256
+ it "should NOT be included in #expired" do
257
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
258
+ @span_model.expired_on?(@query_date).should be_false
259
+ end
260
+ end
261
+
262
+ context "L) start_date == nil & end_date > query_date" do
263
+ before(:all) do
264
+ @span_model = SpanModel.create!(:start_date => nil, :end_date => @query_date + 2.day)
265
+ end
266
+
267
+ it "should be included in #current" do
268
+ SpanModel.current_on(@query_date).should include(@span_model)
269
+ @span_model.current_on?(@query_date).should be_true
270
+ end
271
+
272
+ it "should NOT be included in #future" do
273
+ SpanModel.future_on(@query_date).should_not include(@span_model)
274
+ @span_model.future_on?(@query_date).should be_false
275
+ end
276
+
277
+ it "should NOT be included in #expired" do
278
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
279
+ @span_model.expired_on?(@query_date).should be_false
280
+ end
281
+ end
282
+
283
+ context "M) start_date == nil & end_date == nil" do
284
+ before(:all) do
285
+ @span_model = SpanModel.create!(:start_date => nil, :end_date => nil)
286
+ end
287
+
288
+ it "should be included in #current" do
289
+ SpanModel.current_on(@query_date).should include(@span_model)
290
+ @span_model.current_on?(@query_date).should be_true
291
+ end
292
+
293
+ it "should NOT be included in #future" do
294
+ SpanModel.future_on(@query_date).should_not include(@span_model)
295
+ @span_model.future_on?(@query_date).should be_false
296
+ end
297
+
298
+ it "should NOT be included in #expired" do
299
+ SpanModel.expired_on(@query_date).should_not include(@span_model)
300
+ @span_model.expired_on?(@query_date).should be_false
301
+ end
302
+ end
303
+ end
304
+ end