schedule_fu 0.2

Sign up to get free protection for your applications and to get access to all the features.
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