Remove product cache

This commit is contained in:
Matt-Yorkley
2019-10-04 12:16:38 +01:00
parent da7456e6e0
commit ab330e882e
66 changed files with 18 additions and 1918 deletions

View File

@@ -262,13 +262,10 @@ Metrics/LineLength:
- spec/helpers/order_cycles_helper_spec.rb
- spec/helpers/spree/admin/base_helper_spec.rb
- spec/jobs/confirm_order_job_spec.rb
- spec/jobs/products_cache_integrity_checker_job_spec.rb
- spec/jobs/refresh_products_cache_job_spec.rb
- spec/jobs/subscription_confirm_job_spec.rb
- spec/jobs/subscription_placement_job_spec.rb
- spec/lib/open_food_network/address_finder_spec.rb
- spec/lib/open_food_network/bulk_coop_report_spec.rb
- spec/lib/open_food_network/cached_products_renderer_spec.rb
- spec/lib/open_food_network/customers_report_spec.rb
- spec/lib/open_food_network/enterprise_fee_applicator_spec.rb
- spec/lib/open_food_network/enterprise_fee_calculator_spec.rb

View File

@@ -1,27 +0,0 @@
require 'open_food_network/products_cache_integrity_checker'
module Admin
class CacheSettingsController < Spree::Admin::BaseController
def edit
@results = Exchange.cachable.map do |exchange|
checker = OpenFoodNetwork::ProductsCacheIntegrityChecker
.new(exchange.receiver, exchange.order_cycle)
{
distributor: exchange.receiver,
order_cycle: exchange.order_cycle,
status: checker.ok?,
diff: checker.diff
}
end
end
def update
Spree::Config.set(params[:preferences])
respond_to do |format|
format.html { redirect_to main_app.edit_admin_cache_settings_path }
end
end
end
end

View File

@@ -1,3 +1,5 @@
require 'open_food_network/products_renderer'
module Api
class OrderCyclesController < BaseController
include EnterprisesHelper

View File

@@ -1,5 +1,3 @@
require 'open_food_network/cached_products_renderer'
class ShopController < BaseController
layout "darkswarm"
before_filter :require_distributor_chosen, :set_order_cycles, except: :changeable_orders_alert

View File

@@ -1,30 +0,0 @@
require 'open_food_network/products_cache_integrity_checker'
ProductsCacheIntegrityCheckerJob = Struct.new(:distributor_id, :order_cycle_id) do
def perform
unless checker.ok?
exception = RuntimeError.new(
"Products JSON differs from cached version for distributor: #{distributor_id}, " \
"order cycle: #{order_cycle_id}"
)
Bugsnag.notify(exception) do |report|
report.add_tab(:products_cache, diff: checker.diff.to_s(:text))
end
end
end
private
def checker
OpenFoodNetwork::ProductsCacheIntegrityChecker.new(distributor, order_cycle)
end
def distributor
Enterprise.find distributor_id
end
def order_cycle
OrderCycle.find order_cycle_id
end
end

View File

@@ -1,21 +0,0 @@
require 'open_food_network/products_renderer'
RefreshProductsCacheJob = Struct.new(:distributor_id, :order_cycle_id) do
def perform
Rails.cache.write(key, products_json)
rescue ActiveRecord::RecordNotFound
true
end
private
def key
"products-json-#{distributor_id}-#{order_cycle_id}"
end
def products_json
distributor = Enterprise.find distributor_id
order_cycle = OrderCycle.find order_cycle_id
OpenFoodNetwork::ProductsRenderer.new(distributor, order_cycle).products_json
end
end

View File

@@ -1,13 +1,4 @@
class CoordinatorFee < ActiveRecord::Base
belongs_to :order_cycle
belongs_to :enterprise_fee
after_save :refresh_products_cache
after_destroy :refresh_products_cache
private
def refresh_products_cache
order_cycle.refresh_products_cache
end
end

View File

@@ -10,10 +10,6 @@ class EnterpriseFee < ActiveRecord::Base
has_many :exchange_fees, dependent: :destroy
has_many :exchanges, through: :exchange_fees
after_save :refresh_products_cache
# After destroy, the products cache is refreshed via the after_destroy hook for
# coordinator_fees and exchange_fees
attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :inherits_tax_category
FEE_TYPES = %w(packing transport admin sales fundraising).freeze
@@ -59,8 +55,4 @@ class EnterpriseFee < ActiveRecord::Base
end
true
end
def refresh_products_cache
OpenFoodNetwork::ProductsCache.enterprise_fee_changed self
end
end

View File

@@ -23,9 +23,6 @@ class Exchange < ActiveRecord::Base
validates :order_cycle, :sender, :receiver, presence: true
validates :sender_id, uniqueness: { scope: [:order_cycle_id, :receiver_id, :incoming] }
after_save :refresh_products_cache
after_destroy :refresh_products_cache_from_destroy
accepts_nested_attributes_for :variants
scope :in_order_cycle, lambda { |order_cycle| where(order_cycle_id: order_cycle) }
@@ -98,12 +95,4 @@ class Exchange < ActiveRecord::Base
def participant
incoming? ? sender : receiver
end
def refresh_products_cache
OpenFoodNetwork::ProductsCache.exchange_changed self
end
def refresh_products_cache_from_destroy
OpenFoodNetwork::ProductsCache.exchange_destroyed self
end
end

View File

@@ -1,13 +1,4 @@
class ExchangeFee < ActiveRecord::Base
belongs_to :exchange
belongs_to :enterprise_fee
after_save :refresh_products_cache
after_destroy :refresh_products_cache
private
def refresh_products_cache
exchange.refresh_products_cache
end
end

View File

@@ -1,5 +1,3 @@
require 'open_food_network/products_cache'
class InventoryItem < ActiveRecord::Base
attr_accessible :enterprise, :enterprise_id, :variant, :variant_id, :visible
@@ -13,12 +11,4 @@ class InventoryItem < ActiveRecord::Base
scope :visible, -> { where(visible: true) }
scope :hidden, -> { where(visible: false) }
after_save :refresh_products_cache
private
def refresh_products_cache
OpenFoodNetwork::ProductsCache.inventory_item_changed self
end
end

View File

