acts_as_event_owner 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/LICENSE +24 -0
- data/README.textile +205 -0
- data/Rakefile +28 -0
- data/acts_as_event_owner.gemspec +76 -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/core.rb +38 -0
- data/lib/acts_as_event_owner/event_occurrence.rb +6 -0
- data/lib/acts_as_event_owner/event_specification.rb +152 -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/version.rb +3 -0
- data/lib/acts_as_event_owner.rb +17 -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 +62 -0
- data/spec/acts_as_event_owner/event_specification_spec.rb +365 -0
- data/spec/schema.rb +43 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/model_builders.rb +13 -0
- data/spec/support/user.rb +3 -0
- metadata +106 -0
data/.gitignore
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,205 @@
|
|
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.utc,
|
77
|
+
:repeat => :daily,
|
78
|
+
:generate => false
|
79
|
+
|
80
|
+
@user.events # => []
|
81
|
+
|
82
|
+
@user.events.generate :from => Date.today.to_time.utc, :to => Date.today.to_time.utc + 1.week
|
83
|
+
|
84
|
+
# override the description on a per-generate basis
|
85
|
+
@user.events.generate :from => Date.today.to_time.utc, :to => Date.today.to_time.utc + 1.week,
|
86
|
+
:attributes => { :description => 'acquire cheese balls, like, right away!' }
|
87
|
+
|
88
|
+
@user.events # => (7 ActsAsEventOwner::EventOccurrence objects)
|
89
|
+
</pre>
|
90
|
+
|
91
|
+
h2. Adding custom fields
|
92
|
+
|
93
|
+
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.
|
94
|
+
|
95
|
+
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.
|
96
|
+
|
97
|
+
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@.
|
98
|
+
|
99
|
+
h2. Recurrence rules
|
100
|
+
|
101
|
+
ActsAsEventOwner supports recurrence rules roughly equivalent to those supported by Apple's iCal application. Examples are:
|
102
|
+
|
103
|
+
h3. One-time event
|
104
|
+
|
105
|
+
<pre>
|
106
|
+
EventSpecification.create :description => 'pick up laundry',
|
107
|
+
:start_at => Time.parse("4:00pm")
|
108
|
+
</pre>
|
109
|
+
|
110
|
+
h3. Every day
|
111
|
+
|
112
|
+
<pre>
|
113
|
+
EventSpecification.create :description => 'eat breakfast',
|
114
|
+
:start_at => Time.parse("7:30am"),
|
115
|
+
:repeat => :daily
|
116
|
+
</pre>
|
117
|
+
|
118
|
+
h3. Every three days
|
119
|
+
|
120
|
+
<pre>
|
121
|
+
EventSpecification.create :description => 'call mom',
|
122
|
+
:start_at => Time.parse("10:30am"),
|
123
|
+
:repeat => :daily,
|
124
|
+
:frequency => 3
|
125
|
+
</pre>
|
126
|
+
|
127
|
+
h3. On Monday, Wednesday, and Friday of each week
|
128
|
+
|
129
|
+
<pre>
|
130
|
+
EventSpecification.create :description => 'go to the gym',
|
131
|
+
:start_at => Time.parse("6:30am"),
|
132
|
+
:repeat => :weekly,
|
133
|
+
:on => [ :mo, :we, :fr ]
|
134
|
+
</pre>
|
135
|
+
|
136
|
+
h3. On Thursday, every other week
|
137
|
+
|
138
|
+
<pre>
|
139
|
+
EventSpecification.create :description => 'clean the bathroom',
|
140
|
+
:start_at => Time.parse("8:00pm"),
|
141
|
+
:repeat => :weekly,
|
142
|
+
:frequency => 2, :on => [ :th ]
|
143
|
+
</pre>
|
144
|
+
|
145
|
+
h3. On the 10th and 25th of each month
|
146
|
+
|
147
|
+
<pre>
|
148
|
+
EventSpecification.create :description => 'pick up paycheck',
|
149
|
+
:start_at => Time.parse("9:30am"),
|
150
|
+
:repeat => :monthly,
|
151
|
+
:on => [ 10, 25 ]
|
152
|
+
</pre>
|
153
|
+
|
154
|
+
h3. On the last Saturday of each month
|
155
|
+
|
156
|
+
<pre>
|
157
|
+
EventSpecification.create :description => 'run a marathon',
|
158
|
+
:start_at => Time.parse("6:30am"),
|
159
|
+
:repeat => :monthly,
|
160
|
+
:on_the => :last,
|
161
|
+
:target => [ :sa ]
|
162
|
+
</pre>
|
163
|
+
|
164
|
+
h3. On the last weekday of each month
|
165
|
+
|
166
|
+
<pre>
|
167
|
+
EventSpecification.create :description => 'wine tasting',
|
168
|
+
:start_at => Time.parse("6:30pm"),
|
169
|
+
:repeat => :monthly,
|
170
|
+
:on_the => :last,
|
171
|
+
:target => :wkday
|
172
|
+
</pre>
|
173
|
+
|
174
|
+
h3. Every April 15th
|
175
|
+
|
176
|
+
<pre>
|
177
|
+
EventSpecification.create :description => 'pay taxes',
|
178
|
+
:start_at => Time.parse("4/15/2010 5:00pm"),
|
179
|
+
:repeat => :yearly
|
180
|
+
</pre>
|
181
|
+
|
182
|
+
h3. On the second Thursday in May, every other year, until Dec 31, 2012
|
183
|
+
|
184
|
+
<pre>
|
185
|
+
EventSpecification.create :description => 'freak out',
|
186
|
+
:start_at => Time.now.utc,
|
187
|
+
:repeat => :yearly,
|
188
|
+
:frequency => 2,
|
189
|
+
:on => [ 5 ],
|
190
|
+
:on_the => :second,
|
191
|
+
:target => [ :th ],
|
192
|
+
:until => Time.parse("12/31/2012")
|
193
|
+
</pre>
|
194
|
+
|
195
|
+
h2. Using the Rake task
|
196
|
+
|
197
|
+
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.
|
198
|
+
|
199
|
+
<pre>
|
200
|
+
rake acts_as_event_owner:generate_events FROM=9/1/2010 TO=10/1/2010
|
201
|
+
</pre>
|
202
|
+
|
203
|
+
h1. Credits
|
204
|
+
|
205
|
+
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,28 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
desc "Run all specs"
|
6
|
+
Spec::Rake::SpecTask.new('specs') do |t|
|
7
|
+
t.libs << 'lib'
|
8
|
+
t.spec_files = FileList['spec/**/*.rb']
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => [:specs]
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'jeweler'
|
15
|
+
require 'lib/acts_as_event_owner'
|
16
|
+
Jeweler::Tasks.new do |gemspec|
|
17
|
+
gemspec.name = "acts_as_event_owner"
|
18
|
+
gemspec.version = ActsAsEventOwner::VERSION
|
19
|
+
gemspec.summary = "Simple calendar events for any ActiveRecord model"
|
20
|
+
gemspec.email = "dburkes@netable.com"
|
21
|
+
gemspec.homepage = "http://github.com/dburkes/acts_as_event_owner"
|
22
|
+
gemspec.description = "Simple calendar events for any ActiveRecord model"
|
23
|
+
gemspec.authors = ["Danny Burkes"]
|
24
|
+
gemspec.add_dependency('ri_cal')
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
28
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{acts_as_event_owner}
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Danny Burkes"]
|
12
|
+
s.date = %q{2010-10-17}
|
13
|
+
s.description = %q{Simple calendar events for any ActiveRecord model}
|
14
|
+
s.email = %q{dburkes@netable.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.textile"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.textile",
|
23
|
+
"Rakefile",
|
24
|
+
"acts_as_event_owner.gemspec",
|
25
|
+
"generators/acts_as_event_owner_migration/USAGE",
|
26
|
+
"generators/acts_as_event_owner_migration/acts_as_event_owner_migration_generator.rb",
|
27
|
+
"generators/acts_as_event_owner_migration/templates/acts_as_event_owner_migration.rb",
|
28
|
+
"lib/acts_as_event_owner.rb",
|
29
|
+
"lib/acts_as_event_owner/core.rb",
|
30
|
+
"lib/acts_as_event_owner/event_occurrence.rb",
|
31
|
+
"lib/acts_as_event_owner/event_specification.rb",
|
32
|
+
"lib/acts_as_event_owner/exception.rb",
|
33
|
+
"lib/acts_as_event_owner/railtie.rb",
|
34
|
+
"lib/acts_as_event_owner/version.rb",
|
35
|
+
"lib/generators/acts_as_event_owner/migration/migration_generator.rb",
|
36
|
+
"lib/generators/acts_as_event_owner/migration/templates/active_record/acts_as_event_owner_migration.rb",
|
37
|
+
"lib/generators/acts_as_event_owner_migration/USAGE",
|
38
|
+
"lib/generators/acts_as_event_owner_migration/acts_as_event_owner_migration_generator.rb",
|
39
|
+
"lib/generators/acts_as_event_owner_migration/templates/acts_as_event_owner_migration.rb",
|
40
|
+
"lib/tasks/acts_as_event_owner_tasks.rake",
|
41
|
+
"rails/init.rb",
|
42
|
+
"spec/acts_as_event_owner/core_spec.rb",
|
43
|
+
"spec/acts_as_event_owner/event_specification_spec.rb",
|
44
|
+
"spec/schema.rb",
|
45
|
+
"spec/spec_helper.rb",
|
46
|
+
"spec/support/model_builders.rb",
|
47
|
+
"spec/support/user.rb"
|
48
|
+
]
|
49
|
+
s.homepage = %q{http://github.com/dburkes/acts_as_event_owner}
|
50
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
51
|
+
s.require_paths = ["lib"]
|
52
|
+
s.rubygems_version = %q{1.3.6}
|
53
|
+
s.summary = %q{Simple calendar events for any ActiveRecord model}
|
54
|
+
s.test_files = [
|
55
|
+
"spec/acts_as_event_owner/core_spec.rb",
|
56
|
+
"spec/acts_as_event_owner/event_specification_spec.rb",
|
57
|
+
"spec/schema.rb",
|
58
|
+
"spec/spec_helper.rb",
|
59
|
+
"spec/support/model_builders.rb",
|
60
|
+
"spec/support/user.rb"
|
61
|
+
]
|
62
|
+
|
63
|
+
if s.respond_to? :specification_version then
|
64
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
65
|
+
s.specification_version = 3
|
66
|
+
|
67
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
68
|
+
s.add_runtime_dependency(%q<ri_cal>, [">= 0"])
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<ri_cal>, [">= 0"])
|
71
|
+
end
|
72
|
+
else
|
73
|
+
s.add_dependency(%q<ri_cal>, [">= 0"])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -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,38 @@
|
|
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_owner.event_specifications.find(:all, :conditions => "until IS NULL OR until >= '#{Time.now.utc.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
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'ri_cal'
|
2
|
+
|
3
|
+
module ActsAsEventOwner
|
4
|
+
class EventSpecification < ::ActiveRecord::Base
|
5
|
+
belongs_to :owner, :polymorphic => true
|
6
|
+
has_many :event_occurrences, :dependent => :destroy
|
7
|
+
|
8
|
+
serialize :repeat
|
9
|
+
serialize :on
|
10
|
+
serialize :on_the
|
11
|
+
serialize :target
|
12
|
+
|
13
|
+
ON_THE = { :first => '1', :second => '2', :third => '3', :fourth => '4', :last => '-1' }
|
14
|
+
BYDAYS = { :day => 'SU,MO,TU,WE,TH,FR,SA', :wkday => 'MO,TU,WE,TH,FR', :wkend => 'SU,SA'}
|
15
|
+
|
16
|
+
before_validation :set_defaults
|
17
|
+
validates_presence_of :description
|
18
|
+
validates_inclusion_of :repeat, :in => [:daily,:weekly,:monthly,:yearly], :allow_nil => true
|
19
|
+
validates_inclusion_of :on_the, :in => ON_THE.keys, :allow_nil => true
|
20
|
+
validates_numericality_of :frequency, :allow_nil => true
|
21
|
+
validates_presence_of :start_at
|
22
|
+
validate :validate_recurrence_rules
|
23
|
+
|
24
|
+
attr_accessor :generate
|
25
|
+
after_create :auto_generate_events
|
26
|
+
|
27
|
+
def validate_recurrence_rules
|
28
|
+
case self.repeat
|
29
|
+
when :daily
|
30
|
+
[:on, :on_the, :target].each {|v| errors.add(v, :present) if self.send(v)}
|
31
|
+
|
32
|
+
when :weekly
|
33
|
+
errors.add(:on, "must be an array") if self.on.present? && !self.on.is_a?(Array)
|
34
|
+
[:on_the, :target].each {|v| errors.add(v, :present) if self.send(v)}
|
35
|
+
|
36
|
+
when :monthly
|
37
|
+
if self.on_the
|
38
|
+
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))
|
39
|
+
errors.add(:on, :present) if self.on.present?
|
40
|
+
elsif self.on
|
41
|
+
errors.add(:on, "must be an array") if !self.on.is_a?(Array)
|
42
|
+
[:on_the, :target].each {|v| errors.add(v, :present) if self.send(v)}
|
43
|
+
end
|
44
|
+
|
45
|
+
when :yearly
|
46
|
+
if self.on_the
|
47
|
+
errors.add(:on, "must be an array") if !self.on.present? || !self.on.is_a?(Array)
|
48
|
+
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))
|
49
|
+
elsif self.on
|
50
|
+
errors.add(:on, "must be an array") if !self.on.present? || !self.on.is_a?(Array)
|
51
|
+
else
|
52
|
+
errors.add(:on, :present)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_rrule
|
58
|
+
return nil if !self.valid? || self.repeat.nil?
|
59
|
+
|
60
|
+
components = []
|
61
|
+
|
62
|
+
case self.repeat
|
63
|
+
when :daily
|
64
|
+
|
65
|
+
when :weekly
|
66
|
+
components << "BYDAY=#{self.on.join(',').upcase}" if self.on
|
67
|
+
|
68
|
+
when :monthly
|
69
|
+
if self.on_the
|
70
|
+
components << "BYSETPOS=#{ON_THE[self.on_the]}"
|
71
|
+
components << "BYDAY=#{byday}"
|
72
|
+
end
|
73
|
+
components << "BYMONTHDAY=#{self.on.join(',').upcase}" if self.on
|
74
|
+
|
75
|
+
when :yearly
|
76
|
+
components << "BYMONTH=#{self.on.join(',').upcase}" if self.on
|
77
|
+
components << "BYSETPOS=#{ON_THE[self.on_the]};BYDAY=#{byday}" if self.on_the
|
78
|
+
end
|
79
|
+
|
80
|
+
components.unshift "INTERVAL=#{self.frequency}" if self.frequency
|
81
|
+
components.unshift "FREQ=#{self.repeat.to_s.upcase}"
|
82
|
+
components << "UNTIL=#{self.until.strftime("%Y%m%dT%H%M%SZ")}" if self.until
|
83
|
+
components.join(';')
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_events options={}
|
87
|
+
raise ActsAsEventOwner::Exception.new("Invalid Event Specification") if !valid?
|
88
|
+
|
89
|
+
opts = options.clone
|
90
|
+
opts[:from] ||= self.start_at
|
91
|
+
opts[:to] ||= (opts[:from] + 30.days) if opts[:from]
|
92
|
+
opts[:from] -= 1.second
|
93
|
+
opts[:to] -= 1.second
|
94
|
+
opts[:from] = opts[:to] = nil if opts[:count]
|
95
|
+
attribute_overrides = opts[:attributes] || {}
|
96
|
+
|
97
|
+
# puts "generate #{self.attributes.inspect} from #{opts[:from]} to #{opts[:to]}, extended_attributes = #{extended_attributes.inspect}"
|
98
|
+
|
99
|
+
cal = RiCal.Calendar do |cal|
|
100
|
+
cal.event do |event|
|
101
|
+
event.description self.description
|
102
|
+
event.dtstart(self.start_at) if self.start_at
|
103
|
+
event.dtend(self.end_at) if self.end_at
|
104
|
+
event.rrule = self.to_rrule if self.to_rrule
|
105
|
+
end
|
106
|
+
end
|
107
|
+
event = cal.events.first
|
108
|
+
occurrences = event.occurrences(:starting => opts[:from], :before => opts[:to], :count => opts[:count])
|
109
|
+
occurrences.collect do |occurrence|
|
110
|
+
@@OCCURRENCE_COLUMNS ||= (EventOccurrence.columns.collect(&:name) - EXCLUDED_COLUMNS)
|
111
|
+
@@SPECIFICATION_COLUMNS ||= (EventSpecification.columns.collect(&:name) - EXCLUDED_COLUMNS)
|
112
|
+
additional_columns = (@@SPECIFICATION_COLUMNS).inject({}) do |additional, column|
|
113
|
+
additional[column] = self.attributes[column] if @@OCCURRENCE_COLUMNS.include?(column)
|
114
|
+
additional
|
115
|
+
end
|
116
|
+
|
117
|
+
EventOccurrence.find_or_create_by_owner_id_and_owner_type_and_event_specification_id_and_start_at_and_end_at({
|
118
|
+
:owner_id => self.owner_id, :owner_type => self.owner_type, :event_specification_id => self.id,
|
119
|
+
:start_at => occurrence.start_time, :end_at => occurrence.finish_time}.merge(additional_columns).merge(attribute_overrides.stringify_keys))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.generate_events options={}
|
124
|
+
self.all(:conditions => "until IS NULL OR until >= '#{Time.now.utc.to_s(:db)}'").each {|spec|
|
125
|
+
spec.generate_events(options)
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def repeat
|
130
|
+
self.attributes["repeat"].try(:to_sym)
|
131
|
+
end
|
132
|
+
|
133
|
+
protected
|
134
|
+
|
135
|
+
def set_defaults
|
136
|
+
self.start_at ||= Time.now.utc
|
137
|
+
self.end_at ||= self.start_at + 1.hour
|
138
|
+
self.generate = { :from => self.start_at, :to => self.start_at + 30.days } if self.generate.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
def byday
|
142
|
+
self.target.is_a?(Array) ? self.target.join(',').upcase : BYDAYS[self.target]
|
143
|
+
end
|
144
|
+
|
145
|
+
def auto_generate_events
|
146
|
+
self.generate_events(self.generate) if self.generate
|
147
|
+
self.generate = nil
|
148
|
+
end
|
149
|
+
|
150
|
+
EXCLUDED_COLUMNS = [ "id", "owner_id", "owner_type", "description", "start_at", "end_at", "repeat", "frequency", "on", "on_the", "target", "until", "created_at", "updated_at" ]
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require 'acts_as_event_owner/core'
|
7
|
+
require 'acts_as_event_owner/event_specification'
|
8
|
+
require 'acts_as_event_owner/event_occurrence'
|
9
|
+
require 'acts_as_event_owner/exception'
|
10
|
+
require 'acts_as_event_owner/railtie'
|
11
|
+
require 'acts_as_event_owner/version'
|
12
|
+
|
13
|
+
$LOAD_PATH.shift
|
14
|
+
|
15
|
+
if defined?(ActiveRecord::Base)
|
16
|
+
ActiveRecord::Base.send :include, ActsAsEventOwner::Core
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators/migration'
|
2
|
+
|
3
|
+
module ActsAsEventOwner
|
4
|
+
class MigrationGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
desc "Generates migration for EventSpecification and EventOcurrence models"
|
8
|
+
|
9
|
+
def self.orm
|
10
|
+
Rails::Generators.options[:rails][:orm]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.source_root
|
14
|
+
File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.orm_has_migration?
|
18
|
+
[:active_record].include? orm
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.next_migration_number(path)
|
22
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_migration_file
|
26
|
+
if self.class.orm_has_migration?
|
27
|
+
migration_template 'acts_as_event_owner_migration.rb', File.join('db', 'migrate', 'acts_as_event_owner_migration')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|