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.
- 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
|
+

|
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
|