@@ -23,8 +23,6 @@ class OrderCycle < ActiveRecord::Base
validates :name, :coordinator_id, presence: true
validate :orders_close_at_after_orders_open_at?
after_save :refresh_products_cache
preference :product_selection_from_coordinator_inventory_only, :boolean, default: false
scope :active, lambda {
@@ -247,10 +245,6 @@ class OrderCycle < ActiveRecord::Base
coordinator.users.include? user
end
def refresh_products_cache
OpenFoodNetwork::ProductsCache.order_cycle_changed self
end
def items_bought_by_user(user, distributor)
# The Spree::Order.complete scope only checks for completed_at date
# it does not ensure state is "complete"

View File

@@ -4,9 +4,6 @@ class ProducerProperty < ActiveRecord::Base
default_scope order("#{table_name}.position")
after_save :refresh_products_cache
after_destroy :refresh_products_cache_from_destroy
scope :ever_sold_by, ->(shop) {
joins(producer: { supplied_products: { variants: { exchanges: :order_cycle } } }).
merge(Exchange.outgoing).
@@ -29,14 +26,4 @@ class ProducerProperty < ActiveRecord::Base
Spree::Property.create(name: name, presentation: name)
end
end
private
def refresh_products_cache
OpenFoodNetwork::ProductsCache.producer_property_changed self
end
def refresh_products_cache_from_destroy
OpenFoodNetwork::ProductsCache.producer_property_destroyed self
end
end

View File

@@ -2,16 +2,9 @@ Spree::Classification.class_eval do
belongs_to :product, class_name: "Spree::Product", touch: true
before_destroy :dont_destroy_if_primary_taxon
after_destroy :refresh_products_cache
after_save :refresh_products_cache
private
def refresh_products_cache
product = Spree::Product.with_deleted.find(product_id) if product.blank?
product.refresh_products_cache
end
def dont_destroy_if_primary_taxon
if product.primary_taxon == taxon
errors.add :base, I18n.t(:spree_classification_primary_taxon_error, taxon: taxon.name, product: product.name)

View File

@@ -1,7 +1,4 @@
Spree::Image.class_eval do
after_save :refresh_products_cache
after_destroy :refresh_products_cache
# Spree stores attachent definitions in JSON. This converts the style name and format to
# strings. However, when paperclip encounters these, it doesn't recognise the format.
# Here we solve that problem by converting format and style name to symbols.
@@ -23,10 +20,4 @@ Spree::Image.class_eval do
end
reformat_styles
private
def refresh_products_cache
viewable.try :refresh_products_cache
end
end

View File

@@ -1,12 +1,5 @@
module Spree
OptionType.class_eval do
has_many :products, through: :product_option_types
after_save :refresh_products_cache
private
def refresh_products_cache
products(:reload).each(&:refresh_products_cache)
end
end
end

View File

@@ -1,18 +0,0 @@
module Spree
OptionValue.class_eval do
after_save :refresh_products_cache
around_destroy :refresh_products_cache_from_destroy
private
def refresh_products_cache
variants(:reload).each(&:refresh_products_cache)
end
def refresh_products_cache_from_destroy
vs = variants(:reload).to_a
yield
vs.each(&:refresh_products_cache)
end
end
end

View File

@@ -1,30 +0,0 @@
require 'open_food_network/products_cache'
module Spree
Preference.class_eval do
after_save :refresh_products_cache
# When the setting preferred_product_selection_from_inventory_only has changed, we want to
# refresh all active exchanges for this enterprise.
def refresh_products_cache
if product_selection_from_inventory_only_changed?
OpenFoodNetwork::ProductsCache.distributor_changed(enterprise)
end
end
private
def product_selection_from_inventory_only_changed?
!!(key =~ product_selection_from_inventory_only_regex)
end
def enterprise
enterprise_id = key.match(product_selection_from_inventory_only_regex)[1]
Enterprise.find(enterprise_id)
end
def product_selection_from_inventory_only_regex
/^enterprise\/product_selection_from_inventory_only\/(\d+)$/
end
end
end

View File

@@ -2,8 +2,6 @@ module Spree
Price.class_eval do
acts_as_paranoid without_default_scope: true
after_save :refresh_products_cache
# Allow prices to access associated soft-deleted variants.
def variant
Spree::Variant.unscoped { super }
@@ -16,9 +14,5 @@ module Spree
self.currency = Spree::Config[:currency]
end
end
def refresh_products_cache
variant.andand.refresh_products_cache
end
end
end

View File

@@ -38,7 +38,6 @@ Spree::Product.class_eval do
after_save :remove_previous_primary_taxon_from_taxons
after_save :ensure_standard_variant
after_save :update_units
after_save :refresh_products_cache
# -- Joins
scope :with_order_cycles_outer, -> {
@@ -192,23 +191,17 @@ Spree::Product.class_eval do
def destroy_with_delete_from_order_cycles
transaction do
OpenFoodNetwork::ProductsCache.product_deleted(self) do
touch_distributors
touch_distributors
ExchangeVariant.
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
select(:id)).destroy_all
ExchangeVariant.
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
select(:id)).destroy_all
destroy_without_delete_from_order_cycles
end
destroy_without_delete_from_order_cycles
end
end
alias_method_chain :destroy, :delete_from_order_cycles
def refresh_products_cache
OpenFoodNetwork::ProductsCache.product_changed self
end
private
def set_available_on_to_now

View File

@@ -1,10 +1,5 @@
module Spree
ProductProperty.class_eval do
belongs_to :product, class_name: "Spree::Product", touch: true
after_save :refresh_products_cache
after_destroy :refresh_products_cache
delegate :refresh_products_cache, to: :product
end
end

View File

@@ -20,19 +20,8 @@ module Spree
merge(OrderCycle.active)
}
after_save :refresh_products_cache
# When a Property is destroyed, dependent-destroy will destroy all ProductProperties,
# which will take care of refreshing the products cache
def property
self
end
private
def refresh_products_cache
product_properties(:reload).each(&:refresh_products_cache)
end
end
end

View File

@@ -1,10 +0,0 @@
Spree::StockMovement.class_eval do
after_save :refresh_products_cache
private
def refresh_products_cache
return if stock_item.variant.blank?
OpenFoodNetwork::ProductsCache.variant_changed stock_item.variant
end
end

View File

@@ -4,8 +4,6 @@ Spree::Taxon.class_eval do
attachment_definitions[:icon][:path] = 'public/images/spree/taxons/:id/:style/:basename.:extension'
attachment_definitions[:icon][:url] = '/images/spree/taxons/:id/:style/:basename.:extension'
after_save :refresh_products_cache
# Indicate which filters should be used for this taxon
def applicable_filters
fs = []
@@ -49,10 +47,4 @@ Spree::Taxon.class_eval do
ts[t.enterprise_id.to_i] << t.id
end
end
private
def refresh_products_cache
products(:reload).each(&:refresh_products_cache)
end
end

View File

@@ -1,7 +1,6 @@
require 'open_food_network/enterprise_fee_calculator'
require 'open_food_network/variant_and_line_item_naming'
require 'concerns/variant_stock'
require 'open_food_network/products_cache'
Spree::Variant.class_eval do
extend Spree::LocalizedNumber
@@ -30,7 +29,6 @@ Spree::Variant.class_eval do
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
after_save :update_units
after_save :refresh_products_cache
around_destroy :destruction
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
@@ -108,14 +106,6 @@ Spree::Variant.class_eval do
OpenFoodNetwork::EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for self
end
def refresh_products_cache
if is_master?
product.refresh_products_cache
else
OpenFoodNetwork::ProductsCache.variant_changed self
end
end
private
def update_weight_from_unit_value
@@ -123,21 +113,7 @@ Spree::Variant.class_eval do
end
def destruction
if is_master?
exchange_variants(:reload).destroy_all
yield
product.refresh_products_cache
else
OpenFoodNetwork::ProductsCache.variant_destroyed(self) do
# Remove this association here instead of using dependent: :destroy because
# dependent-destroy acts before this around_filter is called, so ProductsCache
# has no way of knowing which exchanges the variant was a member of.
exchange_variants(:reload).destroy_all
# Destroy the variant
yield
end
end
exchange_variants(:reload).destroy_all
yield
end
end

View File

