Skip to content
This repository has been archived by the owner on Nov 29, 2017. It is now read-only.

Commit

Permalink
Merge pull request #458 from YaleSTC/446_make_future
Browse files Browse the repository at this point in the history
446 make future
  • Loading branch information
njlxyaoxinwei committed Apr 7, 2015
2 parents 1753799 + 99063c8 commit 0182659
Show file tree
Hide file tree
Showing 18 changed files with 500 additions and 338 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ gem 'simple_form' # replaces multiple_select
# replace ActiveSupport::Memoizable
gem 'memoist'

gem 'activerecord-import'

group :development, :test do
gem 'rspec-rails', '~> 3.0.0'
gem 'factory_girl_rails'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ GEM
activesupport (= 3.2.21)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activerecord-import (0.7.0)
activerecord (>= 3.0)
activeresource (3.2.21)
activemodel (= 3.2.21)
activesupport (= 3.2.21)
Expand Down Expand Up @@ -307,6 +309,7 @@ PLATFORMS
ruby

DEPENDENCIES
activerecord-import
annotate
authlogic
better_errors
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/repeating_events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def create
raise @failed if @failed
end
respond_to do |format|
format.html {flash[:notice] = "Successfully created repeating event."; flash[:notice] += " Please note that some events were not created because they started in the past."; redirect_to @repeating_event}
format.html {flash[:notice] = "Successfully created repeating event."; flash[:notice] += " Please note that some events were not created because they started in the past." if warn; redirect_to @repeating_event}
format.js
end
rescue Exception => e
Expand Down Expand Up @@ -87,7 +87,7 @@ def update
respond_to do |format|
format.html {
flash[:notice] = "Successfully edited repeating event."
flash[:notice] += " Please note that some events were not created because they started in the past."
flash[:notice] += " Please note that some events were not created because they started in the past." if warn
redirect_to @repeating_event
}
format.js
Expand Down
8 changes: 4 additions & 4 deletions app/models/calendar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ def deactivate
def activate(wipe)
self.active = true
conditions = ["calendar_id = ? AND start > ?", self.id, Time.now.utc]
conflicts = Shift.check_for_conflicts(Shift.where(conditions), wipe) +
TimeSlot.check_for_conflicts(TimeSlot.where(conditions), wipe)
if conflicts.empty?
s_conflicts = Shift.check_for_conflicts(Shift.where(conditions), wipe, Shift.active)
ts_conflicts = TimeSlot.check_for_conflicts(TimeSlot.where(conditions), wipe, TimeSlot.active)
if s_conflicts.empty? && ts_conflicts.empty?
TimeSlot.where(conditions).update_all(active: true)
Shift.where(conditions).update_all(active: true)
self.save
return false
else
return conflicts
return s_conflicts+','+ts_conflicts
end
end

Expand Down
28 changes: 18 additions & 10 deletions app/models/repeating_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,24 @@ def self.destroy_self_and_future(repeating_event, time=false)

def make_future(wipe)
if self.has_time_slots?
TimeSlot.make_future(self.end_date, self.calendar.id, self.id, self.days_int, self.location_ids, self.start_time, self.end_time, self.calendar.active, wipe)
TimeSlot.make_future(self, wipe)
else
Shift.make_future(self.end_date, self.calendar.id, self.id, self.days_int, self.location_ids.first, self.start_time, self.end_time, self.user_id, Location.find(self.location_ids.first).loc_group.department.id, self.calendar.active, wipe)
Shift.make_future(self, self.locations.first,wipe)
end
end

# returns array of all dates with repeating events
def dates_array
start_date = self.start_date.to_date
end_date = self.end_date.to_date
array = Array.new
return array if end_date<start_date
(start_date..end_date).each do |i|
array << i if self.days_int.include? i.wday
end
array
end

def days
self.days_of_week.split(",").collect{|d| d.to_i.day_of_week} if self.days_of_week
end
Expand Down Expand Up @@ -164,8 +176,6 @@ def start_date_less_than_end_date
def set_start_times
self.start_time = self.start_date.to_time + self.start_time.seconds_since_midnight
self.end_time = self.start_date.to_time + self.end_time.seconds_since_midnight
#self.start_time.change(day: self.start_date.day, month: self.start_date.month, year: self.start_date.year)
# self.end_time = self.end_time.change(day: self.start_date.day, month: self.start_date.month, year: self.start_date.year)
end

