acts_as_flux_capacitor 0.6.3 → 0.6.4

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