andyw8-acts_as_event_owner 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +24 -0
  4. data/README.textile +217 -0
  5. data/Rakefile +10 -0
  6. data/acts_as_event_owner.gemspec +20 -0
  7. data/generators/acts_as_event_owner_migration/USAGE +6 -0
  8. data/generators/acts_as_event_owner_migration/acts_as_event_owner_migration_generator.rb +7 -0
  9. data/generators/acts_as_event_owner_migration/templates/acts_as_event_owner_migration.rb +38 -0
  10. data/lib/acts_as_event_owner.rb +19 -0
  11. data/lib/acts_as_event_owner/core.rb +46 -0
  12. data/lib/acts_as_event_owner/event_occurrence.rb +7 -0
  13. data/lib/acts_as_event_owner/event_specification.rb +186 -0
  14. data/lib/acts_as_event_owner/exception.rb +4 -0
  15. data/lib/acts_as_event_owner/railtie.rb +13 -0
  16. data/lib/acts_as_event_owner/ri_cal_fix.rb +7 -0
  17. data/lib/acts_as_event_owner/version.rb +3 -0
  18. data/lib/generators/acts_as_event_owner/migration/migration_generator.rb +31 -0
  19. data/lib/generators/acts_as_event_owner/migration/templates/active_record/acts_as_event_owner_migration.rb +38 -0
  20. data/lib/generators/acts_as_event_owner_migration/USAGE +6 -0
  21. data/lib/generators/acts_as_event_owner_migration/acts_as_event_owner_migration_generator.rb +7 -0
  22. data/lib/generators/acts_as_event_owner_migration/templates/acts_as_event_owner_migration.rb +38 -0
  23. data/lib/tasks/acts_as_event_owner_tasks.rake +14 -0
  24. data/rails/init.rb +1 -0
  25. data/spec/acts_as_event_owner/core_spec.rb +78 -0
  26. data/spec/acts_as_event_owner/event_specification_spec.rb +454 -0
  27. data/spec/schema.rb +43 -0
  28. data/spec/spec_helper.rb +29 -0
  29. data/spec/support/model_builders.rb +13 -0
  30. data/spec/support/user.rb +3 -0
  31. metadata +99 -0
