Skip to content

Commit 4f615ab

Browse files
committed
Enhance RSVP functionality and optimize event loading
- Ensure user is logged in before managing RSVPs - Optimize event attendance queries to reduce N+1 issues - Preload necessary associations for event and calendar displays
1 parent ad0647e commit 4f615ab

File tree

5 files changed

+117
-25
lines changed

5 files changed

+117
-25
lines changed

app/controllers/better_together/events_controller.rb

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,15 @@ def rsvp_going
4646
def rsvp_cancel
4747
@event = set_resource_instance
4848
authorize @event, :show?
49-
attendance = BetterTogether::EventAttendance.find_by(event: @event, person: helpers.current_person)
49+
50+
# Ensure current_person exists
51+
current_person = helpers.current_person
52+
unless current_person
53+
redirect_to @event, alert: t('better_together.events.login_required', default: 'Please log in to manage RSVPs.')
54+
return
55+
end
56+
57+
attendance = BetterTogether::EventAttendance.find_by(event: @event, person: current_person)
5058
attendance&.destroy
5159
redirect_to @event, notice: t('better_together.events.rsvp_cancelled', default: 'RSVP cancelled')
5260
end
@@ -95,8 +103,14 @@ def rsvp_update(status) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
95103
return
96104
end
97105

98-
attendance = BetterTogether::EventAttendance.find_or_initialize_by(event: @event,
99-
person: helpers.current_person)
106+
# Ensure current_person exists before creating attendance
107+
current_person = helpers.current_person
108+
unless current_person
109+
redirect_to @event, alert: t('better_together.events.login_required', default: 'Please log in to RSVP.')
110+
return
111+
end
112+
113+
attendance = BetterTogether::EventAttendance.find_or_initialize_by(event: @event, person: current_person)
100114
attendance.status = status
101115
authorize attendance
102116
if attendance.save
@@ -106,5 +120,47 @@ def rsvp_update(status) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
106120
end
107121
end
108122
# rubocop:enable Metrics/MethodLength
123+
124+
# Override base controller method to add performance optimizations
125+
def set_resource_instance
126+
super
127+
128+
# Preload associations needed for event show page to avoid N+1 queries
129+
preload_event_associations! unless json_request?
130+
end
131+
132+
def json_request?
133+
request.format.json?
134+
end
135+
136+
def preload_event_associations!
137+
return unless @event
138+
139+
# Preload categories and their translations to avoid N+1 queries
140+
@event.categories.includes(:string_translations).load
141+
142+
# Preload event hosts and their associated models
143+
@event.event_hosts.includes(:host).load
144+
145+
# Preload event attendances to avoid count queries in view
146+
@event.event_attendances.includes(:person).load
147+
148+
# Preload current person's attendance for RSVP buttons
149+
if current_person
150+
@current_attendance = @event.event_attendances.find { |a| a.person_id == current_person.id }
151+
end
152+
153+
# Preload translations for the event itself
154+
@event.string_translations.load
155+
@event.text_translations.load
156+
157+
# Preload cover image attachment to avoid attachment queries
158+
@event.cover_image_attachment&.blob&.load if @event.cover_image.attached?
159+
160+
# Preload location if present
161+
@event.location&.reload if @event.location
162+
163+
self
164+
end
109165
end
110166
end

app/controllers/better_together/people_controller.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def show
1717
:string_translations,
1818
blocks: { background_image_file_attachment: :blob }
1919
)
20+
21+
# Preload calendar associations to avoid N+1 queries
22+
@person.preload_calendar_associations!
2023
end
2124

2225
# GET /people/new

app/models/better_together/person.rb

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,36 +166,67 @@ def after_record_created
166166
# Combines events they're going to, created, and interested in
167167
def all_calendar_events
168168
@all_calendar_events ||= begin
169-
# Events from primary calendar (going)
170-
calendar_events = primary_calendar.events.includes(:string_translations)
169+
# Build a single query to get all relevant events with proper includes
170+
event_ids = Set.new
171171

