From 2e0c8a910d1a495c8ba1d2c408f54bce312d7583 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:07:18 +0000 Subject: [PATCH] Memoize Planting-related methods for performance optimization This commit introduces memoization to various methods in the Planting model, PredictPlanting and PredictHarvest concerns, PlantingsHelper, and PlantingsController. Specifically: - Memoized database-intensive lookups like `nearby_same_crop`, `first_harvest_date`, and `last_harvest_date`. - Memoized calculated fields like `finish_predicted_at`, `expected_lifespan`, and `age_in_days`. - Optimized `PlantingsHelper#transplantable_gardens_by_owner` using a hash to cache results per planting instance within a request. - Applied the `defined?(@variable)` pattern where appropriate to ensure efficient handling of `nil` results. These changes reduce redundant database queries and expensive calculations, particularly during view rendering where these methods are frequently accessed. Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com> --- app/controllers/plantings_controller.rb | 2 +- app/helpers/plantings_helper.rb | 8 +++- app/models/concerns/predict_harvest.rb | 40 +++++++++++-------- app/models/concerns/predict_planting.rb | 51 +++++++++++++++---------- app/models/planting.rb | 18 +++++---- 5 files changed, 73 insertions(+), 46 deletions(-) diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index d903a25da..2d7a26d17 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -160,7 +160,7 @@ class PlantingsController < DataController end def matching_seeds - Seed.where(crop: @planting.crop, owner: @planting.owner) + @matching_seeds ||= Seed.where(crop: @planting.crop, owner: @planting.owner) .where('(finished_at IS NULL OR finished_at >= ?)', @planting.planted_at) .where('(saved_at IS NULL OR saved_at <= ?)', @planting.planted_at) end diff --git a/app/helpers/plantings_helper.rb b/app/helpers/plantings_helper.rb index 1d5345151..04d9203d1 100644 --- a/app/helpers/plantings_helper.rb +++ b/app/helpers/plantings_helper.rb @@ -46,9 +46,13 @@ module PlantingsHelper # Returns a list of gardens the planting can be transplanted to # based on the planting's owner. def transplantable_gardens_by_owner(planting) - garden_ids = planting.owner.gardens.select(:id).to_a + GardenCollaborator.where(member_id: planting.owner.id).select(:garden_id).to_a + @transplantable_gardens ||= {} + cache_key = planting.id || planting.object_id + @transplantable_gardens[cache_key] ||= begin + garden_ids = planting.owner.gardens.select(:id).to_a + GardenCollaborator.where(member_id: planting.owner.id).select(:garden_id).to_a - Garden.active.where.not(id: planting.garden_id).where(id: garden_ids) + Garden.active.where.not(id: planting.garden_id).where(id: garden_ids) + end end def days_from_now_to_last_harvest(planting) diff --git a/app/models/concerns/predict_harvest.rb b/app/models/concerns/predict_harvest.rb index 64d7063d2..d68345252 100644 --- a/app/models/concerns/predict_harvest.rb +++ b/app/models/concerns/predict_harvest.rb @@ -6,23 +6,31 @@ module PredictHarvest included do # dates def first_harvest_date - harvests_with_dates.minimum(:harvested_at) + return @first_harvest_date if defined?(@first_harvest_date) + + @first_harvest_date = harvests_with_dates.minimum(:harvested_at) end def last_harvest_date - harvests_with_dates.maximum(:harvested_at) + return @last_harvest_date if defined?(@last_harvest_date) + + @last_harvest_date = harvests_with_dates.maximum(:harvested_at) end def first_harvest_predicted_at - return unless crop.median_days_to_first_harvest.present? && planted_at.present? + return @first_harvest_predicted_at if defined?(@first_harvest_predicted_at) - planted_at + crop.median_days_to_first_harvest.days + @first_harvest_predicted_at = if crop.median_days_to_first_harvest.present? && planted_at.present? + planted_at + crop.median_days_to_first_harvest.days + end end def last_harvest_predicted_at - return unless crop.median_days_to_last_harvest.present? && planted_at.present? + return @last_harvest_predicted_at if defined?(@last_harvest_predicted_at) - planted_at + crop.median_days_to_last_harvest.days + @last_harvest_predicted_at = if crop.median_days_to_last_harvest.present? && planted_at.present? + planted_at + crop.median_days_to_last_harvest.days + end end # actions @@ -65,16 +73,18 @@ module PredictHarvest end def neighbours_for_harvest_predictions - # use this planting's harvest if any - return harvests if harvests.size.positive? - - # otherwise use nearby plantings - if location - return Harvest.where(planting: nearby_same_crop.has_harvests) - .where.not(planting_id: nil) + @neighbours_for_harvest_predictions ||= begin + # use this planting's harvest if any + if harvests.size.positive? + harvests + # otherwise use nearby plantings + elsif location + Harvest.where(planting: nearby_same_crop.has_harvests) + .where.not(planting_id: nil) + else + Harvest.none + end end - - Harvest.none end private diff --git a/app/models/concerns/predict_planting.rb b/app/models/concerns/predict_planting.rb index ab158cac0..5162513cb 100644 --- a/app/models/concerns/predict_planting.rb +++ b/app/models/concerns/predict_planting.rb @@ -13,40 +13,49 @@ module PredictPlanting # dates def finish_predicted_at - if planted_at.blank? || failed? - nil - elsif crop.median_lifespan.present? - planted_at + crop.median_lifespan.days - elsif crop.parent.present? && crop.parent.median_lifespan.present? - planted_at + crop.parent.median_lifespan.days - end + return @finish_predicted_at if defined?(@finish_predicted_at) + + @finish_predicted_at = if planted_at.blank? || failed? + nil + elsif crop.median_lifespan.present? + planted_at + crop.median_lifespan.days + elsif crop.parent.present? && crop.parent.median_lifespan.present? + planted_at + crop.parent.median_lifespan.days + end end # days def expected_lifespan - if actual_lifespan.present? - actual_lifespan - elsif crop.median_lifespan.present? - crop.median_lifespan - elsif crop.parent.present? && crop.parent.median_lifespan.present? - crop.parent.median_lifespan - end + return @expected_lifespan if defined?(@expected_lifespan) + + @expected_lifespan = if actual_lifespan.present? + actual_lifespan + elsif crop.median_lifespan.present? + crop.median_lifespan + elsif crop.parent.present? && crop.parent.median_lifespan.present? + crop.parent.median_lifespan + end end def actual_lifespan - return unless planted_at.present? && finished_at.present? && !failed? + return @actual_lifespan if defined?(@actual_lifespan) - (finished_at - planted_at).to_i + @actual_lifespan = if planted_at.present? && finished_at.present? && !failed? + (finished_at - planted_at).to_i + end end def age_in_days - return if planted_at.blank? - return if failed? + return @age_in_days if defined?(@age_in_days) - known_last_day ||= finished_at || Time.zone.today - known_last_day = Time.zone.today if known_last_day > Time.zone.today + @age_in_days = if planted_at.blank? || failed? + nil + else + known_last_day = finished_at || Time.zone.today + known_last_day = Time.zone.today if known_last_day > Time.zone.today - (known_last_day - planted_at).to_i + (known_last_day - planted_at).to_i + end end def percentage_grown diff --git a/app/models/planting.rb b/app/models/planting.rb index 05ecc99ec..6b90bc3b8 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -119,14 +119,18 @@ class Planting < ApplicationRecord end def nearby_same_crop - return Planting.none if location.blank? || latitude.blank? || longitude.blank? + return @nearby_same_crop if defined?(@nearby_same_crop) - # latitude, longitude = Geocoder.coordinates(location, params: { limit: 1 }) - Planting.joins(:garden) - .where(crop:) - .located - .where('gardens.latitude < ? AND gardens.latitude > ?', - latitude + 10, latitude - 10) + @nearby_same_crop = if location.blank? || latitude.blank? || longitude.blank? + Planting.none + else + # latitude, longitude = Geocoder.coordinates(location, params: { limit: 1 }) + Planting.joins(:garden) + .where(crop:) + .located + .where('gardens.latitude < ? AND gardens.latitude > ?', + latitude + 10, latitude - 10) + end end private