Extract product JSON rendering to lib class. Fix HTML stripping that never actually worked.

This commit is contained in:
Rohan Mitchell
2016-01-15 13:29:28 +11:00
committed by Rob Harrington
parent 77e74c5642
commit e16ca82e76
5 changed files with 188 additions and 157 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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, "<a href='44'>turtles</a> frogs")
xhr :get, :products
response.body.should have_content "frogs"
response.body.should_not have_content "<a href"
end
it "returns price including fees" do
# Price is 19.99
OpenFoodNetwork::EnterpriseFeeCalculator.any_instance.
stub(:indexed_fees_for).and_return 978.01
xhr :get, :products
response.body.should have_content "998.0"
end
it "includes the primary taxon" do
taxon = create(:taxon)
Spree::Product.any_instance.stub(:primary_taxon).and_return taxon
xhr :get, :products
response.body.should have_content taxon.name
end
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

View File

@@ -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, "<a href='44'>turtles</a> frogs")
json = pr.products
json.should include "frogs"
json.should_not include "<a href"
end
it "returns price including fees" do
# Price is 19.99
OpenFoodNetwork::EnterpriseFeeCalculator.any_instance.
stub(:indexed_fees_for).and_return 978.01
pr.products.should include "998.0"
end
it "includes the primary taxon" do
taxon = create(:taxon)
Spree::Product.any_instance.stub(:primary_taxon).and_return taxon
pr.products.should include taxon.name
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
pr = ProductsRenderer.new(hub, oc)
pr.send(:variants_for_shop_by_id).should == {p.id => [v1]}
end
end
end
end