@@ -12,9 +12,6 @@ class VariantOverride < ActiveRecord::Base
# Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user.
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
after_save :refresh_products_cache_from_save
after_destroy :refresh_products_cache_from_destroy
default_scope where(permission_revoked_at: nil)
scope :for_hubs, lambda { |hubs|
@@ -73,14 +70,4 @@ class VariantOverride < ActiveRecord::Base
end
self
end
private
def refresh_products_cache_from_save
OpenFoodNetwork::ProductsCache.variant_override_changed self
end
def refresh_products_cache_from_destroy
OpenFoodNetwork::ProductsCache.variant_override_destroyed self
end
end

View File

@@ -1,43 +1,11 @@
require 'open_food_network/scope_variant_to_hub'
class Api::ProductSerializer < ActiveModel::Serializer
# TODO
# Prices can't be cached? How?
def serializable_hash
cached_serializer_hash.merge(uncached_serializer_hash)
end
private
def cached_serializer_hash
Api::CachedProductSerializer.new(object, @options).serializable_hash
end
def uncached_serializer_hash
Api::UncachedProductSerializer.new(object, @options).serializable_hash
end
end
class Api::UncachedProductSerializer < ActiveModel::Serializer
attributes :price
def price
if options[:enterprise_fee_calculator]
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
else
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
end
end
end
class Api::CachedProductSerializer < ActiveModel::Serializer
# cached
# delegate :cache_key, to: :object
include ActionView::Helpers::SanitizeHelper
attributes :id, :name, :permalink, :meta_keywords
attributes :group_buy, :notes, :description, :description_html
attributes :properties_with_values
attributes :properties_with_values, :price
has_many :variants, serializer: Api::VariantSerializer
has_one :master, serializer: Api::VariantSerializer
@@ -70,4 +38,12 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
def master
options[:master_variants][object.id].andand.first
end
def price
if options[:enterprise_fee_calculator]
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
else
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
end
end
end

View File

@@ -1,31 +0,0 @@
- content_for :page_title do
= t(:cache_settings)
= form_tag main_app.admin_cache_settings_path, :method => :put do
.field
= hidden_field_tag 'preferences[enable_products_cache?]', '0'
= check_box_tag 'preferences[enable_products_cache?]', '1', Spree::Config[:enable_products_cache?]
= label_tag nil, t('.enable_products_cache')
.form-buttons
= button t(:update), 'icon-refresh'
%br
%br
%h4= t(:cache_state)
%br
%table.index
%thead
%tr
%th= t('.distributor')
%th= t('.order_cycle')
%th= t('.status')
%th= t('.diff')
%tbody
- @results.each do |result|
%tr
%td= result[:distributor].name
%td= result[:order_cycle].name
%td= result[:status] ? t(:ok) : t('.error')
%td
%pre= result[:diff].to_s(:text)

View File

@@ -21,7 +21,6 @@
= configurations_sidebar_menu_item Spree.t(:shipping_categories), admin_shipping_categories_path
= configurations_sidebar_menu_item t(:enterprise_fees), main_app.admin_enterprise_fees_path
= configurations_sidebar_menu_item Spree.t(:analytics_trackers), admin_trackers_path
= configurations_sidebar_menu_item t('admin.cache_settings.edit.title'), main_app.edit_admin_cache_settings_path
= configurations_sidebar_menu_item t('admin.contents.edit.title'), main_app.edit_admin_contents_path
= configurations_sidebar_menu_item t('admin.invoice_settings.edit.title'), main_app.edit_admin_invoice_settings_path
= configurations_sidebar_menu_item t('admin.matomo_settings.edit.title'), main_app.edit_admin_matomo_settings_path

View File

@@ -365,16 +365,6 @@ en:
number_localization_settings: "Number Localization Settings"
enable_localized_number: "Use the international thousand/decimal separator logic"
cache_settings:
edit:
title: "Caching"
distributor: "Distributor"
order_cycle: "Order Cycle"
status: "Status"
diff: "Diff"
error: "Error"
enable_products_cache: "Enable Products Cache?"
invoice_settings:
edit:
title: "Invoice Settings"

View File

@@ -75,8 +75,6 @@ Openfoodnetwork::Application.routes.draw do
resource :contents
resource :cache_settings
resources :column_preferences, only: [], format: :json do
put :bulk_update, on: :collection
end

View File

@@ -12,10 +12,6 @@ job_type :run_file, "cd :path; :environment_variable=:environment bundle exec sc
job_type :enqueue_job, "cd :path; :environment_variable=:environment bundle exec script/enqueue :task :priority :output"
every 1.day, at: '01:00am' do
rake 'ofn:cache:check_products_integrity'
end
every 1.day, at: '2:45am' do
rake 'db2fog:clean' if ENV['S3_BACKUPS_BUCKET']
end

View File

@@ -1,48 +0,0 @@
require 'open_food_network/products_renderer'
# Wrapper for ProductsRenderer that caches the JSON output.
# ProductsRenderer::NoProducts is represented in the cache as nil,
# but re-raised to provide the same interface as ProductsRenderer.
module OpenFoodNetwork
class CachedProductsRenderer
class NoProducts < RuntimeError; end
def initialize(distributor, order_cycle)
@distributor = distributor
@order_cycle = order_cycle
end
def products_json
raise NoProducts, I18n.t(:no_products) if @distributor.nil? || @order_cycle.nil?
products_json = cached_products_json
raise NoProducts, I18n.t(:no_products) if products_json.nil?
products_json
end
private
def cached_products_json
return uncached_products_json unless use_cached_products?
Rails.cache.fetch("products-json-#{@distributor.id}-#{@order_cycle.id}") do
begin
uncached_products_json
rescue ProductsRenderer::NoProducts
nil
end
end
end
def use_cached_products?
Spree::Config[:enable_products_cache?]
end
def uncached_products_json
ProductsRenderer.new(@distributor, @order_cycle).products_json
end
end
end

View File

@@ -1,190 +0,0 @@
require 'open_food_network/products_cache_refreshment'
# When elements of the data model change, refresh the appropriate parts of the products cache.
module OpenFoodNetwork
class ProductsCache
def self.variant_changed(variant)
exchanges_featuring_variants(variant.id).each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.variant_destroyed(variant, &block)
exchanges = exchanges_featuring_variants(variant.id).to_a
block.call
exchanges.each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.product_changed(product)
exchanges_featuring_variants(product.variants.map(&:id)).each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.product_deleted(product, &block)
exchanges = exchanges_featuring_variants(product.reload.variants.map(&:id)).to_a
block.call
exchanges.each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.variant_override_changed(variant_override)
exchanges_featuring_variants(variant_override.variant.id, distributor: variant_override.hub).each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.variant_override_destroyed(variant_override)
variant_override_changed variant_override
end
def self.producer_property_changed(producer_property)
products = producer_property.producer.supplied_products
variants = Spree::Variant.
where(is_master: false, deleted_at: nil).
where(product_id: products)
exchanges_featuring_variants(variants.select(:id)).each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.producer_property_destroyed(producer_property)
producer_property_changed producer_property
end
def self.order_cycle_changed(order_cycle)
return if order_cycle.undated?
return if order_cycle.closed?
order_cycle.exchanges.outgoing.each do |exchange|
refresh_cache exchange.receiver, order_cycle
end
end
def self.exchange_changed(exchange)
if exchange.incoming
refresh_incoming_exchanges(Exchange.where(id: exchange))
else
refresh_outgoing_exchange(exchange)
end
end
def self.exchange_destroyed(exchange)
exchange_changed exchange
end
def self.enterprise_fee_changed(enterprise_fee)
refresh_supplier_fee enterprise_fee
refresh_coordinator_fee enterprise_fee
refresh_distributor_fee enterprise_fee
end
def self.distributor_changed(enterprise)
Exchange.cachable.where(receiver_id: enterprise).each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.inventory_item_changed(inventory_item)
exchanges_featuring_variants(inventory_item.variant.id,
distributor: inventory_item.enterprise).each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.exchanges_featuring_variants(variant_ids, distributor: nil)
exchanges = Exchange.
outgoing.
with_any_variant(variant_ids).
joins(:order_cycle).
merge(OrderCycle.dated).
merge(OrderCycle.not_closed)
exchanges = exchanges.to_enterprise(distributor) if distributor
exchanges
end
private_class_method :exchanges_featuring_variants
def self.refresh_incoming_exchanges(exchanges)
outgoing_exchanges_affected_by(exchanges).each do |outgoing_exchange|
refresh_cache(outgoing_exchange.receiver, outgoing_exchange.order_cycle)
end
end
private_class_method :refresh_incoming_exchanges
def self.refresh_outgoing_exchange(exchange)
return if exchange.order_cycle.undated?
return if exchange.order_cycle.closed?
refresh_cache(exchange.receiver, exchange.order_cycle)
end
private_class_method :refresh_outgoing_exchange
def self.refresh_supplier_fee(enterprise_fee)
refresh_incoming_exchanges(enterprise_fee.exchanges)
end
private_class_method :refresh_supplier_fee
def self.refresh_coordinator_fee(enterprise_fee)
enterprise_fee.order_cycles.each do |order_cycle|
order_cycle_changed order_cycle
end
end
private_class_method :refresh_coordinator_fee
def self.refresh_distributor_fee(enterprise_fee)
enterprise_fee.exchange_fees.
joins(exchange: :order_cycle).
merge(Exchange.outgoing).
merge(OrderCycle.dated).
merge(OrderCycle.not_closed).
each do |exf|
refresh_cache exf.exchange.receiver, exf.exchange.order_cycle
end
end
private_class_method :refresh_distributor_fee
def self.incoming_exchanges(exchanges)
exchanges.
incoming.
joins(:order_cycle).
merge(OrderCycle.dated).
merge(OrderCycle.not_closed)
end
private_class_method :incoming_exchanges
def self.outgoing_exchanges_affected_by(exchanges)
incoming = incoming_exchanges(exchanges)
order_cycle_ids = incoming.select(:order_cycle_id)
variant_ids = ExchangeVariant.where(exchange_id: incoming).select(:variant_id)
Exchange.outgoing.
in_order_cycle(order_cycle_ids).
with_any_variant(variant_ids).
except(:select).select("DISTINCT receiver_id, order_cycle_id")
end
private_class_method :outgoing_exchanges_affected_by
def self.outgoing_exchanges_with_variants(order_cycle, variant_ids)
order_cycle.exchanges.outgoing.
joins(:exchange_variants).
where('exchange_variants.variant_id IN (?)', variant_ids)
end
private_class_method :outgoing_exchanges_with_variants
def self.refresh_cache(distributor, order_cycle)
ProductsCacheRefreshment.refresh distributor, order_cycle
end
private_class_method :refresh_cache
end
end

View File

@@ -1,34 +0,0 @@
require 'open_food_network/products_renderer'
module OpenFoodNetwork
class ProductsCacheIntegrityChecker
def initialize(distributor, order_cycle)
@distributor = distributor
@order_cycle = order_cycle
end
def ok?
diff.none?
end
def diff
@diff ||= Diffy::Diff.new pretty(cached_json), pretty(rendered_json)
end
private
def cached_json
Rails.cache.read("products-json-#{@distributor.id}-#{@order_cycle.id}") || {}.to_json
end
def rendered_json
OpenFoodNetwork::ProductsRenderer.new(@distributor, @order_cycle).products_json
rescue OpenFoodNetwork::ProductsRenderer::NoProducts
nil
end
def pretty(json)
JSON.pretty_generate JSON.parse json
end
end
end

View File

@@ -1,42 +0,0 @@
# When enqueuing a job to refresh the products cache for a particular distribution, there
# is no benefit in having more than one job waiting in the queue to be run.
# Imagine that an admin updates a product. This calls for the products cache to be
# updated, otherwise customers will see stale data.
# Now while that update is running, the admin makes another change to the product. Since this change
# has been made after the previous update started running, the already-running update will not
# include that change - we need another job. So we enqueue another one.
# Before that job starts running, our zealous admin makes yet another change. This time, there
# is a job running *and* there is a job that has not yet started to run. In this case, there's no
# benefit in enqueuing another job. When the previously enqueued job starts running, it will pick up
# our admin's update and include it. So we ignore this change (from a cache refreshment perspective)
# and go home happy to have saved our job worker's time.
module OpenFoodNetwork
class ProductsCacheRefreshment
def self.refresh(distributor, order_cycle)
job = refresh_job(distributor, order_cycle)
enqueue_job(job) unless pending_job?(job)
end
def self.refresh_job(distributor, order_cycle)
RefreshProductsCacheJob.new(distributor.id, order_cycle.id)
end
private_class_method :refresh_job
def self.pending_job?(job)
Delayed::Job.
where(locked_at: nil).
where(handler: job.to_yaml).
exists?
end
private_class_method :pending_job?
def self.enqueue_job(job)
Delayed::Job.enqueue job, priority: 10
end
private_class_method :enqueue_job
end
end

View File

@@ -1,19 +0,0 @@
require 'open_food_network/products_cache_integrity_checker'
namespace :ofn do
namespace :cache do
desc 'check the integrity of the products cache'
task check_products_integrity: :environment do
Exchange.cachable.each do |exchange|
Delayed::Job.enqueue ProductsCacheIntegrityCheckerJob.new(exchange.receiver_id, exchange.order_cycle_id), priority: 20
end
end
desc 'warm the products cache'
task warm_products: :environment do
Exchange.cachable.each do |exchange|
Delayed::Job.enqueue RefreshProductsCacheJob.new(exchange.receiver_id, exchange.order_cycle_id), priority: 10
end
end
end
end

View File

@@ -115,15 +115,6 @@ describe Api::VariantsController, type: :controller do
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).to be_nil
end
context 'when the variant is not the master' do
before { variant.update_attribute(:is_master, false) }
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :soft_delete, variant_id: variant.id, product_id: variant.product.permalink, format: :json
end
end
end
context "as an administrator" do
@@ -150,15 +141,6 @@ describe Api::VariantsController, type: :controller do
expect(assigns(:variant).errors[:product]).to include "must have at least one variant"
end
context 'when the variant is not the master' do
before { variant.update_attribute(:is_master, false) }
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :soft_delete, variant_id: variant.id, product_id: variant.product.permalink, format: :json
end
end
context "deleted variants" do
before do
variant.update_column(:deleted_at, Time.zone.now)

