From 99cb09c6f75bfc0889ccac2e9ec09f6958e45b44 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 20 May 2015 14:05:41 +1000 Subject: [PATCH 01/28] When loading products for shopfront, load all variants in one go --- app/controllers/shop_controller.rb | 28 ++++++++++++++++++--- app/serializers/api/product_serializer.rb | 14 +++-------- spec/controllers/shop_controller_spec.rb | 14 +++++++++++ spec/serializers/product_serializer_spec.rb | 14 ----------- 4 files changed, 42 insertions(+), 28 deletions(-) delete mode 100644 spec/serializers/product_serializer_spec.rb diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 73861def5c..35aece5e1d 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -10,12 +10,15 @@ class ShopController < BaseController end def products - # Can we make this query less slow? - # if @products = products_for_shop + render status: 200, - json: ActiveModel::ArraySerializer.new(@products, each_serializer: Api::ProductSerializer, - current_order_cycle: current_order_cycle, current_distributor: current_distributor).to_json + json: ActiveModel::ArraySerializer.new(@products, + each_serializer: Api::ProductSerializer, + current_order_cycle: current_order_cycle, + current_distributor: current_distributor, + variants: variants_for_shop_by_id).to_json + else render json: "", status: 404 end @@ -46,6 +49,23 @@ class ShopController < BaseController end end + def variants_for_shop_by_id + # We use the in_stock? method here instead of the in_stock scope because we need to + # look up the stock as overridden by VariantOverrides, and the scope method is not affected + # by them. + variants = Spree::Variant. + where(is_master: false). + for_distribution(current_order_cycle, current_distributor). + each { |v| v.scope_to_hub current_distributor }. + select(&:in_stock?) + + variants.inject({}) do |vs, v| + vs[v.product_id] ||= [] + vs[v.product_id] << v + vs + end + end + def taxon_order if current_distributor.preferred_shopfront_taxon_order.present? current_distributor diff --git a/app/serializers/api/product_serializer.rb b/app/serializers/api/product_serializer.rb index 0de794796b..b707aa281c 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -30,8 +30,9 @@ class Api::CachedProductSerializer < ActiveModel::Serializer #cached #delegate :cache_key, to: :object - attributes :id, :name, :permalink, :count_on_hand, :on_demand, :group_buy, - :notes, :description, :properties_with_values + attributes :id, :name, :permalink, :count_on_hand + attributes :on_demand, :group_buy, :notes, :description + attributes :properties_with_values has_many :variants, serializer: Api::VariantSerializer has_many :taxons, serializer: Api::IdSerializer @@ -46,13 +47,6 @@ class Api::CachedProductSerializer < ActiveModel::Serializer end def variants - # We use the in_stock? method here instead of the in_stock scope because we need to - # look up the stock as overridden by VariantOverrides, and the scope method is not affected - # by them. - - object.variants. - for_distribution(options[:current_order_cycle], options[:current_distributor]). - each { |v| v.scope_to_hub options[:current_distributor] }. - select(&:in_stock?) + options[:variants][object.id] || [] end end diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index 4b7c53da19..10f0eed2cd 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -175,4 +175,18 @@ describe ShopController do end end end + + describe "loading variants" do + let(:hub) { create(:distributor_enterprise) } + let(:oc) { create(:simple_order_cycle, distributors: [hub], variants: [v1]) } + let(:p) { create(:simple_product) } + let!(:v1) { create(:variant, product: p, unit_value: 3) } + let!(:v2) { create(:variant, product: p, unit_value: 5) } + + it "scopes variants to distribution" do + controller.stub(:current_order_cycle) { oc } + controller.stub(:current_distributor) { hub } + controller.send(:variants_for_shop_by_id).should == {p.id => [v1]} + end + end end diff --git a/spec/serializers/product_serializer_spec.rb b/spec/serializers/product_serializer_spec.rb deleted file mode 100644 index 0091668dbb..0000000000 --- a/spec/serializers/product_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -describe Api::ProductSerializer do - let(:hub) { create(:distributor_enterprise) } - let(:oc) { create(:simple_order_cycle, distributors: [hub], variants: [v1]) } - let(:p) { create(:simple_product) } - let!(:v1) { create(:variant, product: p, unit_value: 3) } - let!(:v2) { create(:variant, product: p, unit_value: 5) } - - it "scopes variants to distribution" do - s = Api::ProductSerializer.new p, current_distributor: hub, current_order_cycle: oc - json = s.to_json - json.should include v1.options_text - json.should_not include v2.options_text - end -end From c5f00d87bd3b63a750f5282583084b84e6e31df6 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 20 May 2015 15:22:01 +1000 Subject: [PATCH 02/28] When loading products for shopfront, load all master variants in one go --- app/controllers/shop_controller.rb | 46 ++++++++++++++--------- app/serializers/api/product_serializer.rb | 5 +++ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 35aece5e1d..0655624a0b 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -17,7 +17,8 @@ class ShopController < BaseController each_serializer: Api::ProductSerializer, current_order_cycle: current_order_cycle, current_distributor: current_distributor, - variants: variants_for_shop_by_id).to_json + variants: variants_for_shop_by_id, + master_variants: master_variants_for_shop_by_id).to_json else render json: "", status: 404 @@ -49,23 +50,6 @@ class ShopController < BaseController end end - def variants_for_shop_by_id - # We use the in_stock? method here instead of the in_stock scope because we need to - # look up the stock as overridden by VariantOverrides, and the scope method is not affected - # by them. - variants = Spree::Variant. - where(is_master: false). - for_distribution(current_order_cycle, current_distributor). - each { |v| v.scope_to_hub current_distributor }. - select(&:in_stock?) - - variants.inject({}) do |vs, v| - vs[v.product_id] ||= [] - vs[v.product_id] << v - vs - end - end - def taxon_order if current_distributor.preferred_shopfront_taxon_order.present? current_distributor @@ -76,4 +60,30 @@ class ShopController < BaseController "name ASC" end end + + def all_variants_for_shop + # We use the in_stock? method here instead of the in_stock scope because we need to + # look up the stock as overridden by VariantOverrides, and the scope method is not affected + # by them. + Spree::Variant. + for_distribution(current_order_cycle, current_distributor). + each { |v| v.scope_to_hub current_distributor }. + select(&:in_stock?) + end + + def variants_for_shop_by_id + index_by_product_id all_variants_for_shop.reject(&:is_master) + end + + def master_variants_for_shop_by_id + index_by_product_id all_variants_for_shop.select(&:is_master) + end + + def index_by_product_id(variants) + variants.inject({}) do |vs, v| + vs[v.product_id] ||= [] + vs[v.product_id] << v + vs + end + end end diff --git a/app/serializers/api/product_serializer.rb b/app/serializers/api/product_serializer.rb index b707aa281c..4c4aec2310 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -49,4 +49,9 @@ class Api::CachedProductSerializer < ActiveModel::Serializer def variants options[:variants][object.id] || [] end + + def master + options[:master_variants][object.id].andand.first + end + end From 769edbe9d52aebbb6cef71c3a06e90069f789b29 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:04:59 +1000 Subject: [PATCH 03/28] Find the earliest closing times for each distributor in an active order cycle --- app/models/order_cycle.rb | 16 +++++++++++++++- spec/models/order_cycle_spec.rb | 31 +++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 88e4953edc..dfd45230b6 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -92,11 +92,25 @@ class OrderCycle < ActiveRecord::Base with_distributor(distributor).soonest_closing.first end - def self.most_recently_closed_for(distributor) with_distributor(distributor).most_recently_closed.first end + # Find the earliest closing times for each distributor in an active order cycle, and return + # them in the format {distributor_id => closing_time, ...} + def self.earliest_closing_times + Hash[ + Exchange. + outgoing. + joins(:order_cycle). + merge(OrderCycle.active). + group('exchanges.receiver_id'). + select('exchanges.receiver_id AS receiver_id, MIN(order_cycles.orders_close_at) AS earliest_close_at'). + map { |ex| [ex.receiver_id, ex.earliest_close_at.to_time] } + ] + end + + def clone! oc = self.dup oc.name = "COPY OF #{oc.name}" diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 817d2e8e93..507f9977b1 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -313,7 +313,7 @@ describe OrderCycle do @oc.pickup_time_for(@d2).should == '2-8pm Friday' end end - + describe "finding pickup instructions for a distributor" do it "returns the pickup instructions" do @oc.pickup_instructions_for(@d1).should == "Come get it!" @@ -375,7 +375,7 @@ describe OrderCycle do occ.coordinator_fee_ids.should_not be_empty occ.coordinator_fee_ids.should == oc.coordinator_fee_ids - + # to_h gives us a unique hash for each exchange # check that the clone has no additional exchanges occ.exchanges.map(&:to_h).all? do |ex| @@ -402,7 +402,7 @@ describe OrderCycle do describe "finding order cycles opening in the future" do it "should give the soonest opening order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) OrderCycle.first_opening_for(distributor).should == oc end @@ -411,13 +411,32 @@ describe OrderCycle do OrderCycle.first_opening_for(distributor).should == nil end end - + describe "finding open order cycles" do it "should give the soonest closing order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) - oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) + oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) OrderCycle.first_closing_for(distributor).should == oc end end + + describe "finding the earliest closing times for each distributor" do + let(:time1) { 1.week.from_now } + let(:time2) { 2.weeks.from_now } + let(:time3) { 3.weeks.from_now } + let(:e1) { create(:distributor_enterprise) } + let(:e2) { create(:distributor_enterprise) } + let!(:oc1) { create(:simple_order_cycle, orders_close_at: time1, distributors: [e1]) } + let!(:oc2) { create(:simple_order_cycle, orders_close_at: time2, distributors: [e2]) } + let!(:oc3) { create(:simple_order_cycle, orders_close_at: time3, distributors: [e2]) } + + it "returns the closing time, indexed by enterprise id" do + OrderCycle.earliest_closing_times[e1.id].should == time1 + end + + it "returns the earliest closing time" do + OrderCycle.earliest_closing_times[e2.id].should == time2 + end + end end From f940984ca311313e3457da6ec98eb5c044c609f0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:07:11 +1000 Subject: [PATCH 04/28] Pull earliest closing time computations out of the serialization loop --- app/helpers/injection_helper.rb | 2 +- app/serializers/api/enterprise_serializer.rb | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 37794cef9d..a17d6e81f9 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,6 @@ module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors + inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times end def inject_current_order diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 532887ae01..626fe77cad 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -18,14 +18,12 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer attributes :orders_close_at, :active def orders_close_at - OrderCycle.first_closing_for(object).andand.orders_close_at + options[:earliest_closing_times][object.id] end def active - @options[:active_distributors].andand.include? object + options[:active_distributors].andand.include? object end - - end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer From f0e909c92bc7a029ca9fb607accfaa9b4799c871 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:38:33 +1000 Subject: [PATCH 05/28] Look up the shipping services (pickup, delivery) that different hubs provide --- app/models/spree/shipping_method_decorator.rb | 16 +++++++++++ spec/models/spree/shipping_method_spec.rb | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/app/models/spree/shipping_method_decorator.rb b/app/models/spree/shipping_method_decorator.rb index 4a2cf75b47..b8be603048 100644 --- a/app/models/spree/shipping_method_decorator.rb +++ b/app/models/spree/shipping_method_decorator.rb @@ -25,6 +25,22 @@ Spree::ShippingMethod.class_eval do scope :by_name, order('spree_shipping_methods.name ASC') + + # Return the services (pickup, delivery) that different distributors provide, in the format: + # {distributor_id => {pickup: true, delivery: false}, ...} + def self.services + Hash[ + Spree::ShippingMethod. + joins(:distributor_shipping_methods). + group('distributor_id'). + select("distributor_id"). + select("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup"). + select("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery"). + map { |sm| [sm.distributor_id.to_i, {pickup: sm.pickup == 't', delivery: sm.delivery == 't'}] } + ] + end + + def available_to_order_with_distributor_check?(order, display_on=nil) available_to_order_without_distributor_check?(order, display_on) && self.distributors.include?(order.distributor) diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index d6b821e890..8b7f397191 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -55,5 +55,32 @@ module Spree sm.should be_available_to_order o end end + + describe "finding services offered by all distributors" do + let!(:d1) { create(:distributor_enterprise) } + let!(:d2) { create(:distributor_enterprise) } + let!(:d3) { create(:distributor_enterprise) } + let!(:d4) { create(:distributor_enterprise) } + let!(:d1_pickup) { create(:shipping_method, require_ship_address: false, distributors: [d1]) } + let!(:d1_delivery) { create(:shipping_method, require_ship_address: true, distributors: [d1]) } + let!(:d2_pickup) { create(:shipping_method, require_ship_address: false, distributors: [d2]) } + let!(:d3_delivery) { create(:shipping_method, require_ship_address: true, distributors: [d3]) } + + it "reports when the services are available" do + ShippingMethod.services[d1.id].should == {pickup: true, delivery: true} + end + + it "reports when only pickup is available" do + ShippingMethod.services[d2.id].should == {pickup: true, delivery: false} + end + + it "reports when only delivery is available" do + ShippingMethod.services[d3.id].should == {pickup: false, delivery: true} + end + + it "returns no entry when no service is available" do + ShippingMethod.services[d4.id].should be_nil + end + end end end From ee8db23fd99e08dc4037b8feef03079253276e77 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:40:04 +1000 Subject: [PATCH 06/28] Pull shipping method service computations out of the serialization loop --- app/helpers/injection_helper.rb | 2 +- app/serializers/api/enterprise_serializer.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index a17d6e81f9..39831711ff 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,6 @@ module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times + inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times, shipping_method_services: Spree::ShippingMethod.services end def inject_current_order diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 626fe77cad..f7bd06f239 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -42,11 +42,13 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer has_one :address, serializer: Api::AddressSerializer def pickup - object.shipping_methods.where(:require_ship_address => false).present? + services = options[:shipping_method_services][object.id] + services ? services[:pickup] : false end def delivery - object.shipping_methods.where(:require_ship_address => true).present? + services = options[:shipping_method_services][object.id] + services ? services[:delivery] : false end def email From 704955a1854fbf2739779e5a5a6f6f2845535bba Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 15:47:52 +1000 Subject: [PATCH 07/28] Load active distributors where they're needed rather than in most controllers --- app/controllers/base_controller.rb | 3 --- app/controllers/checkout_controller.rb | 3 --- app/controllers/enterprises_controller.rb | 2 +- app/controllers/groups_controller.rb | 1 - app/controllers/home_controller.rb | 4 +--- app/controllers/map_controller.rb | 1 - app/controllers/producers_controller.rb | 3 +-- app/helpers/injection_helper.rb | 18 +++++++++++++++++- app/views/groups/show.html.haml | 6 +++--- spec/controllers/base_controller_spec.rb | 5 ----- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index 1d74df5706..392e2fef64 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -12,9 +12,6 @@ class BaseController < ApplicationController before_filter :check_order_cycle_expiry - def load_active_distributors - @active_distributors ||= Enterprise.distributors_with_active_order_cycles - end private diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 0c42ceb987..11be28ef04 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -12,9 +12,6 @@ class CheckoutController < Spree::CheckoutController include EnterprisesHelper def edit - # Because this controller doesn't inherit from our BaseController - # We need to duplicate the code here - @active_distributors ||= Enterprise.distributors_with_active_order_cycles end def update diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index 097139556c..75ad5c475b 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -4,7 +4,7 @@ class EnterprisesController < BaseController include OrderCyclesHelper # These prepended filters are in the reverse order of execution - prepend_before_filter :load_active_distributors, :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop + prepend_before_filter :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop before_filter :clean_permalink, only: :check_permalink respond_to :js, only: :permalink_checker diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 8653131b5a..6930632966 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,6 +1,5 @@ class GroupsController < BaseController layout 'darkswarm' - before_filter :load_active_distributors def index @groups = EnterpriseGroup.on_front_page.by_position diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 76e179ed22..3bb7a68538 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,11 +1,9 @@ class HomeController < BaseController layout 'darkswarm' - before_filter :load_active_distributors - + def index end def about_us end end - diff --git a/app/controllers/map_controller.rb b/app/controllers/map_controller.rb index a980ba8f40..46a6f5852a 100644 --- a/app/controllers/map_controller.rb +++ b/app/controllers/map_controller.rb @@ -1,6 +1,5 @@ class MapController < BaseController layout 'darkswarm' - before_filter :load_active_distributors def index end diff --git a/app/controllers/producers_controller.rb b/app/controllers/producers_controller.rb index b101a95b7f..42d1d401e5 100644 --- a/app/controllers/producers_controller.rb +++ b/app/controllers/producers_controller.rb @@ -1,7 +1,6 @@ class ProducersController < BaseController layout 'darkswarm' - before_filter :load_active_distributors - + def index end end diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 39831711ff..2229888f98 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,10 @@ module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times, shipping_method_services: Spree::ShippingMethod.services + inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, enterprise_injection_data + end + + def inject_group_enterprises + inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data end def inject_current_order @@ -53,4 +57,16 @@ module InjectionHelper end render partial: "json/injection_ams", locals: {name: name, json: json} end + + + private + + def enterprise_injection_data + @active_distributors ||= Enterprise.distributors_with_active_order_cycles + @earliest_closing_times ||= OrderCycle.earliest_closing_times + @shipping_method_services ||= Spree::ShippingMethod.services + + {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services} + end + end diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 1bc965dfb3..80101d9e8c 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -3,8 +3,8 @@ = inject_enterprises -# inject enterprises in this group --# further hubs and producers of these enterprises can't be resoleved within this small subset -= inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, active_distributors: @active_distributors +-# further hubs and producers of these enterprises can't be resolved within this small subset += inject_group_enterprises #group-page.row.pad-top{"ng-controller" => "GroupPageCtrl"} .small-12.columns.pad-top @@ -95,7 +95,7 @@ = render partial: 'home/fat' = render partial: 'shared/components/enterprise_no_results' - + .small-12.medium-12.large-3.columns = render partial: 'contact' diff --git a/spec/controllers/base_controller_spec.rb b/spec/controllers/base_controller_spec.rb index 1040b0594c..b5ef006c5b 100644 --- a/spec/controllers/base_controller_spec.rb +++ b/spec/controllers/base_controller_spec.rb @@ -24,9 +24,4 @@ describe BaseController do response.should redirect_to root_url flash[:info].should == "The order cycle you've selected has just closed. Please try again!" end - - it "loads active_distributors" do - Enterprise.stub(:distributors_with_active_order_cycles) { 'active distributors' } - controller.load_active_distributors.should == 'active distributors' - end end From 4a59c85b3e83bcf02837c3665f911f90f2072411 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 15:48:40 +1000 Subject: [PATCH 08/28] Inject current hub from AMS rather than RABL --- app/helpers/injection_helper.rb | 4 ++++ app/views/json/_current_hub.rabl | 6 ------ app/views/layouts/darkswarm.html.haml | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 app/views/json/_current_hub.rabl diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 2229888f98..a0aa2f87f1 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -7,6 +7,10 @@ module InjectionHelper inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data end + def inject_current_hub + inject_json_ams "currentHub", current_distributor, Api::EnterpriseSerializer, enterprise_injection_data + end + def inject_current_order inject_json_ams "currentOrder", current_order, Api::CurrentOrderSerializer, current_distributor: current_distributor, current_order_cycle: current_order_cycle end diff --git a/app/views/json/_current_hub.rabl b/app/views/json/_current_hub.rabl deleted file mode 100644 index 103baf9fb3..0000000000 --- a/app/views/json/_current_hub.rabl +++ /dev/null @@ -1,6 +0,0 @@ -object current_distributor -extends 'json/partials/enterprise' - -child suppliers: :producers do - attributes :id -end diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index a627ba4896..5169efa942 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -24,7 +24,7 @@ = render partial: "shared/ie_warning" = javascript_include_tag "iehack" - = inject_json "currentHub", "current_hub" + = inject_current_hub = inject_json "user", "current_user" = inject_json "railsFlash", "flash" = inject_taxons From cf79b90044c219757dc5e2044b9ea1e70944e3b1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 16:24:13 +1000 Subject: [PATCH 09/28] Load relatives of all enterprises in one go --- app/models/enterprise_relationship.rb | 22 +++++++++++++++++++++ spec/models/enterprise_relationship_spec.rb | 12 +++++++++++ 2 files changed, 34 insertions(+) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index fbdef9d52c..22d04a6cd1 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -25,6 +25,28 @@ class EnterpriseRelationship < ActiveRecord::Base scope :by_name, with_enterprises.order('child_enterprises.name, parent_enterprises.name') + # Load an array of the relatives of each enterprise (ie. any enterprise related to it in + # either direction). This array is split into distributors and producers, and has the format: + # {enterprise_id => {distributors: [id, ...], producers: [id, ...]} } + def self.relatives + relationships = EnterpriseRelationship.includes(:child, :parent) + relatives = {} + + relationships.each do |r| + relatives[r.parent_id] ||= {distributors: [], producers: []} + relatives[r.child_id] ||= {distributors: [], producers: []} + + relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer + relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor + + relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer + relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor + end + + relatives + end + + def permissions_list=(perms) perms.andand.each { |name| permissions.build name: name } end diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index e87c204037..cb1743dec1 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -69,4 +69,16 @@ describe EnterpriseRelationship do EnterpriseRelationship.with_permission('two').sort.should == [er1, er2].sort end end + + describe "finding relatives" do + let(:e1) { create(:supplier_enterprise) } + let(:e2) { create(:supplier_enterprise, sells: 'any') } + let!(:er) { create(:enterprise_relationship, parent: e1, child: e2) } + + it "categorises enterprises into distributors and producers" do + EnterpriseRelationship.relatives.should == + {e1.id => {distributors: [e2.id], producers: [e2.id]}, + e2.id => {distributors: [], producers: [e1.id]}} + end + end end From 3afd636577ad2348687a098b01c0dec4d457126e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 16:25:05 +1000 Subject: [PATCH 10/28] Pull relatives computation out of the serialization loop --- app/helpers/injection_helper.rb | 3 ++- app/serializers/api/enterprise_serializer.rb | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index a0aa2f87f1..1d5f4d19e2 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -69,8 +69,9 @@ module InjectionHelper @active_distributors ||= Enterprise.distributors_with_active_order_cycles @earliest_closing_times ||= OrderCycle.earliest_closing_times @shipping_method_services ||= Spree::ShippingMethod.services + @relatives ||= EnterpriseRelationship.relatives - {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services} + {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives} end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index f7bd06f239..a97544b3ee 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -72,11 +72,13 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer end def producers - ActiveModel::ArraySerializer.new(object.suppliers.activated, {each_serializer: Api::IdSerializer}) + relatives = options[:relatives][object.id] + relatives ? relatives[:producers] : [] end def hubs - ActiveModel::ArraySerializer.new(object.distributors.activated, {each_serializer: Api::IdSerializer}) + relatives = options[:relatives][object.id] + relatives ? relatives[:distributors] : [] end # Map svg icons. From 2c92b5a7516b888d87583ad6e35a876bc583d504 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 10:57:14 +1000 Subject: [PATCH 11/28] Find all supplied and distributed taxons --- app/models/spree/taxon_decorator.rb | 36 +++++++++++++++++++++++++++++ spec/models/spree/taxon_spec.rb | 28 ++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 spec/models/spree/taxon_spec.rb diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb index 10ee0b4719..1a26ce73a8 100644 --- a/app/models/spree/taxon_decorator.rb +++ b/app/models/spree/taxon_decorator.rb @@ -9,4 +9,40 @@ Spree::Taxon.class_eval do #fs << Spree::ProductFilters.distributor_filter if Spree::ProductFilters.respond_to? :distributor_filter fs end + + # Find all the taxons of supplied products for each enterprise, indexed by enterprise. + # Format: {enterprise_id => [taxon_id, ...]} + def self.supplied_taxons + taxons = {} + + Spree::Taxon. + joins(:products => :supplier). + select('spree_taxons.*, enterprises.id AS enterprise_id'). + each do |t| + + taxons[t.enterprise_id.to_i] ||= Set.new + taxons[t.enterprise_id.to_i] << t.id + end + + taxons + end + + # Find all the taxons of distributed products for each enterprise, indexed by enterprise. + # Format: {enterprise_id => [taxon_id, ...]} + def self.distributed_taxons + taxons = {} + + Spree::Taxon. + joins(:products). + merge(Spree::Product.with_order_cycles_outer). + where('o_exchanges.incoming = ?', false). + select('spree_taxons.*, o_exchanges.receiver_id AS enterprise_id'). + each do |t| + + taxons[t.enterprise_id.to_i] ||= Set.new + taxons[t.enterprise_id.to_i] << t.id + end + + taxons + end end diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb new file mode 100644 index 0000000000..a0d729c054 --- /dev/null +++ b/spec/models/spree/taxon_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +module Spree + describe Taxon do + let(:e) { create(:supplier_enterprise) } + let(:t0) { p1.taxons.order('id ASC').first } + let(:t1) { create(:taxon) } + let(:t2) { create(:taxon) } + + describe "finding all supplied taxons" do + let!(:p1) { create(:simple_product, supplier: e, taxons: [t1, t2]) } + + it "finds taxons" do + Taxon.supplied_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])} + end + end + + describe "finding all distributed taxons" do + let!(:oc) { create(:simple_order_cycle, distributors: [e], variants: [p1.master]) } + let(:s) { create(:supplier_enterprise) } + let(:p1) { create(:simple_product, supplier: s, taxons: [t1, t2]) } + + it "finds taxons" do + Taxon.distributed_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])} + end + end + end +end From 1a887df4127d932f2f0f40d54a4ffc9f416aa65b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 11:03:53 +1000 Subject: [PATCH 12/28] Pull taxon computation out of the serialization loop --- app/helpers/injection_helper.rb | 4 +++- app/serializers/api/enterprise_serializer.rb | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 1d5f4d19e2..c2e4e6c91a 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -70,8 +70,10 @@ module InjectionHelper @earliest_closing_times ||= OrderCycle.earliest_closing_times @shipping_method_services ||= Spree::ShippingMethod.services @relatives ||= EnterpriseRelationship.relatives + @supplied_taxons ||= Spree::Taxon.supplied_taxons + @distributed_taxons ||= Spree::Taxon.distributed_taxons - {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives} + {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives, supplied_taxons: @supplied_taxons, distributed_taxons: @distributed_taxons} end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index a97544b3ee..d15ee7860a 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -36,11 +36,18 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer :email, :hash, :logo, :promo_image, :path, :pickup, :delivery, :icon, :icon_font, :producer_icon_font, :category, :producers, :hubs - has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer - has_many :supplied_taxons, serializer: Api::IdSerializer + attributes :taxons, :supplied_taxons has_one :address, serializer: Api::AddressSerializer + def taxons + options[:distributed_taxons][object.id] + end + + def supplied_taxons + options[:supplied_taxons][object.id] + end + def pickup services = options[:shipping_method_services][object.id] services ? services[:pickup] : false From dd761719eea337c807645ae32a3f7909a81f2e54 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 11:27:47 +1000 Subject: [PATCH 13/28] Fix undefined Api::IdSerializer error --- app/serializers/api/enterprise_serializer.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index d15ee7860a..6f46d62eda 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -1,4 +1,7 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer + # We reference this here because otherwise the serializer complains about its absence + Api::IdSerializer + def serializable_hash cached_serializer_hash.merge uncached_serializer_hash end @@ -40,6 +43,7 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer has_one :address, serializer: Api::AddressSerializer + def taxons options[:distributed_taxons][object.id] end From 31b726613d5116dc2a1f615aa0f16f55d6f58559 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 12:17:09 +1000 Subject: [PATCH 14/28] Avoid loading enterprise injection data when it's not be needed due to caching --- app/helpers/injection_helper.rb | 14 ++++------ app/serializers/api/enterprise_serializer.rb | 16 +++++------ .../enterprise_injection_data.rb | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 lib/open_food_network/enterprise_injection_data.rb diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index c2e4e6c91a..05057c136b 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,8 @@ +require 'open_food_network/enterprise_injection_data' + module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, enterprise_injection_data + inject_json_ams "enterprises", Enterprise.activated.includes(:address).all, Api::EnterpriseSerializer, enterprise_injection_data end def inject_group_enterprises @@ -66,14 +68,8 @@ module InjectionHelper private def enterprise_injection_data - @active_distributors ||= Enterprise.distributors_with_active_order_cycles - @earliest_closing_times ||= OrderCycle.earliest_closing_times - @shipping_method_services ||= Spree::ShippingMethod.services - @relatives ||= EnterpriseRelationship.relatives - @supplied_taxons ||= Spree::Taxon.supplied_taxons - @distributed_taxons ||= Spree::Taxon.distributed_taxons - - {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives, supplied_taxons: @supplied_taxons, distributed_taxons: @distributed_taxons} + @enterprise_injection_data ||= OpenFoodNetwork::EnterpriseInjectionData.new + {data: @enterprise_injection_data} end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 6f46d62eda..4f38c12964 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -21,11 +21,11 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer attributes :orders_close_at, :active def orders_close_at - options[:earliest_closing_times][object.id] + options[:data].earliest_closing_times[object.id] end def active - options[:active_distributors].andand.include? object + options[:data].active_distributors.andand.include? object end end @@ -45,20 +45,20 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def taxons - options[:distributed_taxons][object.id] + options[:data].distributed_taxons[object.id] end def supplied_taxons - options[:supplied_taxons][object.id] + options[:data].supplied_taxons[object.id] end def pickup - services = options[:shipping_method_services][object.id] + services = options[:data].shipping_method_services[object.id] services ? services[:pickup] : false end def delivery - services = options[:shipping_method_services][object.id] + services = options[:data].shipping_method_services[object.id] services ? services[:delivery] : false end @@ -83,12 +83,12 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer end def producers - relatives = options[:relatives][object.id] + relatives = options[:data].relatives[object.id] relatives ? relatives[:producers] : [] end def hubs - relatives = options[:relatives][object.id] + relatives = options[:data].relatives[object.id] relatives ? relatives[:distributors] : [] end diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb new file mode 100644 index 0000000000..9862418b98 --- /dev/null +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -0,0 +1,27 @@ +module OpenFoodNetwork + class EnterpriseInjectionData + def active_distributors + @active_distributors ||= Enterprise.distributors_with_active_order_cycles + end + + def earliest_closing_times + @earliest_closing_times ||= OrderCycle.earliest_closing_times + end + + def shipping_method_services + @shipping_method_services ||= Spree::ShippingMethod.services + end + + def relatives + @relatives ||= EnterpriseRelationship.relatives + end + + def supplied_taxons + @supplied_taxons ||= Spree::Taxon.supplied_taxons + end + + def distributed_taxons + @distributed_taxons ||= Spree::Taxon.distributed_taxons + end + end +end From e1b4c3b1e4bb75efe713c8b246fdb835004caa36 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 13:47:24 +1000 Subject: [PATCH 15/28] Add benchmarking test for inject_enterprises --- spec/performance/injection_helper_spec.rb | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 spec/performance/injection_helper_spec.rb diff --git a/spec/performance/injection_helper_spec.rb b/spec/performance/injection_helper_spec.rb new file mode 100644 index 0000000000..4ba22d738f --- /dev/null +++ b/spec/performance/injection_helper_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe InjectionHelper, type: :helper do + let(:oc) { create(:simple_order_cycle) } + let(:relative_supplier) { create(:supplier_enterprise) } + let(:relative_distributor) { create(:distributor_enterprise) } + + before do + 50.times do + e = create(:enterprise) + oc.distributors << e + create(:enterprise_relationship, parent: e, child: relative_supplier) + create(:enterprise_relationship, parent: e, child: relative_distributor) + end + end + + it "is performant in injecting enterprises" do + results = [] + 4.times do |i| + ActiveRecord::Base.connection.query_cache.clear + Rails.cache.clear + result = Benchmark.measure { helper.inject_enterprises } + results << result.total if i > 0 + puts result + end + + puts (results.sum / results.count * 1000).round 0 + end +end From 41bc67e2d8e601b111c8e3820206b79e8cf1e04f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 14:46:33 +1000 Subject: [PATCH 16/28] Add benchmark for product serialisation --- spec/performance/shop_controller_spec.rb | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 spec/performance/shop_controller_spec.rb diff --git a/spec/performance/shop_controller_spec.rb b/spec/performance/shop_controller_spec.rb new file mode 100644 index 0000000000..14e258d18d --- /dev/null +++ b/spec/performance/shop_controller_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe ShopController, type: :controller do + let(:d) { create(:distributor_enterprise) } + let(:enterprise_fee) { create(:enterprise_fee) } + let(:order_cycle) { create(:simple_order_cycle, distributors: [d], coordinator_fees: [enterprise_fee]) } + + before do + controller.stub(:current_distributor) { d } + controller.stub(:current_order_cycle) { order_cycle } + end + + describe "fetching products" do + let(:exchange) { order_cycle.exchanges.to_enterprises(d).outgoing.first } + let(:image) { File.open(File.expand_path('../../../app/assets/images/logo.jpg', __FILE__)) } + + before do + 11.times do + p = create(:simple_product) + p.set_property 'Organic Certified', 'NASAA 12345' + v1 = create(:variant, product: p) + v2 = create(:variant, product: p) + Spree::Image.create! viewable_id: p.master.id, viewable_type: 'Spree::Variant', attachment: image + + exchange.variants << [v1, v2] + end + end + + it "returns products via json" do + results = [] + 4.times do |i| + ActiveRecord::Base.connection.query_cache.clear + Rails.cache.clear + result = Benchmark.measure do + xhr :get, :products + response.should be_success + end + + results << result.total if i > 0 + puts result + end + + puts (results.sum / results.count * 1000).round 0 + end + end +end From e74390a013b9b614375c98bd63eb847b804923ed Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 May 2015 16:26:08 +1000 Subject: [PATCH 17/28] Remove controller specs for @active_distributors, now set via helper --- spec/controllers/home_controller_spec.rb | 12 ------------ spec/controllers/map_controller_spec.rb | 13 ------------- spec/controllers/producers_controller_spec.rb | 15 --------------- 3 files changed, 40 deletions(-) delete mode 100644 spec/controllers/map_controller_spec.rb delete mode 100644 spec/controllers/producers_controller_spec.rb diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb index bbbff9a7b1..924462741f 100644 --- a/spec/controllers/home_controller_spec.rb +++ b/spec/controllers/home_controller_spec.rb @@ -9,21 +9,9 @@ describe HomeController do Enterprise.stub(:distributors_with_active_order_cycles) { [distributor] } end - it "sets active distributors" do - get :index - assigns[:active_distributors].should == [distributor] - end - # Exclusion from actual rendered view handled in features/consumer/home it "shows JSON for invisible hubs" do get :index response.body.should have_content invisible_distributor.name end - - # This is done inside the json/hubs Serializer - it "gets the next order cycle for each hub" do - OrderCycle.should_receive(:first_closing_for).twice - get :index - end end - diff --git a/spec/controllers/map_controller_spec.rb b/spec/controllers/map_controller_spec.rb deleted file mode 100644 index fab9b7ac22..0000000000 --- a/spec/controllers/map_controller_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe MapController do - it "loads active distributors" do - active_distributors = double(:distributors) - - Enterprise.stub(:distributors_with_active_order_cycles) { active_distributors } - - get :index - - assigns(:active_distributors).should == active_distributors - end -end diff --git a/spec/controllers/producers_controller_spec.rb b/spec/controllers/producers_controller_spec.rb deleted file mode 100644 index ec3c39036c..0000000000 --- a/spec/controllers/producers_controller_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe ProducersController do - let!(:distributor) { create(:distributor_enterprise) } - - before do - Enterprise.stub(:distributors_with_active_order_cycles) { [distributor] } - Enterprise.stub(:all).and_return([distributor]) - end - - it "sets active distributors" do - get :index - assigns[:active_distributors].should == [distributor] - end -end From 75f1f673ad3d63a6c366947876ce3479b62f050a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 May 2015 16:26:31 +1000 Subject: [PATCH 18/28] Update spec for EnterpriseSerializer --- spec/serializers/enterprise_serializer_spec.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index 1063a042e7..7c467d9c3d 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -3,19 +3,18 @@ describe Api::EnterpriseSerializer do let(:enterprise) { create(:distributor_enterprise) } let(:taxon) { create(:taxon) } + let(:data_class) { Struct.new(:earliest_closing_times, :active_distributors, + :distributed_taxons, :supplied_taxons, + :shipping_method_services, :relatives) } + let(:data) { data_class.new({}, [], {}, {}, {}, {producers: [], distributors: []}) } + it "serializes an enterprise" do - serializer = Api::EnterpriseSerializer.new enterprise + serializer = Api::EnterpriseSerializer.new enterprise, data: data serializer.to_json.should match enterprise.name end - it "includes distributed taxons" do - enterprise.stub(:distributed_taxons).and_return [taxon] - serializer = Api::EnterpriseSerializer.new enterprise - serializer.to_json.should match taxon.id.to_s - end - it "will render urls" do - serializer = Api::EnterpriseSerializer.new enterprise + serializer = Api::EnterpriseSerializer.new enterprise, data: data serializer.to_json.should match "map_005-hub.svg" end end From 3ab7df88e68bf024039f1a59ca64283b3daaef95 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 May 2015 16:26:40 +1000 Subject: [PATCH 19/28] Allow serialization of nil enterprise --- app/serializers/api/enterprise_serializer.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 4f38c12964..fc843a5138 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -3,17 +3,18 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer Api::IdSerializer def serializable_hash + cached_serializer_hash.merge uncached_serializer_hash end private def cached_serializer_hash - Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash + Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash || {} end def uncached_serializer_hash - Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash + Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash || {} end end @@ -31,7 +32,12 @@ end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer cached - delegate :cache_key, to: :object + #delegate :cache_key, to: :object + + def cache_key + object.andand.cache_key + end + attributes :name, :id, :description, :latitude, :longitude, :long_description, :website, :instagram, :linkedin, :twitter, From cdbf02ca20f57b5d776bbe65422c620097e39476 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 12:07:43 +1000 Subject: [PATCH 20/28] EnterpriseRelationship.relatives can find activated enterprises only --- app/models/enterprise_relationship.rb | 14 +++++++++----- spec/models/enterprise_relationship_spec.rb | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index 22d04a6cd1..49c26cb0f8 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -28,7 +28,7 @@ class EnterpriseRelationship < ActiveRecord::Base # Load an array of the relatives of each enterprise (ie. any enterprise related to it in # either direction). This array is split into distributors and producers, and has the format: # {enterprise_id => {distributors: [id, ...], producers: [id, ...]} } - def self.relatives + def self.relatives(activated_only=false) relationships = EnterpriseRelationship.includes(:child, :parent) relatives = {} @@ -36,11 +36,15 @@ class EnterpriseRelationship < ActiveRecord::Base relatives[r.parent_id] ||= {distributors: [], producers: []} relatives[r.child_id] ||= {distributors: [], producers: []} - relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer - relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor + if !activated_only || r.child.activated? + relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer + relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor + end - relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer - relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor + if !activated_only || r.parent.activated? + relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer + relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor + end end relatives diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index cb1743dec1..59e36c37e5 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -80,5 +80,15 @@ describe EnterpriseRelationship do {e1.id => {distributors: [e2.id], producers: [e2.id]}, e2.id => {distributors: [], producers: [e1.id]}} end + + it "finds inactive enterprises by default" do + e1.update_attribute :confirmed_at, nil + EnterpriseRelationship.relatives[e2.id][:producers].should == [e1.id] + end + + it "does not find inactive enterprises when requested" do + e1.update_attribute :confirmed_at, nil + EnterpriseRelationship.relatives(true)[e2.id][:producers].should be_empty + end end end From 69c54e1d704ebd15f5b935e0c93be619b24dc456 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 12:08:21 +1000 Subject: [PATCH 21/28] Only load activated relatives for EnterpriseInjectionData --- app/models/enterprise.rb | 4 ++++ .../enterprise_injection_data.rb | 2 +- .../enterprise_injection_data_spec.rb | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 spec/lib/open_food_network/enterprise_injection_data_spec.rb diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index e921167cf0..61d441fb2e 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -178,6 +178,10 @@ class Enterprise < ActiveRecord::Base count(distinct: true) end + def activated? + confirmed_at.present? && sells != 'unspecified' + end + def set_producer_property(property_name, property_value) transaction do property = Spree::Property.where(name: property_name).first_or_create!(presentation: property_name) diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb index 9862418b98..87516007c6 100644 --- a/lib/open_food_network/enterprise_injection_data.rb +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -13,7 +13,7 @@ module OpenFoodNetwork end def relatives - @relatives ||= EnterpriseRelationship.relatives + @relatives ||= EnterpriseRelationship.relatives(true) end def supplied_taxons diff --git a/spec/lib/open_food_network/enterprise_injection_data_spec.rb b/spec/lib/open_food_network/enterprise_injection_data_spec.rb new file mode 100644 index 0000000000..cb94f2374a --- /dev/null +++ b/spec/lib/open_food_network/enterprise_injection_data_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +module OpenFoodNetwork + describe EnterpriseInjectionData do + describe "relatives" do + let!(:enterprise) { create(:distributor_enterprise) } + let!(:producer) { create(:supplier_enterprise) } + let!(:producer_inactive) { create(:supplier_enterprise, confirmed_at: nil) } + let!(:er_p) { create(:enterprise_relationship, parent: producer, child: enterprise) } + let!(:er_pi) { create(:enterprise_relationship, parent: producer_inactive, child: enterprise) } + + it "only loads activated relatives" do + subject.relatives[enterprise.id][:producers].should_not include producer_inactive.id + end + end + end +end From 3f4f8afacd8b8ca432861da0fa8f6b92e68953df Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 12:19:38 +1000 Subject: [PATCH 22/28] EnterpriseRelationship.relatives does not show duplicates --- app/models/enterprise_relationship.rb | 4 ++-- spec/models/enterprise_relationship_spec.rb | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index 49c26cb0f8..de06a0578e 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -33,8 +33,8 @@ class EnterpriseRelationship < ActiveRecord::Base relatives = {} relationships.each do |r| - relatives[r.parent_id] ||= {distributors: [], producers: []} - relatives[r.child_id] ||= {distributors: [], producers: []} + relatives[r.parent_id] ||= {distributors: Set.new, producers: Set.new} + relatives[r.child_id] ||= {distributors: Set.new, producers: Set.new} if !activated_only || r.child.activated? relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index 59e36c37e5..96f80a65a4 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -74,21 +74,27 @@ describe EnterpriseRelationship do let(:e1) { create(:supplier_enterprise) } let(:e2) { create(:supplier_enterprise, sells: 'any') } let!(:er) { create(:enterprise_relationship, parent: e1, child: e2) } + let(:er_reverse) { create(:enterprise_relationship, parent: e2, child: e1) } it "categorises enterprises into distributors and producers" do EnterpriseRelationship.relatives.should == - {e1.id => {distributors: [e2.id], producers: [e2.id]}, - e2.id => {distributors: [], producers: [e1.id]}} + {e1.id => {distributors: Set.new([e2.id]), producers: Set.new([e2.id])}, + e2.id => {distributors: Set.new([]), producers: Set.new([e1.id])}} end it "finds inactive enterprises by default" do e1.update_attribute :confirmed_at, nil - EnterpriseRelationship.relatives[e2.id][:producers].should == [e1.id] + EnterpriseRelationship.relatives[e2.id][:producers].should == Set.new([e1.id]) end it "does not find inactive enterprises when requested" do e1.update_attribute :confirmed_at, nil EnterpriseRelationship.relatives(true)[e2.id][:producers].should be_empty end + + it "does not show duplicates" do + er_reverse + EnterpriseRelationship.relatives[e2.id][:producers].should == Set.new([e1.id]) + end end end From d478cc1f69a1ed7d4436ef5e323acaec5de5d39c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 14:03:44 +1000 Subject: [PATCH 23/28] Serialize taxons and relatives in expected format --- app/serializers/api/enterprise_serializer.rb | 16 ++++++++---- .../serializers/enterprise_serializer_spec.rb | 25 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index fc843a5138..44364c4dbc 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -3,7 +3,6 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer Api::IdSerializer def serializable_hash - cached_serializer_hash.merge uncached_serializer_hash end @@ -51,11 +50,11 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def taxons - options[:data].distributed_taxons[object.id] + ids_to_objs options[:data].distributed_taxons[object.id] end def supplied_taxons - options[:data].supplied_taxons[object.id] + ids_to_objs options[:data].supplied_taxons[object.id] end def pickup @@ -90,12 +89,12 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def producers relatives = options[:data].relatives[object.id] - relatives ? relatives[:producers] : [] + relatives ? ids_to_objs(relatives[:producers]) : [] end def hubs relatives = options[:data].relatives[object.id] - relatives ? relatives[:distributors] : [] + relatives ? ids_to_objs(relatives[:distributors]) : [] end # Map svg icons. @@ -135,4 +134,11 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer } icon_fonts[object.category] end + + + private + + def ids_to_objs(ids) + ids.andand.map { |id| {id: id} } + end end diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index 7c467d9c3d..f70852d973 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -1,20 +1,31 @@ #require 'spec_helper' describe Api::EnterpriseSerializer do + let(:serializer) { Api::EnterpriseSerializer.new enterprise, data: data } let(:enterprise) { create(:distributor_enterprise) } let(:taxon) { create(:taxon) } - let(:data_class) { Struct.new(:earliest_closing_times, :active_distributors, - :distributed_taxons, :supplied_taxons, - :shipping_method_services, :relatives) } - let(:data) { data_class.new({}, [], {}, {}, {}, {producers: [], distributors: []}) } + let(:data) { OpenStruct.new(earliest_closing_times: {}, + active_distributors: [], + distributed_taxons: {enterprise.id => [123]}, + supplied_taxons: {enterprise.id => [456]}, + shipping_method_services: {}, + relatives: {enterprise.id => {producers: [123], distributors: [456]}}) } it "serializes an enterprise" do - serializer = Api::EnterpriseSerializer.new enterprise, data: data serializer.to_json.should match enterprise.name end - it "will render urls" do - serializer = Api::EnterpriseSerializer.new enterprise, data: data + it "serializes taxons as ids only" do + serializer.serializable_hash[:taxons].should == [{id: 123}] + serializer.serializable_hash[:supplied_taxons].should == [{id: 456}] + end + + it "serializes producers and hubs as ids only" do + serializer.serializable_hash[:producers].should == [{id: 123}] + serializer.serializable_hash[:hubs].should == [{id: 456}] + end + + it "serializes icons" do serializer.to_json.should match "map_005-hub.svg" end end From 8afffdae9ab7d205475ede289213524c5ed14ac1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:13:42 +1000 Subject: [PATCH 24/28] Fix error when product does not have a master variant --- app/assets/javascripts/darkswarm/services/products.js.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/darkswarm/services/products.js.coffee b/app/assets/javascripts/darkswarm/services/products.js.coffee index a07ae1f466..4785adae85 100644 --- a/app/assets/javascripts/darkswarm/services/products.js.coffee +++ b/app/assets/javascripts/darkswarm/services/products.js.coffee @@ -32,8 +32,9 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro if product.variants product.variants = (Variants.register variant for variant in product.variants) variant.product = product for variant in product.variants - product.master.product = product - product.master = Variants.register product.master if product.master + if product.master + product.master.product = product + product.master = Variants.register product.master registerVariantsWithCart: -> for product in @products From 473322c7e6b14a4c24927bff36440364d9138206 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:25:28 +1000 Subject: [PATCH 25/28] CI: Add more robust merge-to-master script --- script/ci/merge_branch_to_master.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/script/ci/merge_branch_to_master.sh b/script/ci/merge_branch_to_master.sh index 3eaae17d56..fd9c602802 100755 --- a/script/ci/merge_branch_to_master.sh +++ b/script/ci/merge_branch_to_master.sh @@ -6,5 +6,9 @@ source ./script/ci/includes.sh echo "--- Verifying branch is based on current master" exit_unless_master_merged -echo "--- Pushing branch" -git push origin $BUILDKITE_COMMIT:master +echo "--- Merging and pushing branch" +git checkout master +git merge origin/master +git merge origin/$BUILDKITE_BRANCH +git push origin master +git checkout origin/$BUILDKITE_BRANCH From c6f6c11a43143d3c820be511b8c940937696c49a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:51:15 +1000 Subject: [PATCH 26/28] Add wait between clicks to fix race condition --- spec/features/admin/bulk_product_update_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 4c52ec2256..f5134f4e81 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -205,8 +205,9 @@ feature %q{ expect(page).to have_selector "a.edit-variant", count: 1 # When I remove two, they should be removed - page.all('a.delete-variant').first.click - page.all('a.delete-variant').first.click + page.all('a.delete-variant', visible: true).first.click + expect(page).to have_selector "tr.variant", count: 2 + page.all('a.delete-variant', visible: true).first.click expect(page).to have_selector "tr.variant", count: 1 # When I fill out variant details and hit update From b3878b126b3c73b6baa1cefd35feb06e6813ce7a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:53:46 +1000 Subject: [PATCH 27/28] Decouple generic injection spec from EnterpriseSerializer --- spec/helpers/injection_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/injection_helper_spec.rb b/spec/helpers/injection_helper_spec.rb index 96d9279ef5..362ca6479b 100644 --- a/spec/helpers/injection_helper_spec.rb +++ b/spec/helpers/injection_helper_spec.rb @@ -4,7 +4,7 @@ describe InjectionHelper do let!(:enterprise) { create(:distributor_enterprise, facebook: "roger") } it "will inject via AMS" do - helper.inject_json_ams("test", [enterprise], Api::EnterpriseSerializer).should match enterprise.name + helper.inject_json_ams("test", [enterprise], Api::IdSerializer).should match /#{enterprise.id}/ end it "injects enterprises" do From 36dc0d5ccd767446a63c9b4dd07bfb4f41a41c5b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 13:00:07 +1000 Subject: [PATCH 28/28] Do not run performance specs in CI --- script/ci/run_tests.sh | 2 +- spec/performance/injection_helper_spec.rb | 2 +- spec/performance/shop_controller_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/ci/run_tests.sh b/script/ci/run_tests.sh index 5539935a8b..f973c139ad 100755 --- a/script/ci/run_tests.sh +++ b/script/ci/run_tests.sh @@ -16,4 +16,4 @@ echo "--- Loading test database" bundle exec rake db:test:load echo "--- Running tests" -bundle exec rspec spec +bundle exec rspec --tag ~performance spec diff --git a/spec/performance/injection_helper_spec.rb b/spec/performance/injection_helper_spec.rb index 4ba22d738f..ef8d937ce1 100644 --- a/spec/performance/injection_helper_spec.rb +++ b/spec/performance/injection_helper_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe InjectionHelper, type: :helper do +describe InjectionHelper, type: :helper, performance: true do let(:oc) { create(:simple_order_cycle) } let(:relative_supplier) { create(:supplier_enterprise) } let(:relative_distributor) { create(:distributor_enterprise) } diff --git a/spec/performance/shop_controller_spec.rb b/spec/performance/shop_controller_spec.rb index 14e258d18d..984581a2ab 100644 --- a/spec/performance/shop_controller_spec.rb +++ b/spec/performance/shop_controller_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ShopController, type: :controller do +describe ShopController, type: :controller, performance: true do let(:d) { create(:distributor_enterprise) } let(:enterprise_fee) { create(:enterprise_fee) } let(:order_cycle) { create(:simple_order_cycle, distributors: [d], coordinator_fees: [enterprise_fee]) }