app/models/seatmap.rb
class Seatmap < ActiveRecord::Base
has_many :showdates
require 'uri'
serialize :zones, Hash
class AsJson
attr_reader :seatmap
attr_accessor :seats, :selected, :unavailable, :holdback
def initialize(seatmap)
# initialize to empty
@seatmap = seatmap
@seats = {}
@selected = []
@unavailable = []
@holdback = []
end
def self.empty
{'map' => [], 'seats' => {}, 'unavailable' => [], 'rows' => 0, 'columns' => 0}.to_json
end
# Return JSON object with fields 'map' (JSON representation of actual seatmap),
# 'seats' (types of seats to display), 'image_url' (background image)
def emit_json
_seatmap = seatmap.json
image_url = seatmap.image_url.to_json
# if seatmap has only one zone, hide zone name during seat selection
hide_zone_name = (!!(seatmap.zones.keys.length == 1)).to_json
# since the 'unavailable' and 'selected' values are used by the actual
# seatmap JS code to identify seats, labels must include the full seating zone display name.
_unavailable = @unavailable.compact.map { |num| seatmap.hover_label_with_zone(num) }.to_json
_selected = @selected.compact.map { |num| seatmap.hover_label_with_zone(num) }.to_json
_holdback = @holdback.compact.map { |num| seatmap.hover_label_with_zone(num) }.to_json
# seat classes: 'r' = regular, 'a' = accessible
seats = {'r' => {'classes' => 'regular'}, 'a' => {'classes' => 'accessible'}}.to_json
%Q{ {"map": #{_seatmap},
"rows": #{seatmap.rows},
"columns": #{seatmap.columns},
"seats": #{seats},
"unavailable": #{_unavailable},
"selected": #{_selected},
"holdback": #{_holdback},
"hideZoneName": #{hide_zone_name},
"image_url": #{image_url} }}
end
end
validates :name, :presence => true, :uniqueness => true
validates :csv, :presence => true
validates_format_of :image_url, :with => URI.regexp, :allow_blank => true
validate :valid_csv, :if => :new_record?
private
def valid_csv
Seatmap::Parser.new(self).parse_csv
end
public
# Return JSON object with fields 'map' (JSON representation of actual seatmap),
# 'seats' (types of seats to display), 'image_url' (background image),
# 'unavailable' (list of unavailable seats for a given showdate)
def self.seatmap_and_unavailable_seats_as_json(showdate, restrict_to_zone: nil, selected: [], is_boxoffice: false)
return Seatmap::AsJson.empty unless (sm = showdate.seatmap)
map = Seatmap::AsJson.new(sm)
map.selected = selected
map.holdback = showdate.house_seats.sort
# if any preselected seats, show them as selected not occupied
occupied = showdate.occupied_seats - selected
occupied += sm.excluded_from_zone(restrict_to_zone) unless restrict_to_zone.blank?
# remove any seats that are held back by boxoffice
occupied += map.holdback unless is_boxoffice
map.unavailable = occupied.sort.uniq
map.emit_json
end
# As above, but ignore any notion of occupied or house seats - just return seatmap showing
# all seats as available
def self.raw_seatmap_as_json(seatmap)
map = Seatmap::AsJson.new(seatmap)
map.selected = map.holdback = map_unavailable = []
map.emit_json
end
# As above, but show House Seats as 'selected', reserved
# seats as 'unavailable', and unreserved seats as 'available'.
# A seat that is BOTH a house seat and occupied just shows as 'unavailable'.
def self.house_seats_seatmap_as_json(showdate)
map = Seatmap::AsJson.new(showdate.seatmap)
map.selected = showdate.open_house_seats
map.unavailable = showdate.occupied_seats
map.emit_json
end
# Return JSON hash of ids to seat counts
def self.capacities_as_json
Hash[Seatmap.all.map { |s| [s.id.to_s, s.seat_count.to_s] }].to_json
end
# How many seats? (includes accessible)
def seat_count
@seat_count ||= seat_list.split(/\s*,\s*/).size
end
def name_with_capacity
"#{name} (#{seat_count})"
end
# Enumerate the associated zones, as SeatingZone instances
def seating_zones
self.zones.keys.map { |short_name| SeatingZone.find_by!(:short_name => short_name) }
end
# Enumerate the seat numbers in a particular zone
def seats_in_zone(zone)
self.zones[zone.short_name]
end
# Seats excluded from a zone (ie, any seats NOT in that zone
def excluded_from_zone(restrict_to_zone)
excluded = []
self.zones.each_pair do |shortname, seats|
excluded += seats unless shortname == restrict_to_zone
end
excluded.sort
end
# To which zone does a seat belong?
def zone_displayed_for(seat)
key = self.zones.keys.detect { |k| zones[k].include?(seat) }
SeatingZone.find_by!(:short_name => key).name
end
# hover label including zone
def hover_label_with_zone(seat)
"#{self.zone_displayed_for(seat)}-#{seat}"
end
# Does this seatmap reference a particular zone or not ?
def references_zone?(zone)
csv =~ /\b#{zone.short_name}:/
end
# Given a collection of vouchers, some of which may have seat numbers, return the subset
# that COULD NOT be accommodated by this seatmap. Used to determine if it's possible to
# change a seatmap for a performance after sales have begun.
def cannot_accommodate(vouchers)
seats = self.seat_list.split(/\s*,\s*/)
vouchers.select do |v|
! v.seat.blank? && ! seats.include?(v.seat)
end
end
# seatmap editor/parser stuff
def includes_seat?(seat)
seat_list.match Regexp.new("\\b#{seat}\\b")
end
def update!
Seatmap::Parser.new(self).parse_csv
save!
end
end