View File

@@ -60,11 +60,6 @@ module Spree
expect(response).to render_template('spree/admin/shared/_destroy')
end
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :destroy, id: variant.id, product_id: variant.product.permalink, format: 'js'
end
it 'destroys all its exchanges' do
exchange = create(:exchange)
variant.exchanges << exchange
@@ -99,11 +94,6 @@ module Spree
)
end
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :destroy, id: variant.id, product_id: variant.product.permalink, format: 'js'
end
it 'destroys all its exchanges' do
exchange = create(:exchange)
variant.exchanges << exchange

View File

@@ -1,46 +0,0 @@
require 'spec_helper'
require 'open_food_network/products_renderer'
feature 'Caching' do
include AuthenticationWorkflow
include WebHelper
before { quick_login_as_admin }
describe "displaying integrity checker results" do
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) { create(:open_order_cycle, distributors: [distributor]) }
it "displays results when things are good" do
# Given matching data
Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", "[1, 2, 3]\n"
allow(OpenFoodNetwork::ProductsRenderer).to receive(:new) {
double(:pr, products_json: "[1, 2, 3]\n")
}
# When I visit the cache status page
visit spree.admin_path
click_link 'Configuration'
click_link 'Caching'
# Then I should see some status information
expect(page).to have_content "OK"
end
it "displays results when there are errors" do
# Given matching data
Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", "[1, 2, 3]\n"
allow(OpenFoodNetwork::ProductsRenderer).to receive(:new) {
double(:pr, products_json: "[1, 3]\n")
}
# When I visit the cache status page
visit spree.admin_path
click_link 'Configuration'
click_link 'Caching'
# Then I should see some status information
expect(page).to have_content "Error"
end
end
end

View File

@@ -281,35 +281,6 @@ feature '
end
end
describe "inventory settings", js: true do
let!(:enterprise) { create(:distributor_enterprise) }
let!(:product) { create(:simple_product) }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [enterprise], variants: [product.variants.first]) }
before do
Delayed::Job.destroy_all
quick_login_as_admin
# This test relies on preference persistence, so we'll turn it on for this spec only.
# It will be turned off again automatically by reset_spree_preferences in spec_helper.
Spree::Preferences::Store.instance.persistence = true
end
it "refreshes the cache when I change what products appear on my shopfront" do
# Given a product that's not in my inventory, but is in an active order cycle
# When I change which products appear on the shopfront
visit edit_admin_enterprise_path(enterprise)
within(".side_menu") { click_link 'Inventory Settings' }
choose 'enterprise_preferred_product_selection_from_inventory_only_1'
# Then a job should have been enqueued to refresh the cache
expect do
click_button 'Update'
end.to enqueue_job RefreshProductsCacheJob, distributor_id: enterprise.id, order_cycle_id: order_cycle.id
end
end
context "as an Enterprise user", js: true do
let(:supplier1) { create(:supplier_enterprise, name: 'First Supplier') }
let(:supplier2) { create(:supplier_enterprise, name: 'Another Supplier') }

View File

@@ -180,46 +180,6 @@ feature "As a consumer I want to check out my cart", js: true do
end
end
context "in the shopfront with cache enabled" do
around do |example|
original_config = Spree::Config[:enable_products_cache?]
example.run
Spree::Config[:enable_products_cache?] = original_config
end
let(:control_product) { create(:taxed_product, supplier: supplier, price: 110, zone: zone, tax_rate_amount: 0.1) }
let(:control_variant) { control_product.variants.first }
let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [variant, control_variant]) }
it "does not show item after all stock of an item is checked out (tesging cache update on checkout)" do
Spree::Config[:enable_products_cache?] = true
variant.update_attributes on_hand: 5
flush_jobs
visit shop_path
fill_in "variants[#{variant.id}]", with: '5'
wait_until { !cart_dirty }
visit checkout_path
checkout_as_guest
fill_out_details
fill_out_billing_address
choose free_shipping.name
choose check_without_fee.name
place_order
expect(page).to have_content "Your order has been processed successfully"
flush_jobs
visit shop_path
# The presence of the control product ensures the list of products is fully loaded
# before we verify the sold product is not present
expect(page).to have_content control_product.name
expect(page).not_to have_content product.name
end
end
context "on the checkout page" do
before do
visit checkout_path