def adjust_for_multi_day
Expand All @@ -174,18 +184,16 @@ def adjust_for_multi_day

def is_within_calendar
unless self.calendar.default
errors.add(:base, "Repeating event start and end dates must be within the range of the calendar.") if self.start_date < self.calendar.start_date || self.end_date > self.calendar.end_date
errors.add(:base, "Repeating event start and end dates must be within the range of the calendar.") if self.start_date.to_date < self.calendar.start_date.to_date || self.end_date.to_date > self.calendar.end_date.to_date
end
end

def adjust_for_past_event
if self.start_time <= Time.now
self.start_date = self.start_date.change(day: Date.today.day, month: Date.today.month, year: Date.today.year)
duration = self.end_time - self.start_time
self.start_time = self.start_time.change(day: Date.today.day, month: Date.today.month, year: Date.today.year)
self.end_time = self.end_time.change(day: Date.today.day, month: Date.today.month, year: Date.today.year)
end
if self.start_time <= Time.now
self.start_time = self.start_time.change(day: Date.tomorrow.day, month: Date.tomorrow.month, year: Date.tomorrow.year)
self.end_time = self.end_time.change(day: Date.tomorrow.day, month: Date.tomorrow.month, year: Date.tomorrow.year)
self.end_time = self.start_time + duration
end
end

Expand Down
132 changes: 50 additions & 82 deletions app/models/shift.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,97 +117,65 @@ def self.mass_delete_with_dependencies(shifts_to_erase)
end



#This method creates the multitude of shifts required for repeating_events to work
#in order to work efficiently, it makes a few GIANT sql insert calls -mike
def self.make_future(end_date, cal_id, r_e_id, days, loc_id, start_time, end_time, user_id, department_id, active, wipe)
#We need several inner arrays with one big outer one, b/c sqlite freaks out
#if the sql insert call is too big. The "make" arrays are then used for making
#the shifts, and the "test" for finding conflicts.
outer_make = []
inner_make = []
outer_test = []
inner_test = []
diff = end_time - start_time
#Take each day and build an arrays containing the pieces of the sql queries
days.each do |day|
seed_start_time = (start_time.wday == day ? start_time : start_time.next(day))
seed_end_time = seed_start_time+diff
while seed_end_time <= (end_date + 1.day)
if active
inner_test.push "(user_id = #{user_id} AND active = #{true} AND department_id = #{department_id} AND start <= '#{seed_end_time.utc}' AND end >= '#{seed_start_time.utc}')"
else
inner_test.push "(user_id = #{user_id} AND calendar_id = #{cal_id} AND department_id = #{department_id} AND start <= '#{seed_end_time.utc}' AND end >= '#{seed_start_time.utc}')"
end
inner_make.push "#{loc_id}, #{cal_id}, #{r_e_id}, '#{seed_start_time.utc}', '#{seed_end_time.utc}', '#{Time.now.utc}', '#{Time.now.utc}', #{user_id}, #{department_id}, #{active}"
#Once the array becomes big enough that the sql call will insert 450 rows, start over w/ a new array
#without this bit, sqlite freaks out if you are inserting a larger number of rows. Might need to be changed
#for other databases (it can probably be higher for other ones I think, which would result in faster execution)
if inner_make.length > 450
outer_make.push inner_make
inner_make = []
outer_test.push inner_test
inner_test = []
end
seed_start_time = seed_start_time.next(day)
seed_end_time = seed_start_time + diff
end
#handle leftovers or the case where there are less than 450 rows to be inserted
end
outer_make.push inner_make unless inner_make.empty?
outer_test.push inner_test unless inner_test.empty?
#Look for conflicts, delete them if wipe is on, and either complain about
#conflicts or make the new shifts
if wipe
outer_test.each do |sh|
Shift.mass_delete_with_dependencies(Shift.where(sh.join(" OR ")))
end
outer_make.each do |s|
sql = "INSERT INTO shifts (location_id , calendar_id , repeating_event_id , start , end , created_at , updated_at , user_id , department_id , active ) SELECT #{s.join(" UNION ALL SELECT ")};"
ActiveRecord::Base.connection.execute sql
end
return false
def self.make_future(event, location, wipe)
cal = event.calendar
if cal.active
shift_scope = Shift.active
else
out = []
outer_test.each do |s|
out += Shift.where(s.join(" OR "))
end
if out.empty?
outer_make.each do |s|
sql = "INSERT INTO shifts (location_id , calendar_id , repeating_event_id , start , end , created_at , updated_at , user_id , department_id , active ) SELECT #{s.join(" UNION ALL SELECT ")};"
ActiveRecord::Base.connection.execute sql
end
shift_scope = Shift.where(calendar_id: cal.id)
end
dept = location.department
user_id = event.user_id
dates = event.dates_array
table = Shift.arel_table
shifts_all = Array.new
duration = event.end_time - event.start_time
conflict_all = nil
dates.each do |date|
start_time_on_date = date.to_time + event.start_time.seconds_since_midnight
end_time_on_date = start_time_on_date + duration
shifts_all << Shift.new(power_signed_up: true, repeating_event_id: event.id, location_id: location.id, calendar_id: cal.id, start: start_time_on_date, end: end_time_on_date, user_id: user_id, department_id: dept.id, active: cal.active)
end
conflict_msg = Shift.check_for_conflicts(shifts_all, wipe, shift_scope)
if conflict_msg.empty?
if shifts_all.map(&:valid?).all?
Shift.import shifts_all
return false
else
invalid_shifts = shifts_all.select{|s| !s.valid?}
return invalid_shifts.map{|s| "#{s.to_message_name}: #{s.errors.full_messages.join('; ')}"}.join('. ')
end
return out.collect{|t| "The shift for "+t.to_message_name+" conflicts. Use wipe to fix."}.join(",")
else
return conflict_msg + " have conflict. Use wipe to fix."
end
end


