class MyPortfoliosController < ApplicationController | |
before_filter :authenticate_user! | |
def show | |
@portfolio_properties = my_portfolio_filter.filtered_properties | |
.includes(:city, :submarket, :photos, :videos, :spaces) | |
.page(page) | |
.per(10) | |
.decorate | |
@portfolio_spaces = my_portfolio_filter.filtered_spaces(@portfolio_properties.collect(&:id)) | |
.includes(:videos, :property => :users) | |
end | |
private | |
end |
And here was our view:
- content_for :title, "My Portfolio - VTS" | |
= cache [ "v2", 'my_portfolio', current_user.all_properties_cache_key, current_user.all_spaces_cache_key ] do | |
.my_portfolio#main | |
= render partial: 'search_filters' | |
= render partial: 'multishare_bar' | |
#search-results | |
= render partial: 'search_results' | |
#footer-links |
This being a page that show properties and spaces, we are attempting to cache the entire page based on some keys that get updated whenever any of the spaces or properties within the user's portfolio is updated. But here's the problem, our queries are doing some eager loading from the database. Even though we were fragment caching the entire page, each cached hit was still hitting the database, creating a bunch of active record objects and then never actually using them. Even on a cached hit, this is what I saw in the logs:
(15.0ms) SELECT "spaces"."property_id" FROM "spaces" INNER JOIN "properties" ON "properties"."id" = "spaces"."property_id" LEFT OUTER JOIN "user_properties" ON "user_properties"."property_id" = "properties"."id" LEFT OUTER JOIN "user_spaces" ON "user_spaces"."space_id" = "spaces"."id" WHERE ((user_spaces.user_id = 2023 and spaces.lease_type = 'sublease') or (user_properties.user_id = 2023 and spaces.lease_type = 'direct')) AND (spaces.status!='removed') ORDER BY "spaces"."status", "spaces"."id" | |
Property Load (9.3ms) SELECT distinct properties.* FROM "properties" LEFT OUTER JOIN "user_properties" ON "user_properties"."property_id" = "properties"."id" LEFT OUTER JOIN "spaces" ON "spaces"."property_id" = "properties"."id" AND spaces.status != 'removed' LEFT OUTER JOIN "user_spaces" ON "user_spaces"."space_id" = "spaces"."id" WHERE "properties"."id" IN (142, 143, 207, 208, 214, 304, 297, 305, 349, 351, 352, 353, 453, 454, 456, 457, 459, 461, 482, 483, 490, 508, 534, 536, 567, 596, 597, 598, 622, 621, 665, 670, 707, 710, 739, 740, 797, 801, 803, 350, 531, 659, 660, 661, 662, 667, 669, 666, 668, 858, 915, 987, 982, 983, 984, 993, 994, 995, 975, 968, 990, 989, 964, 966, 967, 997, 985, 977, 971, 1002, 1003, 1004) AND (user_properties.user_id = 2023 or user_spaces.user_id = 2023) ORDER BY "properties"."status", "properties"."id" LIMIT 10 OFFSET 0 | |
Space Load (1.7ms) SELECT "spaces".* FROM "spaces" WHERE "spaces"."property_id" IN (142, 143, 207, 208, 214, 297, 304, 305, 349, 350) AND (spaces.status != 'removed') ORDER BY position | |
City Load (0.8ms) SELECT "cities".* FROM "cities" WHERE "cities"."id" IN (41598, 41811, 41613, 41620, 41712) | |
Submarket Load (0.3ms) SELECT "submarkets".* FROM "submarkets" WHERE "submarkets"."id" IN (59, 60) | |
PropertyPhoto Load (0.6ms) SELECT "photos".* FROM "photos" WHERE "photos"."type" IN ('PropertyPhoto') AND "photos"."imageable_type" = 'Property' AND "photos"."imageable_id" IN (142, 143, 207, 208, 214, 297, 304, 305, 349, 350) ORDER BY "photos"."position" | |
PropertyVideo Load (0.6ms) SELECT "property_videos".* FROM "property_videos" WHERE "property_videos"."property_id" IN (142, 143, 207, 208, 214, 297, 304, 305, 349, 350) | |
Video Load (0.4ms) SELECT "videos".* FROM "videos" WHERE "videos"."id" IN (4379, 2912, 2913, 2914, 2915, 2916, 4380, 4422, 4423, 4427, 4466, 4484, 2312, 2313, 2314, 2315, 2316, 4485) |
To fix this, we moved all of the query logic out of the controller show method and into a our query object where it is safely memoized
class MyPortfoliosController < ApplicationController | |
before_filter :authenticate_user! | |
helper_method :portfolio_spaces | |
helper_method :portfolio_properties | |
def show | |
end | |
private | |
def portfolio_properties | |
my_portfolio_filter.filtered_properties | |
end | |
def portfolio_spaces | |
my_portfolio_filter.filtered_spaces | |
end | |
class MyPortfolioFilter | |
def filtered_spaces | |
@filtered_spaces ||= filtered_spaces_query(filtered_properties.collect(&:id)).includes(:videos, :property => :users) | |
end | |
def filtered_properties | |
@filtered_properties ||= user.my_properties.where(id: filtered_spaces_query.pluck(:property_id).uniq) | |
.includes(:city, :submarket, :photos, :videos, :spaces) | |
.page(page) | |
.per(10) | |
.decorate | |
end | |
Now instead of accessing instance variables created within the controller show method, our view accesses helper methods lazily from within the cache fragment.
- if portfolio_properties.any? | |
%fieldset#portfolio-properties | |
= paginate portfolio_properties | |
= render partial: 'portfolio_properties', collection: portfolio_properties, as: :property | |
= paginate portfolio_properties |
Our cached my portfolio page no longer needlessly loads dozens of active record objects.
No comments:
Post a Comment