View File

@@ -1,27 +0,0 @@
require 'spec_helper'
require 'open_food_network/products_renderer'
describe ProductsCacheIntegrityCheckerJob do
describe "reporting on differences between the products cache and the current products" do
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) { create(:simple_order_cycle) }
let(:job) { ProductsCacheIntegrityCheckerJob.new distributor.id, order_cycle.id }
let(:cache_key) { "products-json-#{distributor.id}-#{order_cycle.id}" }
before do
Rails.cache.write(cache_key, "[1, 2, 3]\n")
allow(OpenFoodNetwork::ProductsRenderer).to receive(:new) { double(:pr, products_json: "[1, 3]\n") }
end
it "reports errors" do
expect(Bugsnag).to receive(:notify)
run_job job
end
it "deals with nil cached_json" do
Rails.cache.delete(cache_key)
expect(Bugsnag).to receive(:notify)
run_job job
end
end
end

View File

@@ -1,53 +0,0 @@
require 'spec_helper'
require 'open_food_network/products_renderer'
describe RefreshProductsCacheJob do
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) { create(:simple_order_cycle) }
context 'when the enterprise and the order cycle exist' do
before do
refresh_products_cache_job = instance_double(OpenFoodNetwork::ProductsRenderer, products_json: 'products')
allow(OpenFoodNetwork::ProductsRenderer).to receive(:new).with(distributor, order_cycle) { refresh_products_cache_job }
end
it 'renders products and writes them to cache' do
run_job RefreshProductsCacheJob.new distributor.id, order_cycle.id
expect(Rails.cache.read("products-json-#{distributor.id}-#{order_cycle.id}")).to eq 'products'
end
end
context 'when the order cycle does not exist' do
before do
allow(OrderCycle)
.to receive(:find)
.with(order_cycle.id)
.and_raise(ActiveRecord::RecordNotFound)
end
it 'does not raise' do
expect {
run_job RefreshProductsCacheJob.new(distributor.id, order_cycle.id)
}.not_to raise_error
end
it 'returns true' do
refresh_products_cache_job = RefreshProductsCacheJob.new(distributor.id, order_cycle.id)
expect(refresh_products_cache_job.perform).to eq(true)
end
end
describe "fetching products JSON" do
let(:job) { RefreshProductsCacheJob.new(distributor.id, order_cycle.id) }
let(:products_renderer) { instance_double(OpenFoodNetwork::ProductsRenderer, products_json: nil) }
before do
allow(OpenFoodNetwork::ProductsRenderer).to receive(:new).with(distributor, order_cycle) { products_renderer }
end
it "fetches products JSON" do
job.perform
expect(OpenFoodNetwork::ProductsRenderer).to have_received(:new).with(distributor, order_cycle) { products_renderer }
end
end
end

View File

@@ -1,134 +0,0 @@
require 'spec_helper'
require 'open_food_network/cached_products_renderer'
module OpenFoodNetwork
describe CachedProductsRenderer do
let(:distributor) { double(:distributor, id: 123) }
let(:order_cycle) { double(:order_cycle, id: 456) }
let(:cached_products_renderer) { CachedProductsRenderer.new(distributor, order_cycle) }
# keeps global state unchanged
around do |example|
original_config = Spree::Config[:enable_products_cache?]
example.run
Spree::Config[:enable_products_cache?] = original_config
end
describe "#products_json" do
let(:products_renderer) do
double(ProductsRenderer, products_json: 'uncached products')
end
before do
allow(ProductsRenderer)
.to receive(:new)
.with(distributor, order_cycle) { products_renderer }
end
context "products cache toggle" do
before do
Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", 'products'
end
context "disabled" do
before do
Spree::Config[:enable_products_cache?] = false
end
it "returns uncached products JSON" do
expect(cached_products_renderer.products_json).to eq 'uncached products'
end
end
context "enabled" do
before do
Spree::Config[:enable_products_cache?] = true
end
it "returns the cached JSON" do
expect(cached_products_renderer.products_json).to eq 'products'
end
end
end
context "products cache enabled" do
before do
Spree::Config[:enable_products_cache?] = true
end
describe "when the distribution is not set" do
let(:cached_products_renderer) { CachedProductsRenderer.new(nil, nil) }
it "raises an exception and returns no products" do
expect { cached_products_renderer.products_json }.to raise_error CachedProductsRenderer::NoProducts
end
end
describe "when the products JSON is already cached" do
before do
Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", 'products'
end
it "returns the cached JSON" do
expect(cached_products_renderer.products_json).to eq 'products'
end
it "raises an exception when there are no products" do
Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", nil
expect { cached_products_renderer.products_json }.to raise_error CachedProductsRenderer::NoProducts
end
end
describe "when the products JSON is not cached" do
let(:cache_key) { "products-json-#{distributor.id}-#{order_cycle.id}" }
let(:cached_json) { Rails.cache.read(cache_key) }
let(:cache_present) { Rails.cache.exist?(cache_key) }
let(:products_renderer) do
double(ProductsRenderer, products_json: 'fresh products')
end
before do
Rails.cache.delete(cache_key)
allow(ProductsRenderer)
.to receive(:new)
.with(distributor, order_cycle) { products_renderer }
end
describe "when there are products" do
it "returns products as JSON" do
expect(cached_products_renderer.products_json).to eq 'fresh products'
end
it "caches the JSON" do
cached_products_renderer.products_json
expect(cached_json).to eq 'fresh products'
end
end
describe "when there are no products" do
let(:products_renderer) { double(ProductsRenderer) }
before do
allow(products_renderer).to receive(:products_json).and_raise ProductsRenderer::NoProducts
allow(ProductsRenderer)
.to receive(:new)
.with(distributor, order_cycle) { products_renderer }
end
it "raises an error" do
expect { cached_products_renderer.products_json }.to raise_error CachedProductsRenderer::NoProducts
end
it "caches the products as nil" do
expect { cached_products_renderer.products_json }.to raise_error CachedProductsRenderer::NoProducts
expect(cache_present).to be
expect(cached_json).to be_nil
end
end
end
end
end
end
end

View File

@@ -1,69 +0,0 @@
require 'spec_helper'
require 'open_food_network/products_cache_refreshment'
module OpenFoodNetwork
describe ProductsCacheRefreshment do
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) { create(:simple_order_cycle) }
before { Delayed::Job.destroy_all }
describe "when there are no tasks enqueued" do
it "enqueues the task" do
expect do
ProductsCacheRefreshment.refresh distributor, order_cycle
end.to enqueue_job RefreshProductsCacheJob
end
it "enqueues the job with a lower than default priority" do
ProductsCacheRefreshment.refresh distributor, order_cycle
job = Delayed::Job.last
expect(job.priority).to be > Delayed::Worker.default_priority
end
end
describe "when there is an enqueued task, and it is running" do
before do
job = Delayed::Job.enqueue RefreshProductsCacheJob.new distributor.id, order_cycle.id
job.update_attributes! locked_by: 'asdf', locked_at: Time.now
end
it "enqueues another task" do
expect do
ProductsCacheRefreshment.refresh distributor, order_cycle
end.to enqueue_job RefreshProductsCacheJob
end
end
describe "when there are two enqueued tasks, and one is running" do
before do
job1 = Delayed::Job.enqueue RefreshProductsCacheJob.new distributor.id, order_cycle.id
job1.update_attributes! locked_by: 'asdf', locked_at: Time.now
job2 = Delayed::Job.enqueue RefreshProductsCacheJob.new distributor.id, order_cycle.id
end
it "does not enqueue another task" do
expect do
ProductsCacheRefreshment.refresh distributor, order_cycle
end.not_to enqueue_job RefreshProductsCacheJob
end
end
describe "enqueuing tasks with different distributions" do
let(:distributor2) { create(:distributor_enterprise) }
let(:order_cycle2) { create(:simple_order_cycle) }
before do
job1 = Delayed::Job.enqueue RefreshProductsCacheJob.new distributor.id, order_cycle.id
job1.update_attributes! locked_by: 'asdf', locked_at: Time.now
job2 = Delayed::Job.enqueue RefreshProductsCacheJob.new distributor.id, order_cycle.id
end
it "ignores tasks with differing distributions when choosing whether to enqueue a job" do
expect do
ProductsCacheRefreshment.refresh distributor2, order_cycle2
end.to enqueue_job RefreshProductsCacheJob
end
end
end
end