172-
# Events they created
173-
created_events = Event.includes(:string_translations).where(creator_id: id)
172+
# Get event IDs from calendar entries (going events)
173+
calendar_event_ids = primary_calendar.calendar_entries.pluck(:event_id)
174+
event_ids.merge(calendar_event_ids)
174175

175-
# Events they're interested in (but not going)
176-
interested_event_ids = event_attendances.where(status: 'interested').pluck(:event_id)
177-
interested_events = Event.includes(:string_translations).where(id: interested_event_ids)
176+
# Get event IDs from attendances (interested events)
177+
attendance_event_ids = event_attendances.pluck(:event_id)
178+
event_ids.merge(attendance_event_ids)
178179

179-
# Combine all events, removing duplicates by ID
180-
all_events = (calendar_events.to_a + created_events.to_a + interested_events.to_a)
181-
all_events.uniq(&:id)
180+
# Get event IDs from created events
181+
created_event_ids = Event.where(creator_id: id).pluck(:id)
182+
event_ids.merge(created_event_ids)
183+
184+
# Single query to fetch all events with necessary includes
185+
if event_ids.any?
186+
Event.includes(:string_translations, :text_translations)
187+
.where(id: event_ids.to_a)
188+
.to_a
189+
else
190+
[]
191+
end
182192
end
183193
end
184194

185195
# Determines the relationship type for an event
186196
# Returns: :going, :created, :interested, or :calendar
187197
def event_relationship_for(event)
198+
# Check if they created it first (highest priority)
188199
return :created if event.creator_id == id
189200

190-
attendance = event_attendances.find_by(event: event)
201+
# Use memoized attendances to avoid N+1 queries
202+
@_event_attendances_by_event_id ||= event_attendances.index_by(&:event_id)
203+
attendance = @_event_attendances_by_event_id[event.id]
204+
191205
return attendance.status.to_sym if attendance
192206

193-
# Check if it's in their calendar (fallback)
194-
return :going if primary_calendar.events.include?(event)
207+
# Check if it's in their calendar (for events added directly to calendar)
208+
@_calendar_event_ids ||= Set.new(primary_calendar.calendar_entries.pluck(:event_id))
209+
return :going if @_calendar_event_ids.include?(event.id)
195210

196211
:calendar # Default for calendar events
197212
end
198213

214+
# Preloads associations needed for calendar display to avoid N+1 queries
215+
def preload_calendar_associations!
216+
# Preload event attendances
217+
event_attendances.includes(:event).load
218+
219+
# Preload calendar entries
220+
primary_calendar.calendar_entries.includes(:event).load
221+
222+
# Reset memoized variables
223+
@all_calendar_events = nil
224+
@_event_attendances_by_event_id = nil
225+
@_calendar_event_ids = nil
226+
227+
self
228+
end
229+
199230
include ::BetterTogether::RemoveableAttachment
200231
end
201232
end

app/views/better_together/events/show.html.erb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151

