From aa0ee65d78d1fcc3621c73260b5925fdeb90e413 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:55:46 +0000 Subject: [PATCH] Optimize CropsHelper with caching and memoization - Implement instance-level memoization for `crop_or_parent` and `display_seed_availability` - Use `Rails.cache.fetch` for `crop_jsonld_data` to improve performance of JSON-LD generation - Optimize `display_seed_availability` to avoid redundant queries - Fix a potential `NameError` in `crop_jsonld_data` by initializing `images` properly - Ensure memoization keys handle non-persisted objects and nil results correctly Co-authored-by: CloCkWeRX <365751+CloCkWeRX@users.noreply.github.com> --- app/helpers/crops_helper.rb | 139 +++++++++++++++++++++--------------- 1 file changed, 81 insertions(+), 58 deletions(-) diff --git a/app/helpers/crops_helper.rb b/app/helpers/crops_helper.rb index e331a1d05..1ac4c626e 100644 --- a/app/helpers/crops_helper.rb +++ b/app/helpers/crops_helper.rb @@ -2,28 +2,47 @@ module CropsHelper def crop_or_parent(crop, attribute) - default = crop.send(attribute) - return default if default.present? + @crop_or_parent_cache ||= {} + cache_key = [crop.persisted? ? crop.id : crop.object_id, attribute] + return @crop_or_parent_cache[cache_key] if @crop_or_parent_cache.key?(cache_key) - parent = crop - while parent = parent.parent - return parent.send(attribute) if parent&.send(attribute).present? + @crop_or_parent_cache[cache_key] = begin + value = crop.send(attribute) + if value.blank? + parent = crop + while (parent = parent.parent) + parent_value = parent.send(attribute) + if parent_value.present? + value = parent_value + break + end + end + end + value end - - # For scopes, arrays, etc return the empty value - default end def display_seed_availability(member, crop) - seeds = member.seeds.where(crop:) - total_quantity = seeds.where.not(quantity: nil).sum(:quantity) + @seed_availability_cache ||= {} + cache_key = [ + member.persisted? ? member.id : member.object_id, + crop.persisted? ? crop.id : crop.object_id + ] + return @seed_availability_cache[cache_key] if @seed_availability_cache.key?(cache_key) - return "You don't have any seeds of this crop." if seeds.none? + @seed_availability_cache[cache_key] = begin + seeds = member.seeds.where(crop:) - if total_quantity == 0 - "You have an unknown quantity of seeds of this crop." - else - "You have #{total_quantity} #{Seed.model_name.human(count: total_quantity)} of this crop." + if seeds.none? + "You don't have any seeds of this crop." + else + total_quantity = seeds.where.not(quantity: nil).sum(:quantity) + if total_quantity == 0 + "You have an unknown quantity of seeds of this crop." + else + "You have #{total_quantity} #{Seed.model_name.human(count: total_quantity)} of this crop." + end + end end end @@ -40,53 +59,57 @@ module CropsHelper end def crop_jsonld_data(crop, full_attributes: true) - same_as_urls = [crop.en_wikipedia_url] - crop.scientific_names.each do |scientific_name| - same_as_urls << "https://www.wikidata.org/wiki/#{scientific_name.wikidata_id}" if scientific_name.wikidata_id.present? - end - - subject_of_entities = [] - if full_attributes - if crop.en_youtube_url.present? - subject_of_entities << { - '@type': "VideoObject", - url: crop.en_youtube_url - } - end - - crop.posts.each do |post| - subject_of_entities << { - '@type': "SocialMediaPosting", - url: post_url(post), - author: { - '@type': 'Person', - name: post.author.login_name - }, - datePublished: post.created_at - } + Rails.cache.fetch([crop.cache_key_with_version, "jsonld", full_attributes]) do + same_as_urls = [crop.en_wikipedia_url] + crop.scientific_names.each do |scientific_name| + if scientific_name.wikidata_id.present? + same_as_urls << "https://www.wikidata.org/wiki/#{scientific_name.wikidata_id}" + end end + subject_of_entities = [] images = [] - crop.photos.each do |photo| - images << photo.fullsize_url + if full_attributes + if crop.en_youtube_url.present? + subject_of_entities << { + '@type': "VideoObject", + url: crop.en_youtube_url + } + end + + crop.posts.each do |post| + subject_of_entities << { + '@type': "SocialMediaPosting", + url: post_url(post), + author: { + '@type': 'Person', + name: post.author.login_name + }, + datePublished: post.created_at + } + end + + crop.photos.each do |photo| + images << photo.fullsize_url + end end + + # TODO: Review plantings, seeds, harvests as a subtype of social media post or event that ended? Or creative work? + # has_many :plantings, dependent: :destroy + # has_many :seeds, dependent: :destroy + # has_many :harvests, dependent: :destroy + + { + '@context': "https://schema.org", + '@type': "BioChemEntity", + name: crop.name, + taxonomicRange: crop.scientific_names.map(&:name), + description: crop.description, + sameAs: same_as_urls, + alternateName: crop.alternate_names.map(&:name), + subjectOf: subject_of_entities, + image: images + }.compact end - - # TODO: Review plantings, seeds, harvests as a subtype of social media post or event that ended? Or creative work? - # has_many :plantings, dependent: :destroy - # has_many :seeds, dependent: :destroy - # has_many :harvests, dependent: :destroy - - { - '@context': "https://schema.org", - '@type': "BioChemEntity", - name: crop.name, - taxonomicRange: crop.scientific_names.map(&:name), - description: crop.description, - sameAs: same_as_urls, - alternateName: crop.alternate_names.map(&:name), - subjectOf: subject_of_entities, - image: images - }.compact end end