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,4 @@
1
+ module ActsAsEventOwner
2
+ class Exception < ::Exception
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ require 'acts_as_event_owner'
2
+
3
+ module ActsAsEventOwner
4
+ if defined? Rails::Railtie
5
+ require 'rails'
6
+ class Railtie < Rails::Railtie
7
+ rake_tasks do
8
+ puts Dir.pwd
9
+ load "tasks/acts_as_event_owner_tasks.rake"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # https://github.com/dburkes/acts_as_event_owner/issues/14
2
+
3
+ class Time
4
+ def self.get_zone(*args)
5
+ Time.find_zone!(*args)
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsEventOwner
2
+ VERSION = "1.1.4" unless defined? ActsAsEventOwner::VERSION
3
+ 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
@@ -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,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,14 @@
1
+ namespace :acts_as_event_owner do
2
+ task :require_from do
3
+ raise "Set FROM to something understandable by Time.parse" if !ENV['FROM']
4
+ end
5
+
6
+ task :require_to do
7
+ raise "Set TO to something understandable by Time.parse" if !ENV['TO']
8
+ end
9
+
10
+ desc "Generate all events within a certain time window"
11
+ task :generate_events => [:environment, :require_from, :require_to] do
12
+ ActsAsEventOwner::EventSpecification.all(:conditions => "until IS NULL OR until >= '#{Time.zone.now.to_s(:db)}'").each {|spec| spec.generate_events(options)}
13
+ end
14
+ end
@@ -0,0 +1 @@
1
+ require 'acts_as_event_owner'
@@ -0,0 +1,78 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe ActsAsEventOwner::Core do
4
+ before(:each) do
5
+ clean_database!
6
+ @user = User.create :name => 'dude'
7
+ Time.zone = 'Eastern Time (US & Canada)'
8
+ @now = Time.zone.now
9
+ @bod = Date.today.to_time.in_time_zone
10
+ end
11
+
12
+ it "adds associations to the host object" do
13
+ lambda {
14
+ @user.events
15
+ @user.event_specifications
16
+ }.should_not raise_error
17
+ end
18
+
19
+ it "adds event specifications to the host object" do
20
+ lambda {
21
+ @user.event_specifications.create :description => 'walk the dog', :start_at => @now, :repeat => :daily, :frequency => 1
22
+ }.should change(EventSpecification, :count).by(1)
23
+
24
+ specs = @user.reload.event_specifications
25
+ specs.size.should == 1
26
+ end
27
+
28
+ it "adds events to the host object" do
29
+ @user.event_specifications.create :description => 'walk the dog', :start_at => @now, :repeat => :daily, :frequency => 1, :generate => false
30
+ @user.event_specifications.create :description => 'go to the gym', :start_at => @now, :repeat => :daily, :frequency => 2, :generate => false
31
+
32
+ lambda {
33
+ @user.events.generate :from => @bod, :to => @bod + 1.week
34
+ }.should change(EventOccurrence, :count).by(11)
35
+ end
36
+
37
+ it "injects events into the association immediately" do
38
+ @user.event_specifications.create :description => 'walk the dog', :start_at => @now, :repeat => :daily, :frequency => 1, :generate => false
39
+ @user.events.should be_empty
40
+ @user.events.generate :from => @bod, :to => @bod + 1.week
41
+ @user.events.should be_present
42
+ @user.events.size.should == 7
43
+ end
44
+
45
+ describe "events association" do
46
+ before(:each) do
47
+ @new_event = EventOccurrence.new
48
+ @new_event.should be_valid
49
+ end
50
+
51
+ it "raises an exception if #<< is called" do
52
+ lambda { @user.events << @new_event }.should raise_error(ActsAsEventOwner::Exception)
53
+ end
54
+
55
+ it "raises an exception is #build is called" do
56
+ lambda { @user.events.build @new_event.attributes }.should raise_error(ActsAsEventOwner::Exception)
57
+ end
58
+
59
+ it "raises an exception if #create is called" do
60
+ lambda { @user.events.create @new_event.attributes }.should raise_error(ActsAsEventOwner::Exception)
61
+ end
62
+
63
+ describe "extensions" do
64
+ before(:each) do
65
+ @user.event_specifications.create :description => 'walk the dog', :start_at => @bod - 7.days, :repeat => :daily, :frequency => 1
66
+ @user.events.length.should == 30
67
+ end
68
+
69
+ it "provides an extension for upcoming events" do
70
+ @user.events.upcoming.length.should == 22
71
+ end
72
+
73
+ it "provides an extension for past events" do
74
+ @user.events.past.length.should == 8
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,454 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe ActsAsEventOwner::EventSpecification do
4
+ before(:each) do
5
+ clean_database!
6
+ Time.zone = 'Eastern Time (US & Canada)'
7
+ end
8
+
9
+ describe "defaults" do
10
+ it "defaults start_at to now" do
11
+ now = Time.zone.now
12
+ Time.stub!(:now).and_return(now)
13
+ spec = new_event_specification
14
+ spec.should be_valid
15
+ spec.start_at.should == now
16
+ end
17
+
18
+ it "defaults duration to one hour" do
19
+ spec = new_event_specification
20
+ spec.should be_valid
21
+ spec.end_at.should == spec.start_at + 1.hour
22
+ end
23
+
24
+ it "defaults repeat until forever" do
25
+ spec = new_event_specification(:repeat => :daily)
26
+ spec.should be_valid
27
+ spec.until.should be_nil
28
+ end
29
+ end
30
+
31
+ describe "validations" do
32
+ it "requires a valid repeat interval" do
33
+ spec = new_event_specification(:repeat => :bogus)
34
+ spec.should_not be_valid
35
+ spec.errors[:repeat].should be_present
36
+ end
37
+
38
+ it "requires a description" do
39
+ spec = new_event_specification(:description => nil)
40
+ spec.should_not be_valid
41
+ spec.errors[:description].should be_present
42
+ end
43
+ end
44
+
45
+ describe "non-recurring events" do
46
+ it "passes validations" do
47
+ new_event_specification.should be_valid
48
+ end
49
+
50
+ it "does not generate an RRULE" do
51
+ new_event_specification.to_rrule.should be_nil
52
+ end
53
+ end
54
+
55
+ describe "events recurring multiple times per day" do
56
+ it "passes validations" do
57
+ new_event_specification(:repeat => :by_hour, :target => [8, 12, 16]).should be_valid
58
+ end
59
+
60
+ it "defaults start_at to the first occurrence" do
61
+ now = Time.zone.now
62
+ Time.stub!(:now).and_return(Time.local(2011, 1, 15, 8, 23))
63
+ create_event_specification(:repeat => :by_hour, :target => [8, 12, 16], :generate => false).start_at.should == Time.local(2011, 1, 15, 12, 00)
64
+
65
+ Time.stub!(:now).and_return(Time.local(2011, 1, 15, 23, 23))
66
+ create_event_specification(:repeat => :by_hour, :target => [8, 12, 16], :generate => false).start_at.should == Time.local(2011, 1, 16, 8, 00)
67
+ end
68
+
69
+ it "does not support invalid recurrence specifications" do
70
+ spec = new_event_specification(:repeat => :by_hour)
71
+ spec.should_not be_valid
72
+ spec.errors[:target].should be_present
73
+
74
+ spec = new_event_specification(:repeat => :by_hour, :on_the => :first)
75
+ spec.should_not be_valid
76
+ spec.errors[:on_the].should be_present
77
+
78
+ spec = new_event_specification(:repeat => :by_hour, :on => [1,2])
79
+ spec.should_not be_valid
80
+ spec.errors[:on].should be_present
81
+
82
+ spec = new_event_specification(:repeat => :by_hour, :target => 8)
83
+ spec.should_not be_valid
84
+ spec.errors[:target].should be_present
85
+ end
86
+
87
+ it "generates an RRULE" do
88
+ # every day at 08:00, 12:00, and 16:00
89
+ new_event_specification(:repeat => :by_hour, :target => [8,12,16]).to_rrule.should == "FREQ=DAILY;BYHOUR=8,12,16"
90
+ end
91
+
92
+ end
93
+
94
+ describe "events recurring daily" do
95
+ it "passes validations" do
96
+ new_event_specification(:repeat => :daily).should be_valid
97
+ new_event_specification(:repeat => :daily, :frequency => 4).should be_valid
98
+ end
99
+
100
+ it "does not support invalid recurrence specifications" do
101
+ spec = new_event_specification(:repeat => :daily, :frequency => 'foo')
102
+ spec.should_not be_valid
103
+ spec.errors[:frequency].should be_present
104
+
105
+ spec = new_event_specification(:repeat => :daily, :on => [1, 2])
106
+ spec.should_not be_valid
107
+ spec.errors[:on].should be_present
108
+
109
+ spec = new_event_specification(:repeat => :daily, :on_the => :first)
110
+ spec.should_not be_valid
111
+ spec.errors[:on_the].should be_present
112
+
113
+ spec = new_event_specification(:repeat => :daily, :on_the => :first, :target => :wkday)
114
+ spec.should_not be_valid
115
+ spec.errors[:target].should be_present
116
+ end
117
+
118
+ it "defaults frequency to 1" do
119
+ new_event_specification(:repeat => :daily).frequency.should == 1
120
+ end
121
+
122
+ it "generates an RRULE" do
123
+ # every day
124
+ new_event_specification(:repeat => :daily).to_rrule.should == "FREQ=DAILY;INTERVAL=1"
125
+ # every four days
126
+ new_event_specification(:repeat => :daily, :frequency => 4).to_rrule.should == "FREQ=DAILY;INTERVAL=4"
127
+ end
128
+ end
129
+
130
+ describe "events recurring weekly" do
131
+ it "passes validations" do
132
+ new_event_specification(:repeat => :weekly).should be_valid
133
+ new_event_specification(:repeat => :weekly, :frequency => 2).should be_valid
134
+ new_event_specification(:repeat => :weekly, :on => [:mo, :we, :fr]).should be_valid
135
+ new_event_specification(:repeat => :weekly, :frequency => 2, :on => [:mo, :we, :fr]).should be_valid
136
+ new_event_specification(:repeat => :weekly, :frequency => 2, :on => [:mo, :we, :fr], :until => Time.parse("2010-12-31")).should be_valid
137
+ end
138
+
139
+ it "does not support invalid recurrence specifications" do
140
+ spec = new_event_specification(:repeat => :weekly, :frequency => 'foo')
141
+ spec.should_not be_valid
142
+ spec.errors[:frequency].should be_present
143
+
144
+ spec = new_event_specification(:repeat => :weekly, :on_the => :first, :target => :wkend)
145
+ spec.should_not be_valid
146
+ spec.errors[:on_the].should be_present
147
+ spec.errors[:target].should be_present
148
+
149
+ spec = new_event_specification(:repeat => :weekly, :on => '2')
150
+ spec.should_not be_valid
151
+ spec.errors[:on].should be_present
152
+ end
153
+
154
+ it "generates an RRULE" do
155
+ # every week
156
+ new_event_specification(:repeat => :weekly).to_rrule.should == "FREQ=WEEKLY;INTERVAL=1"
157
+ # every two weeks
158
+ new_event_specification(:repeat => :weekly, :frequency => 2).to_rrule.should == "FREQ=WEEKLY;INTERVAL=2"
159
+ # every monday, wednesday, and friday
160
+ new_event_specification(:repeat => :weekly, :on => [:mo, :we, :fr]).to_rrule.should == "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR"
161
+ # every other monday, wednesday, and friday
162
+ new_event_specification(:repeat => :weekly, :frequency => 2, :on => [:mo, :we, :fr]).to_rrule.should == "FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR"
163
+ # every other monday, wednesday, and friday, until 12/31/2010
164
+ new_event_specification(:repeat => :weekly, :frequency => 2, :on => [:mo, :we, :fr], :until => Time.parse("2010-12-31")).to_rrule.should == "FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR;UNTIL=20101231T000000"
165
+ end
166
+ end
167
+
168
+ describe "events recurring monthly" do
169
+ it "passes validations" do
170
+ new_event_specification(:repeat => :monthly).should be_valid
171
+ new_event_specification(:repeat => :monthly, :frequency => 2).should be_valid
172
+ new_event_specification(:repeat => :monthly, :frequency => 2, :on => [1, 15, 20]).should be_valid
173
+ new_event_specification(:repeat => :monthly, :frequency => 2, :on_the => :third, :target => :wkday).should be_valid
174
+ new_event_specification(:repeat => :monthly, :frequency => 2, :on_the => :third, :target => [:mo, :we], :until => Time.parse("2010-12-31")).should be_valid
175
+ end
176
+
177
+ it "does not support invalid recurrence specification" do
178
+ spec = new_event_specification(:repeat => :monthly, :frequency => 'foo')
179
+ spec.should_not be_valid
180
+ spec.errors[:frequency].should be_present
181
+
182
+ spec = new_event_specification(:repeat => :monthly, :on => 2)
183
+ spec.should_not be_valid
184
+ spec.errors[:on].should be_present
185
+
186
+ spec = new_event_specification(:repeat => :monthly, :on => [2], :on_the => :first, :target => :wkday)
187
+ spec.should_not be_valid
188
+ spec.errors[:on].should be_present
189
+
190
+ spec = new_event_specification(:repeat => :monthly, :on_the => 2)
191
+ spec.should_not be_valid
192
+ spec.errors[:on_the].should be_present
193
+
194
+ spec = new_event_specification(:repeat => :monthly, :on_the => :first, :target => :we)
195
+ spec.should_not be_valid
196
+ spec.errors[:target].should be_present
197
+
198
+ spec = new_event_specification(:repeat => :monthly, :on_the => :first, :on => [2])
199
+ spec.should_not be_valid
200
+ spec.errors[:on].should be_present
201
+ end
202
+
203
+ it "generates an RRULE" do
204
+ # every month
205
+ new_event_specification(:repeat => :monthly).to_rrule.should == "FREQ=MONTHLY;INTERVAL=1"
206
+ # every two months
207
+ new_event_specification(:repeat => :monthly, :frequency => 2).to_rrule.should == "FREQ=MONTHLY;INTERVAL=2"
208
+ # every other month, on the 1st, 15th, and 20th
209
+ new_event_specification(:repeat => :monthly, :frequency => 2, :on => [1, 15, 20]).to_rrule.should == "FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=1,15,20"
210
+ # every other month, on the third weekday of the month
211
+ new_event_specification(:repeat => :monthly, :frequency => 2, :on_the => :third, :target => :wkday).to_rrule.should == "FREQ=MONTHLY;INTERVAL=2;BYSETPOS=3;BYDAY=MO,TU,WE,TH,FR"
212
+ # every other month, on the third monday and third wednesday, until 12/31/2010
213
+ new_event_specification(:repeat => :monthly, :frequency => 2, :on_the => :third, :target => [:mo, :we], :until => Time.parse("2010-12-31")).to_rrule.should == "FREQ=MONTHLY;INTERVAL=2;BYSETPOS=3;BYDAY=MO,WE;UNTIL=20101231T000000"
214
+ end
215
+ end
216
+
217
+ describe "events recurring yearly" do
218
+ it "passes validations" do
219
+ new_event_specification(:repeat => :yearly).should be_valid
220
+ new_event_specification(:repeat => :yearly, :frequency => 3).should be_valid
221
+ new_event_specification(:repeat => :yearly, :on => [1,7]).should be_valid
222
+ new_event_specification(:repeat => :yearly, :frequency => 2, :on => [1,7]).should be_valid
223
+ new_event_specification(:repeat => :yearly, :on => [1,7], :on_the => :first, :target => :wkend).should be_valid
224
+ new_event_specification(:repeat => :yearly, :frequency => 2, :on => [1,7], :on_the => :first, :target => :wkday, :until => Time.parse("2010-12-31")).should be_valid
225
+ end
226
+
227
+ it "does not support invalid recurrence rules" do
228
+ spec = new_event_specification(:repeat => :yearly, :frequency => 'foo')
229
+ spec.should_not be_valid
230
+ spec.errors[:frequency].should be_present
231
+
232
+ spec = new_event_specification(:repeat => :yearly, :on => 2)
233
+ spec.should_not be_valid
234
+ spec.errors[:on].should be_present
235
+
236
+ spec = new_event_specification(:repeat => :yearly, :on => [2], :on_the => 'first')
237
+ spec.should_not be_valid
238
+ spec.errors[:on_the].should be_present
239
+
240
+ spec = new_event_specification(:repeat => :yearly, :on => [2], :on_the => :first, :target => 2)
241
+ spec.should_not be_valid
242
+ spec.errors[:target].should be_present
243
+ end
244
+
245
+ it "generates an RRULE" do
246
+ # every year
247
+ new_event_specification(:repeat => :yearly).to_rrule.should == "FREQ=YEARLY;INTERVAL=1"
248
+ # every third year
249
+ new_event_specification(:repeat => :yearly, :frequency => 3).to_rrule.should == "FREQ=YEARLY;INTERVAL=3"
250
+ # every year in january and july
251
+ new_event_specification(:repeat => :yearly, :on => [1,7]).to_rrule.should == "FREQ=YEARLY;INTERVAL=1;BYMONTH=1,7"
252
+ # every other year, in january and july
253
+ new_event_specification(:repeat => :yearly, :frequency => 2, :on => [1,7]).to_rrule.should == "FREQ=YEARLY;INTERVAL=2;BYMONTH=1,7"
254
+ # every year, on the first weekend day in january and july
255
+ new_event_specification(:repeat => :yearly, :on => [1,7], :on_the => :first, :target => :wkend).to_rrule.should == "FREQ=YEARLY;INTERVAL=1;BYMONTH=1,7;BYSETPOS=1;BYDAY=SU,SA"
256
+ # every other year, on the first weekday in january and july, until 12/31/2010
257
+ new_event_specification(:repeat => :yearly, :frequency => 2, :on => [1,7], :on_the => :first, :target => :wkday, :until => Time.parse("2010-12-31")).to_rrule.should == "FREQ=YEARLY;INTERVAL=2;BYMONTH=1,7;BYSETPOS=1;BYDAY=MO,TU,WE,TH,FR;UNTIL=20101231T000000"
258
+ end
259
+ end
260
+
261
+ describe "#generate_events" do
262
+ before(:each) do
263
+ @now = Time.zone.now
264
+ @bod = Date.today.to_time
265
+ end
266
+
267
+ describe "non-recurring events" do
268
+ before(:each) do
269
+ @spec = create_event_specification :start_at => @now, :added_string => 'foo', :added_boolean => true, :added_datetime => Date.yesterday.to_time.in_time_zone, :generate => false
270
+ end
271
+
272
+ it "generates a single event" do
273
+ lambda {
274
+ @spec.generate_events
275
+ }.should change(EventOccurrence, :count).by(1)
276
+ end
277
+
278
+ it "copies base columns from event_specifications to event occurences" do
279
+ @spec.generate_events
280
+ @spec.event_occurrences.first.description.should == @spec.description
281
+ end
282
+
283
+ it "copies added columns from event_specifications to event_occurrences" do
284
+ @spec.generate_events
285
+ @spec.event_occurrences.first.added_string.should == @spec.added_string
286
+ @spec.event_occurrences.first.added_boolean.should == @spec.added_boolean
287
+ @spec.event_occurrences.first.added_datetime.should == @spec.added_datetime
288
+ end
289
+
290
+ it "allows attribute overrides" do
291
+ @spec.generate_events :attributes => { :description => 'something new', :added_string => 'something else new'}
292
+ @spec.event_occurrences.first.description.should == 'something new'
293
+ @spec.event_occurrences.first.added_string.should == 'something else new'
294
+ end
295
+ end
296
+
297
+ describe "recurring events" do
298
+ before(:each) do
299
+ @spec = create_event_specification :description => 'walk the dog', :start_at => @now, :repeat => :daily, :frequency => 1, :generate => false
300
+ end
301
+
302
+ it "generates recurring events according to the rrule" do
303
+ lambda {
304
+ @spec.generate_events :from => @bod, :to => @bod + 1.week
305
+ }.should change(EventOccurrence, :count).by(7)
306
+ end
307
+
308
+ it "does not generate events before the specified :from" do
309
+ lambda {
310
+ @spec.generate_events :from => @bod + 1.day, :to => @bod + 1.week
311
+ }.should change(EventOccurrence, :count).by(6)
312
+ end
313
+
314
+ it "does not generate events after the specified :to" do
315
+ lambda {
316
+ @spec.generate_events :from => @bod + 1.day, :to => @bod + 6.days
317
+ }.should change(EventOccurrence, :count).by(5)
318
+ end
319
+
320
+ it "does not generate more events than the specified :count" do
321
+ lambda {
322
+ @spec.generate_events :from => @bod, :to => @bod + 1.week, :count => 3
323
+ }.should change(EventOccurrence, :count).by(3)
324
+ end
325
+
326
+ it "returns the new events" do
327
+ events = @spec.generate_events :from => @bod, :to => @bod + 1.week
328
+ events.should be_present
329
+ events.first.class.should == EventOccurrence
330
+ end
331
+
332
+ it "returns but does not persist duplicate events" do
333
+ lambda {
334
+ @spec.generate_events :from => @bod, :to => @bod + 1.week
335
+ }.should change(EventOccurrence, :count).by(7)
336
+
337
+ lambda {
338
+ events = @spec.generate_events :from => @bod, :to => @bod + 1.week
339
+ events.should be_present
340
+ events.size.should == 7
341
+ }.should_not change(EventOccurrence, :count)
342
+ end
343
+
344
+ it "raises an exception if the event specification is invalid" do
345
+ spec = new_event_specification(:description => nil)
346
+ spec.should_not be_valid
347
+ lambda {
348
+ spec.generate_events :from => @bod
349
+ }.should raise_error(ActsAsEventOwner::Exception)
350
+ end
351
+
352
+ it "does not generate events for specifications that are past their end_at" do
353
+ @spec.update_attributes! :start_at => @now - 1.week, :until => @now - 2.days
354
+ lambda {
355
+ @spec.generate_events :from => @bod, :to => @bod + 1.week
356
+ }.should_not change(EventOccurrence, :count)
357
+ end
358
+ end
359
+ end
360
+
361
+ describe "autogeneration" do
362
+ before(:each) do
363
+ @now = Time.zone.now
364
+ @bod = Date.today.to_time
365
+ end
366
+
367
+ def create_daily_event(generate=nil)
368
+ create_event_specification :description => 'walk the dog', :start_at => @now, :repeat => :daily, :frequency => 2, :generate => generate
369
+ end
370
+
371
+ it "generates 30 days worth of events by default" do
372
+ lambda {
373
+ create_daily_event
374
+ }.should change(EventOccurrence, :count).by(15)
375
+ end
376
+
377
+ it "does not generate any events if the :generate attribute is set to false" do
378
+ lambda {
379
+ create_daily_event(false)
380
+ }.should_not change(EventOccurrence, :count)
381
+ end
382
+
383
+ it "generates events according the :generate attribute" do
384
+ lambda {
385
+ create_daily_event(:to => @now + 15.days)
386
+ }.should change(EventOccurrence, :count).by(8)
387
+
388
+ lambda {
389
+ create_daily_event(:count => 5)
390
+ }.should change(EventOccurrence, :count).by(5)
391
+ end
392
+ end
393
+
394
+ describe "self.generate_events" do
395
+ before(:each) do
396
+ @now = Time.zone.now
397
+ @bod = Date.today.to_time
398
+ @walking_the_dog = create_event_specification :description => 'walk the dog', :start_at => @now, :repeat => :daily, :frequency => 1, :generate => false
399
+ @taking_out_the_trash = create_event_specification :description => 'take out the trash', :start_at => @now, :repeat => :daily, :frequency => 3, :generate => false
400
+ end
401
+
402
+ it "generates events for all event specifications" do
403
+ lambda {
404
+ ActsAsEventOwner::EventSpecification.generate_events :from => @bod, :to => @bod + 1.week
405
+ }.should change(EventOccurrence, :count).by(10)
406
+ end
407
+ end
408
+
409
+ describe "timezone support" do
410
+ context "repeating events" do
411
+ context "with UTC times" do
412
+ before do
413
+ Time.zone = 'UTC'
414
+ end
415
+
416
+ it "generates the occurrences properly" do
417
+ @now = Time.zone.local(2011, 1, 16, 23, 00) # sunday
418
+ spec = create_event_specification :description => "mwf event", :start_at => @now, :repeat => :weekly, :on => [:mo, :we, :fr]
419
+ occurrence = spec.event_occurrences[1]
420
+ occurrence.start_at.in_time_zone.wday.should == 1
421
+ occurrence.start_at.in_time_zone.hour.should == 23
422
+ end
423
+ end
424
+
425
+ context "with local times" do
426
+ before(:each) do
427
+ Time.zone = 'Pacific Time (US & Canada)'
428
+ end
429
+
430
+ it "generates the occurrences properly" do
431
+ @now = Time.zone.local(2011, 1, 16, 23, 00) # sunday
432
+ spec = create_event_specification :description => "mwf event", :start_at => @now, :repeat => :weekly, :on => [:mo, :we, :fr]
433
+ occurrence = spec.event_occurrences[1].reload
434
+ # puts "*************** #{occurrence.start_at}"
435
+ # puts "*************** #{occurrence.start_at.zone}"
436
+ occurrence.start_at.in_time_zone.wday.should == 1
437
+ occurrence.start_at.in_time_zone.hour.should == 23
438
+ end
439
+ end
440
+
441
+ context "with conflicting time zones" do
442
+ it "generates events with erroneous start_at values" do
443
+ Time.zone = 'Pacific Time (US & Canada)'
444
+ @now = Time.zone.local(2011, 1, 16, 23, 00) # sunday
445
+ spec = create_event_specification :description => "mwf event", :start_at => @now, :repeat => :weekly, :on => [:mo, :we, :fr], :generate => false
446
+ Time.zone = 'Eastern Time (US & Canada)'
447
+ spec.generate_events
448
+ occurrence = spec.event_occurrences[1]
449
+ occurrence.start_at.hour.should_not == 23
450
+ end
451
+ end
452
+ end
453
+ end
454
+ end