From e326e590db5c62ec310bd58076cff73b9d5d5a3c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 8 Mar 2013 14:10:40 +1100 Subject: [PATCH] Implement and use scopes for finding products by distributor via order cycle or product distribution --- app/controllers/application_controller.rb | 4 +- app/helpers/spree/base_helper_decorator.rb | 7 ++- app/models/spree/product_decorator.rb | 56 +++++++++++-------- .../queries_product_distribution.rb | 16 ++++++ spec/features/consumer/taxonomy_spec.rb | 51 ++++++++++++++++- 5 files changed, 109 insertions(+), 25 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ef345ba390..803833a7ec 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,5 @@ +require 'open_food_web/queries_product_distribution' + class ApplicationController < ActionController::Base protect_from_forgery @@ -12,7 +14,7 @@ class ApplicationController < ActionController::Base def load_data_for_sidebar @suppliers = Enterprise.is_primary_producer @order_cycles = OrderCycle.active - @distributors = QueriesProductDistribution.active_distributors + @distributors = OpenFoodWeb::QueriesProductDistribution.active_distributors end # All render calls within the block will be performed with the specified format diff --git a/app/helpers/spree/base_helper_decorator.rb b/app/helpers/spree/base_helper_decorator.rb index 138d0a4742..36b0f682a2 100644 --- a/app/helpers/spree/base_helper_decorator.rb +++ b/app/helpers/spree/base_helper_decorator.rb @@ -7,7 +7,12 @@ module Spree css_class = (current_taxon && current_taxon.self_and_ancestors.include?(taxon)) ? 'current' : nil products = Product.in_taxon(taxon) - products = products.in_distributor(current_distributor) if current_distributor + + #products = products.in_distributor(current_distributor) if current_distributor + #products = products.in_order_cycle(current_order_cycle) if current_order_cycle + + products = OpenFoodWeb::QueriesProductDistribution.products_available_for(products, current_distributor, current_order_cycle) + num_products = products.count content_tag :li, :class => css_class do diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 454f821035..2361c11b7a 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -10,36 +10,48 @@ Spree::Product.class_eval do validates_presence_of :supplier + + # -- Joins + scope :with_product_distributions_outer, joins('LEFT OUTER JOIN product_distributions ON product_distributions.product_id = spree_products.id') + + scope :with_order_cycles_outer, joins('LEFT OUTER JOIN spree_variants ON (spree_variants.product_id = spree_products.id)'). + joins('LEFT OUTER JOIN exchange_variants ON (exchange_variants.variant_id = spree_variants.id)'). + joins('LEFT OUTER JOIN exchanges ON (exchanges.id = exchange_variants.exchange_id)'). + joins('LEFT OUTER JOIN order_cycles ON (order_cycles.id = exchanges.order_cycle_id)') + + scope :with_order_cycles_inner, joins('INNER JOIN spree_variants ON (spree_variants.product_id = spree_products.id)'). + joins('INNER JOIN exchange_variants ON (exchange_variants.variant_id = spree_variants.id)'). + joins('INNER JOIN exchanges ON (exchanges.id = exchange_variants.exchange_id)'). + joins('INNER JOIN order_cycles ON (order_cycles.id = exchanges.order_cycle_id)') + + # -- Scopes scope :in_supplier, lambda { |supplier| where(:supplier_id => supplier) } - scope :in_distributor, lambda { |distributor| joins(:product_distributions).where('product_distributions.distributor_id = ?', (distributor.respond_to?(:id) ? distributor.id : distributor.to_i)) } + # Find products that are distributed via the given distributor EITHER through a product distribution OR through an order cycle + scope :in_distributor, lambda { |distributor| + distributor = distributor.respond_to?(:id) ? distributor.id : distributor.to_i - scope :in_supplier_or_distributor, lambda { |enterprise| select('distinct spree_products.*'). - joins('LEFT OUTER JOIN product_distributions ON product_distributions.product_id=spree_products.id'). - where('supplier_id=? OR product_distributions.distributor_id=?', - enterprise.respond_to?(:id) ? enterprise.id : enterprise.to_i, - enterprise.respond_to?(:id) ? enterprise.id : enterprise.to_i) } - - scope :in_order_cycle_distributor, lambda { |distributor| - joins('INNER JOIN spree_variants ON (spree_variants.product_id = spree_products.id)'). - joins('INNER JOIN exchange_variants ON (exchange_variants.variant_id=spree_variants.id)'). - joins('INNER JOIN exchanges ON (exchanges.id = exchange_variants.exchange_id)'). - joins('INNER JOIN order_cycles ON (order_cycles.id = exchanges.order_cycle_id)'). - where('exchanges.sender_id = order_cycles.coordinator_id'). - where('exchanges.receiver_id = ?', (distributor.respond_to?(:id) ? distributor.id : distributor.to_i)) + with_product_distributions_outer.with_order_cycles_outer. + where('product_distributions.distributor_id = ? OR (exchanges.sender_id = order_cycles.coordinator_id AND exchanges.receiver_id = ?)', distributor, distributor). + select('distinct spree_products.*') } - scope :in_supplier_or_order_cycle_distributor, lambda { |enterprise| - enterprise_id = enterprise.respond_to?(:id) ? enterprise.id : enterprise.to_i + # Find products that are supplied by a given enterprise or distributed via that enterprise EITHER through a product distribution OR through an order cycle + scope :in_supplier_or_distributor, lambda { |enterprise| + enterprise = enterprise.respond_to?(:id) ? enterprise.id : enterprise.to_i - select('distinct spree_products.*'). - joins('LEFT OUTER JOIN spree_variants ON (spree_variants.product_id = spree_products.id)'). - joins('LEFT OUTER JOIN exchange_variants ON (exchange_variants.variant_id=spree_variants.id)'). - joins('LEFT OUTER JOIN exchanges ON (exchanges.id = exchange_variants.exchange_id)'). - joins('LEFT OUTER JOIN order_cycles ON (order_cycles.id = exchanges.order_cycle_id)'). - where('(exchanges.sender_id = order_cycles.coordinator_id AND exchanges.receiver_id = ?) OR (spree_products.supplier_id=?)', enterprise_id, enterprise_id) + with_product_distributions_outer.with_order_cycles_outer. + where('spree_products.supplier_id = ? OR product_distributions.distributor_id = ? OR (exchanges.sender_id = order_cycles.coordinator_id AND exchanges.receiver_id = ?)', enterprise, enterprise, enterprise). + select('distinct spree_products.*') } + # Find products that are distributed by the given order cycle + scope :in_order_cycle, lambda { |order_cycle| with_order_cycles_inner. + where('exchanges.sender_id = order_cycles.coordinator_id'). + where('order_cycles.id = ?', order_cycle) } + + + # -- Methods def shipping_method_for_distributor(distributor) distribution = self.product_distributions.find_by_distributor_id(distributor) diff --git a/lib/open_food_web/queries_product_distribution.rb b/lib/open_food_web/queries_product_distribution.rb index fe5f73d522..a4c17a4095 100644 --- a/lib/open_food_web/queries_product_distribution.rb +++ b/lib/open_food_web/queries_product_distribution.rb @@ -4,6 +4,12 @@ module OpenFoodWeb (active_distributors_for_product_distributions + active_distributors_for_order_cycles).sort_by { |d| d.name }.uniq end + def self.products_available_for(products, distributor=nil, order_cycle=nil) + products = products.in_distributor(distributor) if distributor + products = products.in_order_cycle(order_cycle) if order_cycle + products + end + private @@ -12,6 +18,16 @@ module OpenFoodWeb end def self.active_distributors_for_order_cycles + # Can I create this with merge scopes? + # ie. Enterprise.is_distributor.join(:order_cycle_distributor).merge(OrderCycle.active) + + # Try this: + Enterprise.joins('LEFT INNER JOIN exchanges ON (exchanges.receiver_id = enterprises.id)').joins('LEFT INNER JOIN order_cycles ON (order_cycles.id = exchanges.order_cycle.id)'). + merge(OrderCycle.active).select('DISTINCT enterprises.*') + + # Then I can make each of these methods into a scope on Enterprise, and ultimately a single scope + # Then we don't need this class. + OrderCycle.active.map { |oc| oc.distributors }.flatten.uniq end diff --git a/spec/features/consumer/taxonomy_spec.rb b/spec/features/consumer/taxonomy_spec.rb index 9dbfe1d4d7..a759a920ca 100644 --- a/spec/features/consumer/taxonomy_spec.rb +++ b/spec/features/consumer/taxonomy_spec.rb @@ -8,7 +8,15 @@ feature %q{ include AuthenticationWorkflow include WebHelper - scenario "viewing product counts when no distributor selected" do + # How should this work with distributors/order cycles? + # - No distributor or OC selected - all shown + # - Distributor selected - any from that distributor in any OC + # - OC selected - any in that OC from any distributor + # - Both selected - filter for both + + # Also keep specs for distributors outside order cycles. + + scenario "viewing product counts when no distributor or order cycle is selected" do # Given some taxons and some products taxonomy = Spree::Taxonomy.find_by_name('Products') || create(:taxonomy, :name => 'Products') taxonomy_root = taxonomy.root @@ -48,15 +56,56 @@ feature %q{ 2.times { create(:product, :taxons => [taxon_three], :distributors => [other_distributor]) } 2.times { create(:product, :taxons => [taxon_three], :distributors => [my_distributor]) } + p = create(:product, :taxons => [taxon_one]) + oc = create(:simple_order_cycle, distributors: [my_distributor], variants: [p.master]) + # When I visit the home page and select my distributor visit spree.select_distributor_order_path(my_distributor) within('nav#filters') { click_link my_distributor.name } page.should have_content 'You are shopping at My Distributor' # Then I should see distributor-scoped product counts next to the taxons + page.should have_selector 'nav#taxonomies li', :text => 'Taxon one (1)' + page.should have_selector 'nav#taxonomies li', :text => 'Taxon two (0)' + page.should have_selector 'nav#taxonomies li', :text => 'Taxon three (2)' + end + + scenario "viewing product counts when an order cycle is selected" do + # Given some taxons and some products and some order cycles + taxonomy = Spree::Taxonomy.find_by_name('Products') || create(:taxonomy, :name => 'Products') + taxonomy_root = taxonomy.root + + taxon_one = create(:taxon, :name => 'Taxon one', :parent_id => taxonomy_root.id) + taxon_two = create(:taxon, :name => 'Taxon two', :parent_id => taxonomy_root.id) + taxon_three = create(:taxon, :name => 'Taxon three', :parent_id => taxonomy_root.id) + + supplier = create(:supplier_enterprise, :name => 'My Supplier') + distributor = create(:distributor_enterprise, :name => 'My Distributor') + + t1p1 = create(:product, :taxons => [taxon_one]) + t2p1 = create(:product, :taxons => [taxon_two]) + t2p2 = create(:product, :taxons => [taxon_two]) + t3p1 = create(:product, :taxons => [taxon_three]) + t3p2 = create(:product, :taxons => [taxon_three]) + + oc1 = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [t1p1.master, t2p1.master, t2p2.master]) + oc2 = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [t3p1.master, t3p2.master]) + + # When I visit the home page and select my order cycle + visit root_path + choose oc2.name + click_button 'Choose Order Cycle' + page.should have_content 'Your order cycle has been selected.' + + pending "TODO: Test that products by ProductDistribution are not shown" + + # Then I should see order cycle-scoped product counts next to the taxons page.should have_selector 'nav#taxonomies li', :text => 'Taxon one (0)' page.should have_selector 'nav#taxonomies li', :text => 'Taxon two (0)' page.should have_selector 'nav#taxonomies li', :text => 'Taxon three (2)' end + scenario "viewing product counts when both a distributor and an order cycle are selected" + + end