################## # This code sample contains the logic for a page used by arts festival attendees to filter # the festival's events and find a show they're interested in seeing. # You can see the event list in action here: http://pghkids.org/events ################## ################## # FestivityEventsController, which receives a request with search criteria and returns # a list of matching events to the view. ################## class FestivityEventsController < ApplicationController include Festivity::Mixins::NotFound no_login_required trusty_layout 'base' # Event search requests are cached; because some requests are for the full page and some are AJAX # caching separates requests by format. caches_action :index, cache_path: proc { |c| c.params.except(:_).merge(format: request.xhr?)} def index # Default sort is by event start date order_by = params[:sort] ? params[:sort] : "start_date" @title = "#{current_site.festivity_festival_name}: Events" # Pass search criteria to the FestivityEventList model to find relevant events @events = FestivityEventList.search( {dates: search_dates.join(","), categories: params[:categories]}, order_by).events # If the request is AJAX, only return the event list itself, not the full page if request.xhr? render partial: "event_list" else render 'index' end end end ################## # FestivityEventList model, used to collect all matching events ################## class FestivityEventList attr_reader :events def initialize(event_performances) @events = event_performances.group_by {|perf| perf.event_id }. map { |perfs| FestivityEventList::FestivityEvent.new(perfs[0], perfs[1]) } end def self.search(criteria, order_by) # Build the SQL where clause from the supplied criteria where_clause = parse_criteria(criteria) # Search the FestivityEventPerformance view and build a new FestivityEventList object with the results FestivityEventList.new( FestivityEventList::FestivityEventPerformance. includes(:assets). joins(:festivity_categories). where(where_clause). group("performance_id"). order("featured_item DESC, #{order_by} ASC"). preload(:festivity_categories) ) end private # The order of querying, depending on what is passed: # - If dates are passed, we search both start and end date between midnight and 11:59pm of that date. # That query returns any matching event ids. # - The event ids returned, if any, are added to the where clause for the next query # - Any category ids passed are added to the where clause as well. def self.parse_criteria(criteria) where_clause = {} event_ids = event_ids_for_dates(criteria[:dates]) if criteria[:dates] where_clause["site_id"] = Page.current_site.id where_clause["event_id"] = event_ids if event_ids where_clause["festivity_categories.id"] = criteria[:categories].split(",") if criteria[:categories] where_clause end # Return a list of unique event ids that match the provided dates def self.event_ids_for_dates(dates) FestivityEventList::FestivityEventPerformance.where(date_criteria(dates)).map {|e| e.event_id}.uniq end # Create a condition for start and end date between midnight and 11:59pm # for each date passed in and return the SQL condition def self.date_criteria(dates_string) date_queries = dates_string.split(',').map do |date_string| start_date = DateTime.parse(date_string) end_date = start_date.advance(hours: 23, minutes: 59) <<-SQL ( (start_date >= '#{start_date}' AND start_date <= '#{end_date}') OR (end_date >= '#{start_date}' AND end_date <= '#{end_date}') ) SQL end date_queries.join(" OR ") end end ################## # FestivityEventPerformance model, tied to a database view which collects event performance data ################## class FestivityEventList::FestivityEventPerformance < ActiveRecord::Base self.table_name = 'festivity_event_performances' after_initialize :readonly! has_many :festivity_page_categories, foreign_key: :page_id, primary_key: :event_id has_many :festivity_categories, through: :festivity_page_categories has_many :page_attachments, primary_key: :event_id, foreign_key: :page_id has_many :assets, through: :page_attachments end ################## # FestivityEvent model representing events which matched the search criteria and wrapping matching performances ################## class FestivityEventList::FestivityEvent include Festivity::Admin::AssetsHelper attr_reader :id, :performances, :locations, :categories, :title, :short_description, :assets, :header, :sub_header, :featured_item, :buy_url def initialize(event_id, performances) @id = event_id @performances = performances # For event-level information, like title, just use the first event performance @title = performances.first.event_title @short_description = performances.first.short_description @header = performances.first.header @sub_header = performances.first.sub_header @featured_item = performances.first.featured_item @buy_url = performances.first.buy_url @locations = self.performances. map{ |performance| FestivityEventList::FestivityLocation.new ({ id: performance.location_id, slug: performance.location_slug, title: performance.location_title, directions_url: performance.festivity_directions_url, area_id: performance.area_id, area_slug: performance.area_slug, area_title: performance.area_title}) }. uniq{ |location| location.id } @categories = performances.first.festivity_categories @assets = performances.first.assets end end ################## # Event List view, demonstrating how matched events are displayed in the browser ################## #event-list-items - @events.each do |event| .row.event-list-item{class: event.id, data:{ genre: event.categories.first.id, date: event.performances.first.start_date, location: event.locations.first.id} } %hr .event-list-item__photo.col-xs-12.col-sm-4 .photo = link_to event_path(event.id) do %img.img-responsive{ src: "#{event.image}"} - if event.featured_item .event_list-item__photo-featured-item Featured Event! .event-list-item__info.col-xs-12.col-sm-8 = link_to event_path(event.id) do %h2 = event.title %h3 %span.strong = event.header %span.light = event.sub_header %hr %p - if event.locations.count == 1 - event_location = event.locations.first =link_to location_path(id: event_location.slug) do = event_location.title @ =link_to area_path(id: event_location.area_slug) do = event_location.area_title - else Multiple Locations - if event.performances.count > 1 %p Multiple dates and times  %button.btn.btn-sm.btn-default.btn-popover{type: "button", data: {content: date_time_popover(event.performances), html: "true", placement:"top", toggle: "popover"}, title:"All Dates and Times"} Show all - else - event.performances.each do |perf| %p = perf.start_date.strftime("%A, %B %d") = ", " = perf.start_date.strftime("%I:%M%p").downcase = " - " = perf.end_date.strftime('%I:%M%p').downcase %hr %p .event-list-item__info_short_description %span = event.short_description.html_safe %p .event-list-item__button-group = link_to "Details", event_path(event.id), class: 'btn event-list-item__btn' - unless event.buy_url.blank? = link_to "Tickets", "#{event.buy_url}", class: 'btn event-list-item__btn', target: '_blank' - unless event.locations.first.directions_url.blank? = link_to "Directions", "#{event.locations.first.directions_url}", class: 'btn event-list-item__btn', target: '_blank'