acts_as_flux_capacitor 0.6.2 → 0.6.3
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 +5 -0
- data/Rakefile +3 -3
- data/lib/acts_as_flux_capacitor/acts_as_flux_capacitor.rb +302 -301
- data/lib/acts_as_flux_capacitor/core_class_extensions.rb +15 -15
- data/lib/acts_as_flux_capacitor/range_overlap.rb +37 -37
- data/lib/acts_as_flux_capacitor/temporal.rb +112 -111
- data/lib/acts_as_flux_capacitor/version.rb +4 -4
- data/spec/report.html +1 -1
- metadata +2 -2
data/History.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'config/requirements'
|
2
|
-
require 'config/hoe' # setup Hoe + all gem configuration
|
3
|
-
|
1
|
+
require 'config/requirements'
|
2
|
+
require 'config/hoe' # setup Hoe + all gem configuration
|
3
|
+
|
4
4
|
Dir['tasks/**/*.rake'].each { |rake| load rake }
|
@@ -1,229 +1,231 @@
|
|
1
|
-
module HolmesLabs #:nodoc:
|
2
|
-
module Acts #:nodoc:
|
3
|
-
module FluxCapacitor
|
4
|
-
module ActiveRecord #:nodoc:
|
5
|
-
def self.included(base) # :nodoc:
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
include OperateOnEvents
|
19
|
-
include OperateOnDurations
|
1
|
+
module HolmesLabs #:nodoc:
|
2
|
+
module Acts #:nodoc:
|
3
|
+
module FluxCapacitor
|
4
|
+
module ActiveRecord #:nodoc:
|
5
|
+
def self.included(base) # :nodoc:
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def acts_as_flux_capacitor
|
11
|
+
unless acts_as_flux_capacitor?
|
12
|
+
class << self
|
13
|
+
alias_method :original_find, :find
|
14
|
+
end
|
15
|
+
end
|
16
|
+
include Temporal
|
17
|
+
include InstanceMethods
|
18
|
+
include OperateOnEvents
|
19
|
+
include OperateOnDurations
|
20
20
|
include CompareEvents
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
include Validation
|
22
|
+
end
|
23
|
+
|
24
|
+
def flux_capacitor?
|
25
|
+
included_modules.include?(InstanceMethods)
|
26
|
+
end
|
27
|
+
alias acts_as_flux_capacitor? flux_capacitor?
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
module InstanceMethods #:nodoc:
|
33
|
-
def self.included(base) # :nodoc:
|
32
|
+
module InstanceMethods #:nodoc:
|
33
|
+
def self.included(base) # :nodoc:
|
34
34
|
base.extend ClassMethods
|
35
|
-
base.extend QueryGenerators
|
35
|
+
base.extend QueryGenerators
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
|
40
|
+
CUSTOM_FIND_OPTIONS = [
|
41
|
+
:before, # raw sql
|
42
|
+
:after, # raw sql
|
43
|
+
|
44
|
+
:now,
|
45
|
+
:at,
|
46
|
+
|
47
|
+
:past,
|
48
|
+
:present,
|
49
|
+
:future,
|
50
|
+
|
51
|
+
:from,
|
52
|
+
:to,
|
53
|
+
|
54
|
+
:starts_before, # raw sql
|
55
|
+
:ends_after # raw sql
|
56
|
+
]
|
57
|
+
|
58
|
+
def find(*args)
|
59
|
+
options = args.extract_options!
|
60
|
+
|
61
|
+
current_time = options.delete(:current_time) || Time.now
|
62
|
+
temporal_conditions = extract_temporal_conditions!(options)
|
63
|
+
sql_temporal = sql_generate_temporal(temporal_conditions,current_time)
|
64
|
+
|
65
|
+
with_scope( :find => {
|
66
|
+
:conditions => sql_temporal ,
|
67
|
+
:order => sql_oldest_first }) do
|
68
|
+
original_find(*(args << options))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def overlap(an_event,another_event)
|
73
|
+
an_event.to_range.overlap(another_event.to_range)
|
74
|
+
end
|
75
|
+
|
76
|
+
def overlap?(an_event,another_event)
|
77
|
+
an_event.to_range.overlap?(another_event.to_range)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def extract_temporal_conditions!(options)
|
83
|
+
temporal_conditions = {}
|
84
|
+
|
85
|
+
options.each do |key,val|
|
86
|
+
temporal_conditions.merge!({key, options.delete(key)}) if CUSTOM_FIND_OPTIONS.include?(key)
|
87
|
+
end
|
88
|
+
|
89
|
+
temporal_conditions
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
module QueryGenerators
|
94
|
+
private
|
95
|
+
|
96
|
+
def sql_before(some_time, current_time)
|
97
|
+
# table_name comes from ActiveRecord.
|
98
|
+
"#{table_name}.ends_at < '#{some_time.to_s(:db)}'"
|
99
|
+
end
|
100
|
+
alias sql_to sql_before
|
101
|
+
alias sql_ends_before sql_before
|
102
|
+
|
103
|
+
def sql_after(some_time , current_time)
|
104
|
+
# table_name comes from ActiveRecord.
|
105
|
+
"#{table_name}.begins_at > '#{some_time.to_s(:db)}'"
|
106
|
+
end
|
107
|
+
alias sql_from sql_after
|
108
|
+
alias sql_begins_after sql_after
|
109
|
+
|
110
|
+
def sql_at(some_time , current_time)
|
111
|
+
sql_join_conditions([ sql_starts_before( some_time , current_time),
|
112
|
+
sql_ends_after( some_time , current_time)])
|
113
|
+
end
|
114
|
+
|
115
|
+
def sql_ends_after(some_time, current_time)
|
116
|
+
"#{table_name}.ends_at > '#{some_time.to_s(:db)}'"
|
117
|
+
end
|
118
|
+
|
119
|
+
def sql_starts_before(some_time , current_time)
|
120
|
+
"#{table_name}.begins_at < '#{some_time.to_s(:db)}'"
|
121
|
+
end
|
122
|
+
|
123
|
+
def sql_past(is_true , current_time)
|
124
|
+
if is_true
|
125
|
+
sql_before(current_time, current_time)
|
126
|
+
else
|
127
|
+
sql_join_conditions_or([ sql_present( current_time),
|
128
|
+
sql_future( current_time)])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
alias sql_ended sql_past
|
132
|
+
|
133
|
+
def sql_present(is_true , current_time)
|
134
|
+
if is_true
|
135
|
+
sql_at(current_time,current_time)
|
136
|
+
else
|
137
|
+
sql_join_conditions_or([ sql_past( current_time),
|
138
|
+
sql_future( current_time)])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
alias sql_now sql_present
|
142
|
+
|
143
|
+
def sql_future(is_true, current_time)
|
144
|
+
if is_true
|
145
|
+
sql_after(current_time,current_time)
|
146
|
+
else
|
147
|
+
sql_join_conditions_or([ sql_present(current_time),
|
148
|
+
sql_past( current_time)])
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def sql_oldest_first(attrib = :begins_at)
|
153
|
+
"#{table_name}.#{attrib.to_s} ASC"
|
154
|
+
end
|
155
|
+
|
156
|
+
def sql_join_conditions(conditions)
|
157
|
+
"#{conditions.join(") AND (")}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def sql_join_conditions_or(conditions)
|
161
|
+
"#{conditions.join(") OR (")}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def sql_generate_temporal temporal_conditions , current_time
|
165
|
+
sql_conditions = []
|
166
|
+
temporal_conditions.each do |condition,parameter|
|
167
|
+
sql_conditions << send("sql_#{condition}".to_sym , parameter, current_time)
|
168
|
+
end
|
169
|
+
|
170
|
+
sql_join_conditions(sql_conditions)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_range
|
175
|
+
(start..finish)
|
36
176
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
options = args.extract_options!
|
60
|
-
|
61
|
-
current_time = options.delete(:current_time) || Time.now
|
62
|
-
temporal_conditions = extract_temporal_conditions!(options)
|
63
|
-
sql_temporal = sql_generate_temporal(temporal_conditions,current_time)
|
64
|
-
|
65
|
-
with_scope( :find => {
|
66
|
-
:conditions => sql_temporal ,
|
67
|
-
:order => sql_oldest_first }) do
|
68
|
-
original_find(*(args << options))
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def overlap(an_event,another_event)
|
73
|
-
an_event.to_range.overlap(another_event.to_range)
|
74
|
-
end
|
75
|
-
|
76
|
-
def overlap?(an_event,another_event)
|
77
|
-
an_event.to_range.overlap?(another_event.to_range)
|
78
|
-
end
|
79
|
-
private
|
80
|
-
def extract_temporal_conditions!(options)
|
81
|
-
temporal_conditions = {}
|
82
|
-
|
83
|
-
options.each do |key,val|
|
84
|
-
temporal_conditions.merge!({key, options.delete(key)}) if CUSTOM_FIND_OPTIONS.include?(key)
|
85
|
-
end
|
86
|
-
|
87
|
-
temporal_conditions
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
module QueryGenerators
|
92
|
-
private
|
93
|
-
|
94
|
-
def sql_before(some_time, current_time)
|
95
|
-
# table_name comes from ActiveRecord.
|
96
|
-
"#{table_name}.ends_at < '#{some_time.to_s(:db)}'"
|
97
|
-
end
|
98
|
-
alias sql_to sql_before
|
99
|
-
alias sql_ends_before sql_before
|
100
|
-
|
101
|
-
def sql_after(some_time , current_time)
|
102
|
-
# table_name comes from ActiveRecord.
|
103
|
-
"#{table_name}.begins_at > '#{some_time.to_s(:db)}'"
|
104
|
-
end
|
105
|
-
alias sql_from sql_after
|
106
|
-
alias sql_begins_after sql_after
|
107
|
-
|
108
|
-
def sql_at(some_time , current_time)
|
109
|
-
sql_join_conditions([ sql_starts_before( some_time , current_time),
|
110
|
-
sql_ends_after( some_time , current_time)])
|
111
|
-
end
|
112
|
-
|
113
|
-
def sql_ends_after(some_time, current_time)
|
114
|
-
"#{table_name}.ends_at > '#{some_time.to_s(:db)}'"
|
115
|
-
end
|
116
|
-
|
117
|
-
def sql_starts_before(some_time , current_time)
|
118
|
-
"#{table_name}.begins_at < '#{some_time.to_s(:db)}'"
|
119
|
-
end
|
120
|
-
|
121
|
-
def sql_past(is_true , current_time)
|
122
|
-
if is_true
|
123
|
-
sql_before(current_time, current_time)
|
124
|
-
else
|
125
|
-
sql_join_conditions_or([ sql_present( current_time),
|
126
|
-
sql_future( current_time)])
|
127
|
-
end
|
128
|
-
end
|
129
|
-
alias sql_ended sql_past
|
130
|
-
|
131
|
-
def sql_present(is_true , current_time)
|
132
|
-
if is_true
|
133
|
-
sql_at(current_time,current_time)
|
134
|
-
else
|
135
|
-
sql_join_conditions_or([ sql_past( current_time),
|
136
|
-
sql_future( current_time)])
|
137
|
-
end
|
138
|
-
end
|
139
|
-
alias sql_now sql_present
|
140
|
-
|
141
|
-
def sql_future(is_true, current_time)
|
142
|
-
if is_true
|
143
|
-
sql_after(current_time,current_time)
|
144
|
-
else
|
145
|
-
sql_join_conditions_or([ sql_present(current_time),
|
146
|
-
sql_past( current_time)])
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def sql_oldest_first(attrib = :begins_at)
|
151
|
-
"#{table_name}.#{attrib.to_s} ASC"
|
152
|
-
end
|
153
|
-
|
154
|
-
def sql_join_conditions(conditions)
|
155
|
-
"#{conditions.join(") AND (")}"
|
156
|
-
end
|
157
|
-
|
158
|
-
def sql_join_conditions_or(conditions)
|
159
|
-
"#{conditions.join(") OR (")}"
|
160
|
-
end
|
161
|
-
|
162
|
-
def sql_generate_temporal temporal_conditions , current_time
|
163
|
-
sql_conditions = []
|
164
|
-
temporal_conditions.each do |condition,parameter|
|
165
|
-
sql_conditions << send("sql_#{condition}".to_sym , parameter, current_time)
|
166
|
-
end
|
167
|
-
|
168
|
-
sql_join_conditions(sql_conditions)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def to_range
|
173
|
-
(start..finish)
|
174
|
-
end
|
175
|
-
alias range to_range
|
176
|
-
|
177
|
-
def start ; read_attribute(:begins_at) ; end
|
178
|
-
def start=(val) ; write_attribute(:begins_at,val); end
|
179
|
-
|
180
|
-
def finish ; read_attribute(:ends_at) ; end
|
181
|
-
def finish=(val) ; write_attribute(:ends_at,val) ; end
|
182
|
-
|
183
|
-
alias start_time start
|
184
|
-
alias start_time= start=
|
185
|
-
alias beginning start
|
186
|
-
alias beginning= start=
|
187
|
-
alias end_time finish
|
188
|
-
alias end_time= finish=
|
189
|
-
alias from start
|
190
|
-
alias from= start=
|
191
|
-
alias to finish
|
192
|
-
alias to= finish=
|
193
|
-
alias ending finish
|
194
|
-
alias ending= finish=
|
195
|
-
end # InstanceMethods
|
196
|
-
|
177
|
+
alias range to_range
|
178
|
+
|
179
|
+
def start ; read_attribute(:begins_at) ; end
|
180
|
+
def start=(val) ; write_attribute(:begins_at,val); end
|
181
|
+
|
182
|
+
def finish ; read_attribute(:ends_at) ; end
|
183
|
+
def finish=(val) ; write_attribute(:ends_at,val) ; end
|
184
|
+
|
185
|
+
alias start_time start
|
186
|
+
alias start_time= start=
|
187
|
+
alias beginning start
|
188
|
+
alias beginning= start=
|
189
|
+
alias end_time finish
|
190
|
+
alias end_time= finish=
|
191
|
+
alias from start
|
192
|
+
alias from= start=
|
193
|
+
alias to finish
|
194
|
+
alias to= finish=
|
195
|
+
alias ending finish
|
196
|
+
alias ending= finish=
|
197
|
+
end # InstanceMethods
|
198
|
+
|
197
199
|
module OperateOnEvents
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
def started?(time = Time.now)
|
218
|
-
self.start_time.before?(time)
|
219
|
-
end
|
220
|
-
alias began? started?
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
200
|
+
def at?(obj=Time.now)
|
201
|
+
bigger_than = self.start_time.before?(extract_time(obj,:start_time)) &&
|
202
|
+
self.end_time.after?( extract_time(obj,:end_time ))
|
203
|
+
smaller_than = self.start_time.after?( extract_time(obj,:start_time)) &&
|
204
|
+
self.end_time.before?( extract_time(obj,:end_time))
|
205
|
+
same_size = self.start_time == extract_time(obj,:start_time) &&
|
206
|
+
self.end_time == extract_time(obj,:end_time)
|
207
|
+
return bigger_than || smaller_than || same_size
|
208
|
+
end
|
209
|
+
alias happening_at? at?
|
210
|
+
alias happened_at? at?
|
211
|
+
|
212
|
+
def happening_now?(time = Time.now)
|
213
|
+
at?(time)
|
214
|
+
end
|
215
|
+
alias in_progress? happening_now?
|
216
|
+
alias now? happening_now?
|
217
|
+
alias present? happening_now?
|
218
|
+
|
219
|
+
def started?(time = Time.now)
|
220
|
+
self.start_time.before?(time)
|
221
|
+
end
|
222
|
+
alias began? started?
|
223
|
+
|
224
|
+
def ended?(time = Time.now)
|
225
|
+
self.end_time.before?(time)
|
226
|
+
end
|
227
|
+
alias finished? ended?
|
228
|
+
|
227
229
|
def <=>(obj)
|
228
230
|
if self.before?(obj)
|
229
231
|
return -1
|
@@ -235,94 +237,94 @@ private
|
|
235
237
|
return 'ActsAsEvent: non-fatal error in #status'
|
236
238
|
end
|
237
239
|
end
|
238
|
-
|
240
|
+
|
239
241
|
def transpose(duration)
|
240
242
|
new_event = self.clone
|
241
|
-
|
242
|
-
end
|
243
|
-
alias shift transpose
|
244
|
-
alias reschedule transpose
|
245
|
-
alias move transpose
|
246
|
-
|
247
|
-
|
248
|
-
self.beginning
|
249
|
-
|
250
|
-
|
251
|
-
end
|
252
|
-
alias shift! transpose!
|
253
|
-
alias reschedule! transpose!
|
254
|
-
alias move! transpose!
|
255
|
-
end # OperateOnEvents
|
256
|
-
|
257
|
-
module OperateOnDurations
|
258
|
-
def elapsed(time = Time.now)
|
243
|
+
new_event.transpose!(duration)
|
244
|
+
end
|
245
|
+
alias shift transpose
|
246
|
+
alias reschedule transpose
|
247
|
+
alias move transpose
|
248
|
+
|
249
|
+
def transpose!(duration)
|
250
|
+
self.beginning += duration
|
251
|
+
self.ending += duration
|
252
|
+
return self
|
253
|
+
end
|
254
|
+
alias shift! transpose!
|
255
|
+
alias reschedule! transpose!
|
256
|
+
alias move! transpose!
|
257
|
+
end # OperateOnEvents
|
258
|
+
|
259
|
+
module OperateOnDurations
|
260
|
+
def elapsed(time = Time.now)
|
259
261
|
time_since_beginning = self.class.length_of_time_since(self.beginning,time)
|
260
|
-
|
261
|
-
end
|
262
|
-
|
263
|
-
def remaining(time = Time.now)
|
262
|
+
self.now?(time) ? time_since_beginning : nil
|
263
|
+
end
|
264
|
+
|
265
|
+
def remaining(time = Time.now)
|
264
266
|
time_until_end = self.class.length_of_time_until(self.ending,time)
|
265
|
-
|
266
|
-
end
|
267
|
-
|
267
|
+
self.now?(time) ? time_until_end : nil
|
268
|
+
end
|
269
|
+
|
268
270
|
def percent_complete(time = Time.now)
|
269
|
-
|
270
|
-
|
271
|
-
end
|
272
|
-
alias percent_elapsed percent_complete
|
273
|
-
alias percent_finished percent_complete
|
274
|
-
|
271
|
+
amount_complete = self.elapsed(time)
|
272
|
+
amount_complete ? 100 * ((amount_complete * 1.0) / duration) : nil
|
273
|
+
end
|
274
|
+
alias percent_elapsed percent_complete
|
275
|
+
alias percent_finished percent_complete
|
276
|
+
|
275
277
|
def percent_remaining(time = Time.now)
|
276
|
-
|
277
|
-
|
278
|
-
end
|
279
|
-
end # OperateOnDurations
|
280
|
-
|
281
|
-
module CompareEvents
|
278
|
+
amount_remaining = self.remaining(time)
|
279
|
+
amount_remaining ? 100 * (( amount_remaining * 1.0) / duration) : nil
|
280
|
+
end
|
281
|
+
end # OperateOnDurations
|
282
|
+
|
283
|
+
module CompareEvents
|
282
284
|
def overlaps_with?(other_event)
|
283
|
-
self.to_range.overlaps?(other_event.to_range)
|
284
|
-
end
|
285
|
+
self.to_range.overlaps?(other_event.to_range)
|
286
|
+
end
|
287
|
+
|
288
|
+
def overlap_with(other_event)
|
289
|
+
(self.to_range.overlap(other_event.to_range)).size
|
290
|
+
end
|
291
|
+
alias overlap overlap_with
|
285
292
|
|
286
|
-
def overlap_with(other_event)
|
287
|
-
(self.to_range.overlap(other_event.to_range)).size
|
288
|
-
end
|
289
|
-
alias overlap overlap_with
|
290
|
-
|
291
293
|
def back_to_back_with?(other_event,tolerance = 2.seconds)
|
292
|
-
|
293
|
-
|
294
|
-
|
294
|
+
# tolerance is in seconds.
|
295
|
+
self.start_time.before?(other_event.start_time) ? first_to_start = self : first_to_start = other_event
|
296
|
+
first_to_start.ends_at.approx_eql?(other_event.begins_at,tolerance)
|
297
|
+
end
|
298
|
+
|
299
|
+
def duration
|
300
|
+
self.class.length_of_time_until(self.ending,self.beginning)
|
301
|
+
end
|
302
|
+
alias length_of_time duration
|
303
|
+
alias length duration
|
304
|
+
|
305
|
+
def duration=(new_duration)
|
306
|
+
self.ending = self.beginning + new_duration
|
307
|
+
end
|
308
|
+
alias length_of_time= duration=
|
309
|
+
alias length= duration=
|
310
|
+
end # CompareEvents
|
311
|
+
|
312
|
+
module Validation
|
313
|
+
|
314
|
+
def validate
|
315
|
+
if self.begins_at.nil?
|
316
|
+
errors.add(:begins_at, "must have a start time")
|
317
|
+
if self.ends_at.nil?
|
318
|
+
errors.add(:ends_at, "must have an end time")
|
319
|
+
end
|
320
|
+
else
|
321
|
+
self.beginning.after?(self.ending) ? errors.add_to_base("An event cannot end before it begins") : nil
|
322
|
+
end
|
295
323
|
end
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
alias length_of_time duration
|
301
|
-
alias length duration
|
302
|
-
|
303
|
-
def duration=(new_duration)
|
304
|
-
self.ending = self.beginning + new_duration
|
305
|
-
end
|
306
|
-
alias length_of_time= duration=
|
307
|
-
alias length= duration=
|
308
|
-
end # CompareEvents
|
309
|
-
|
310
|
-
module Validation
|
311
|
-
|
312
|
-
def validate
|
313
|
-
if self.begins_at.nil?
|
314
|
-
errors.add(:begins_at, "must have a start time")
|
315
|
-
if self.ends_at.nil?
|
316
|
-
errors.add(:ends_at, "must have an end time")
|
317
|
-
end
|
318
|
-
else
|
319
|
-
self.beginning.after?(self.ending) ? errors.add_to_base("An event cannot end before it begins") : nil
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
end # ActiveRecord
|
324
|
-
end # FluxCapacitor
|
325
|
-
end # Acts
|
324
|
+
end
|
325
|
+
end # ActiveRecord
|
326
|
+
end # FluxCapacitor
|
327
|
+
end # Acts
|
326
328
|
end # HolmesLabs
|
327
329
|
ActiveRecord::Base.send :include, HolmesLabs::Acts::FluxCapacitor::ActiveRecord
|
328
330
|
|
@@ -332,9 +334,8 @@ ActiveRecord::Base.send :include, HolmesLabs::Acts::FluxCapacitor::ActiveRecord
|
|
332
334
|
# force exclusivity of events at any given time via parameter to acts_as_event()
|
333
335
|
# add new validations
|
334
336
|
# rake tasks
|
335
|
-
# parameter to comparison operator to still allow true if overlap
|
336
337
|
|
337
338
|
|
338
339
|
# NOTES
|
339
340
|
###########
|
340
|
-
# verify #find_* returns ordered results unless specified otherwise.
|
341
|
+
# verify #find_* returns ordered results unless specified otherwise.
|
@@ -1,18 +1,18 @@
|
|
1
1
|
class Time
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
2
|
+
|
3
|
+
@@approx_eql_tolerance_in_seconds = 2
|
4
|
+
|
5
|
+
def Time.approx_eql_tolerance_in_seconds
|
6
|
+
@@approx_eql_tolerance_in_seconds
|
7
|
+
end
|
8
|
+
|
9
|
+
def Time.approx_eql_tolerance_in_seconds=(seconds)
|
10
|
+
@@approx_eql_tolerance_in_seconds = seconds
|
11
|
+
end
|
12
|
+
|
13
|
+
def approx_eql? other_time,tolerance = @@approx_eql_tolerance_in_seconds
|
14
|
+
times_to_i = [self,other_time].collect { |time| time.to_i }
|
15
|
+
(self - other_time).abs < tolerance
|
16
|
+
end
|
17
17
|
end
|
18
18
|
|
@@ -1,42 +1,42 @@
|
|
1
1
|
class Range
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# Returns true if ranges overlap, false otherwise
|
9
|
-
def overlaps? other
|
10
|
-
|
11
|
-
|
2
|
+
|
3
|
+
# the overlaps?(range) method comes from ActiveSupport::CoreExtensions::Range
|
4
|
+
# thanks to Nick Ang for suggesting its use.
|
5
|
+
# most of its tests were written by Jack Christensen of
|
6
|
+
# http://www.jackchristensen.com/article/2/detecting-overlapping-ranges
|
7
|
+
|
8
|
+
# Returns true if ranges overlap, false otherwise
|
9
|
+
def overlaps? other
|
10
|
+
include?(other.first) || other.include?(first)
|
11
|
+
end
|
12
12
|
|
13
|
-
|
13
|
+
# all of this added by jim cropcho:
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
15
|
+
def overlap(other_range)
|
16
|
+
return 0 unless self.overlaps?(other_range)
|
17
|
+
([self.first,other_range.first].max)..([self.last,other_range.last].min)
|
18
|
+
end
|
19
|
+
|
20
|
+
def size
|
21
|
+
if self.first.is_a?(Time) && self.last.is_a?(Time)
|
22
|
+
time_size
|
23
|
+
elsif self.first.is_a?(Fixnum) || self.first.is_a?(Float) &&
|
24
|
+
self.last.is_a?(Fixnum) || self.last.is_a?(Float)
|
25
|
+
number_size
|
26
|
+
else
|
27
|
+
raise "size cannot handle these object types."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias length size
|
31
|
+
|
32
32
|
private
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
|
34
|
+
def time_size
|
35
|
+
raise "both endpoints of Range must be Time objects." unless self.last.is_a?(Time) && self.first.is_a?(Time)
|
36
|
+
return self.last-self.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def number_size # technically, off by infinity^(-1) when either or both endpoints are excluded. meh.
|
40
|
+
return self.last-self.first+1
|
41
|
+
end
|
42
42
|
end
|
@@ -4,124 +4,125 @@ module Temporal
|
|
4
4
|
base.extend ClassMethods
|
5
5
|
base.extend ClassAndInstanceMethods
|
6
6
|
end
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def range_between(obj,another_obj)
|
10
|
+
first_obj,second_obj = ordered_tuple(obj,another_obj)
|
11
|
+
beginning = extract_time(first_obj,:ends_at)
|
12
|
+
ending = extract_time(second_obj,:begins_at)
|
13
|
+
beginning.before?(ending) ? (beginning..ending).to_time : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def period_between(obj,another_obj)
|
17
|
+
first_obj,second_obj = ordered_tuple(obj,another_obj)
|
18
|
+
beginning = extract_time(first_obj,:ends_at)
|
19
|
+
ending = extract_time(second_obj,:begins_at)
|
20
|
+
beginning.before_or_at?(ending) ? ending - beginning : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def length_of_time_until(obj,reference_obj=Time.now)
|
24
|
+
begins_at = extract_time(obj,:begins_at)
|
25
|
+
ends_at = extract_time(obj,:ends_at)
|
26
|
+
reference_point = extract_time(reference_obj,:ends_at)
|
27
|
+
|
28
|
+
time_until_begins = begins_at - reference_point
|
29
|
+
# has not started
|
30
|
+
if time_until_begins >= 0
|
31
|
+
time_until_begins
|
32
|
+
# has started, but not ended
|
33
|
+
elsif time_until_begins < 0 && reference_point.before?(ends_at)
|
34
|
+
0
|
35
|
+
# has ended
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def length_of_time_since(obj,reference_obj=Time.now)
|
42
|
+
begins_at = extract_time(obj,:begins_at)
|
43
|
+
ends_at = extract_time(obj,:ends_at)
|
44
|
+
|
45
|
+
reference_point = extract_time(reference_obj,:begins_at)
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
47
|
+
time_since_ended = reference_point - ends_at
|
48
|
+
has_started_but_not_ended = time_since_ended <= 0 && reference_point.after?(begins_at)
|
49
|
+
has_ended = time_since_ended >= 0
|
50
|
+
if has_ended
|
51
|
+
time_since_ended
|
52
|
+
elsif has_started_but_not_ended
|
53
|
+
0
|
54
|
+
else # has not started
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module ClassAndInstanceMethods
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
62
|
+
private
|
63
|
+
|
64
|
+
def ordered_tuple(an_obj,other_obj)
|
65
|
+
if an_obj.before?(other_obj)
|
66
|
+
return an_obj,other_obj
|
67
|
+
else
|
68
|
+
return other_obj,an_obj
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_time(obj,accessor)
|
73
|
+
# if object possesses accessor method, return obj.accessor.
|
74
|
+
# if not, return object. This is useful for fudging duck typing-
|
75
|
+
# style interaction between Events and Times/Dates, etc.
|
76
|
+
obj.respond_to?(accessor.to_sym) ? obj.send(accessor.to_sym) : obj
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module InstanceMethods
|
81
|
+
|
82
|
+
def self.included(base) # :nodoc:
|
82
83
|
base.extend ClassMethods
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
84
|
+
end
|
85
|
+
|
86
|
+
def before?(obj)
|
87
|
+
extract_time(self,:ends_at) < extract_time(obj,:begins_at)
|
88
|
+
end
|
89
|
+
|
90
|
+
def before_or_at? obj
|
91
|
+
extract_time(self,:ends_at) <= extract_time(obj,:begins_at)
|
92
|
+
end
|
93
|
+
|
94
|
+
def after?(obj)
|
95
|
+
extract_time(self,:begins_at) > extract_time(obj,:ends_at)
|
96
|
+
end
|
97
|
+
|
98
|
+
def after_or_at? obj
|
99
|
+
extract_time(self,:ends_at) <= extract_time(obj,:begins_at)
|
100
|
+
end
|
101
|
+
|
102
|
+
def between? obj,another_obj
|
103
|
+
first_obj,second_obj = ordered_tuple(obj,another_obj)
|
104
|
+
beginning_between = extract_time(first_obj,:ends_at)
|
105
|
+
ending_between = extract_time(second_obj,:begins_at)
|
106
|
+
|
107
|
+
self.start_time.after_or_at?(beginning_between) &&
|
108
|
+
self.end_time.before_or_at?(ending_between)
|
109
|
+
end
|
109
110
|
|
110
111
|
def in_the_future?(time = Time.now)
|
111
|
-
|
112
|
+
extract_time(self,:begins_at).after?(time)
|
112
113
|
end
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
114
|
+
alias future? in_the_future?
|
115
|
+
|
116
|
+
def in_the_past?(time=Time.now)
|
117
|
+
extract_time(self,:ends_at).before?(time)
|
118
|
+
end
|
119
|
+
alias past? in_the_past?
|
120
|
+
end
|
120
121
|
|
121
|
-
protected
|
122
|
-
|
123
|
-
|
124
|
-
|
122
|
+
protected
|
123
|
+
|
124
|
+
include InstanceMethods
|
125
|
+
include ClassAndInstanceMethods
|
125
126
|
end
|
126
127
|
|
127
128
|
[Time,Date,DateTime].each { |klass| klass.send :include , Temporal }
|
@@ -1,11 +1,11 @@
|
|
1
|
-
module HolmesLabs #:nodoc:
|
2
|
-
module Acts #:nodoc:
|
3
|
-
module FluxCapacitor
|
1
|
+
module HolmesLabs #:nodoc:
|
2
|
+
module Acts #:nodoc:
|
3
|
+
module FluxCapacitor
|
4
4
|
module VERSION #:nodoc:
|
5
5
|
|
6
6
|
MAJOR = 0
|
7
7
|
MINOR = 6
|
8
|
-
TINY =
|
8
|
+
TINY = 3
|
9
9
|
|
10
10
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
11
11
|
|
data/spec/report.html
CHANGED
@@ -296,7 +296,7 @@ a {
|
|
296
296
|
<dd class="spec passed"><span class="passed_spec_name">should determine if two times are approximately equal (tolerance @ 0.1 seconds)</span></dd>
|
297
297
|
</dl>
|
298
298
|
</div>
|
299
|
-
<script type="text/javascript">document.getElementById('duration').innerHTML = "Finished in <strong>0.
|
299
|
+
<script type="text/javascript">document.getElementById('duration').innerHTML = "Finished in <strong>0.38148 seconds</strong>";</script>
|
300
300
|
<script type="text/javascript">document.getElementById('totals').innerHTML = "40 examples, 0 failures";</script>
|
301
301
|
</div>
|
302
302
|
</div>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_flux_capacitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Cropcho
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-05-
|
12
|
+
date: 2008-05-29 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|