schedule_fu 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +34 -0
  3. data/Rakefile +40 -0
  4. data/app/models/calendar.rb +26 -0
  5. data/app/models/calendar_date.rb +76 -0
  6. data/app/models/calendar_event.rb +157 -0
  7. data/app/models/calendar_event_date.rb +8 -0
  8. data/app/models/calendar_event_mod.rb +8 -0
  9. data/app/models/calendar_event_type.rb +3 -0
  10. data/app/models/calendar_recurrence.rb +9 -0
  11. data/config/routes.rb +2 -0
  12. data/db/migrate/20120121210741_add_schedule_fu_tables.rb +167 -0
  13. data/lib/schedule_fu.rb +8 -0
  14. data/lib/schedule_fu/calendar_helper.rb +247 -0
  15. data/lib/schedule_fu/engine.rb +5 -0
  16. data/lib/schedule_fu/finder.rb +16 -0
  17. data/lib/schedule_fu/parser.rb +108 -0
  18. data/lib/schedule_fu/schedule_fu_helper.rb +17 -0
  19. data/lib/schedule_fu/version.rb +3 -0
  20. data/lib/tasks/schedule_fu_tasks.rake +4 -0
  21. data/test/calendar_event_test.rb +229 -0
  22. data/test/dummy/README.rdoc +261 -0
  23. data/test/dummy/Rakefile +7 -0
  24. data/test/dummy/app/assets/javascripts/application.js +15 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/test/dummy/app/controllers/application_controller.rb +3 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  29. data/test/dummy/config.ru +4 -0
  30. data/test/dummy/config/application.rb +56 -0
  31. data/test/dummy/config/boot.rb +10 -0
  32. data/test/dummy/config/database.yml +9 -0
  33. data/test/dummy/config/environment.rb +5 -0
  34. data/test/dummy/config/environments/development.rb +37 -0
  35. data/test/dummy/config/environments/production.rb +67 -0
  36. data/test/dummy/config/environments/test.rb +37 -0
  37. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/test/dummy/config/initializers/inflections.rb +15 -0
  39. data/test/dummy/config/initializers/mime_types.rb +5 -0
  40. data/test/dummy/config/initializers/secret_token.rb +7 -0
  41. data/test/dummy/config/initializers/session_store.rb +8 -0
  42. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/test/dummy/config/locales/en.yml +5 -0
  44. data/test/dummy/config/routes.rb +4 -0
  45. data/test/dummy/db/migrate/20120121220057_add_schedule_fu_tables.schedule_fu.rb +168 -0
  46. data/test/dummy/db/schema.rb +82 -0
  47. data/test/dummy/log/development.log +283 -0
  48. data/test/dummy/log/test.log +2224 -0
  49. data/test/dummy/public/404.html +26 -0
  50. data/test/dummy/public/422.html +26 -0
  51. data/test/dummy/public/500.html +25 -0
  52. data/test/dummy/public/favicon.ico +0 -0
  53. data/test/dummy/script/rails +6 -0
  54. data/test/factories/calendar.rb +5 -0
  55. data/test/factories/calendar_event.rb +35 -0
  56. data/test/test_helper.rb +20 -0
  57. metadata +215 -0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Angel N. Sciortino
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,34 @@
1
+ # ScheduleFu
2
+
3
+ ScheduleFu allows scheduling events with dates and times. It includes both the
4
+ model and view portions of a calendar. See
5
+ [RSchedule](http://github.com/angelic/rschedule) for an example application using it.
6
+
7
+ To generate the ScheduleFu migrations, run:
8
+
9
+ rake db:migrate:schedule_fu
10
+
11
+ ### Tables
12
+
13
+ * calendars: distinct calendar that can be associated with your own models
14
+ * calendar_events: distinct event
15
+ * calendar_recurrences: specific information on particular recurring dates
16
+ * calendar_event_mods: modification to a particular calendar_event
17
+ * calendar_event_types: different types of events
18
+ * calendar_dates: has a row for every day and will automatically generate rows for a
19
+ year before or after any date used in an event.
20
+ * calendar_event_dates: a view that has a row for each date included in the event, original or modified information if a column was modified (time, description, etc), plus some additional informational columns
21
+
22
+ ### Informational columns in calendar_event_dates
23
+
24
+ * added: true if this date was added as a mod and not in the original event
25
+ * modified: for dates that are included in the original event but have been modified (time, description, etc)
26
+ * removed: true if this particular date was removed (named scopes :removed and :not_removed available in calendar_event_dates)
27
+
28
+ ### Database diagram
29
+ The database diagram can also be found in docs/database.png along with the original Dia file.
30
+
31
+ ![database.png](http://angelic.github.com/schedule_fu/database.png)
32
+
33
+ This plugin borrows a lot from [acts_as_calendar](http://github.com/dball/acts_as_calendar)
34
+ and [calendar_helper](http://github.com/topfunky/calendar_helper).
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ScheduleFu'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,26 @@
1
+ class Calendar < ActiveRecord::Base
2
+ include ScheduleFu::Finder
3
+
4
+ has_many :events, :class_name=>'CalendarEvent', :dependent => :destroy
5
+
6
+ def max_events_per_day_without_time_set(*args)
7
+ conditions = conditions_for_date_finders(*args)
8
+ conditions[0] << ' AND (calendar_event_dates.start_time IS NULL OR calendar_event_dates.end_time IS NULL) AND calendars.id = ?'
9
+ conditions << self.id
10
+ row = CalendarEvent.count({:joins => [:calendar, :dates],
11
+ :conditions => conditions, :group => 'calendar_date_id',
12
+ :order => 'count_all DESC', :limit => 1})
13
+ row.empty? ? 0 : row.first[1]
14
+ end
15
+
16
+ def to_ical
17
+ ical = Icalendar::Calendar.new
18
+ ical.prodid = 'ScheduleFu'
19
+ ical.name = 'Foo'
20
+ ical.to_ical
21
+ events.each do |event|
22
+ ical.event do
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ require 'set'
2
+ class CalendarDate < ActiveRecord::Base
3
+ extend ScheduleFu::Finder
4
+
5
+ has_many :event_dates, :class_name=>'CalendarEventDate', :readonly => true
6
+ has_many :events, :through => :event_dates
7
+
8
+ validates_presence_of :value
9
+ validates_inclusion_of :weekday, :in => 0..6
10
+ validates_inclusion_of :monthday, :in => 1..31
11
+ validates_inclusion_of :monthweek, :in => 0..4
12
+ validates_inclusion_of :month, :in => 1..12
13
+
14
+ before_validation :derive_date_parts, :on => :create
15
+
16
+ scope :by_dates, lambda {|*args| {:conditions => conditions_for_date_finders(*args)}}
17
+ scope :by_values, lambda{|*args| {:conditions => ["value in (?)", args]}}
18
+
19
+ def self.find_by_value(value)
20
+ find(:first, :conditions => { :value => value })
21
+ end
22
+
23
+ def self.create_for_dates(start_date = nil, end_date = nil)
24
+ start_date ||= Date.today
25
+ end_date ||= 5.years.since(start_date)
26
+ range = start_date..end_date
27
+ existing_dates = Set.new
28
+ self.by_dates(range).each {|d| existing_dates << d.value }
29
+ range.each do |date|
30
+ begin
31
+ self.create(:value => date) unless existing_dates.include?(date)
32
+ rescue; end
33
+ end
34
+ end
35
+
36
+ def self.create_for_date(date)
37
+ self.create_for_dates(date, date)
38
+ end
39
+
40
+ @@create_lock = Mutex.new
41
+
42
+ def self.get_and_create_dates(range)
43
+ range = range.first.to_date..range.last.to_date
44
+ dates = self.by_dates(range)
45
+ if dates.size < range.to_a.size
46
+ CalendarDate.create_for_dates(range.first, range.last)
47
+ Thread.new do
48
+ start_date = 1.year.ago(range.first).to_date
49
+ end_date = 1.year.since(range.last).to_date
50
+ @@create_lock.synchronize do
51
+ CalendarDate.create_for_dates(start_date, end_date)
52
+ end
53
+ end
54
+ dates = self.by_dates(range)
55
+ end
56
+ dates
57
+ end
58
+
59
+ private
60
+
61
+ def derive_date_parts
62
+ self.weekday = value.wday
63
+ self.monthday = value.mday
64
+ self.monthweek = (monthday - 1) / 7
65
+ date = value
66
+ self.month = date.month
67
+ days_until_next_month = 0
68
+ while date = date.next
69
+ days_until_next_month += 1
70
+ break if date.month != self.month
71
+ end
72
+ if days_until_next_month <= 7
73
+ self.lastweek = true
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,157 @@
1
+ class CalendarEvent < ActiveRecord::Base
2
+ belongs_to :calendar
3
+ belongs_to :event_type, :class_name => 'CalendarEventType',
4
+ :foreign_key => :calendar_event_type_id
5
+
6
+ has_many :mods, :class_name => 'CalendarEventMod', :dependent => :destroy
7
+ has_many :recurrences, :class_name=>'CalendarRecurrence', :dependent => :destroy
8
+ has_many :event_dates, :class_name=>'CalendarEventDate', :readonly => true
9
+ has_many :dates, :through => :event_dates, :readonly => true
10
+
11
+ (0..6).each do |n|
12
+ attr_accessor "_repeat_#{n}".to_sym
13
+
14
+ define_method "repeat_#{n}=" do |*args|
15
+ sym = "@_repeat_#{n}".to_sym
16
+ selected = args.first == '1'|| args.first == true
17
+ self.instance_variable_set(sym, selected)
18
+ end
19
+
20
+ define_method "repeat_#{n}" do
21
+ var = self.instance_variable_get("@_repeat_#{n}".to_sym)
22
+ return var unless var.nil?
23
+ return recurrences.find_by_weekday(n) ? true : false
24
+ end
25
+ end
26
+
27
+ validates_presence_of :calendar, :start_date, :calendar_event_type_id
28
+ before_save :create_dates_for_range
29
+ # after_save :add_occurrences
30
+ after_save :add_recurrences
31
+
32
+ scope :with_time_set, :conditions => 'calendar_event_dates.start_time IS NOT NULL AND calendar_event_dates.end_time IS NOT NULL'
33
+ scope :without_time_set, :conditions => 'calendar_event_dates.start_time IS NULL OR calendar_event_dates.end_time IS NULL'
34
+
35
+ # def to_rrules
36
+ # return nil unless recurrences
37
+ # rrules = []
38
+ # weekly = []
39
+ # recurrences.each do |recurrence|
40
+ # if recurrence.monthly?
41
+ # rrules << (params = {'FREQ' => 'MONTHLY'})
42
+ # if !recurrence.weekly?
43
+ # params['BYMONTHDAY'] = recurrence.monthday.to_s
44
+ # else
45
+ # icalmw = ((mw = recurrence.monthweek) >= 0) ? mw + 1 : mw
46
+ # icaldc = Icalendar::DAYCODES[recurrence.weekday]
47
+ # params['BYDAY'] = icalmw.to_s + icaldc
48
+ # end
49
+ # else
50
+ # weekly << recurrence.weekday
51
+ # end
52
+ # end
53
+ # if !weekly.empty?
54
+ # rrules << {'FREQ' => 'WEEKLY',
55
+ # 'BYDAY' => weekly.map {|w| Icalendar::DAYCODES[w]}.join(',')}
56
+ # end
57
+ # rrules
58
+ # end
59
+
60
+ def date_range
61
+ e_date = self.end_date.blank? ? self.start_date : self.end_date
62
+ self.start_date.to_date..e_date.to_date
63
+ end
64
+
65
+ def event_type_matches?(*event_types)
66
+ return false unless event_type
67
+ name = event_type.name.to_sym
68
+ event_types.each {|et| return true if et.to_sym == name }
69
+ return false
70
+ end
71
+
72
+ def any_weekday_selected?
73
+ (0..6).each do |n|
74
+ return true if weekday_selected?(n)
75
+ end
76
+ return false
77
+ end
78
+
79
+ def weekday_selected?(n)
80
+ self.send("repeat_#{n}") == true
81
+ end
82
+
83
+ # creates Calendar Event Mods for new dates by date value
84
+ # takes an array
85
+ def add_dates_by_date_value(date_values)
86
+ transaction do
87
+ date_values.each do |date_value|
88
+ next if date_value.blank?
89
+ begin
90
+ CalendarDate.create_for_date(date_value.to_date)
91
+ event_date = event_dates.first(:conditions => {:date_value => date_value})
92
+ next if event_date && !event_date.removed?
93
+ if event_date
94
+ event_date.mod.destroy
95
+ else
96
+ date_id = CalendarDate.find_by_value(date_value).id
97
+ mods.create(:calendar_date_id => date_id)
98
+ end
99
+ rescue
100
+ errors.add_to_base("Invalid date: #{date_value}")
101
+ raise ActiveRecord::Rollback
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # creates Calendar Event Mods for removed dates by date id
108
+ # takes an array
109
+ def remove_dates_by_id(date_ids)
110
+ date_ids.each do |id|
111
+ next if id.blank?
112
+ event_date = event_dates.first(:conditions => {:calendar_date_id => id})
113
+ next unless event_date && !event_date.removed?
114
+ if event_date.added? || event_date.modified?
115
+ event_date.mod.destroy
116
+ end
117
+ unless event_date.added?
118
+ mods.create(:calendar_date_id => id, :removed => true)
119
+ end
120
+ end
121
+ end
122
+
123
+ protected
124
+ def create_dates_for_range
125
+ CalendarDate.get_and_create_dates(self.date_range)
126
+ end
127
+
128
+ # def add_occurrences
129
+ # if event_type.name == "norepeat"
130
+ # self.recurrences = []
131
+ # self.occurrences = []
132
+ # date_range.each {|d| self.occurrences << CalendarDate.by_values(d) }
133
+ # end
134
+ # end
135
+
136
+ def add_recurrences
137
+ if event_type_matches?(:norepeat, :weekdays, :daily)
138
+ self.recurrences = []
139
+ else
140
+ (0..6).each {|n| self.send("repeat_#{n}=", self.send("repeat_#{n}"))}
141
+ self.recurrences = []
142
+ # self.occurrences = []
143
+ self.recurrences.create(parse_recurrence_params_by_type)
144
+ end
145
+ end
146
+
147
+ def parse_recurrence_params_by_type
148
+ if event_type_matches?(:weekly)
149
+ arr = []
150
+ (0..6).each {|n| arr << {:weekday => n} if weekday_selected?(n) }
151
+ arr
152
+ elsif event_type_matches?(:monthly, :yearly)
153
+ {:monthday => start_date.mday, :weekday => start_date.wday,
154
+ :monthweek => (start_date.mday - 1) / 7, :month => start_date.month}
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,8 @@
1
+ class CalendarEventDate < ActiveRecord::Base
2
+ belongs_to :event, :class_name => 'CalendarEvent', :foreign_key => 'calendar_event_id'
3
+ belongs_to :date, :class_name => 'CalendarDate', :foreign_key => 'calendar_date_id'
4
+ belongs_to :mod, :class_name => 'CalendarEventMod', :foreign_key => 'calendar_event_mod_id'
5
+
6
+ scope :removed, :conditions => {:removed => true}, :order => 'date_value'
7
+ scope :not_removed, :conditions => {:removed => false}, :order => 'date_value'
8
+ end
@@ -0,0 +1,8 @@
1
+ class CalendarEventMod < ActiveRecord::Base
2
+ belongs_to :event, :class_name => 'CalendarEvent', :foreign_key => 'calendar_event_id'
3
+ belongs_to :date, :class_name => 'CalendarDate', :foreign_key => 'calendar_date_id'
4
+ has_one :event_date, :class_name => 'CalendarEventDate', :foreign_key => 'calendar_event_mod_id'
5
+
6
+ validates_presence_of :calendar_event_id, :calendar_date_id
7
+ validates_uniqueness_of :calendar_event_id, :scope => :calendar_date_id
8
+ end
@@ -0,0 +1,3 @@
1
+ class CalendarEventType < ActiveRecord::Base
2
+ has_many :calendar_events
3
+ end
@@ -0,0 +1,9 @@
1
+ class CalendarRecurrence < ActiveRecord::Base
2
+ belongs_to :event, :class_name=>'CalendarEvent', :foreign_key => :calendar_event_id
3
+
4
+ validates_presence_of :event
5
+ validates_inclusion_of :weekday, :in => 0..6, :allow_nil => true
6
+ validates_inclusion_of :monthday, :in => 1..31, :allow_nil => true
7
+ validates_inclusion_of :monthweek, :in => -1..4, :allow_nil => true
8
+ validates_inclusion_of :month, :in => 1..12, :allow_nil => true
9
+ end
@@ -0,0 +1,2 @@
1
+ ScheduleFu::Engine.routes.draw do
2
+ end
@@ -0,0 +1,167 @@
1
+ require 'calendar'
2
+ require 'calendar_event_type'
3
+
4
+ class AddScheduleFuTables < ActiveRecord::Migration
5
+ def self.up
6
+ create_table :calendars do |t|
7
+ t.column :desc, :text
8
+ end
9
+ Calendar.create
10
+
11
+ create_table :calendar_dates do |t|
12
+ t.column :value, :date, :null=>false
13
+ t.column :weekday, :integer, :limit => 1, :null=>false
14
+ t.column :monthweek, :integer, :limit => 1, :null=>false
15
+ t.column :monthday, :integer, :limit => 1, :null=>false
16
+ t.column :month, :integer, :limit => 1, :null=>false
17
+ t.column :lastweek, :boolean, :null=>false, :default=>false
18
+ end
19
+ add_index :calendar_dates, :value, :unique => true
20
+
21
+ create_table :calendar_event_types do |t|
22
+ t.column :name, :string
23
+ t.column :desc, :string
24
+ end
25
+ norepeat_id = create_event_type("norepeat", "Does not repeat").id
26
+ weekdays_id = create_event_type("weekdays", "Weekdays (M-F)").id
27
+ daily_id = create_event_type("daily", "Daily").id
28
+ weekly_id = create_event_type("weekly", "Weekly").id
29
+ monthly_id = create_event_type("monthly", "Monthly").id
30
+ yearly_id = create_event_type("yearly", "Yearly").id
31
+
32
+ create_table :calendar_events do |t|
33
+ t.column :calendar_id, :integer, :null=>false
34
+ t.column :calendar_event_type_id, :integer
35
+ t.column :by_day_of_month, :boolean, :null => false, :default => false
36
+ t.column :start_date, :date
37
+ t.column :end_date, :date
38
+ t.column :start_time, :time
39
+ t.column :end_time, :time
40
+ t.column :desc, :text
41
+ t.column :long_desc, :text
42
+ end
43
+
44
+ create_table :calendar_event_mods do |t|
45
+ t.column :calendar_event_id, :integer, :null=>false
46
+ t.column :calendar_date_id, :integer, :null=>false
47
+ t.column :start_time, :time
48
+ t.column :end_time, :time
49
+ t.column :desc, :text
50
+ t.column :long_desc, :text
51
+ t.column :removed, :boolean, :null=>false, :default=>false
52
+ end
53
+ add_index :calendar_event_mods, [:calendar_event_id, :calendar_date_id],
54
+ :unique => true, :name => "calendar_event_mods_for_event_and_date"
55
+
56
+ create_table :calendar_recurrences do |t|
57
+ t.column :calendar_event_id, :integer, :null=>false
58
+ t.column :weekday, :integer, :limit => 1
59
+ t.column :monthweek, :integer, :limit => 1
60
+ t.column :monthday, :integer, :limit => 1
61
+ t.column :month, :integer, :limit => 1
62
+ end
63
+
64
+ monthly_and_yearly_where_sql = "
65
+ (
66
+ ce.by_day_of_month = false
67
+ AND cr.weekday = cd.weekday
68
+ AND (
69
+ cr.monthweek = cd.monthweek
70
+ OR (
71
+ cr.monthweek = -1
72
+ AND cd.lastweek = true
73
+ )
74
+ )
75
+ ) OR (
76
+ ce.by_day_of_month = true AND cr.monthday = cd.monthday
77
+ )
78
+ "
79
+
80
+ execute <<-END_SQL
81
+ CREATE VIEW calendar_event_dates AS
82
+ SELECT
83
+ ce.id
84
+ AS calendar_event_id,
85
+ cd.id
86
+ AS calendar_date_id,
87
+ cem.id
88
+ AS calendar_event_mod_id,
89
+ cd.value
90
+ AS date_value,
91
+ COALESCE(cem.start_time, ce.start_time)
92
+ AS start_time,
93
+ COALESCE(cem.end_time, ce.end_time)
94
+ AS end_time,
95
+ COALESCE(cem.desc, ce.desc)
96
+ AS 'desc',
97
+ COALESCE(cem.long_desc, ce.long_desc)
98
+ AS long_desc,
99
+ ((ce.calendar_event_type_id IN (4,5,6) AND cr.id IS NULL)
100
+ OR (ce.start_date IS NOT NULL AND cd.value < ce.start_date)
101
+ OR (ce.end_date IS NOT NULL AND cd.value > ce.end_date)
102
+ OR (ce.calendar_event_type_id = #{weekdays_id} AND cd.weekday not in (1,2,3,4,5)))
103
+ AS added,
104
+ (cem.id IS NOT NULL AND cem.removed = true)
105
+ AS removed,
106
+ (cem.id IS NOT NULL AND cem.removed = false AND
107
+ (cem.start_time IS NOT NULL OR cem.end_time IS NOT NULL
108
+ OR cem.desc IS NOT NULL OR cem.long_desc IS NOT NULL))
109
+ AS modified
110
+ FROM calendar_dates cd
111
+ LEFT OUTER JOIN calendar_events ce ON
112
+ (ce.start_date IS NULL OR ce.start_date IS NOT NULL)
113
+ LEFT OUTER JOIN calendar_event_mods cem
114
+ ON cem.calendar_date_id = cd.id
115
+ AND cem.calendar_event_id = ce.id
116
+ LEFT OUTER JOIN calendar_recurrences cr ON cr.calendar_event_id = ce.id
117
+ AND ce.calendar_event_type_id IN (#{weekly_id},#{monthly_id},#{yearly_id})
118
+ WHERE
119
+ (
120
+ cd.id IS NOT NULL OR cem.id IS NOT NULL
121
+ ) AND (
122
+ ((ce.start_date IS NULL OR cd.value >= ce.start_date)
123
+ AND (ce.end_date IS NULL OR cd.value <= ce.end_date))
124
+ OR cem.id IS NOT NULL
125
+ ) AND (
126
+ cem.id IS NOT NULL
127
+ OR (
128
+ ce.calendar_event_type_id = #{norepeat_id}
129
+ AND ce.start_date = cd.value
130
+ ) OR (
131
+ ce.calendar_event_type_id = #{weekdays_id} AND cd.weekday IN (1,2,3,4,5)
132
+ ) OR (
133
+ ce.calendar_event_type_id = #{daily_id}
134
+ ) OR (
135
+ ce.calendar_event_type_id = #{weekly_id}
136
+ AND cr.weekday = cd.weekday
137
+ ) OR (
138
+ ce.calendar_event_type_id = #{monthly_id}
139
+ AND (
140
+ #{monthly_and_yearly_where_sql}
141
+ ) OR (
142
+ ce.calendar_event_type_id = #{yearly_id}
143
+ AND cd.month = cr.month
144
+ AND (
145
+ #{monthly_and_yearly_where_sql}
146
+ )
147
+ )
148
+ )
149
+ );
150
+ END_SQL
151
+ end
152
+
153
+ def self.down
154
+ execute "DROP VIEW calendar_event_dates"
155
+ drop_table :calendar_recurrences
156
+ drop_table :calendar_event_mods
157
+ drop_table :calendar_events
158
+ drop_table :calendar_event_types
159
+ remove_index :calendar_dates, :value
160
+ drop_table :calendar_dates
161
+ drop_table :calendars
162
+ end
163
+
164
+ def self.create_event_type(name, desc)
165
+ CalendarEventType.create(:name => name, :desc => desc)
166
+ end
167
+ end