Skip to content

Instantly share code, notes, and snippets.

@sipple
Last active August 29, 2015 14:22
Show Gist options
  • Save sipple/c388e163a27fb7c8f0f9 to your computer and use it in GitHub Desktop.
Save sipple/c388e163a27fb7c8f0f9 to your computer and use it in GitHub Desktop.
Code Sample - Festivity Events List
##################
# 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&nbsp;
%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'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment