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.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README.textile +217 -0
- data/Rakefile +10 -0
- data/acts_as_event_owner.gemspec +20 -0
- data/generators/acts_as_event_owner_migration/USAGE +6 -0
- data/generators/acts_as_event_owner_migration/acts_as_event_owner_migration_generator.rb +7 -0
- data/generators/acts_as_event_owner_migration/templates/acts_as_event_owner_migration.rb +38 -0
- data/lib/acts_as_event_owner.rb +19 -0
- data/lib/acts_as_event_owner/core.rb +46 -0
- data/lib/acts_as_event_owner/event_occurrence.rb +7 -0
- data/lib/acts_as_event_owner/event_specification.rb +186 -0
- data/lib/acts_as_event_owner/exception.rb +4 -0
- data/lib/acts_as_event_owner/railtie.rb +13 -0
- data/lib/acts_as_event_owner/ri_cal_fix.rb +7 -0
- data/lib/acts_as_event_owner/version.rb +3 -0
- data/lib/generators/acts_as_event_owner/migration/migration_generator.rb +31 -0
- data/lib/generators/acts_as_event_owner/migration/templates/active_record/acts_as_event_owner_migration.rb +38 -0
- data/lib/generators/acts_as_event_owner_migration/USAGE +6 -0
- data/lib/generators/acts_as_event_owner_migration/acts_as_event_owner_migration_generator.rb +7 -0
- data/lib/generators/acts_as_event_owner_migration/templates/acts_as_event_owner_migration.rb +38 -0
- data/lib/tasks/acts_as_event_owner_tasks.rake +14 -0
- data/rails/init.rb +1 -0
- data/spec/acts_as_event_owner/core_spec.rb +78 -0
- data/spec/acts_as_event_owner/event_specification_spec.rb +454 -0
- data/spec/schema.rb +43 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/model_builders.rb +13 -0
- data/spec/support/user.rb +3 -0
- metadata +99 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
+
|
data/README.textile
ADDED
@@ -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!
|
data/Rakefile
ADDED
@@ -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,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,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
|