mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-15 23:57:48 +00:00
Extract product JSON rendering to lib class. Fix HTML stripping that never actually worked.
This commit is contained in:
committed by
Rob Harrington
parent
77e74c5642
commit
e16ca82e76
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
84
lib/open_food_network/products_renderer.rb
Normal file
84
lib/open_food_network/products_renderer.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
91
spec/lib/open_food_network/products_renderer_spec.rb
Normal file
91
spec/lib/open_food_network/products_renderer_spec.rb
Normal 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
|
||||
Reference in New Issue
Block a user