#Used for activating calendars, check/wipe conflicts -Mike
def self.check_for_conflicts(shifts, wipe)
#big_array is just an array of arrays, the inner arrays being less than 450
#elements so sql doesn't freak
big_array = shifts.batch(450)
if big_array.empty?
""
elsif wipe
big_array.each do |sh|
Shift.mass_delete_with_dependencies(Shift.where(sh.collect{|s| "(user_id = #{s.user_id} AND active = #{true} AND department_id = #{s.department_id} AND start < \"#{s.end.utc}\" AND end > \"#{s.start.utc})\")"}.join(" OR ")))
end
return ""
else
out=big_array.collect do |sh|
Shift.where(sh.collect{|s| "(user_id = #{s.user_id} AND active = #{true} AND department_id = #{s.department_id} AND start < \"#{s.end.utc}\" AND end > \"#{s.start.utc}\")"}.join(" OR ")).collect{|t| "The shift for "+t.to_message_name+"."}.join(",")
end
if out.collect(&:empty?).all?
return ""
else
return out.join(",")+","
def self.check_for_conflicts(shifts, wipe, shift_scope)
return "" if shifts.empty?
table = Shift.arel_table
shifts_with_conflict = Array.new
shifts.each_slice(450) do |shs|
conflict_all = nil
shs.each do |s|
conflict_condition = table[:user_id].eq(s.user_id).and(table[:department_id].eq(s.department_id)).and(table[:start].lt(s.end)).and(table[:end].gt(s.start))
if conflict_all.nil?
conflict_all = conflict_condition
else
conflict_all = conflict_all.or(conflict_condition)
end
end
shifts_with_conflict = shifts_with_conflict + shift_scope.where(conflict_all)
shifts_with_conflict.uniq!
end
if wipe
Shift.mass_delete_with_dependencies(shifts_with_conflict)
elsif !shifts_with_conflict.empty?
return shifts_with_conflict.map{|s| "The shift for #{s.to_message_name}."}.join(',')
end
return ""
end


# ==================
# = Object methods =
# ==================
Expand Down Expand Up @@ -572,7 +540,7 @@ def set_active

def is_within_calendar
unless self.calendar.default
errors.add(:base, "Shift start and end dates must be within the range of the calendar.") if self.start < self.calendar.start_date || self.end > self.calendar.end_date
errors.add(:base, "Shift start and end dates must be within the range of the calendar.") if self.start.to_date < self.calendar.start_date.to_date || self.end.to_date > self.calendar.end_date.to_date
end
end

Expand Down
Loading

0 comments on commit 0182659

Please sign in to comment.