View File

@@ -1,431 +0,0 @@
require 'open_food_network/products_cache'
require 'spec_helper'
module OpenFoodNetwork
describe ProductsCache do
describe "when a variant changes" do
let(:variant) { create(:variant) }
let(:variant_undistributed) { create(:variant) }
let(:supplier) { create(:supplier_enterprise) }
let(:coordinator) { create(:distributor_enterprise) }
let(:distributor) { create(:distributor_enterprise) }
let(:oc_undated) { create(:undated_order_cycle, distributors: [distributor], variants: [variant]) }
let(:oc_upcoming) { create(:upcoming_order_cycle, suppliers: [supplier], coordinator: coordinator, distributors: [distributor], variants: [variant]) }
let(:oc_open) { create(:open_order_cycle, distributors: [distributor], variants: [variant]) }
let(:oc_closed) { create(:closed_order_cycle, distributors: [distributor], variants: [variant]) }
it "refreshes distributions with upcoming order cycles" do
oc_upcoming
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc_upcoming)
ProductsCache.variant_changed variant
end
it "refreshes distributions with open order cycles" do
oc_open
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc_open)
ProductsCache.variant_changed variant
end
it "does not refresh distributions with undated order cycles" do
oc_undated
expect(ProductsCache).not_to receive(:refresh_cache).with(distributor, oc_undated)
ProductsCache.variant_changed variant
end
it "does not refresh distributions with closed order cycles" do
oc_closed
expect(ProductsCache).not_to receive(:refresh_cache).with(distributor, oc_closed)
ProductsCache.variant_changed variant
end
it "limits refresh to outgoing exchanges" do
oc_upcoming
expect(ProductsCache).not_to receive(:refresh_cache).with(coordinator, oc_upcoming)
ProductsCache.variant_changed variant
end
it "does not refresh distributions where the variant does not appear" do
oc_undated; oc_upcoming; oc_open; oc_closed
variant_undistributed
expect(ProductsCache).not_to receive(:refresh_cache)
ProductsCache.variant_changed variant_undistributed
end
end
describe "when a variant is destroyed" do
let(:variant) { create(:variant) }
let(:distributor) { create(:distributor_enterprise) }
let!(:oc) { create(:open_order_cycle, distributors: [distributor], variants: [variant]) }
it "refreshes the cache based on exchanges the variant was in before destruction" do
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc)
variant.destroy
end
it "performs the cache refresh after the variant has been destroyed" do
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc) do
expect(Spree::Variant.where(id: variant.id)).to be_empty
end
variant.destroy
end
end
describe "when a product changes" do
let(:product) { create(:simple_product) }
let(:v1) { create(:variant, product: product) }
let(:v2) { create(:variant, product: product) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let(:oc) { create(:open_order_cycle) }
let!(:ex1) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d1, variants: [v1]) }
let!(:ex2) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d2, variants: [v1, v2]) }
before { product.reload }
it "refreshes the distribution each variant appears in, once each" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc).once
ProductsCache.product_changed product
end
end
describe "when a product is deleted" do
let(:product) { create(:simple_product) }
let(:variant) { create(:variant, product: product) }
let(:distributor) { create(:distributor_enterprise) }
let!(:oc) { create(:open_order_cycle, distributors: [distributor], variants: [variant]) }
it "refreshes the cache based on exchanges the variant was in before destruction" do
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc)
product.destroy
end
it "performs the cache refresh after the product has been removed from the order cycle" do
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc) do
expect(product.reload.deleted_at).not_to be_nil
end
product.destroy
end
end
describe "when a variant override changes" do
let(:variant) { create(:variant) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let!(:vo) { create(:variant_override, variant: variant, hub: d1) }
let!(:oc) { create(:open_order_cycle, distributors: [d1, d2], variants: [variant]) }
it "refreshes the distributions that the variant override affects" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.variant_override_changed vo
end
it "does not refresh other distributors of the variant" do
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc).never
ProductsCache.variant_override_changed vo
end
end
describe "when a variant override is destroyed" do
let(:vo) { double(:variant_override) }
it "performs the same refresh as a variant override change" do
expect(ProductsCache).to receive(:variant_override_changed).with(vo)
ProductsCache.variant_override_destroyed vo
end
end
describe "when a producer property is changed" do
let(:s) { create(:supplier_enterprise) }
let(:pp) { s.producer_properties.last }
let(:product) { create(:simple_product, supplier: s) }
let(:v1) { create(:variant, product: product) }
let(:v2) { create(:variant, product: product) }
let(:v_deleted) { create(:variant, product: product) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let(:d3) { create(:distributor_enterprise) }
let(:oc) { create(:open_order_cycle) }
let!(:ex1) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d1, variants: [v1]) }
let!(:ex2) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d2, variants: [v1, v2]) }
let!(:ex3) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d3, variants: [product.master, v_deleted]) }
before do
v_deleted.deleted_at = Time.now
v_deleted.save
s.set_producer_property :organic, 'NASAA 12345'
end
it "refreshes the distributions the supplied variants appear in" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc).once
ProductsCache.producer_property_changed pp
end
it "doesn't respond to master or deleted variants" do
expect(ProductsCache).to receive(:refresh_cache).with(d3, oc).never
ProductsCache.producer_property_changed pp
end
end
describe "when a producer property is destroyed" do
let(:producer_property) { double(:producer_property) }
it "triggers the same update as a change to the producer property" do
expect(ProductsCache).to receive(:producer_property_changed).with(producer_property)
ProductsCache.producer_property_destroyed producer_property
end
end
describe "when an order cycle is changed" do
let(:variant) { create(:variant) }
let(:s) { create(:supplier_enterprise) }
let(:c) { create(:distributor_enterprise) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let!(:oc_open) { create(:open_order_cycle, suppliers: [s], coordinator: c, distributors: [d1, d2], variants: [variant]) }
let!(:oc_upcoming) { create(:upcoming_order_cycle, suppliers: [s], coordinator: c, distributors: [d1, d2], variants: [variant]) }
before do
oc_open.reload
oc_upcoming.reload
end
it "updates each outgoing distribution in an upcoming order cycle" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc_upcoming).once
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc_upcoming).once
ProductsCache.order_cycle_changed oc_upcoming
end
it "updates each outgoing distribution in an open order cycle" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc_open).once
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc_open).once
ProductsCache.order_cycle_changed oc_open
end
it "does nothing when the order cycle has been made undated" do
expect(ProductsCache).to receive(:refresh_cache).never
oc_open.orders_open_at = oc_open.orders_close_at = nil
oc_open.save!
end
it "does nothing when the order cycle has been closed" do
expect(ProductsCache).to receive(:refresh_cache).never
oc_open.orders_open_at = 2.weeks.ago
oc_open.orders_close_at = 1.week.ago
oc_open.save!
end
it "does not update incoming exchanges" do
expect(ProductsCache).to receive(:refresh_cache).with(c, oc_open).never
ProductsCache.order_cycle_changed oc_open
end
end
describe "when an exchange is changed" do
let(:s) { create(:supplier_enterprise) }
let(:c) { create(:distributor_enterprise) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let(:v) { create(:variant) }
let(:oc) { create(:open_order_cycle, coordinator: c) }
describe "incoming exchanges" do
let!(:ex1) { create(:exchange, order_cycle: oc, sender: s, receiver: c, incoming: true, variants: [v]) }
let!(:ex2) { create(:exchange, order_cycle: oc, sender: c, receiver: d1, incoming: false, variants: [v]) }
let!(:ex3) { create(:exchange, order_cycle: oc, sender: c, receiver: d2, incoming: false, variants: []) }
before { oc.reload }
it "updates distributions that include one of the supplier's variants" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.exchange_changed ex1
end
it "doesn't update distributions that don't include any of the supplier's variants" do
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc).never
ProductsCache.exchange_changed ex1
end
end
describe "outgoing exchanges" do
let!(:ex) { create(:exchange, order_cycle: oc, sender: c, receiver: d1, incoming: false) }
it "does not update for undated order cycles" do
oc.update_attributes! orders_open_at: nil, orders_close_at: nil
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).never
ProductsCache.exchange_changed ex
end
it "updates for upcoming order cycles" do
oc.update_attributes! orders_open_at: 1.week.from_now, orders_close_at: 2.weeks.from_now
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.exchange_changed ex
end
it "updates for open order cycles" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.exchange_changed ex
end
it "does not update for closed order cycles" do
oc.update_attributes! orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).never
ProductsCache.exchange_changed ex
end
end
end
describe "when an exchange is destroyed" do
let(:exchange) { double(:exchange) }
it "triggers the same update as a change to the exchange" do
expect(ProductsCache).to receive(:exchange_changed).with(exchange)
ProductsCache.exchange_destroyed exchange
end
end
describe "when an enterprise fee is changed" do
let(:s) { create(:supplier_enterprise) }
let(:c) { create(:distributor_enterprise) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let(:ef) { create(:enterprise_fee) }
let(:ef_coord) { create(:enterprise_fee, order_cycles: [oc]) }
let(:oc) { create(:open_order_cycle, coordinator: c) }
describe "updating exchanges when it's a supplier fee" do
let(:v) { create(:variant) }
let!(:ex1) { create(:exchange, order_cycle: oc, sender: s, receiver: c, incoming: true, variants: [v], enterprise_fees: [ef]) }
let!(:ex2) { create(:exchange, order_cycle: oc, sender: c, receiver: d1, incoming: false, variants: [v]) }
let!(:ex3) { create(:exchange, order_cycle: oc, sender: c, receiver: d2, incoming: false, variants: []) }
before { ef.reload }
describe "updating distributions that include one of the supplier's variants" do
it "does not update undated order cycles" do
oc.update_attributes! orders_open_at: nil, orders_close_at: nil
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).never
ProductsCache.enterprise_fee_changed ef
end
it "updates upcoming order cycles" do
oc.update_attributes! orders_open_at: 1.week.from_now, orders_close_at: 2.weeks.from_now
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.enterprise_fee_changed ef
end
it "updates open order cycles" do
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.enterprise_fee_changed ef
end
it "does not update closed order cycles" do
oc.update_attributes! orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).never
ProductsCache.enterprise_fee_changed ef
end
end
it "doesn't update distributions that don't include any of the supplier's variants" do
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc).never
ProductsCache.enterprise_fee_changed ef
end
end
it "updates order cycles when it's a coordinator fee" do
ef_coord
expect(ProductsCache).to receive(:order_cycle_changed).with(oc).once
ProductsCache.enterprise_fee_changed ef_coord
end
describe "updating exchanges when it's a distributor fee" do
let(:ex0) { create(:exchange, order_cycle: oc, sender: s, receiver: c, incoming: true, enterprise_fees: [ef]) }
let(:ex1) { create(:exchange, order_cycle: oc, sender: c, receiver: d1, incoming: false, enterprise_fees: [ef]) }
let(:ex2) { create(:exchange, order_cycle: oc, sender: c, receiver: d2, incoming: false, enterprise_fees: []) }
describe "updating distributions that include the fee" do
it "does not update undated order cycles" do
oc.update_attributes! orders_open_at: nil, orders_close_at: nil
ex1
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).never
ProductsCache.enterprise_fee_changed ef
end
it "updates upcoming order cycles" do
oc.update_attributes! orders_open_at: 1.week.from_now, orders_close_at: 2.weeks.from_now
ex1
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.enterprise_fee_changed ef
end
it "updates open order cycles" do
ex1
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).once
ProductsCache.enterprise_fee_changed ef
end
it "does not update closed order cycles" do
oc.update_attributes! orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago
ex1
expect(ProductsCache).to receive(:refresh_cache).with(d1, oc).never
ProductsCache.enterprise_fee_changed ef
end
end
it "doesn't update exchanges that don't include the fee" do
ex1; ex2
expect(ProductsCache).to receive(:refresh_cache).with(d2, oc).never
ProductsCache.enterprise_fee_changed ef
end
it "doesn't update incoming exchanges" do
ex0
expect(ProductsCache).to receive(:refresh_cache).with(c, oc).never
ProductsCache.enterprise_fee_changed ef
end
end
end
describe "when a distributor enterprise is changed" do
let(:d) { create(:distributor_enterprise) }
let(:oc) { create(:open_order_cycle, distributors: [d]) }
it "updates each distribution the enterprise is active in" do
expect(ProductsCache).to receive(:refresh_cache).with(d, oc)
ProductsCache.distributor_changed d
end
end
describe "when an inventory item is changed" do
let!(:d) { create(:distributor_enterprise) }
let!(:v) { create(:variant) }
let!(:oc1) { create(:open_order_cycle, distributors: [d], variants: [v]) }
let(:oc2) { create(:open_order_cycle, distributors: [d], variants: []) }
let!(:ii) { create(:inventory_item, enterprise: d, variant: v) }
it "updates each distribution for that enterprise+variant" do
expect(ProductsCache).to receive(:refresh_cache).with(d, oc1)
ProductsCache.inventory_item_changed ii
end
it "doesn't update distributions that don't feature the variant" do
oc2
expect(ProductsCache).to receive(:refresh_cache).with(d, oc2).never
ProductsCache.inventory_item_changed ii
end
end
describe "refreshing the cache" do
let(:distributor) { double(:distributor) }
let(:order_cycle) { double(:order_cycle) }
it "notifies ProductsCacheRefreshment" do
expect(ProductsCacheRefreshment).to receive(:refresh).with(distributor, order_cycle)
ProductsCache.send(:refresh_cache, distributor, order_cycle)
end
end
end
end

