diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index ef78605b41..8d7b9ab274 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -1,4 +1,4 @@ -require 'open_food_network/scope_product_to_hub' +require 'open_food_network/products_renderer' class ShopController < BaseController layout "darkswarm" @@ -10,22 +10,13 @@ class ShopController < BaseController end def products - if @products = products_for_shop + begin + products_json = OpenFoodNetwork::ProductsRenderer.new(current_distributor, current_order_cycle).products - enterprise_fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new current_distributor, current_order_cycle + render json: products_json - render status: 200, - 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, - master_variants: master_variants_for_shop_by_id, - enterprise_fee_calculator: enterprise_fee_calculator, - ).to_json - - else - render json: "", status: 404 + rescue OpenFoodNetwork::ProductsRenderer::NoProducts + render status: 404, json: '' end end @@ -42,55 +33,4 @@ class ShopController < BaseController end end - private - - def products_for_shop - if current_order_cycle - scoper = OpenFoodNetwork::ScopeProductToHub.new(current_distributor) - - current_order_cycle. - valid_products_distributed_by(current_distributor). - order(taxon_order). - each { |p| scoper.scope(p) }. - select { |p| !p.deleted? && p.has_stock_for_distribution?(current_order_cycle, current_distributor) } - end - end - - def taxon_order - if current_distributor.preferred_shopfront_taxon_order.present? - current_distributor - .preferred_shopfront_taxon_order - .split(",").map { |id| "primary_taxon_id=#{id} DESC" } - .join(",") + ", name ASC" - else - "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. - scoper = OpenFoodNetwork::ScopeVariantToHub.new(current_distributor) - Spree::Variant. - for_distribution(current_order_cycle, current_distributor). - each { |v| scoper.scope(v) }. - 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 5a1d1b5c86..03a66afe89 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -34,6 +34,7 @@ end class Api::CachedProductSerializer < ActiveModel::Serializer #cached #delegate :cache_key, to: :object + include ActionView::Helpers::SanitizeHelper attributes :id, :name, :permalink, :count_on_hand attributes :on_demand, :group_buy, :notes, :description @@ -48,6 +49,10 @@ class Api::CachedProductSerializer < ActiveModel::Serializer has_many :images, serializer: Api::ImageSerializer has_one :supplier, serializer: Api::IdSerializer + def description + strip_tags object.description + end + def properties_with_values object.properties_including_inherited end diff --git a/lib/open_food_network/products_renderer.rb b/lib/open_food_network/products_renderer.rb new file mode 100644 index 0000000000..d745b1b794 --- /dev/null +++ b/lib/open_food_network/products_renderer.rb @@ -0,0 +1,84 @@ +require 'open_food_network/scope_product_to_hub' + +module OpenFoodNetwork + class ProductsRenderer + class NoProducts < Exception; end + + def initialize(distributor, order_cycle) + @distributor = distributor + @order_cycle = order_cycle + end + + def products + products = products_for_shop + + if products + enterprise_fee_calculator = EnterpriseFeeCalculator.new @distributor, @order_cycle + + ActiveModel::ArraySerializer.new(products, + each_serializer: Api::ProductSerializer, + current_order_cycle: @order_cycle, + current_distributor: @distributor, + variants: variants_for_shop_by_id, + master_variants: master_variants_for_shop_by_id, + enterprise_fee_calculator: enterprise_fee_calculator, + ).to_json + else + raise NoProducts.new + end + end + + + private + + def products_for_shop + if @order_cycle + scoper = ScopeProductToHub.new(@distributor) + + @order_cycle. + valid_products_distributed_by(@distributor). + order(taxon_order). + each { |p| scoper.scope(p) }. + select { |p| !p.deleted? && p.has_stock_for_distribution?(@order_cycle, @distributor) } + end + end + + def taxon_order + if @distributor.preferred_shopfront_taxon_order.present? + @distributor + .preferred_shopfront_taxon_order + .split(",").map { |id| "primary_taxon_id=#{id} DESC" } + .join(",") + ", name ASC" + else + "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. + scoper = OpenFoodNetwork::ScopeVariantToHub.new(@distributor) + Spree::Variant. + for_distribution(@order_cycle, @distributor). + each { |v| scoper.scope(v) }. + 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 +end diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index 2c7105b356..09ea85dd44 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -37,7 +37,7 @@ describe ShopController do controller.current_order_cycle.should == oc2 end - context "RABL tests" do + context "JSON tests" do render_views it "should return the order cycle details when the oc is selected" do oc1 = create(:simple_order_cycle, distributors: [d]) @@ -86,7 +86,7 @@ describe ShopController do describe "requests and responses" do let(:product) { create(:product) } before do - exchange.variants << product.master + exchange.variants << product.variants.first end it "returns products via json" do @@ -102,95 +102,6 @@ describe ShopController do response.body.should be_empty end end - - describe "sorting" do - let(:t1) { create(:taxon) } - let(:t2) { create(:taxon) } - let!(:p1) { create(:product, name: "abc", primary_taxon_id: t2.id) } - let!(:p2) { create(:product, name: "def", primary_taxon_id: t1.id) } - let!(:p3) { create(:product, name: "ghi", primary_taxon_id: t2.id) } - let!(:p4) { create(:product, name: "jkl", primary_taxon_id: t1.id) } - - before do - exchange.variants << p1.variants.first - exchange.variants << p2.variants.first - exchange.variants << p3.variants.first - exchange.variants << p4.variants.first - end - - it "sorts products by the distributor's preferred taxon list" do - d.stub(:preferred_shopfront_taxon_order) {"#{t1.id},#{t2.id}"} - controller.stub(:current_order_cycle).and_return order_cycle - xhr :get, :products - assigns[:products].should == [p2, p4, p1, p3] - end - - it "alphabetizes products by name when taxon list is not set" do - d.stub(:preferred_shopfront_taxon_order) {""} - controller.stub(:current_order_cycle).and_return order_cycle - xhr :get, :products - assigns[:products].should == [p1, p2, p3, p4] - end - end - - context "RABL tests" do - render_views - let(:product) { create(:product) } - let(:variant) { product.variants.first } - - before do - exchange.variants << variant - controller.stub(:current_order_cycle).and_return order_cycle - end - - it "only returns products for the current order cycle" do - xhr :get, :products - response.body.should have_content product.name - end - - it "doesn't return products not in stock" do - variant.update_attribute(:count_on_hand, 0) - xhr :get, :products - response.body.should_not have_content product.name - end - - it "strips html from description" do - product.update_attribute(:description, "turtles frogs") - xhr :get, :products - response.body.should have_content "frogs" - response.body.should_not have_content " [v1]} end end end diff --git a/spec/lib/open_food_network/products_renderer_spec.rb b/spec/lib/open_food_network/products_renderer_spec.rb new file mode 100644 index 0000000000..875eba1ac9 --- /dev/null +++ b/spec/lib/open_food_network/products_renderer_spec.rb @@ -0,0 +1,91 @@ +require 'spec_helper' +require 'open_food_network/products_renderer' + +module OpenFoodNetwork + describe ProductsRenderer do + let(:d) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle, distributors: [d], coordinator: create(:distributor_enterprise)) } + let(:exchange) { Exchange.find(order_cycle.exchanges.to_enterprises(d).outgoing.first.id) } + let(:pr) { ProductsRenderer.new(d, order_cycle) } + + describe "sorting" do + let(:t1) { create(:taxon) } + let(:t2) { create(:taxon) } + let!(:p1) { create(:product, name: "abc", primary_taxon_id: t2.id) } + let!(:p2) { create(:product, name: "def", primary_taxon_id: t1.id) } + let!(:p3) { create(:product, name: "ghi", primary_taxon_id: t2.id) } + let!(:p4) { create(:product, name: "jkl", primary_taxon_id: t1.id) } + + before do + exchange.variants << p1.variants.first + exchange.variants << p2.variants.first + exchange.variants << p3.variants.first + exchange.variants << p4.variants.first + end + + it "sorts products by the distributor's preferred taxon list" do + d.stub(:preferred_shopfront_taxon_order) {"#{t1.id},#{t2.id}"} + products = pr.send(:products_for_shop) + products.should == [p2, p4, p1, p3] + end + + it "alphabetizes products by name when taxon list is not set" do + d.stub(:preferred_shopfront_taxon_order) {""} + products = pr.send(:products_for_shop) + products.should == [p1, p2, p3, p4] + end + end + + context "JSON tests" do + let(:product) { create(:product) } + let(:variant) { product.variants.first } + + before do + exchange.variants << variant + end + + it "only returns products for the current order cycle" do + pr.products.should include product.name + end + + it "doesn't return products not in stock" do + variant.update_attribute(:count_on_hand, 0) + pr.products.should_not include product.name + end + + it "strips html from description" do + product.update_attribute(:description, "turtles frogs") + json = pr.products + json.should include "frogs" + json.should_not include " [v1]} + end + end + end +end