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 ADDED
@@ -0,0 +1,4 @@
1
+ == 0.5 2008-05-18
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
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
@@ -0,0 +1,7 @@
1
+
2
+ For more information on acts_as_flux_capacitor, see http://acts_as_flux_capacitor.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
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
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
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.