shcedule db design

# This is only set for availability
t.integer day_id, # if none means set a temp hour, values from [null, 1,2,3,4,5,6]

# This is only set for none availability
t.integer :state, default: 0, null: false # available or unavailable, for with day_id is available

# When they set their end time it might possibly be on depending on their timezone.
#
# For a one off notification the start_date and end_date will be a UTC date with the exact date and time they will be
# available or unavailable.
# with a day_id time start from 1970-01-03 ~ 1970-01-10, 3 left for the time offset day
t.datetime :start_date, null: false
t.datetime :end_date, null: false

query

scope :available_for_notification, -> {
  now = Time.zone.now.utc
  week_with_day = 4 + now.wday
  data = {
    available: TutorNotificationHour.states[:available],
    unavailable: TutorNotificationHour.states[:unavailable],
    now: now,
    now_recurring: now.change(year: 1970, month: 1, day: week_with_day),
    end_week_day_recurring: now.change(year: 1970, month: 1, day: 3),
    wday: now.wday
  }

  find_by_sql [
    "SELECT DISTINCT tutors.* FROM tutors
    INNER JOIN tutor_notification_hours AS available_period ON available_period.tutor_id = tutors.id
    LEFT OUTER JOIN tutor_notification_hours AS unavailable_period ON unavailable_period.tutor_id = tutors.id
      AND unavailable_period.state = :unavailable AND :now between unavailable_period.start_date and unavailable_period.end_date
    WHERE (
      (:now_recurring between available_period.start_date and available_period.end_date)
        OR (:wday = 6 AND :end_week_day_recurring between available_period.start_date and available_period.end_date)
          OR (:now between available_period.start_date and available_period.end_date AND available_period.state = :available)
    )
    AND unavailable_period.id IS NULL",
    data
  ]
}

colorfulberry

衣服不破不丢,奋斗不死不休!