@@ -0,0 +1,8 @@
1
+ .idea
2
+ *.tmproj
3
+ *.db
4
+ *.swp
5
+ .DS_Store
6
+ *.gem
7
+ *.rvmrc
8
+ *.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in acts_as_event_owner.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 2010 Danny Burkes
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
24
+
@@ -0,0 +1,217 @@
1
+ h1. ActsAsEventOwner
2
+
3
+ ActsAsEventOwner is an ActiveRecord extension that adds calendar event management to any ActiveRecord model. Models that declare themselves as @acts_as_event_owner@ gain two @has_many@ associations- one for the event specifications, and one for the event occurrences.
4
+
5
+ ActsAsEventOwner supports recurring events, with roughly the same recurrence rule capabilities as Apple's iCal application. Under the hood, ActsAsEventOwner uses "ri_cal":http://github.com/rubyredrick/ri_cal to provide recurring event support.
6
+
7
+ h1. Installation
8
+
9
+ h2. Rails 2.3.x
10
+
11
+ ActsAsEventOwner is available both as a gem and as a plugin.
12
+
13
+ h3. Installing as a Rails 2.3.x plugin
14
+
15
+ To install as a plugin, just do
16
+
17
+ <pre>
18
+ script/plugin install git://github.com/dburkes/acts_as_event_owner_.git
19
+ </pre>
20
+
21
+ You'll also need to install the @ri_cal@ gem by the method of your choice (bundler, system gems, etc).
22
+
23
+ h3. Installing as a Rails 2.3.x gem
24
+
25
+ To install as a gem, use your preferred method of gem installation, _e.g._ Bundler, @config.gem@, etc.
26
+
27
+ h3. Rails 2.3.x post-installation steps
28
+
29
+ After installation, generate a migration to add ActsAsEventOwner tables to your database:
30
+
31
+ <pre>
32
+ script/generate acts_as_event_owner_migration
33
+ rake db:migrate
34
+ </pre>
35
+
36
+ If you want to use the acts_as_event_owner rake tasks, put the following in your Rakefile:
37
+
38
+ <pre>
39
+ if Gem.searcher.find('acts_as_event_owner')
40
+ Dir["#{Gem.searcher.find('acts_as_event_owner').full_gem_path}/**/tasks/*.rake"].each { |ext| load ext }
41
+ end
42
+ </pre>
43
+
44
+ h2. Rails 3
45
+
46
+ Just add it to your Gemfile, like so:
47
+
48
+ <pre>
49
+ gem 'acts_as_event_owner'
50
+ </pre>
51
+
52
+ Then do:
53
+
54
+ <pre>
55
+ bundle install
56
+ </pre>
57
+
58
+ h3. Rails 3 post-installation steps
59
+
60
+ After installation, generate a migration to add ActsAsEventOwner tables to your database:
61
+
62
+ <pre>
63
+ rails generate acts_as_event_owner:migration
64
+ rake db:migrate
65
+ </pre>
66
+
67
+ h1. Usage
68
+
69
+ <pre>
70
+ class User < ActiveRecord::Base
71
+ acts_as_event_owner
72
+ end
73
+
74
+ @user = User.create :name => 'Alvin Seville'
75
+ @user.event_specifications.create :description => 'acquire cheese balls',
76
+ :start_at => Date.today.to_time,
77
+ :repeat => :daily,
78
+ :generate => false
79
+
80
+ @user.events # => []
81
+
82
+ @user.events.generate :from => Date.today.to_time, :to => Date.today.to_time + 1.week
83
+
84
+ # override the description on a per-generate basis
85
+ @user.events.generate :from => Date.today.to_time - 1.day, :to => Date.today.to_time + 1.week,
86
+ :attributes => { :description => 'acquire cheese balls, like, right away!' }
87
+
88
+ @user.events # => (8 ActsAsEventOwner::EventOccurrence objects)
89
+
90
+ @user.events.past # => (1 ActsAsEventOwner::EventOccurrence objects)
91
+ @user.events.upcoming #=> (7 ActsAsEventOwner::EventOccurrence objects)
92
+ </pre>
93
+
94
+ h2. Adding custom fields
95
+
96
+ You can create your own migrations to add custom fields to the event objects- just make sure that you add the same fields to both the @event_specifications@ and @event_occurrences@ tables.
97
+
98
+ When you create an @EventSpecification@, set the value of your custom fields, then, later, when you call @generate@, the values of those fields in the @EventSpecification@ will be copied over to any generated @EventOcurrence@ records.
99
+
100
+ Just like you can do with the standard @:description@ attribute, you can override the default value of your custom fields with the @:attributes@ parameter when you call @generate@.
101
+
102
+ h2. Recurrence rules
103
+
104
+ ActsAsEventOwner supports recurrence rules roughly equivalent to those supported by Apple's iCal application. Examples are:
105
+
106
+ h3. One-time event
107
+
108
+ <pre>
109
+ EventSpecification.create :description => 'pick up laundry',
110
+ :start_at => Time.parse("4:00pm")
111
+ </pre>
112
+
113
+ h3. Every day at 08:00, 13:00, and 18:00
114
+
115
+ <pre>
116
+ EventSpecification.create :description => 'walk the dog',
117
+ :start_at => Time.parse("8:00am"),
118
+ :repeat => :per_hour,
119
+ :target => [8,13,18]
120
+ </pre>
121
+
122
+ h3. Every day
123
+
124
+ <pre>
125
+ EventSpecification.create :description => 'eat breakfast',
126
+ :start_at => Time.parse("7:30am"),
127
+ :repeat => :daily
128
+ </pre>
129
+
130
+ h3. Every three days
131
+
132
+ <pre>
133
+ EventSpecification.create :description => 'call mom',
134
+ :start_at => Time.parse("10:30am"),
135
+ :repeat => :daily,
136
+ :frequency => 3
137
+ </pre>
138
+
139
+ h3. On Monday, Wednesday, and Friday of each week
140
+
141
+ <pre>
142
+ EventSpecification.create :description => 'go to the gym',
143
+ :start_at => Time.parse("6:30am"),
144
+ :repeat => :weekly,
145
+ :on => [ :mo, :we, :fr ]
146
+ </pre>
147
+
148
+ h3. On Thursday, every other week
149
+
150
+ <pre>
151
+ EventSpecification.create :description => 'clean the bathroom',
152
+ :start_at => Time.parse("8:00pm"),
153
+ :repeat => :weekly,
154
+ :frequency => 2, :on => [ :th ]
155
+ </pre>
156
+
157
+ h3. On the 10th and 25th of each month
158
+
159
+ <pre>
160
+ EventSpecification.create :description => 'pick up paycheck',
161
+ :start_at => Time.parse("9:30am"),
162
+ :repeat => :monthly,
163
+ :on => [ 10, 25 ]
164
+ </pre>
165
+
166
+ h3. On the last Saturday of each month
167
+
168
+ <pre>
169
+ EventSpecification.create :description => 'run a marathon',
170
+ :start_at => Time.parse("6:30am"),
171
+ :repeat => :monthly,
172
+ :on_the => :last,
173
+ :target => [ :sa ]
174
+ </pre>
175
+
176
+ h3. On the last weekday of each month
177
+
178
+ <pre>
179
+ EventSpecification.create :description => 'wine tasting',
180
+ :start_at => Time.parse("6:30pm"),
181
+ :repeat => :monthly,
182
+ :on_the => :last,
183
+ :target => :wkday
184
+ </pre>
185
+
186
+ h3. Every April 15th
187
+
188
+ <pre>
189
+ EventSpecification.create :description => 'pay taxes',
190
+ :start_at => Time.parse("4/15/2010 5:00pm"),
191
+ :repeat => :yearly
192
+ </pre>
193
+
194
+ h3. On the second Thursday in May, every other year, until Dec 31, 2012
195
+
196
+ <pre>
197
+ EventSpecification.create :description => 'freak out',
198
+ :start_at => Time.zone.now,
199
+ :repeat => :yearly,
200
+ :frequency => 2,
201
+ :on => [ 5 ],
202
+ :on_the => :second,
203
+ :target => [ :th ],
204
+ :until => Time.parse("12/31/2012")
205
+ </pre>
206
+
207
+ h2. Using the Rake task
208
+
209
+ A rake task is included to generate occurrences of recurring events. For example, you might run this out of a cron job each day to generate any recurring events for the next 30 days, or whatever.
210
+
211
+ <pre>
212
+ rake acts_as_event_owner:generate_events FROM=9/1/2010 TO=10/1/2010
213
+ </pre>
214
+
215
+ h1. Credits
216
+
217
+ ActsAsEventOwner was developed for Josh Pigford and "Sabotage Media LLC":http://madebysabotage.com, for use in their excellent "Critterly":http://critterly.com product. Big thanks to Josh and Sabotage for agreeing to make ActsAsEventOwner available by open source!
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rdoc/task'
3
+ require 'rspec/core/rake_task'
4
+ require 'bundler'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+
10
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acts_as_event_owner/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "andyw8-acts_as_event_owner"
8
+ gem.version = ActsAsEventOwner::VERSION
9
+ gem.authors = ["Danny Burkes", "Andy Waite"]
10
+ gem.email = ["dburkes@netable.com", "github@andywaite.com"]
11
+ gem.description = "Simple calendar events for any ActiveRecord model"
12
+ gem.summary = "Simple calendar events for any ActiveRecord model"
13
+ gem.homepage = "http://github.com/andyw8/acts_as_event_owner"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.add_dependency 'ri_cal'
20
+ end
@@ -0,0 +1,6 @@
1
+ Usage:
2
+
3
+ script/generate acts_as_event_owner_migration
4
+
5
+ This will create a migration that will add the acts_as_event_owner tables to your database
6
+
@@ -0,0 +1,7 @@
1
+ class ActsAsEventOwnerMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'acts_as_event_owner_migration.rb', File.join('db', 'migrate'), :migration_file_name => "acts_as_event_owner_migration"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ class ActsAsEventOwnerMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :event_specifications do |t|
4
+ t.integer :owner_id
5
+ t.string :owner_type
6
+ t.string :description
7
+ t.datetime :start_at
8
+ t.datetime :end_at
9
+ t.string :repeat # daily, weekly, monthly, yearly
10
+ t.integer :frequency, :default => 1 # every 'n' days, weeks, months, or years
11
+ t.string :on # su, mo, tu, we, th, fr, sa, 1-31, jan-dec
12
+ t.string :on_the # first, second, third, fourth, last
13
+ t.string :target # su, mo, tu, we, th, fr, sa, day, wkday, wkend
14
+ t.datetime :until
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :event_specifications, [:owner_id, :owner_type]
19
+
20
+ create_table :event_occurrences do |t|
21
+ t.integer :owner_id
22
+ t.string :owner_type
23
+ t.integer :event_specification_id
24
+ t.datetime :start_at
25
+ t.datetime :end_at
26
+ t.string :description
27
+ t.timestamps
28
+ end
29
+
30
+ add_index :event_occurrences, [:owner_id, :owner_type]
31
+ add_index :event_occurrences, :event_specification_id
32
+ end
33
+
34
+ def self.down
35
+ drop_table :event_specifications
36
+ drop_table :event_occurrences
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'active_support/time_with_zone'
3
+ require 'active_record'
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+
7
+ require 'acts_as_event_owner/ri_cal_fix'
8
+ require 'acts_as_event_owner/core'
9
+ require 'acts_as_event_owner/event_specification'
10
+ require 'acts_as_event_owner/event_occurrence'
11
+ require 'acts_as_event_owner/exception'
12
+ require 'acts_as_event_owner/railtie'
13
+ require 'acts_as_event_owner/version'
14
+
15
+ $LOAD_PATH.shift
16
+
17
+ if defined?(ActiveRecord::Base)
18
+ ActiveRecord::Base.send :include, ActsAsEventOwner::Core
19
+ end
@@ -0,0 +1,46 @@
1
+ module ActsAsEventOwner
2
+ module Core
3
+ def self.included(base)
4
+ base.send :extend, ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def acts_as_event_owner options = {}
9
+ include InstanceMethods
10
+
11
+ class_eval do
12
+ has_many :event_specifications, :class_name => ActsAsEventOwner::EventSpecification.name, :as => :owner, :dependent => :destroy
13
+ has_many :events, :class_name => ActsAsEventOwner::EventOccurrence.name, :as => :owner, :readonly => true do
14
+ def generate(options={})
15
+ proxy_association.owner.event_specifications.find(:all, :conditions => "until IS NULL OR until >= '#{Time.zone.now.to_s(:db)}'").each {|spec| spec.generate_events(options)}
16
+ self.reload
17
+ end
18
+
19
+ def <<(obj)
20
+ raise ActsAsEventOwner::Exception.new("Do not add events directly- add event specifications, then call events.generate")
21
+ end
22
+
23
+ def build(attributes={})
24
+ raise ActsAsEventOwner::Exception.new("Do not build events directly- build event specifications, then call events.generate")
25
+ end
26
+
27
+ def create(attributes={})
28
+ raise ActsAsEventOwner::Exception.new("Do not create events directly- build event specifications, then call events.generate")
29
+ end
30
+
31
+ def upcoming
32
+ find(:all, :conditions => ["start_at >= ?", Time.zone.now])
33
+ end
34
+
35
+ def past
36
+ find(:all, :conditions => ["start_at < ?", Time.zone.now])
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ module InstanceMethods
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ module ActsAsEventOwner
2
+ class EventOccurrence < ::ActiveRecord::Base
3
+ unloadable
4
+ belongs_to :owner, :polymorphic => true
5
+ belongs_to :event_specification, :class_name => "::ActsAsEventOwner::EventSpecification"
6
+ end
7
+ end
@@ -0,0 +1,186 @@
1
+ require 'ri_cal'
2
+
3
+ module ActsAsEventOwner
4
+ class EventSpecification < ::ActiveRecord::Base
5
+ unloadable
6
+ belongs_to :owner, :polymorphic => true
7
+ has_many :event_occurrences, :dependent => :destroy, :class_name => "::ActsAsEventOwner::EventOccurrence"
8
+
9
+ serialize :repeat
10
+ serialize :on
11
+ serialize :on_the
12
+ serialize :target
13
+
14
+ ON_THE = { :first => '1', :second => '2', :third => '3', :fourth => '4', :last => '-1' }
15
+ BYDAYS = { :day => 'SU,MO,TU,WE,TH,FR,SA', :wkday => 'MO,TU,WE,TH,FR', :wkend => 'SU,SA'}
16
+
17
+ before_validation :set_defaults
18
+ validates_presence_of :description
19
+ validates_inclusion_of :repeat, :in => [:by_hour,:daily,:weekly,:monthly,:yearly], :allow_nil => true
20
+ validates_inclusion_of :on_the, :in => ON_THE.keys, :allow_nil => true
21
+ validates_numericality_of :frequency, :allow_nil => true
22
+ validates_presence_of :start_at
23
+ validate :validate_recurrence_rules
24
+ after_validation :set_defaults_after_validation
25
+
26
+ attr_accessor :generate
27
+ after_create :auto_generate_events
28
+
29
+ def validate_recurrence_rules
30
+ case self.repeat
31
+ when :by_hour
32
+ errors.add(:target, "must be an array") if !self.target.present? || !self.target.is_a?(Array)
33
+ [:on, :on_the].each {|v| errors.add(v, :present) if self.send(v)}
34
+
35
+ when :daily
36
+ [:on, :on_the, :target].each {|v| errors.add(v, :present) if self.send(v)}
37
+
38
+ when :weekly
39
+ errors.add(:on, "must be an array") if self.on.present? && !self.on.is_a?(Array)
40
+ [:on_the, :target].each {|v| errors.add(v, :present) if self.send(v)}
41
+
42
+ when :monthly
43
+ if self.on_the
44
+ errors.add(:target, "must be an array, :day, :wkday, or :wkend") if self.target.nil? || !(self.target.is_a?(Array) || BYDAYS.keys.include?(self.target))
45
+ errors.add(:on, :present) if self.on.present?
46
+ elsif self.on
47
+ errors.add(:on, "must be an array") if !self.on.is_a?(Array)
48
+ [:on_the, :target].each {|v| errors.add(v, :present) if self.send(v)}
49
+ end
50
+
51
+ when :yearly
52
+ if self.on_the
53
+ errors.add(:on, "must be an array") if !self.on.present? || !self.on.is_a?(Array)
54
+ errors.add(:target, "must be an array, :day, :wkday, or :wkend") if self.target.nil? || !(self.target.is_a?(Array) || BYDAYS.keys.include?(self.target))
55
+ elsif self.on
56
+ errors.add(:on, "must be an array") if !self.on.present? || !self.on.is_a?(Array)
57
+ end
58
+ end
59
+ end
60
+
61
+ def to_rrule
62
+ return nil if !self.valid? || self.repeat.nil?
63
+
64
+ components = []
65
+ repeat = self.repeat
66
+ frequency = self.frequency
67
+
68
+ case self.repeat
69
+ when :by_hour
70
+ repeat = "DAILY"
71
+ components << "BYHOUR=#{self.target.join(',')}"
72
+ frequency = nil
73
+
74
+ when :daily
75
+
76
+ when :weekly
77
+ components << "BYDAY=#{self.on.join(',').upcase}" if self.on
78
+
79
+ when :monthly
80
+ if self.on_the
81
+ components << "BYSETPOS=#{ON_THE[self.on_the]}"
82
+ components << "BYDAY=#{byday}"
83
+ end
84
+ components << "BYMONTHDAY=#{self.on.join(',').upcase}" if self.on
85
+
86
+ when :yearly
87
+ components << "BYMONTH=#{self.on.join(',').upcase}" if self.on
88
+ components << "BYSETPOS=#{ON_THE[self.on_the]};BYDAY=#{byday}" if self.on_the
89
+ end
90
+
91
+ components.unshift "INTERVAL=#{frequency}" if frequency
92
+ components.unshift "FREQ=#{repeat.to_s.upcase}"
93
+ components << "UNTIL=#{self.until.strftime("%Y%m%dT%H%M%S")}" if self.until
94
+ components.join(';')
95
+ end
96
+
97
+ def generate_events options={}
98
+ raise ActsAsEventOwner::Exception.new("Invalid Event Specification") if !valid?
99
+
100
+ opts = options.clone
101
+ opts[:from] ||= self.start_at
102
+ opts[:to] ||= (opts[:from] + 30.days) if opts[:from]
103
+ opts[:from] -= 1.second
104
+ opts[:to] -= 1.second
105
+ opts[:from] = opts[:to] = nil if opts[:count]
106
+ attribute_overrides = opts[:attributes] || {}
107
+
108
+ # puts "generate #{self.attributes.inspect} from #{opts[:from]} to #{opts[:to]} with #{attribute_overrides.inspect}"
109
+
110
+ start_at = self.start_at
111
+ end_at = self.end_at
112
+ cal = RiCal.Calendar do |cal|
113
+ cal.event do |event|
114
+ event.description self.description
115
+ event.dtstart(start_at.in_time_zone) if start_at
116
+ event.dtend(end_at.in_time_zone) if end_at
117
+ event.rrule = self.to_rrule if self.to_rrule
118
+ end
119
+ end
120
+ event = cal.events.first
121
+ # puts "event is #{event.inspect}"
122
+ occurrences = event.occurrences(:starting => opts[:from], :before => opts[:to], :count => opts[:count])
123
+ # puts "got #{occurrences.length} occurrences"
124
+ occurrences.collect do |occurrence|
125
+ @@OCCURRENCE_COLUMNS ||= (EventOccurrence.columns.collect(&:name) - EXCLUDED_COLUMNS)
126
+ @@SPECIFICATION_COLUMNS ||= (EventSpecification.columns.collect(&:name) - EXCLUDED_COLUMNS)
127
+ additional_columns = (@@SPECIFICATION_COLUMNS).inject({}) do |additional, column|
128
+ additional[column] = self.attributes[column] if @@OCCURRENCE_COLUMNS.include?(column)
129
+ additional
130
+ end
131
+
132
+ # puts "*********** #{occurrence.start_time} : #{occurrence.start_time.zone}"
133
+ # puts "*********** #{Time.zone.at(occurrence.start_time.to_i)}"
134
+
135
+ hash = {
136
+ :owner_id => self.owner_id, :owner_type => self.owner_type, :event_specification_id => self.id,
137
+ :description => occurrence.description, :start_at => occurrence.start_time.utc,
138
+ :end_at => occurrence.finish_time.utc}.stringify_keys.merge(additional_columns).merge(attribute_overrides.stringify_keys)
139
+
140
+ EventOccurrence.find_or_create_by_owner_id_and_owner_type_and_event_specification_id_and_start_at_and_end_at(hash)
141
+ end
142
+ end
143
+
144
+ def self.generate_events options={}
145
+ self.all(:conditions => "until IS NULL OR until >= '#{Time.zone.now.to_s(:db)}'").each {|spec|
146
+ spec.generate_events(options)
147
+ }
148
+ end
149
+
150
+ def repeat
151
+ self.attributes["repeat"].try(:to_sym)
152
+ end
153
+
154
+ protected
155
+
156
+ def set_defaults
157
+ self.start_at ||= Time.zone.now
158
+ self.end_at ||= self.start_at + 1.hour
159
+ self.generate = { :from => self.start_at, :to => self.start_at + 30.days } if self.generate.nil?
160
+ end
161
+
162
+ def set_defaults_after_validation
163
+ if self.errors.empty? && self.repeat == :by_hour
164
+ current_start_hour = self.start_at.hour
165
+ closest_repeat_hour = self.target.detect {|h| h > current_start_hour}
166
+ if closest_repeat_hour
167
+ self.start_at = Time.local(self.start_at.year, self.start_at.month, self.start_at.day, closest_repeat_hour)
168
+ else
169
+ tomorrow = self.start_at + 24.hours
170
+ self.start_at = Time.local(tomorrow.year, tomorrow.month, tomorrow.day, self.target.first)
171
+ end
172
+ end
173
+ end
174
+
175
+ def byday
176
+ self.target.is_a?(Array) ? self.target.join(',').upcase : BYDAYS[self.target]
177
+ end
178
+
179
+ def auto_generate_events
180
+ self.generate_events(self.generate) if self.generate
181
+ self.generate = nil
182
+ end
183
+
184
+ EXCLUDED_COLUMNS = [ "id", "owner_id", "owner_type", "description", "start_at", "end_at", "repeat", "frequency", "on", "on_the", "target", "until", "created_at", "updated_at" ]
185
+ end
186
+ end