acts_as_span 0.0.5

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,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