5252
<!-- RSVP Actions -->
5353
<% if current_person && @event.scheduled? %>
54-
<% attendance = BetterTogether::EventAttendance.find_by(event: @event, person: current_person) %>
54+
<% attendance = @current_attendance %>
5555
<div class="container my-3">
5656
<div class="d-flex gap-2">
5757
<%= button_to rsvp_interested_event_path(@event), method: :post, class: "btn btn-outline-danger #{'active' if attendance&.status == 'interested'}", style: "#{attendance&.status == 'interested' ? '--bs-btn-active-bg: #e91e63; --bs-btn-active-border-color: #e91e63;' : '--bs-btn-color: #e91e63; --bs-btn-border-color: #e91e63; --bs-btn-hover-bg: #e91e63; --bs-btn-hover-border-color: #e91e63;'}" do %>
@@ -67,8 +67,9 @@
6767
<% end %>
6868
</div>
6969
<div class="text-muted mt-2">
70-
<% going_count = BetterTogether::EventAttendance.where(event: @event, status: 'going').count %>
71-
<% interested_count = BetterTogether::EventAttendance.where(event: @event, status: 'interested').count %>
70+
<% attendance_counts = @event.event_attendances.group(:status).count %>
71+
<% going_count = attendance_counts['going'] || 0 %>
72+
<% interested_count = attendance_counts['interested'] || 0 %>
7273
<small>
7374
<%= t('better_together.events.rsvp_counts', default: 'Going: %{going} · Interested: %{interested}', going: going_count, interested: interested_count) %>
7475
</small>
@@ -93,7 +94,7 @@
9394
<%# Show the Attendees tab only to organizers (reuse invitation policy check) %>
9495
<% invitation = BetterTogether::EventInvitation.new(invitable: @event, inviter: current_person) %>
9596
<% if policy(invitation).create? %>
96-
<% attendees_count = BetterTogether::EventAttendance.where(event: @event).count %>
97+
<% attendees_count = @event.event_attendances.count %>
9798
<%= link_to "#{t('globals.tabs.attendees', default: 'Attendees')} (#{attendees_count})", '#attendees', class: 'nav-link', id: 'attendees-tab', data: { bs_toggle: 'tab', bs_target: '#attendees', turbo: false, 'better_together--tabs-target': 'tab' }, role: 'tab', aria: { controls: 'attendees', selected: 'false' }, tabindex: '0' %>
9899
<% end %>
99100
<% end %>
@@ -150,7 +151,7 @@
150151
<%# Render the existing invitations panel inside this attendees pane %>
151152
<%= render 'better_together/events/invitations_panel' %>
152153

153-
<% attendances = BetterTogether::EventAttendance.includes(:person).where(event: @event) %>
154+
<% attendances = @event.event_attendances %>
154155
<% if attendances.any? %>
155156
<hr/>
156157
<h6 class="mb-2"><%= t('better_together.events.attendees', default: 'Attendees') %></h6>

app/views/better_together/people/_calendar_section.html.erb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
<%= t('better_together.people.calendar.title', default: 'Personal Calendar') %>
55
</h6>
66

7-
<% if person.primary_calendar.calendar_entries.any? || person.event_attendances.any? || person.created_resources.where(type: 'BetterTogether::Event').any? %>
7+
<% all_events = person.all_calendar_events %>
8+
<% if all_events.any? %>
89
<!-- Simple Calendar View -->
910
<div class="calendar-container">
10-
<%= month_calendar(events: person.all_calendar_events) do |date, events| %>
11+
<%= month_calendar(events: all_events) do |date, events| %>
1112
<% events.each do |event| %>
1213
<% event_url = better_together.event_path(event) %>
1314
<% icon_data = event_relationship_icon(person, event) %>
@@ -38,7 +39,7 @@
3839
<%= t('better_together.people.calendar.upcoming_events', default: 'Upcoming Events') %>
3940
</h6>
4041

41-
<% upcoming_events = person.all_calendar_events.select { |e| e.starts_at && e.starts_at >= Time.current }.sort_by(&:starts_at).first(5) %>
42+
<% upcoming_events = all_events.select { |e| e.starts_at && e.starts_at >= Time.current }.sort_by(&:starts_at).first(5) %>
4243
<% if upcoming_events.any? %>
4344
<div class="list-group">
4445
<% upcoming_events.each do |event| %>
@@ -85,7 +86,7 @@
8586
<%= t('better_together.people.calendar.recent_past_events', default: 'Recent Past Events') %>
8687
</h6>
8788

88-
<% past_events = person.all_calendar_events.select { |e| e.starts_at && e.starts_at < Time.current }.sort_by(&:starts_at).reverse.first(3) %>
89+
<% past_events = all_events.select { |e| e.starts_at && e.starts_at < Time.current }.sort_by(&:starts_at).reverse.first(3) %>
8990
<% if past_events.any? %>
9091
<div class="list-group">
9192
<% past_events.each do |event| %>

0 commit comments

Comments
 (0)