acts_as_flux_capacitor 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt CHANGED
@@ -7,9 +7,13 @@ Rakefile
7
7
  config/hoe.rb
8
8
  config/requirements.rb
9
9
  lib/acts_as_flux_capacitor.rb
10
- lib/acts_as_flux_capacitor/acts_as_flux_capacitor.rb
10
+ lib/acts_as_flux_capacitor/base.rb
11
+ lib/acts_as_flux_capacitor/sql.rb
12
+ lib/acts_as_flux_capacitor/compare_events.rb
13
+ lib/acts_as_flux_capacitor/operate_on_events.rb
14
+ lib/acts_as_flux_capacitor/operate_on_durations.rb
11
15
  lib/acts_as_flux_capacitor/core_class_extensions.rb
12
- lib/acts_as_flux_capacitor/range_overlap.rb
16
+ lib/acts_as_flux_capacitor/validation.rb
13
17
  lib/acts_as_flux_capacitor/temporal.rb
14
18
  lib/acts_as_flux_capacitor/version.rb
15
19
  script/console
data/config/hoe.rb CHANGED
@@ -8,7 +8,7 @@ RUBYFORGE_PROJECT = 'aafc' # The unix name for your project
8
8
  HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
9
  DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
10
  EXTRA_DEPENDENCIES = [
11
- # ['activesupport', '>= 1.3.1']
11
+ ['activerecord' , '>= 1.2.0']
12
12
  ] # An array of rubygem dependencies [name, version]
13
13
 
14
14
  @config_file = "~/.rubyforge/user-config.yml"
@@ -0,0 +1,129 @@
1
+ module HolmesLabs::Acts::FluxCapacitor::ActiveRecord #:nodoc:
2
+
3
+ def self.included(base) # :nodoc:
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def acts_as_flux_capacitor
9
+ unless acts_as_flux_capacitor?
10
+ class << self
11
+ alias_method :original_find, :find
12
+ end
13
+ end
14
+ include Temporal
15
+ include InstanceMethods
16
+ include OperateOnEvents
17
+ include OperateOnDurations
18
+ include CompareEvents
19
+ include Validation
20
+ end
21
+
22
+ def flux_capacitor?
23
+ included_modules.include? InstanceMethods
24
+ end
25
+ alias acts_as_flux_capacitor? flux_capacitor?
26
+ end
27
+
28
+ protected
29
+
30
+ module InstanceMethods #:nodoc:
31
+ def self.included(base) # :nodoc:
32
+ base.extend ClassMethods
33
+ base.extend QueryGenerators
34
+ end
35
+
36
+ module ClassMethods
37
+
38
+ CUSTOM_FIND_OPTIONS = [
39
+ :before, # raw sql
40
+ :after, # raw sql
41
+
42
+ :now,
43
+ :at,
44
+
45
+ :past,
46
+ :present,
47
+ :future,
48
+
49
+ :from,
50
+ :to,
51
+
52
+ :starts_before, # raw sql
53
+ :ends_after # raw sql
54
+ ]
55
+
56
+ def find(*args)
57
+ options = args.extract_options!
58
+
59
+ current_time = options.delete(:current_time) || Time.now
60
+ temporal_conditions = extract_temporal_conditions!(options)
61
+ sql_temporal = sql_generate_temporal(temporal_conditions,current_time)
62
+
63
+ with_scope( :find => {
64
+ :conditions => sql_temporal ,
65
+ :order => sql_oldest_first }) do
66
+ original_find(*(args << options))
67
+ end
68
+ end
69
+
70
+ def overlap(an_event,another_event)
71
+ an_event.to_range.overlap(another_event.to_range)
72
+ end
73
+
74
+ def overlap?(an_event,another_event)
75
+ an_event.to_range.overlap?(another_event.to_range)
76
+ end
77
+
78
+ private
79
+
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
+ def to_range
92
+ (start..finish)
93
+ end
94
+ alias range to_range
95
+
96
+ def start ; read_attribute(:begins_at) ; end
97
+ def start=(val) ; write_attribute(:begins_at,val); end
98
+
99
+ def finish ; read_attribute(:ends_at) ; end
100
+ def finish=(val) ; write_attribute(:ends_at,val) ; end
101
+
102
+ alias start_time start
103
+ alias start_time= start=
104
+ alias beginning start
105
+ alias beginning= start=
106
+ alias end_time finish
107
+ alias end_time= finish=
108
+ alias from start
109
+ alias from= start=
110
+ alias to finish
111
+ alias to= finish=
112
+ alias ending finish
113
+ alias ending= finish=
114
+ end # InstanceMethods
115
+ end
116
+
117
+ ActiveRecord::Base.send :include, HolmesLabs::Acts::FluxCapacitor::ActiveRecord
118
+
119
+ # TODO
120
+ ###########
121
+ # allow events to be created by start time and duration
122
+ # force exclusivity of events at any given time via parameter to acts_as_event()
123
+ # add new validations
124
+ # rake tasks
125
+
126
+
127
+ # NOTES
128
+ ###########
129
+ # verify #find_* returns ordered results unless specified otherwise.
@@ -0,0 +1,29 @@
1
+ module HolmesLabs::Acts::FluxCapacitor::ActiveRecord::CompareEvents
2
+ def overlaps_with?(other_event)
3
+ self.to_range.overlaps?(other_event.to_range)
4
+ end
5
+
6
+ def overlap_with(other_event)
7
+ (self.to_range.overlap(other_event.to_range)).size
8
+ end
9
+ alias overlap overlap_with
10
+
11
+ def back_to_back_with?(other_event,tolerance = 2.seconds)
12
+ # tolerance is in seconds.
13
+ self.start_time.before?(other_event.start_time) ? first_to_start = self : first_to_start = other_event
14
+ first_to_start.ends_at.approx_eql?(other_event.begins_at,tolerance)
15
+ end
16
+
17
+ def duration
18
+ self.class.length_of_time_until(self.ending,self.beginning)
19
+ end
20
+ alias length_of_time duration
21
+ alias length duration
22
+
23
+ def duration=(new_duration)
24
+ self.ending = self.beginning + new_duration
25
+ end
26
+ alias length_of_time= duration=
27
+ alias length= duration=
28
+ end
29
+
@@ -16,3 +16,45 @@ class Time
16
16
  end
