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