Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 90 additions & 32 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,46 +72,104 @@ def set_event
end

def fetch_upcoming_events
events = [
Workshop.includes(:chapter, :sponsors, :host, :permissions, :organisers)
.upcoming
.joins(:chapter)
.merge(Chapter.active)
]
events << Meeting.upcoming.all
events << Event.upcoming.includes(:venue, :sponsors, :sponsorships, :permissions, :organisers).all

sorted = events.compact.flatten.sort_by(&:date_and_time)
return [{}, nil] if sorted.empty?

pagy, paginated = pagy(sorted, items: 20)
paginated ||= []

grouped = paginated.group_by(&:date)
result = paginated_events(upcoming: true)
return [{}, nil] if result.nil?

rows, pagy = result
events = load_events(rows)
grouped = events.group_by(&:date)
decorated = grouped.transform_values { |items| EventPresenter.decorate_collection(items) }

[decorated, pagy]
end

def fetch_past_events
events = [
Workshop.includes(:chapter, :sponsors, :host, :permissions, :organisers)
.past
.joins(:chapter)
.merge(Chapter.active)
]
events << Meeting.past.all
events << Event.past.includes(:venue, :sponsors, :sponsorships, :permissions, :organisers).all

sorted = events.compact.flatten.sort_by(&:date_and_time).reverse
return [{}, nil] if sorted.empty?

pagy, paginated = pagy(sorted, items: 20)
paginated ||= []

grouped = paginated.group_by(&:date)
result = paginated_events(upcoming: false)
return [{}, nil] if result.nil?

rows, pagy = result
events = load_events(rows)
grouped = events.group_by(&:date)
decorated = grouped.transform_values { |items| EventPresenter.decorate_collection(items) }

[decorated, pagy]
end

# Builds a UNION ALL query across Workshops, Meetings, and Events,
# paginates at the database level, and returns (id, event_type) pairs
# for the current page. Only the 20 visible rows come back from the DB.
def paginated_events(upcoming:)
now = Time.zone.now
page = (params[:page] || 1).to_i
direction = upcoming ? "ASC" : "DESC"
comparator = upcoming ? :gteq : :lt

# Pure Arel subqueries — no ActiveRecord relation bind params to leak
w = Arel::Table.new(:workshops)
ch = Arel::Table.new(:chapters)
ws = Arel::SelectManager.new(w)
ws.project(w[:id], w[:date_and_time], Arel.sql("'Workshop' AS event_type"))
ws.join(ch).on(w[:chapter_id].eq(ch[:id]))
ws.where(ch[:active].eq(true).and(w[:date_and_time].public_send(comparator, now)))

m = Arel::Table.new(:meetings)
ms = Arel::SelectManager.new(m)
ms.project(m[:id], m[:date_and_time], Arel.sql("'Meeting' AS event_type"))
ms.where(m[:date_and_time].public_send(comparator, now))

e = Arel::Table.new(:events)
es = Arel::SelectManager.new(e)
es.project(e[:id], e[:date_and_time], Arel.sql("'Event' AS event_type"))
es.where(e[:date_and_time].public_send(comparator, now))

union = Arel::Nodes::UnionAll.new(
Arel::Nodes::UnionAll.new(ws, ms), es
)

# COUNT at DB level — single query
count_query = Arel::SelectManager.new
count_query.from(union.as("events"))
count_query.project(Arel.star.count)
total = ActiveRecord::Base.connection.select_value(count_query.to_sql).to_i
return nil if total.zero?

pagy_opts = { count: total, page: page, limit: 20, request: request }
pagy_opts[:request] = Pagy::Request.new(pagy_opts)
pagy = Pagy::Offset.new(**pagy_opts)

# Only 20 rows leave the database
pagination_query = Arel::SelectManager.new
pagination_query.from(union.as("events"))
pagination_query.project(:id, :event_type)
pagination_query.order(Arel.sql("date_and_time #{direction}"))
pagination_query.skip(pagy.offset).take(pagy.limit)

rows = ActiveRecord::Base.connection.select_all(pagination_query.to_sql)
[rows, pagy]
end

# Loads full ActiveRecord objects for the paginated (id, event_type) pairs
# with eager loading, preserving the UNION order.
def load_events(rows)
grouped = rows.each_with_object({}) do |row, hash|
(hash[row["event_type"]] ||= []) << row["id"].to_i
end

workshops = Workshop.includes(:chapter, :sponsors, :permissions, :organisers, :workshop_host)
.where(id: grouped["Workshop"])
.to_a.index_by(&:id)
meetings = Meeting.where(id: grouped["Meeting"])
.to_a.index_by(&:id)
events = Event.includes(:venue, :sponsors, :sponsorships, :permissions, :organisers)
.where(id: grouped["Event"])
.to_a.index_by(&:id)

rows.filter_map do |row|
case row["event_type"]
when "Workshop" then workshops[row["id"].to_i]
when "Meeting" then meetings[row["id"].to_i]
when "Event" then events[row["id"].to_i]
end
end
end
end