17
17
  end
18
18
 
19
+ class Range
20
+
21
+ # the overlaps?(range) method comes from ActiveSupport::CoreExtensions::Range
22
+ # thanks to Nick Ang for suggesting its use.
23
+ # most of its tests were written by Jack Christensen of
24
+ # http://www.jackchristensen.com/article/2/detecting-overlapping-ranges
25
+
26
+ # Returns true if ranges overlap, false otherwise
27
+ def overlaps? other
28
+ include?(other.first) || other.include?(first)
29
+ end
30
+
31
+ # all of this added by jim cropcho:
32
+
33
+ def overlap(other_range)
34
+ return 0 unless self.overlaps?(other_range)
35
+ ([self.first,other_range.first].max)..([self.last,other_range.last].min)
36
+ end
37
+
38
+ def size
39
+ if self.first.is_a?(Time) && self.last.is_a?(Time)
40
+ time_size
41
+ elsif self.first.is_a?(Fixnum) || self.first.is_a?(Float) &&
42
+ self.last.is_a?(Fixnum) || self.last.is_a?(Float)
43
+ number_size
44
+ else
45
+ raise "size cannot handle these object types."
46
+ end
47
+ end
48
+ alias length size
49
+
50
+ private
51
+
52
+ def time_size
53
+ raise "both endpoints of Range must be Time objects." unless self.last.is_a?(Time) && self.first.is_a?(Time)
54
+ return self.last-self.first
55
+ end
56
+
57
+ def number_size # technically, off by infinity^(-1) when either or both endpoints are excluded. meh.
58
+ return self.last-self.first+1
59
+ end
60
+ end
@@ -0,0 +1,23 @@
1
+ module HolmesLabs::Acts::FluxCapacitor::ActiveRecord::OperateOnDurations
2
+ def elapsed(time = Time.now)
3
+ time_since_beginning = self.class.length_of_time_since(self.beginning,time)
4
+ self.now?(time) ? time_since_beginning : nil
5
+ end
6
+
7
+ def remaining(time = Time.now)
8
+ time_until_end = self.class.length_of_time_until(self.ending,time)
9
+ self.now?(time) ? time_until_end : nil
10
+ end
11
+
12
+ def percent_complete(time = Time.now)
13
+ amount_complete = self.elapsed(time)
14
+ amount_complete ? 100 * ((amount_complete * 1.0) / duration) : nil
15
+ end
16
+ alias percent_elapsed percent_complete
17
+ alias percent_finished percent_complete
18
+
19
+ def percent_remaining(time = Time.now)
20
+ amount_remaining = self.remaining(time)
21
+ amount_remaining ? 100 * (( amount_remaining * 1.0) / duration) : nil
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ module HolmesLabs::Acts::FluxCapacitor::ActiveRecord::OperateOnEvents
2
+ def at?(obj=Time.now)
3
+ bigger_than = self.start_time.before?(extract_time(obj,:start_time)) &&
4
+ self.end_time.after?( extract_time(obj,:end_time ))
5
+ smaller_than = self.start_time.after?( extract_time(obj,:start_time)) &&
6
+ self.end_time.before?( extract_time(obj,:end_time))
7
+ same_size = self.start_time == extract_time(obj,:start_time) &&
8
+ self.end_time == extract_time(obj,:end_time)
9
+ return bigger_than || smaller_than || same_size
10
+ end
11
+ alias happening_at? at?
12
+ alias happened_at? at?
13
+
14
+ def happening_now?(time = Time.now)
15
+ at?(time)
16
+ end
17
+ alias in_progress? happening_now?
18
+ alias now? happening_now?
19
+ alias present? happening_now?
20
+
21
+ def started?(time = Time.now)
22
+ self.start_time.before?(time)
23
+ end
24
+ alias began? started?
25
+
26
+ def ended?(time = Time.now)
27
+ self.end_time.before?(time)
28
+ end
29
+ alias finished? ended?
30
+
31
+ def <=>(obj)
32
+ if self.before?(obj)
33
+ return -1
34
+ elsif at?(obj)
35
+ return 0
36
+ elsif self.after?(obj)
37
+ return 1
38
+ else
39
+ return 'acts_as_flux_capacitor: non-fatal error in #<=>'
40
+ end
41
+ end
42
+
43
+ def transpose(duration)
44
+ new_event = self.clone
45
+ new_event.transpose!(duration)
46
+ end
47
+ alias shift transpose
48
+ alias reschedule transpose
49
+ alias move transpose
50
+
51
+ def transpose!(duration)
52
+ self.beginning += duration
53
+ self.ending += duration
54
+ return self
55
+ end
56
+ alias shift! transpose!
57
+ alias reschedule! transpose!
58
+ alias move! transpose!
59
+ end
60
+
@@ -0,0 +1,81 @@
1
+ module HolmesLabs::Acts::FluxCapacitor::ActiveRecord::InstanceMethods::QueryGenerators #:nodoc:
2
+
3
+ private
4
+
5
+ def sql_before(some_time, current_time)
6
+ # table_name comes from ActiveRecord.
7
+ "#{table_name}.ends_at < '#{some_time.to_s(:db)}'"
8
+ end
9
+ alias sql_to sql_before
10
+ alias sql_ends_before sql_before
11
+
12
+ def sql_after(some_time , current_time)
13
+ # table_name comes from ActiveRecord.
14
+ "#{table_name}.begins_at > '#{some_time.to_s(:db)}'"
15
+ end
16
+ alias sql_from sql_after
17
+ alias sql_begins_after sql_after
18
+
19
+ def sql_at(some_time , current_time)
20
+ sql_join_conditions([ sql_starts_before( some_time , current_time),
21
+ sql_ends_after( some_time , current_time)])
22
+ end
23
+
24
+ def sql_ends_after(some_time, current_time)
25
+ "#{table_name}.ends_at > '#{some_time.to_s(:db)}'"
26
+ end
27
+
28
+ def sql_starts_before(some_time , current_time)
29
+ "#{table_name}.begins_at < '#{some_time.to_s(:db)}'"
30
+ end
31
+
32
+ def sql_past(is_true , current_time)
33
+ if is_true
34
+ sql_before(current_time, current_time)
35
+ else
36
+ sql_join_conditions_or([ sql_present( current_time),
37
+ sql_future( current_time)])
38
+ end
39
+ end
40
+ alias sql_ended sql_past
41
+
42
+ def sql_present(is_true , current_time)
43
+ if is_true
44
+ sql_at(current_time,current_time)
45
+ else
46
+ sql_join_conditions_or([ sql_past( current_time),
47
+ sql_future( current_time)])
48
+ end
49
+ end
50
+ alias sql_now sql_present
51
+
52
+ def sql_future(is_true, current_time)
53
+ if is_true
54
+ sql_after(current_time,current_time)
55
+ else
56
+ sql_join_conditions_or([ sql_present(current_time),
57
+ sql_past( current_time)])
58
+ end
59
+ end
60
+
61
+ def sql_oldest_first(attrib = :begins_at)
62
+ "#{table_name}.#{attrib.to_s} ASC"
63
+ end
64
+
65
+ def sql_join_conditions(conditions)
66
+ "#{conditions.join(") AND (")}"
67
+ end
68
+
69
+ def sql_join_conditions_or(conditions)
70
+ "#{conditions.join(") OR (")}"
71
+ end
72
+
73
+ def sql_generate_temporal temporal_conditions , current_time
74
+ sql_conditions = []
75
+ temporal_conditions.each do |condition,parameter|
76
+ sql_conditions << send("sql_#{condition}".to_sym , parameter, current_time)
77
+ end
78
+
79
+ sql_join_conditions(sql_conditions)
80
+ end
81
+ end
@@ -0,0 +1,13 @@
1
+ module HolmesLabs::Acts::FluxCapacitor::ActiveRecord::Validation #:nodoc:
2
+
3
+ def validate
4
+ if self.begins_at.nil?
5
+ errors.add(:begins_at, "must have a start time")
6
+ if self.ends_at.nil?
7
+ errors.add(:ends_at, "must have an end time")
8
+ end
9
+ else
10
+ self.beginning.after?(self.ending) ? errors.add_to_base("An event cannot end before it begins") : nil
11
+ end
12
+ end
13
+ end
@@ -5,7 +5,7 @@ module HolmesLabs #:nodoc:
5
5
 
6
6
  MAJOR = 0
7
7
  MINOR = 6
8
- TINY = 3
8
+ TINY = 4
9
9
 
10
10
  STRING = [MAJOR, MINOR, TINY].join('.')
11
11
 
@@ -1,12 +1,11 @@
1
- SUBDIRECTORY = *['lib','acts_as_flux_capacitor']
2
-
3
- require 'rubygems'
4
- require 'activerecord'
5
- require 'date'
6
-
7
- require File.join(SUBDIRECTORY, 'version')
8
- require File.join(SUBDIRECTORY, 'temporal')
9
- require File.join(SUBDIRECTORY, 'range_overlap')
10
- require File.join(SUBDIRECTORY, 'core_class_extensions')
11
- require File.join(SUBDIRECTORY, 'acts_as_flux_capacitor')
1
+ LIB_DIRECTORY = *['lib','acts_as_flux_capacitor']
12
2
 
3
+ require File.join(LIB_DIRECTORY, 'version')
4
+ require File.join(LIB_DIRECTORY, 'temporal')
5
+ require File.join(LIB_DIRECTORY, 'core_class_extensions')
6
+ require File.join(LIB_DIRECTORY, 'base')
7
+ require File.join(LIB_DIRECTORY, 'sql')
8
+ require File.join(LIB_DIRECTORY, 'validation')
9
+ require File.join(LIB_DIRECTORY, 'operate_on_events')
10
+ require File.join(LIB_DIRECTORY, 'operate_on_durations')
11
+ require File.join(LIB_DIRECTORY, 'compare_events')
data/spec/report.html CHANGED
@@ -289,14 +289,19 @@ a {
289
289
  </div>
290
290
  <div class="example_group">
291
291
  <dl>
292
- <dt id="example_group_8">The extensions to the Time class</dt>
292
+ <dt id="example_group_8">The extensions to</dt>
293
+ </dl>
294
+ </div>
295
+ <div class="example_group">
296
+ <dl>
297
+ <dt id="example_group_9">The extensions to the Time class</dt>
293
298
  <script type="text/javascript">moveProgressBar('97.5');</script>
294
299
  <dd class="spec passed"><span class="passed_spec_name">should determine if two times are approximately equal (tolerance @ 1 second)</span></dd>
295
300
  <script type="text/javascript">moveProgressBar('100.0');</script>
296
301
  <dd class="spec passed"><span class="passed_spec_name">should determine if two times are approximately equal (tolerance @ 0.1 seconds)</span></dd>
297
302
  </dl>
298
303
  </div>
299
- <script type="text/javascript">document.getElementById('duration').innerHTML = "Finished in <strong>0.38148 seconds</strong>";</script>
304
+ <script type="text/javascript">document.getElementById('duration').innerHTML = "Finished in <strong>0.424051 seconds</strong>";</script>
300
305
  <script type="text/javascript">document.getElementById('totals').innerHTML = "40 examples, 0 failures";</script>
301
306
  </div>
302
307
  </div>
data/website/index.html CHANGED
@@ -33,7 +33,7 @@
33
33
  <h1>acts_as_flux_capacitor</h1>
34
34
  <div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/acts_as_flux_capacitor"; return false'>
35
35
  <p>Get Version</p>
36
- <a href="http://rubyforge.org/projects/acts_as_flux_capacitor" class="numbers">0.5.2</a>
36
+ <a href="http://rubyforge.org/projects/acts_as_flux_capacitor" class="numbers">0.6.4</a>
37
37
  </div>
38
38
  <h1>&#x2192; &#8216;acts_as_flux_capacitor&#8217;</h1>
39
39
 
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.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Cropcho
@@ -9,10 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-05-29 00:00:00 -04:00
12
+ date: 2008-07-11 00:00:00 -04:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.7.0
24
+ version:
16
25
  description: Acts as Flux Capacitor is a better way to work with time-centric ActiveRecord models. Make it feel good to manipulate objects representing real-world events and/or objects with a finite period of database persistence!
17
26
  email:
18
27
  - jim.cropcho@gmail.com
@@ -37,9 +46,13 @@ files:
37
46
  - config/hoe.rb
38
47
  - config/requirements.rb
39
48
  - lib/acts_as_flux_capacitor.rb
40
- - lib/acts_as_flux_capacitor/acts_as_flux_capacitor.rb
49
+ - lib/acts_as_flux_capacitor/base.rb
50
+ - lib/acts_as_flux_capacitor/sql.rb
51
+ - lib/acts_as_flux_capacitor/compare_events.rb
52
+ - lib/acts_as_flux_capacitor/operate_on_events.rb
53
+ - lib/acts_as_flux_capacitor/operate_on_durations.rb
41
54
  - lib/acts_as_flux_capacitor/core_class_extensions.rb
42
- - lib/acts_as_flux_capacitor/range_overlap.rb
55
+ - lib/acts_as_flux_capacitor/validation.rb
43
56
  - lib/acts_as_flux_capacitor/temporal.rb
44
57
  - lib/acts_as_flux_capacitor/version.rb
45
58
  - script/console
@@ -88,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
101
  requirements: []
89
102
 
90
103
  rubyforge_project: aafc
91
- rubygems_version: 1.1.1
104
+ rubygems_version: 1.2.0
92
105
  signing_key:
93
106
  specification_version: 2
94
107
  summary: Acts as Flux Capacitor is a better way to work with time-centric ActiveRecord models. Make it feel good to manipulate objects representing real-world events and/or objects with a finite period of database persistence!
@@ -1,341 +0,0 @@
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
- include CompareEvents
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
31
-
32
- module InstanceMethods #:nodoc:
33
- def self.included(base) # :nodoc:
34
- base.extend ClassMethods
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)
176
- end
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
-
199
- module OperateOnEvents
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
-
229
- def <=>(obj)
230
- if self.before?(obj)
231
- return -1
232
- elsif at?(obj)
233
- return 0
234
- elsif self.after?(obj)
235
- return 1
236
- else
237
- return 'ActsAsEvent: non-fatal error in #status'
238
- end
239
- end
240
-
241
- def transpose(duration)
242
- new_event = self.clone
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)
261
- time_since_beginning = self.class.length_of_time_since(self.beginning,time)
262
- self.now?(time) ? time_since_beginning : nil
263
- end
264
-
265
- def remaining(time = Time.now)
266
- time_until_end = self.class.length_of_time_until(self.ending,time)
267
- self.now?(time) ? time_until_end : nil
268
- end
269
-
270
- def percent_complete(time = Time.now)
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
-
277
- def percent_remaining(time = Time.now)
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
284
- def overlaps_with?(other_event)
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
292
-
293
- def back_to_back_with?(other_event,tolerance = 2.seconds)
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
323
- end
324
- end
325
- end # ActiveRecord
326
- end # FluxCapacitor
327
- end # Acts
328
- end # HolmesLabs
329
- ActiveRecord::Base.send :include, HolmesLabs::Acts::FluxCapacitor::ActiveRecord
330
-
331
- # TODO
332
- ###########
333
- # allow events to be created by start time and duration
334
- # force exclusivity of events at any given time via parameter to acts_as_event()
335
- # add new validations
336
- # rake tasks
337
-
338
-
339
- # NOTES
340
- ###########
341
- # verify #find_* returns ordered results unless specified otherwise.
@@ -1,42 +0,0 @@
1
- class Range
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
-
13
- # all of this added by jim cropcho:
14
-
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
- private
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
- end