Last active
August 18, 2018 18:54
-
-
Save tomasc/cb288f83d8254fc08a83db83449d8925 to your computer and use it in GitHub Desktop.
mongoid_recurring_views
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'mongoid' | |
module Mongoid | |
class CreateView < Struct.new(:collection_name, :view_on, :pipeline) | |
def self.call(*args) | |
new(*args).call | |
end | |
def call | |
Mongoid.clients.each do |name, _| | |
client = Mongoid.client(name) | |
unless client.collections.map(&:name).include?(collection_name) | |
client.command(create: collection_name, viewOn: view_on, pipeline: pipeline) | |
end | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class EventPage < Modulor::Page | |
concerning :Views do | |
VIEW_NAME = [EventPage.collection.name, 'view'].join('__').freeze | |
EXPANDED_VIEW_NAME = [EventPage.collection.name, 'expanded_view'].join('__').freeze | |
end | |
concerning :ViewFields do | |
# this page is using two MongoDB views (https://docs.mongodb.com/manual/core/views/) | |
# • `event_pages__view` | |
# • `event_pages__expanded_view` | |
# generated automatically on app boot | |
# to filter events either as grouped (ie one page represents multiple occurrences) | |
# or as expanded (ie page is multiplied according to the number of occurrences it assumes) | |
included do | |
field :_dtstart, type: DateTime | |
field :_dtend, type: DateTime | |
field :_all_day, type: Boolean | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'mongoid/create_view' | |
# EVENTS | |
Mongoid::CreateView.call( | |
::EventPage::EXPANDED_VIEW_NAME, | |
EventPage.collection.name, | |
[ | |
{ '$match': { '_type': EventPage.to_s } }, | |
{ '$addFields': { '_web_modules': '$web_modules' } }, | |
{ '$unwind': '$_web_modules' }, | |
{ '$unwind': '$_web_modules.expanded_occurrences' }, | |
{ '$addFields': { | |
'_dtstart': '$_web_modules.expanded_occurrences.dtstart', | |
'_dtend': '$_web_modules.expanded_occurrences.dtend', | |
'_all_day': '$_web_modules.expanded_occurrences.all_day', | |
'_sort_key': '$_web_modules.expanded_occurrences.dtstart' | |
} | |
} | |
] | |
) | |
Mongoid::CreateView.call( | |
::EventPage::VIEW_NAME, | |
EventPage.collection.name, | |
[ | |
{ '$match': { 'web_modules._type': EventHeaderModule.to_s } }, | |
{ '$addFields': { | |
'_sort_key': { | |
'$ifNull': [ | |
{ '$min': { '$filter': { 'input': { '$arrayElemAt': ['$web_modules.expanded_occurrences.dtstart', 0] }, 'as': 'dtstart', 'cond': { '$gte': ['$$dtstart', 'new Date()'] } } } }, | |
{ '$max': { '$filter': { 'input': { '$arrayElemAt': ['$web_modules.expanded_occurrences.dtstart', 0] }, 'as': 'dtstart', 'cond': { '$lt': ['$$dtstart', 'new Date()'] } } } } | |
] | |
} | |
} | |
} | |
] | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# inherit from this class to create events with multiple occurences | |
# | |
# concerning :Occurences do | |
# included do | |
# # these are user-defined | |
# embeds_many :occurrences, class_name: 'Occurrence', order: :dtstart.asc | |
# accepts_nested_attributes_for :occurrences, allow_destroy: true | |
# | |
# # these are generated | |
# embeds_many :expanded_occurrences, class_name: 'Occurrence', order: :dtstart.asc | |
# before_validation :update_expanded_occurrences | |
# | |
# validates :occurrences, presence: true | |
# validates :expanded_occurrences, presence: true | |
# end | |
# | |
# def recurring? | |
# occurrences.all?(&:recurring?) | |
# end | |
# | |
# private | |
# | |
# def update_expanded_occurrences | |
# self.expanded_occurrences = occurrences.flat_map(&:expand_occurrences) | |
# end | |
# end | |
module Modulor | |
class Occurrence | |
include Mongoid::Document | |
include Modulor::HasCacheKey | |
SCHEDULE_DURATION = 3.months | |
concerning :Fields do | |
included do | |
field :dtstart, type: DateTime | |
field :dtend, type: DateTime | |
field :all_day, type: Boolean, default: false | |
before_validation :set_dtend | |
before_validation :adjust_dates_for_all_day | |
validates :dtstart, presence: true, unless: -> { recurring? && all_day? } | |
validates :dtend, presence: true, unless: -> { recurring? && all_day? } | |
end | |
def <=>(other) | |
sort_key <=> other.sort_key | |
end | |
def sort_key | |
dtstart | |
end | |
def dstart | |
dtstart.to_date | |
end | |
def dend | |
dtend.to_date | |
end | |
private | |
def set_dtend | |
return if dtend.present? | |
self.dtend ||= dtstart | |
end | |
def adjust_dates_for_all_day | |
return unless all_day? | |
self.dtstart = dtstart.beginning_of_day | |
self.dtend = dtend.end_of_day | |
end | |
end | |
concerning :Recurrence do | |
included do | |
field :schedule, type: MongoidIceCubeExtension::Schedule | |
field :schedule_dtend, type: DateTime | |
before_validation :nil_schedule, unless: -> { schedule.present? } | |
def schedule_dtend | |
super || Time.zone.now + SCHEDULE_DURATION | |
end | |
end | |
def recurring? | |
schedule.present? | |
end | |
def recurrence_rule | |
return unless schedule | |
schedule.recurrence_rules.first | |
end | |
def recurrence_rule=(value) | |
case value | |
when NilClass, 'null' | |
@recurrence_rule = nil | |
self.schedule = nil | |
else | |
@recurrence_rule = IceCube::Rule.from_hash(JSON.parse(value)) | |
schedule_start_time = dtstart.try(:in_time_zone) || Time.zone.now | |
self.schedule = IceCube::Schedule.new(schedule_start_time) do |s| | |
s.add_recurrence_rule(@recurrence_rule) | |
end | |
end | |
end | |
def expand_occurrences | |
return unless dtstart | |
return unless dtend | |
return expand_recurring_occurrences if recurring? | |
Range.new(dtstart.to_date, dtend.to_date).each_with_index.map do |date, index| | |
occurence_dtstart = dtstart + index.days | |
occurence_dtend = occurence_dtstart.change(hour: dtend.hour, min: dtend.min, sec: dtend.sec) | |
self.class.new(dtstart: occurence_dtstart, dtend: occurence_dtend, all_day: all_day) | |
end | |
end | |
private | |
def expand_recurring_occurrences | |
schedule.occurrences(schedule_dtend).map do |occurrence| | |
occurrence_dtstart = occurrence.start_time | |
occurrence_dtstart = occurrence_dtstart.change(hour: dtstart.hour, min: dtstart.minute) if dtstart | |
occurrence_dtend = occurrence.end_time | |
occurrence_dtend = occurrence_dtend.change(hour: dtend.hour, min: dtend.minute) if dtend | |
self.class.new(dtstart: occurrence_dtstart, dtend: occurrence_dtend, all_day: all_day) | |
end | |
end | |
def nil_schedule | |
self.schedule = nil | |
end | |
end | |
concerning :Scopes do | |
included do | |
scope :for_datetime, -> (datetime) { | |
lte(dtstart: datetime).gte(dtend: datetime) | |
} | |
scope :for_datetime_range, ->(dtstart, dtend) { | |
dtstart = dtstart.beginning_of_day if dtstart.instance_of?(Date) | |
dtstart = dtstart.utc | |
dtend = dtend.end_of_day if dtend.instance_of?(Date) | |
dtend = dtend.utc | |
lte(dtstart: dtend.to_datetime).gte(dtend: dtstart.to_datetime) | |
} | |
scope :from_datetime, ->(dtstart) { | |
dtstart = dtstart.beginning_of_day if dtstart.instance_of?(Date) | |
dtstart = dtstart.utc | |
gte(dtstart: dtstart.to_datetime) | |
} | |
scope :to_datetime, ->(dtend) { | |
dtend = dtend.end_of_day if dtend.instance_of?(Date) | |
dtend = dtend.utc | |
lte(dtend: dtend.to_datetime) | |
} | |
scope :now, -> { for_datetime(Time.zone.now) } | |
scope :next, -> { from_datetime(Time.zone.now.beginning_of_day) } | |
scope :past, -> { to_datetime(Time.zone.now.beginning_of_day) } | |
scope :order_by_dtstart, -> (order = :asc) { order(dtstart: order) } | |
scope :order_by_dtend, -> (order = :asc) { order(dtend: order) } | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- EventPage.with(collection: ::EventPage::VIEW_NAME) do | |
= EventPage.criteria.… | |
- EventPage.with(collection: ::EventPage::EXPANDED_VIEW_NAME) do | |
= EventPage.criteria.… |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment