acts_as_flux_capacitor 0.5.0
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 +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +35 -0
- data/PostInstall.txt +7 -0
- data/README.txt +48 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +73 -0
- data/config/requirements.rb +15 -0
- data/lib/acts_as_flux_capacitor.rb +9 -0
- data/lib/acts_as_flux_capacitor/acts_as_flux_capacitor.rb +352 -0
- data/lib/acts_as_flux_capacitor/core_class_extensions.rb +33 -0
- data/lib/acts_as_flux_capacitor/range_overlap.rb +58 -0
- data/lib/acts_as_flux_capacitor/temporal.rb +122 -0
- data/lib/acts_as_flux_capacitor/version.rb +9 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +82 -0
- data/setup.rb +1585 -0
- data/spec/acts_as_flux_capacitor_spec.rb +190 -0
- data/spec/database.yml +8 -0
- data/spec/fixtures.rb +57 -0
- data/spec/report/report.html +274 -0
- data/spec/schema.rb +10 -0
- data/spec/spec.opts +8 -0
- data/spec/spec_helper.rb +47 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +20 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- data/website/index.html +141 -0
- data/website/index.txt +83 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.html.erb +48 -0
- metadata +101 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Jim
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
PostInstall.txt
|
5
|
+
README.txt
|
6
|
+
Rakefile
|
7
|
+
config/hoe.rb
|
8
|
+
config/requirements.rb
|
9
|
+
lib/acts_as_flux_capacitor.rb
|
10
|
+
lib/acts_as_flux_capacitor/acts_as_flux_capacitor.rb
|
11
|
+
lib/acts_as_flux_capacitor/core_class_extensions.rb
|
12
|
+
lib/acts_as_flux_capacitor/range_overlap.rb
|
13
|
+
lib/acts_as_flux_capacitor/temporal.rb
|
14
|
+
lib/acts_as_flux_capacitor/version.rb
|
15
|
+
script/console
|
16
|
+
script/destroy
|
17
|
+
script/generate
|
18
|
+
script/txt2html
|
19
|
+
setup.rb
|
20
|
+
spec/acts_as_flux_capacitor_spec.rb
|
21
|
+
spec/database.yml
|
22
|
+
spec/fixtures.rb
|
23
|
+
spec/report/report.html
|
24
|
+
spec/schema.rb
|
25
|
+
spec/spec.opts
|
26
|
+
spec/spec_helper.rb
|
27
|
+
tasks/deployment.rake
|
28
|
+
tasks/environment.rake
|
29
|
+
tasks/rspec.rake
|
30
|
+
tasks/website.rake
|
31
|
+
website/index.html
|
32
|
+
website/index.txt
|
33
|
+
website/javascripts/rounded_corners_lite.inc.js
|
34
|
+
website/stylesheets/screen.css
|
35
|
+
website/template.html.erb
|
data/PostInstall.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= acts_as_flux_capacitor
|
2
|
+
|
3
|
+
* FIX (url)
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
FIX (describe your package)
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* FIX (list of requirements)
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* FIX (sudo gem install, anything else)
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright (c) 2008 FIX
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/config/hoe.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'acts_as_flux_capacitor/version'
|
2
|
+
|
3
|
+
AUTHOR = 'Jim Cropcho' # can also be an array of Authors
|
4
|
+
EMAIL = "jim.cropcho@gmail.com"
|
5
|
+
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!"
|
6
|
+
GEM_NAME = 'acts_as_flux_capacitor' # what ppl will type to install your gem
|
7
|
+
RUBYFORGE_PROJECT = 'aafc' # The unix name for your project
|
8
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
9
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
10
|
+
EXTRA_DEPENDENCIES = [
|
11
|
+
# ['activesupport', '>= 1.3.1']
|
12
|
+
] # An array of rubygem dependencies [name, version]
|
13
|
+
|
14
|
+
@config_file = "~/.rubyforge/user-config.yml"
|
15
|
+
@config = nil
|
16
|
+
RUBYFORGE_USERNAME = "JimCropcho"
|
17
|
+
def rubyforge_username
|
18
|
+
unless @config
|
19
|
+
begin
|
20
|
+
@config = YAML.load(File.read(File.expand_path(@config_file)))
|
21
|
+
rescue
|
22
|
+
puts <<-EOS
|
23
|
+
ERROR: No rubyforge config file found: #{@config_file}
|
24
|
+
Run 'rubyforge setup' to prepare your env for access to Rubyforge
|
25
|
+
- See http://newgem.rubyforge.org/rubyforge.html for more details
|
26
|
+
EOS
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
end
|
30
|
+
RUBYFORGE_USERNAME.replace @config["username"]
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
REV = nil
|
35
|
+
# UNCOMMENT IF REQUIRED:
|
36
|
+
# REV = YAML.load(`svn info`)['Revision']
|
37
|
+
VERS = ActsAsFluxCapacitor::VERSION::STRING + (REV ? ".#{REV}" : "")
|
38
|
+
RDOC_OPTS = ['--quiet', '--title', 'acts_as_flux_capacitor documentation',
|
39
|
+
"--opname", "index.html",
|
40
|
+
"--line-numbers",
|
41
|
+
"--main", "README",
|
42
|
+
"--inline-source"]
|
43
|
+
|
44
|
+
class Hoe
|
45
|
+
def extra_deps
|
46
|
+
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
|
47
|
+
@extra_deps
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Generate all the Rake tasks
|
52
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
53
|
+
$hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
54
|
+
p.developer(AUTHOR, EMAIL)
|
55
|
+
p.description = DESCRIPTION
|
56
|
+
p.summary = DESCRIPTION
|
57
|
+
p.url = HOMEPATH
|
58
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
59
|
+
p.test_globs = ["test/**/test_*.rb"]
|
60
|
+
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
|
61
|
+
|
62
|
+
# == Optional
|
63
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
64
|
+
#p.extra_deps = EXTRA_DEPENDENCIES
|
65
|
+
|
66
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
67
|
+
end
|
68
|
+
|
69
|
+
CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
|
70
|
+
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
71
|
+
$hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
72
|
+
$hoe.rsync_args = '-av --delete --ignore-errors'
|
73
|
+
$hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
include FileUtils
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
%w[rake hoe newgem rubigen].each do |req_gem|
|
6
|
+
begin
|
7
|
+
require req_gem
|
8
|
+
rescue LoadError
|
9
|
+
puts "This Rakefile requires the '#{req_gem}' RubyGem."
|
10
|
+
puts "Installation: gem install #{req_gem} -y"
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
|
@@ -0,0 +1,9 @@
|
|
1
|
+
SUBDIRECTORY = 'acts_as_flux_capacitor'
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require File.join(SUBDIRECTORY, 'version')
|
5
|
+
require File.join(SUBDIRECTORY, 'temporal')
|
6
|
+
require File.join(SUBDIRECTORY, 'range_overlap')
|
7
|
+
require File.join(SUBDIRECTORY, 'core_class_extensions')
|
8
|
+
require File.join(SUBDIRECTORY, 'acts_as_flux_capacitor')
|
9
|
+
|
@@ -0,0 +1,352 @@
|
|
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)
|
62
|
+
temporal_conditions = extract_temporal_conditions!(options)
|
63
|
+
sql_temporal = sql_generate_temporal(temporal_conditions,current_time)
|
64
|
+
|
65
|
+
puts temporal_conditions.size
|
66
|
+
puts sql_temporal
|
67
|
+
|
68
|
+
with_scope( :find => {
|
69
|
+
:conditions => sql_temporal ,
|
70
|
+
:order => sql_oldest_first }) do
|
71
|
+
original_find(*(args << options))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def overlap(an_event,another_event)
|
76
|
+
an_event.to_range.overlap(another_event.to_range)
|
77
|
+
end
|
78
|
+
|
79
|
+
def overlap?(an_event,another_event)
|
80
|
+
an_event.to_range.overlap?(another_event.to_range)
|
81
|
+
end
|
82
|
+
private
|
83
|
+
def extract_temporal_conditions!(options)
|
84
|
+
temporal_conditions = {}
|
85
|
+
|
86
|
+
options.each do |key,val|
|
87
|
+
temporal_conditions.merge!({key, options.delete(key)}) if CUSTOM_FIND_OPTIONS.include?(key)
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
require 'pp'# ; pp "(#{temporal_conditions.class}) #{temporal_conditions.first.keys}: #{temporal_conditions.first.values}"
|
92
|
+
pp temporal_conditions
|
93
|
+
temporal_conditions
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
module QueryGenerators
|
98
|
+
private
|
99
|
+
|
100
|
+
def sql_before(some_time, current_time = nil)
|
101
|
+
# table_name comes from ActiveRecord.
|
102
|
+
"#{table_name}.ends_at < '#{some_time.to_s(:db)}'"
|
103
|
+
end
|
104
|
+
alias sql_to sql_before
|
105
|
+
alias sql_ends_before sql_before
|
106
|
+
|
107
|
+
def sql_after(some_time , current_time = nil)
|
108
|
+
# table_name comes from ActiveRecord.
|
109
|
+
"#{table_name}.begins_at > '#{some_time.to_s(:db)}'"
|
110
|
+
end
|
111
|
+
alias sql_from sql_after
|
112
|
+
alias sql_begins_after sql_after
|
113
|
+
|
114
|
+
def sql_at(some_time , current_time = Time.now)
|
115
|
+
sql_join_conditions([ sql_starts_before( some_time),
|
116
|
+
sql_ends_after( some_time)])
|
117
|
+
end
|
118
|
+
|
119
|
+
def sql_ends_after(some_time , current_time = nil)
|
120
|
+
"#{table_name}.ends_at > '#{some_time.to_s(:db)}'"
|
121
|
+
end
|
122
|
+
|
123
|
+
def sql_starts_before(some_time , current_time = nil)
|
124
|
+
"#{table_name}.begins_at < '#{some_time.to_s(:db)}'"
|
125
|
+
end
|
126
|
+
|
127
|
+
def sql_past(is_true , current_time = Time.now)
|
128
|
+
if is_true
|
129
|
+
sql_before(current_time)
|
130
|
+
else
|
131
|
+
sql_join_conditions_or([ sql_present( Time.now),
|
132
|
+
sql_future( Time.now)])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
alias sql_ended sql_past
|
136
|
+
|
137
|
+
def sql_present(is_true , current_time = Time.now)
|
138
|
+
if is_true
|
139
|
+
sql_at(current_time)
|
140
|
+
else
|
141
|
+
sql_join_conditions_or([ sql_past( Time.now),
|
142
|
+
sql_future( Time.now)])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
alias sql_now sql_present
|
146
|
+
|
147
|
+
def sql_future(is_true, current_time = Time.now)
|
148
|
+
if is_true
|
149
|
+
sql_after(current_time)
|
150
|
+
else
|
151
|
+
sql_join_conditions_or([ sql_present( Time.now),
|
152
|
+
sql_past( Time.now)])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def sql_oldest_first(attrib = :begins_at)
|
157
|
+
"#{table_name}.#{attrib.to_s} ASC"
|
158
|
+
end
|
159
|
+
|
160
|
+
def sql_join_conditions(conditions)
|
161
|
+
"#{conditions.join(") AND (")}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def sql_join_conditions_or(conditions)
|
165
|
+
"#{conditions.join(") OR (")}"
|
166
|
+
end
|
167
|
+
|
168
|
+
def sql_generate_temporal(temporal_conditions,current_time)
|
169
|
+
sql_conditions = []
|
170
|
+
temporal_conditions.each do |condition,parameter|
|
171
|
+
puts "#{condition}: #{parameter}"
|
172
|
+
sql_conditions << send("sql_#{condition}" , parameter , current_time)
|
173
|
+
end
|
174
|
+
|
175
|
+
require 'pp' ; pp sql_conditions
|
176
|
+
pp sql_join_conditions(sql_conditions)
|
177
|
+
sql_join_conditions(sql_conditions)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_range
|
182
|
+
(start..finish)
|
183
|
+
end
|
184
|
+
alias range to_range
|
185
|
+
|
186
|
+
def start ; read_attribute(:begins_at) ; end
|
187
|
+
def start=(val) ; write_attribute(:begins_at,val); end
|
188
|
+
|
189
|
+
def finish ; read_attribute(:ends_at) ; end
|
190
|
+
def finish=(val) ; write_attribute(:ends_at,val) ; end
|
191
|
+
|
192
|
+
alias start_time start
|
193
|
+
alias start_time= start=
|
194
|
+
alias beginning start
|
195
|
+
alias beginning= start=
|
196
|
+
alias end_time finish
|
197
|
+
alias end_time= finish=
|
198
|
+
alias from start
|
199
|
+
alias from= start=
|
200
|
+
alias to finish
|
201
|
+
alias to= finish=
|
202
|
+
alias ending finish
|
203
|
+
alias ending= finish=
|
204
|
+
end # InstanceMethods
|
205
|
+
|
206
|
+
module OperateOnEvents
|
207
|
+
def at?(obj=Time.now)
|
208
|
+
bigger_than = self.start_time.before?(extract_time(obj,:start_time)) &&
|
209
|
+
self.end_time.after?( extract_time(obj,:end_time ))
|
210
|
+
smaller_than = self.start_time.after?( extract_time(obj,:start_time)) &&
|
211
|
+
self.end_time.before?( extract_time(obj,:end_time))
|
212
|
+
same_size = self.start_time == extract_time(obj,:start_time) &&
|
213
|
+
self.end_time == extract_time(obj,:end_time)
|
214
|
+
return bigger_than || smaller_than || same_size
|
215
|
+
end
|
216
|
+
alias happening_at? at?
|
217
|
+
alias happened_at? at?
|
218
|
+
|
219
|
+
def happening_now?(time = Time.now)
|
220
|
+
at?(time)
|
221
|
+
end
|
222
|
+
alias in_progress? happening_now?
|
223
|
+
alias now? happening_now?
|
224
|
+
alias present? happening_now?
|
225
|
+
|
226
|
+
def started?(time = Time.now)
|
227
|
+
self.start_time.before?(time)
|
228
|
+
end
|
229
|
+
alias began? started?
|
230
|
+
|
231
|
+
def ended?(time = Time.now)
|
232
|
+
self.end_time.before?(time)
|
233
|
+
end
|
234
|
+
alias finished? ended?
|
235
|
+
|
236
|
+
def <=>(obj)
|
237
|
+
if self.before?(obj)
|
238
|
+
return -1
|
239
|
+
elsif at?(obj)
|
240
|
+
return 0
|
241
|
+
elsif self.after?(obj)
|
242
|
+
return 1
|
243
|
+
else
|
244
|
+
return 'ActsAsEvent: non-fatal error in #status'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def transpose(duration)
|
249
|
+
new_event = self.clone
|
250
|
+
new_event.transpose!(duration)
|
251
|
+
end
|
252
|
+
alias shift transpose
|
253
|
+
alias reschedule transpose
|
254
|
+
alias move transpose
|
255
|
+
|
256
|
+
def transpose!(duration)
|
257
|
+
self.beginning += duration
|
258
|
+
self.ending += duration
|
259
|
+
return self
|
260
|
+
end
|
261
|
+
alias shift! transpose!
|
262
|
+
alias reschedule! transpose!
|
263
|
+
alias move! transpose!
|
264
|
+
end # OperateOnEvents
|
265
|
+
|
266
|
+
module OperateOnDurations
|
267
|
+
def elapsed(time = Time.now)
|
268
|
+
time_since_beginning = self.class.length_of_time_since(self.beginning,time)
|
269
|
+
self.now?(time) ? time_since_beginning : nil
|
270
|
+
end
|
271
|
+
|
272
|
+
def remaining(time = Time.now)
|
273
|
+
time_until_end = self.class.length_of_time_until(self.ending,time)
|
274
|
+
self.now?(time) ? time_until_end : nil
|
275
|
+
end
|
276
|
+
|
277
|
+
def percent_complete(time = Time.now)
|
278
|
+
amount_complete = self.elapsed(time)
|
279
|
+
amount_complete ? 100 * ((amount_complete * 1.0) / duration) : nil
|
280
|
+
end
|
281
|
+
alias percent_elapsed percent_complete
|
282
|
+
alias percent_finished percent_complete
|
283
|
+
|
284
|
+
def percent_remaining(time = Time.now)
|
285
|
+
amount_remaining = self.remaining(time)
|
286
|
+
amount_remaining ? 100 * (( amount_remaining * 1.0) / duration) : nil
|
287
|
+
end
|
288
|
+
end # OperateOnDurations
|
289
|
+
|
290
|
+
module CompareEvents
|
291
|
+
def overlaps_with?(other_event)
|
292
|
+
#this method possesses the associative property.
|
293
|
+
self.to_range.overlap?(other_event.to_range)
|
294
|
+
end
|
295
|
+
|
296
|
+
def overlap_with(other_event)
|
297
|
+
(self.to_range.overlap(other_event.to_range)).size
|
298
|
+
end
|
299
|
+
alias overlap overlap_with
|
300
|
+
|
301
|
+
def back_to_back_with?(other_event,tolerance = 0)
|
302
|
+
# tolerance is in seconds.
|
303
|
+
# first_event,second_event = self.class.send(:ordered_tuple,self,other_event)
|
304
|
+
# first_event.ends_at.approx_eql?(second_event.begins_at,tolerance)
|
305
|
+
self.start_time.before?(other_event.start_time) ? first_to_start = self : first_to_start = other_event
|
306
|
+
first_to_start.ends_at.approx_eql?(other_event.begins_at,tolerance)
|
307
|
+
end
|
308
|
+
|
309
|
+
def duration
|
310
|
+
self.class.length_of_time_until(self.ending,self.beginning)
|
311
|
+
end
|
312
|
+
alias length_of_time duration
|
313
|
+
alias length duration
|
314
|
+
|
315
|
+
def duration=(new_duration)
|
316
|
+
self.ending = self.beginning + new_duration
|
317
|
+
end
|
318
|
+
alias length_of_time= duration=
|
319
|
+
alias length= duration=
|
320
|
+
end # CompareEvents
|
321
|
+
|
322
|
+
module Validation
|
323
|
+
|
324
|
+
def validate
|
325
|
+
if self.begins_at.nil?
|
326
|
+
errors.add(:begins_at, "must have a start time")
|
327
|
+
if self.ends_at.nil?
|
328
|
+
errors.add(:ends_at, "must have an end time")
|
329
|
+
end
|
330
|
+
else
|
331
|
+
self.beginning.after?(self.ending) ? errors.add_to_base("An event cannot end before it begins") : nil
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end # ActiveRecord
|
336
|
+
end # FluxCapacitor
|
337
|
+
end # Acts
|
338
|
+
end # HolmesLabs
|
339
|
+
ActiveRecord::Base.send :include, HolmesLabs::Acts::FluxCapacitor::ActiveRecord
|
340
|
+
|
341
|
+
# TODO
|
342
|
+
###########
|
343
|
+
# allow events to be created by start time and duration
|
344
|
+
# force exclusivity of events at any given time via parameter to acts_as_event()
|
345
|
+
# add new validations
|
346
|
+
# rake tasks
|
347
|
+
# parameter to comparison operator to still allow true if overlap
|
348
|
+
|
349
|
+
|
350
|
+
# NOTES
|
351
|
+
###########
|
352
|
+
# verify #find_* returns ordered results unless specified otherwise.
|