schedule_fu 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +34 -0
- data/Rakefile +40 -0
- data/app/models/calendar.rb +26 -0
- data/app/models/calendar_date.rb +76 -0
- data/app/models/calendar_event.rb +157 -0
- data/app/models/calendar_event_date.rb +8 -0
- data/app/models/calendar_event_mod.rb +8 -0
- data/app/models/calendar_event_type.rb +3 -0
- data/app/models/calendar_recurrence.rb +9 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20120121210741_add_schedule_fu_tables.rb +167 -0
- data/lib/schedule_fu.rb +8 -0
- data/lib/schedule_fu/calendar_helper.rb +247 -0
- data/lib/schedule_fu/engine.rb +5 -0
- data/lib/schedule_fu/finder.rb +16 -0
- data/lib/schedule_fu/parser.rb +108 -0
- data/lib/schedule_fu/schedule_fu_helper.rb +17 -0
- data/lib/schedule_fu/version.rb +3 -0
- data/lib/tasks/schedule_fu_tasks.rake +4 -0
- data/test/calendar_event_test.rb +229 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +9 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/db/migrate/20120121220057_add_schedule_fu_tables.schedule_fu.rb +168 -0
- data/test/dummy/db/schema.rb +82 -0
- data/test/dummy/log/development.log +283 -0
- data/test/dummy/log/test.log +2224 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/factories/calendar.rb +5 -0
- data/test/factories/calendar_event.rb +35 -0
- data/test/test_helper.rb +20 -0
- metadata +215 -0
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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).
|
data/Rakefile
ADDED
@@ -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,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
|
data/config/routes.rb
ADDED
@@ -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
|