diff --git a/app/models/distributor_shipping_method.rb b/app/models/distributor_shipping_method.rb index 5c69d18084..a2c81506dd 100644 --- a/app/models/distributor_shipping_method.rb +++ b/app/models/distributor_shipping_method.rb @@ -1,5 +1,5 @@ class DistributorShippingMethod < ActiveRecord::Base self.table_name = "distributors_shipping_methods" - belongs_to :shipping_method, class_name: Spree::ShippingMethod + belongs_to :shipping_method, class_name: Spree::ShippingMethod, touch: true belongs_to :distributor, class_name: Enterprise, touch: true end diff --git a/app/models/spree/classification_decorator.rb b/app/models/spree/classification_decorator.rb index fec270bc14..20608bad94 100644 --- a/app/models/spree/classification_decorator.rb +++ b/app/models/spree/classification_decorator.rb @@ -1,5 +1,6 @@ Spree::Classification.class_eval do belongs_to :product, class_name: "Spree::Product", touch: true + belongs_to :taxon, class_name: "Spree::Taxon", touch: true before_destroy :dont_destroy_if_primary_taxon diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index d136c2b681..fe20fe5194 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -12,7 +12,7 @@ Spree::Product.class_eval do has_many :option_types, through: :product_option_types, dependent: :destroy belongs_to :supplier, class_name: 'Enterprise', touch: true - belongs_to :primary_taxon, class_name: 'Spree::Taxon' + belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true delegate_belongs_to :master, :unit_value, :unit_description delegate :images_attributes=, :display_as=, to: :master diff --git a/app/services/cache_service.rb b/app/services/cache_service.rb index 610b4ca6b4..36c33025f5 100644 --- a/app/services/cache_service.rb +++ b/app/services/cache_service.rb @@ -22,4 +22,17 @@ class CacheService def self.latest_timestamp_by_class(cached_class) cached_class.maximum(:updated_at).to_i end + + module FragmentCaching + # Rails' caching in views is called "Fragment Caching" and uses some slightly different logic. + # Note: supplied keys are actually prepended with "view/" under the hood. + + def self.ams_all_taxons_key + "inject-all-taxons-#{CacheService.latest_timestamp_by_class(Spree::Taxon)}" + end + + def self.ams_all_properties_key + "inject-all-properties-#{CacheService.latest_timestamp_by_class(Spree::Property)}" + end + end end diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 1f92137e9e..3986d9ace8 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -48,8 +48,10 @@ = inject_current_hub = inject_current_user = inject_rails_flash - = inject_taxons - = inject_properties + - cache CacheService::FragmentCaching.ams_all_taxons_key do + = inject_taxons + - cache CacheService::FragmentCaching.ams_all_properties_key do + = inject_properties = inject_current_order = inject_currency_config = yield :injection_data diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb index 8bc386b2a6..dd6db131af 100644 --- a/lib/open_food_network/enterprise_injection_data.rb +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -10,11 +10,25 @@ module OpenFoodNetwork end def shipping_method_services - @shipping_method_services ||= Spree::ShippingMethod.services + @shipping_method_services ||= begin + CacheService.cached_data_by_class("shipping_method_services", Spree::ShippingMethod) do + # This result relies on a simple join with DistributorShippingMethod. + # Updated DistributorShippingMethod records touch their associated Spree::ShippingMethod. + Spree::ShippingMethod.services + end + end end def supplied_taxons - @supplied_taxons ||= Spree::Taxon.supplied_taxons + @supplied_taxons ||= begin + CacheService.cached_data_by_class("supplied_taxons", Spree::Taxon) do + # This result relies on a join with associated supplied products, through the + # class Classification which maps the relationship. Classification records touch + # their associated Spree::Taxon when updated. A Spree::Product's primary_taxon + # is also touched when changed. + Spree::Taxon.supplied_taxons + end + end end def all_distributed_taxons diff --git a/spec/features/consumer/caching/darkwarm_caching_spec.rb b/spec/features/consumer/caching/darkwarm_caching_spec.rb new file mode 100644 index 0000000000..6dcbc18ac5 --- /dev/null +++ b/spec/features/consumer/caching/darkwarm_caching_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "spec_helper" + +feature "Darkswarm data caching", js: true, caching: true do + let!(:taxon) { create(:taxon, name: "Cached Taxon") } + let!(:property) { create(:property, presentation: "Cached Property") } + + let!(:producer) { create(:supplier_enterprise) } + let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, is_primary_producer: true) } + let!(:product) { create(:simple_product, supplier: producer, primary_taxon: taxon, taxons: [taxon], properties: [property]) } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: distributor) } + let(:exchange) { order_cycle.exchanges.outgoing.where(receiver_id: distributor.id).first } + + before do + exchange.variants << product.variants.first + end + + describe "caching injected taxons and properties" do + it "caches taxons and properties" do + expect(Spree::Taxon).to receive(:all) { [taxon] } + expect(Spree::Property).to receive(:all) { [property] } + + visit shops_path + + expect(Spree::Taxon).to_not receive(:all) + expect(Spree::Property).to_not receive(:all) + + visit shops_path + end + + it "invalidates caches for taxons and properties" do + visit shops_path + + taxon_timestamp1 = CacheService.latest_timestamp_by_class(Spree::Taxon) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_taxons_key}" + + property_timestamp1 = CacheService.latest_timestamp_by_class(Spree::Property) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_properties_key}" + + toggle_filters + + within "#hubs .filter-row" do + expect(page).to have_content taxon.name + expect(page).to have_content property.presentation + end + + taxon.name = "Changed Taxon" + taxon.save + property.presentation = "Changed Property" + property.save + + visit shops_path + + taxon_timestamp2 = CacheService.latest_timestamp_by_class(Spree::Taxon) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_taxons_key}" + + property_timestamp2 = CacheService.latest_timestamp_by_class(Spree::Property) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_properties_key}" + + expect(taxon_timestamp1).to_not eq taxon_timestamp2 + expect(property_timestamp1).to_not eq property_timestamp2 + + toggle_filters + + within "#hubs .filter-row" do + expect(page).to have_content "Changed Taxon" + expect(page).to have_content "Changed Property" + end + end + end + + def expect_cached(key) + expect(Rails.cache.exist?(key)).to be true + end +end diff --git a/spec/features/consumer/producers_spec.rb b/spec/features/consumer/producers_spec.rb index f8d7e3bb57..d58ebe1397 100644 --- a/spec/features/consumer/producers_spec.rb +++ b/spec/features/consumer/producers_spec.rb @@ -16,8 +16,8 @@ feature ' let(:taxon_fruit) { create(:taxon, name: 'Fruit') } let(:taxon_veg) { create(:taxon, name: 'Vegetables') } - let!(:product1) { create(:simple_product, supplier: producer1, taxons: [taxon_fruit]) } - let!(:product2) { create(:simple_product, supplier: producer2, taxons: [taxon_veg]) } + let!(:product1) { create(:simple_product, supplier: producer1, primary_taxon: taxon_fruit, taxons: [taxon_fruit]) } + let!(:product2) { create(:simple_product, supplier: producer2, primary_taxon: taxon_veg, taxons: [taxon_veg]) } let(:shop) { create(:distributor_enterprise) } let!(:er) { create(:enterprise_relationship, parent: shop, child: producer1) } diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 87db26883c..236e556b79 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -110,13 +110,13 @@ feature 'Shops', js: true do describe "taxon badges" do let!(:closed_oc) { create(:closed_order_cycle, distributors: [shop], variants: [p_closed.variants.first]) } - let!(:p_closed) { create(:simple_product, taxons: [taxon_closed]) } + let!(:p_closed) { create(:simple_product, primary_taxon: taxon_closed, taxons: [taxon_closed]) } let(:shop) { create(:distributor_enterprise, with_payment_and_shipping: true) } let(:taxon_closed) { create(:taxon, name: 'Closed') } describe "open shops" do let!(:open_oc) { create(:open_order_cycle, distributors: [shop], variants: [p_open.variants.first]) } - let!(:p_open) { create(:simple_product, taxons: [taxon_open]) } + let!(:p_open) { create(:simple_product, primary_taxon: taxon_open, taxons: [taxon_open]) } let(:taxon_open) { create(:taxon, name: 'Open') } it "shows taxons for open order cycles only" do diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index 7bbc506a0f..94501eaa4e 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -109,5 +109,15 @@ module Spree expect(shipping_method.include?(address)).to be true end end + + describe "touches" do + let!(:distributor) { create(:distributor_enterprise) } + let!(:shipping_method) { create(:shipping_method) } + let(:add_distributor) { shipping_method.distributors << distributor } + + it "is touched when applied to a distributor" do + expect{ add_distributor }.to change { shipping_method.reload.updated_at} + end + end end end diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 5a565c5146..54a0069da9 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -28,5 +28,23 @@ module Spree expect(Taxon.distributed_taxons(:current)).to eq(e.id => Set.new([t1.id])) end end + + describe "touches" do + let!(:taxon1) { create(:taxon) } + let!(:taxon2) { create(:taxon) } + let!(:taxon3) { create(:taxon) } + let!(:product) { create(:simple_product, primary_taxon: taxon1, taxons: [taxon1, taxon2]) } + + it "is touched when a taxon is applied to a product" do + expect{ product.taxons << taxon3 }.to change { taxon3.reload.updated_at } + end + + it "is touched when assignment of primary_taxon on a product changes" do + expect do + product.primary_taxon = taxon2 + product.save + end.to change { taxon2.reload.updated_at } + end + end end end