View File

@@ -1,19 +0,0 @@
require 'spec_helper'
describe CoordinatorFee do
describe "products caching" do
let(:order_cycle) { create(:simple_order_cycle) }
let(:enterprise_fee) { create(:enterprise_fee) }
it "refreshes the products cache on change" do
expect(OpenFoodNetwork::ProductsCache).to receive(:order_cycle_changed).with(order_cycle)
order_cycle.coordinator_fees << enterprise_fee
end
it "refreshes the products cache on destruction" do
order_cycle.coordinator_fees << enterprise_fee
expect(OpenFoodNetwork::ProductsCache).to receive(:order_cycle_changed).with(order_cycle)
order_cycle.coordinator_fee_refs.first.destroy
end
end
end

View File

@@ -12,12 +12,6 @@ describe EnterpriseFee do
describe "callbacks" do
let(:ef) { create(:enterprise_fee) }
it "refreshes the products cache when saved" do
expect(OpenFoodNetwork::ProductsCache).to receive(:enterprise_fee_changed).with(ef)
ef.name = 'foo'
ef.save
end
it "removes itself from order cycle coordinator fees when destroyed" do
oc = create(:simple_order_cycle, coordinator_fees: [ef])

View File

@@ -1,19 +0,0 @@
require 'spec_helper'
describe ExchangeFee do
describe "products caching" do
let(:exchange) { create(:exchange) }
let(:enterprise_fee) { create(:enterprise_fee) }
it "refreshes the products cache on change" do
expect(OpenFoodNetwork::ProductsCache).to receive(:exchange_changed).with(exchange)
exchange.enterprise_fees << enterprise_fee
end
it "refreshes the products cache on destruction" do
exchange.enterprise_fees << enterprise_fee
expect(OpenFoodNetwork::ProductsCache).to receive(:exchange_changed).with(exchange)
exchange.reload.exchange_fees.destroy_all
end
end
end

View File

