I have a form that accepts a true/false value for every hour of the day for 3 weeks (total of 504 1 hour time slots).
I am trying to reduce the time it takes for this page to load (currently 3 seconds locally, but 15 seconds on heroku; more than twice the time of any other page on the site).
The code
The form looks like this:
<% 504.times do |i| %>
<% availability = @physician.availabilities.find { |a| a.time_slot == @current_hour + (i + 1)*3600 } %>
<% availability ||= @physician.availabilities.build(time_slot: all_one_hour_slots[i] ) %>
# Form etc
Small numbers of i would be fine, but because it happens 504 times, it's slow.
When I view the server logs, it has the appearance of an n+1 problem (which I think may be caused by @physician.availabilities.find). But I'm not sure if that can be successfully avoided whilst maintaining functionality.
Question
How can I retain functionality, but make the page load faster?
Other notes
- Every page on the site loads in under 7 seconds, so this page being 15 seconds is quite problematic.
- The heroku dyno is standard, and the heroku postgres database is hobby-basic.
- I am open to using whatever approach is best to reduce time. For example, if code optimisation is possible, that would be great. But if increasing the postgres database from hobby-basic to standard-0 would help, I'd consider that too.
- A sample of the logs (this is 6 lines almost identical, but there are 504 total like this!):
CACHE (0.0ms) SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1 [["physician_id", 10]]
↳ app/views/physicians/_form.html.erb:37
CACHE (0.0ms) SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1 [["physician_id", 10]]
↳ app/views/physicians/_form.html.erb:37
CACHE (0.0ms) SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1 [["physician_id", 10]]
↳ app/views/physicians/_form.html.erb:37
CACHE (0.0ms) SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1 [["physician_id", 10]]
↳ app/views/physicians/_form.html.erb:37
CACHE (0.0ms) SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1 [["physician_id", 10]]
↳ app/views/physicians/_form.html.erb:37
CACHE (0.0ms) SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1 [["physician_id", 10]]
↳ app/views/physicians/_form.html.erb:37
Longer explanation
Here's the form in the view
<% 504.times do |i| %>
<% availability = @physician.availabilities.find { |a| a.time_slot == @current_hour + (i + 1)*3600 } %>
<% availability ||= @physician.availabilities.build(time_slot: all_one_hour_slots_from_one_hour_after_current_time[i] ) %>
<% if @appointments.pluck(:start_time).any? { |e| e == availability.time_slot } %>
<b><%= availability.time_slot.to_s + " Time slot booked" %></b>
<% else %><%# Line 37 %>
<%= form.fields_for :availabilities, availability do |availability_form| %>
<%= availability_form.hidden_field :time_slot, value: availability.time_slot %>
<%= availability_form.label :_destroy do %>
<%= availability_form.check_box :_destroy, {checked: availability.persisted?}, '0', '1' %>
<%= nice_datetime(availability.time_slot) %>
<% end %>
<% end %><%# end form.fields_for %>
<% end %><%# end booked vs available %>
<% end %><%# end 504 loop %>
And here's the controller action
# physicians controller
@physician = current_user.physician
@appointments = Physician.find(current_user.physician.id).appointments
@current_hour = Time.current.beginning_of_hour
A quick explanation in words: basically the 504 loop is just looping through 504 1 hour time slots and either displaying " Time slot booked" if that time is already booked, or a checkbox if the time slot is not booked (so the physician can decide whether to make themselves available for that time slot).
Lastly, full logs here