@@ -91,21 +91,6 @@ describe Exchange do
end
end
describe "products caching" do
let!(:exchange) { create(:exchange) }
it "refreshes the products cache on change" do
expect(OpenFoodNetwork::ProductsCache).to receive(:exchange_changed).with(exchange)
exchange.pickup_time = 'asdf'
exchange.save
end
it "refreshes the products cache on destruction" do
expect(OpenFoodNetwork::ProductsCache).to receive(:exchange_destroyed).with(exchange)
exchange.destroy
end
end
describe "scopes" do
let(:supplier) { create(:supplier_enterprise) }
let(:coordinator) { create(:distributor_enterprise, is_primary_producer: true) }

View File

@@ -1,16 +0,0 @@
require 'spec_helper'
require 'open_food_network/products_cache'
describe InventoryItem do
describe "caching" do
let(:ii) { create(:inventory_item) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:inventory_item_changed).with(ii)
ii.visible = false
ii.save
end
# Inventory items are not destroyed
end
end

View File

@@ -27,18 +27,6 @@ describe OrderCycle do
oc.save!
end
describe "products cache" do
let(:oc) { create(:open_order_cycle) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:order_cycle_changed).with(oc)
oc.name = 'asdf'
oc.save
end
# On destroy, we're removing distributions, so no updates to the products cache are required
end
it "has exchanges" do
oc = create(:simple_order_cycle)

View File

@@ -71,17 +71,4 @@ describe ProducerProperty do
end
end
end
describe "products caching" do
it "refreshes the products cache on change" do
expect(OpenFoodNetwork::ProductsCache).to receive(:producer_property_changed).with(pp)
pp.value = 123
pp.save
end
it "refreshes the products cache on destruction" do
expect(OpenFoodNetwork::ProductsCache).to receive(:producer_property_destroyed).with(pp)
pp.destroy
end
end
end

View File

@@ -11,18 +11,5 @@ module Spree
expect(classification.destroy).to be false
expect(classification.errors.messages[:base]).to eq(["Taxon #{taxon.name} is the primary taxon of #{product.name} and cannot be deleted"])
end
describe "callbacks" do
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
classification
end
it "refreshes the products cache on destroy" do
classification
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
classification.destroy
end
end
end
end

View File

@@ -14,23 +14,5 @@ module Spree
expect(Image.format_styles(formatted)).to eq(mini: ["48x48>", :png])
end
end
describe "callbacks" do
let!(:product) { create(:simple_product) }
let!(:image_file) { File.open("#{Rails.root}/app/assets/images/logo-white.png") }
let!(:image) { Image.create(viewable_id: product.master.id, viewable_type: 'Spree::Variant', alt: "image", attachment: image_file) }
it "refreshes the products cache when changed" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
image.alt = 'asdf'
image.save
end
it "refreshes the products cache when destroyed" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
image.destroy
end
end
end
end

View File

@@ -1,28 +0,0 @@
require 'spec_helper'
module Spree
describe OptionType do
describe "products cache" do
let!(:product) { create(:simple_product, option_types: [option_type]) }
let(:variant) { product.variants.first }
let(:option_type) { create(:option_type) }
let(:option_value) { create(:option_value, option_type: option_type) }
before do
option_type.reload
variant.option_values << option_value
end
it "refreshes the products cache on change, via product" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
option_type.name = 'foo'
option_type.save!
end
it "refreshes the products cache on destruction, via option value destruction" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_changed).with(variant)
option_type.destroy
end
end
end
end

View File

@@ -1,26 +0,0 @@
require 'spec_helper'
module Spree
describe OptionValue do
describe "products cache" do
let(:variant) { create(:variant) }
let(:option_value) { create(:option_value) }
before do
variant.option_values << option_value
option_value.reload
end
it "refreshes the products cache on change, via variant" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_changed).with(variant)
option_value.name = 'foo'
option_value.save!
end
it "refreshes the products cache on destruction, via variant" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_changed).with(variant)
option_value.destroy
end
end
end
end

View File

@@ -1,23 +0,0 @@
require 'spec_helper'
module Spree
describe Preference do
describe "refreshing the products cache" do
it "reports when product_selection_from_inventory_only has changed" do
p = Preference.new(key: 'enterprise/product_selection_from_inventory_only/123')
expect(p.send(:product_selection_from_inventory_only_changed?)).to be true
end
it "reports when product_selection_from_inventory_only has not changed" do
p = Preference.new(key: 'enterprise/shopfront_message/123')
expect(p.send(:product_selection_from_inventory_only_changed?)).to be false
end
it "looks up the referenced enterprise" do
e = create(:distributor_enterprise)
p = Preference.new(key: "enterprise/product_selection_from_inventory_only/#{e.id}")
expect(p.send(:enterprise)).to eql e
end
end
end
end

View File

@@ -5,23 +5,6 @@ module Spree
let(:variant) { create(:variant) }
let(:price) { variant.default_price }
describe "callbacks" do
it "refreshes the products cache on change" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_changed).with(variant)
price.amount = 123
price.save
end
# Do not refresh on price destruction - this (only?) happens when variant is destroyed,
# and in that case the variant will take responsibility for refreshing the cache
it "does not refresh the cache when variant is not set" do
# Creates a price without the back link to variant
create(:product, master: create(:variant))
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_changed).never
end
end
context "when variant is soft-deleted" do
before do
variant.destroy

View File

@@ -1,21 +0,0 @@
require 'spec_helper'
module Spree
describe ProductProperty do
describe "callbacks" do
let(:product) { product_property.product }
let(:product_property) { create(:product_property) }
it "refreshes the products cache on save, via Product" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
product_property.value = 123
product_property.save
end
it "refreshes the products cache on destroy" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
product_property.destroy
end
end
end
end

View File

@@ -172,20 +172,6 @@ module Spree
describe "callbacks" do
let(:product) { create(:simple_product) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
product.name = 'asdf'
product.save
end
it "refreshes the products cache on delete" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_deleted).with(product)
product.destroy
end
# On destroy, all distributed variants are refreshed by a Variant around_destroy
# callback, so we don't need to do anything on the product model.
describe "touching affected enterprises when the product is deleted" do
let(:product) { create(:simple_product) }
let(:supplier) { product.supplier }

View File

@@ -95,17 +95,5 @@ module Spree
end
end
end
describe "callbacks" do
let(:property) { product_property.property }
let(:product) { product_property.product }
let(:product_property) { create(:product_property) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
property.name = 'asdf'
property.save
end
end
end
end

View File

@@ -6,21 +6,6 @@ module Spree
let!(:t1) { create(:taxon) }
let!(:t2) { create(:taxon) }
describe "callbacks" do
let!(:p2) { create(:simple_product, taxons: [t1], primary_taxon: t2) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(p2)
t1.name = 'asdf'
t1.save
end
it "refreshes the products cache on destroy" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(p2)
t1.destroy
end
end
describe "finding all supplied taxons" do
let!(:p1) { create(:simple_product, supplier: e, taxons: [t1, t2]) }

View File

@@ -1,6 +1,5 @@
require 'spec_helper'
require 'open_food_network/option_value_namer'
require 'open_food_network/products_cache'
module Spree
describe Variant do
@@ -165,39 +164,6 @@ module Spree
end
end
describe "callbacks" do
let(:variant) { create(:variant) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_changed).with(variant)
variant.sku = 'abc123'
variant.save
end
it "refreshes the products cache on destroy" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
variant.destroy
end
context "when it is the master variant" do
let(:product) { create(:simple_product) }
let(:master) { product.master }
it "refreshes the products cache for the entire product on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_changed).never
master.sku = 'abc123'
master.save
end
it "refreshes the products cache for the entire product on destroy" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product)
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).never
master.destroy
end
end
end
describe "indexing variants by id" do
let!(:v1) { create(:variant) }
let!(:v2) { create(:variant) }

View File

@@ -113,21 +113,6 @@ describe VariantOverride do
end
end
describe "callbacks" do
let!(:vo) { create(:variant_override, hub: hub, variant: variant) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_override_changed).with(vo)
vo.price = 123.45
vo.save
end
it "refreshes the products cache on destroy" do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_override_destroyed).with(vo)
vo.destroy
end
end
describe "delegated price" do
let!(:variant_with_price) { create(:variant, price: 123.45) }
let(:price_object) { variant_with_price.default_price }