forked from spree/spree_starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
402 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,344 @@ | ||
module Spree | ||
module ProductScopes | ||
This comment has been minimized.
Sorry, something went wrong. |
||
extend ActiveSupport::Concern | ||
|
||
included do | ||
cattr_accessor :search_scopes do | ||
[] | ||
end | ||
|
||
def self.add_search_scope(name, &block) | ||
singleton_class.send(:define_method, name.to_sym, &block) | ||
search_scopes << name.to_sym | ||
end | ||
|
||
def self.simple_scopes | ||
[ | ||
:ascend_by_updated_at, | ||
:descend_by_updated_at, | ||
:ascend_by_name, | ||
:descend_by_name, | ||
:ascend_by_postcode | ||
] | ||
end | ||
|
||
def self.add_simple_scopes(scopes) | ||
scopes.each do |name| | ||
# We should not define price scopes here, as they require something slightly different | ||
next if name.to_s.include?('master_price') | ||
|
||
parts = name.to_s.match(/(.*)_by_(.*)/) | ||
scope(name.to_s, -> { order(Arel.sql("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? 'ASC' : 'DESC'}")) }) | ||
end | ||
end | ||
|
||
def self.property_conditions(property) | ||
properties = Property.table_name | ||
case property | ||
when String then { "#{properties}.name" => property } | ||
when Property then { "#{properties}.id" => property.id } | ||
else { "#{properties}.id" => property.to_i } | ||
end | ||
end | ||
|
||
add_simple_scopes simple_scopes | ||
|
||
add_search_scope :ascend_by_master_price do | ||
order("#{price_table_name}.amount ASC") | ||
end | ||
|
||
add_search_scope :descend_by_master_price do | ||
order("#{price_table_name}.amount DESC") | ||
end | ||
|
||
add_search_scope :price_between do |low, high| | ||
where(Price.table_name => { amount: low..high }) | ||
end | ||
|
||
add_search_scope :master_price_lte do |price| | ||
where("#{price_table_name}.amount <= ?", price) | ||
end | ||
|
||
add_search_scope :master_price_gte do |price| | ||
where("#{price_table_name}.amount >= ?", price) | ||
end | ||
|
||
add_search_scope :in_stock do | ||
joins(:variants_including_master).merge(Spree::Variant.in_stock) | ||
end | ||
|
||
add_search_scope :backorderable do | ||
joins(:variants_including_master).merge(Spree::Variant.backorderable) | ||
end | ||
|
||
add_search_scope :in_stock_or_backorderable do | ||
joins(:variants_including_master).merge(Spree::Variant.in_stock_or_backorderable) | ||
end | ||
|
||
|
||
# Select nearest vendors | ||
shop = Spree::Vendor.first | ||
|
||
# Create CASE based on vendor_id table | ||
#h = shop | ||
#x = {} | ||
#h.each_with_index {|v, i| x[i] = v} | ||
#h | ||
#x.sort_by {|_key, value| value}.to_h | ||
order_sql | ||
vendor_ids = shop.nearbys; | ||
vendors_ids.map {|id| "WHEN spree_product.vendor_id = '#{id} then ${index}'" | ||
|
||
# Add search scope | ||
add_search_scope :ascend_by_postcode do | ||
This comment has been minimized.
Sorry, something went wrong.
chytreg
Member
|
||
Spree::Product.joins(master: :default_price).order(Arel.sql()).all | ||
end | ||
|
||
|
||
# vendor_ids= [4,6,3,1]; order_sql = "CASE"; vendors_ids.map {|id| "WHEN spree_product.vendor_id = '#{id} then ${index}'" | ||
# "Case" + n*"WHEN then 1" + "ELSE" | ||
# "CASE #{tbl.join(" ")} ELSE #{vendor_ids.size} END" | ||
|
||
|
||
# This scope selects products in taxon AND all its descendants | ||
# If you need products only within one taxon use | ||
# | ||
# Spree::Product.joins(:taxons).where(Taxon.table_name => { id: taxon.id }) | ||
# | ||
# If you're using count on the result of this scope, you must use the | ||
# `:distinct` option as well: | ||
# | ||
# Spree::Product.in_taxon(taxon).count(distinct: true) | ||
# | ||
# This is so that the count query is distinct'd: | ||
# | ||
# SELECT COUNT(DISTINCT "spree_products"."id") ... | ||
# | ||
# vs. | ||
# | ||
# SELECT COUNT(*) ... | ||
add_search_scope :in_taxon do |taxon| | ||
includes(:classifications). | ||
where('spree_products_taxons.taxon_id' => taxon.cached_self_and_descendants_ids). | ||
order('spree_products_taxons.position ASC') | ||
end | ||
|
||
# This scope selects products in all taxons AND all its descendants | ||
# If you need products only within one taxon use | ||
# | ||
# Spree::Product.taxons_id_eq([x,y]) | ||
add_search_scope :in_taxons do |*taxons| | ||
taxons = get_taxons(taxons) | ||
taxons.first ? prepare_taxon_conditions(taxons) : where(nil) | ||
end | ||
|
||
add_search_scope :ascend_by_taxons_min_position do |taxon_ids| | ||
joins(:classifications). | ||
where(Classification.table_name => { taxon_id: taxon_ids }). | ||
select( | ||
[ | ||
"#{Product.table_name}.*", | ||
"MIN(#{Classification.table_name}.position) AS min_position" | ||
].join(', ') | ||
). | ||
group(:id). | ||
order(min_position: :asc) | ||
end | ||
|
||
# a scope that finds all products having property specified by name, object or id | ||
add_search_scope :with_property do |property| | ||
joins(:properties).where(property_conditions(property)) | ||
end | ||
|
||
# a simple test for product with a certain property-value pairing | ||
# note that it can test for properties with NULL values, but not for absent values | ||
add_search_scope :with_property_value do |property, value| | ||
joins(:properties). | ||
where("#{ProductProperty.table_name}.value = ?", value). | ||
where(property_conditions(property)) | ||
end | ||
|
||
add_search_scope :with_property_values do |property_filter_param, property_values| | ||
joins(product_properties: :property). | ||
where(Property.table_name => { filter_param: property_filter_param }). | ||
where(ProductProperty.table_name => { filter_param: property_values.map(&:parameterize) }) | ||
end | ||
|
||
add_search_scope :with_option do |option| | ||
option_types = OptionType.table_name | ||
conditions = case option | ||
when String then { "#{option_types}.name" => option } | ||
when OptionType then { "#{option_types}.id" => option.id } | ||
else { "#{option_types}.id" => option.to_i } | ||
end | ||
|
||
joins(:option_types).where(conditions) | ||
end | ||
|
||
add_search_scope :with_option_value do |option, value| | ||
option_values = OptionValue.table_name | ||
option_type_id = case option | ||
when String then OptionType.find_by(name: option) || option.to_i | ||
when OptionType then option.id | ||
else option.to_i | ||
end | ||
|
||
conditions = "#{option_values}.name = ? AND #{option_values}.option_type_id = ?", value, option_type_id | ||
group('spree_products.id').joins(variants_including_master: :option_values).where(conditions) | ||
end | ||
|
||
# Finds all products which have either: | ||
# 1) have an option value with the name matching the one given | ||
# 2) have a product property with a value matching the one given | ||
add_search_scope :with do |value| | ||
includes(variants_including_master: :option_values). | ||
includes(:product_properties). | ||
where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value) | ||
end | ||
|
||
# Finds all products that have a name containing the given words. | ||
add_search_scope :in_name do |words| | ||
like_any([:name], prepare_words(words)) | ||
end | ||
|
||
# Finds all products that have a name or meta_keywords containing the given words. | ||
add_search_scope :in_name_or_keywords do |words| | ||
like_any([:name, :meta_keywords], prepare_words(words)) | ||
end | ||
|
||
# Finds all products that have a name, description, meta_description or meta_keywords containing the given keywords. | ||
add_search_scope :in_name_or_description do |words| | ||
like_any([:name, :description, :meta_description, :meta_keywords], prepare_words(words)) | ||
end | ||
|
||
# Finds all products that have the ids matching the given collection of ids. | ||
# Alternatively, you could use find(collection_of_ids), but that would raise an exception if one product couldn't be found | ||
add_search_scope :with_ids do |*ids| | ||
where(id: ids) | ||
end | ||
|
||
# Sorts products from most popular (popularity is extracted from how many | ||
# times use has put product in cart, not completed orders) | ||
# | ||
# there is alternative faster and more elegant solution, it has small drawback though, | ||
# it doesn stack with other scopes :/ | ||
# | ||
# joins: "LEFT OUTER JOIN (SELECT line_items.variant_id as vid, COUNT(*) as cnt FROM line_items GROUP BY line_items.variant_id) AS popularity_count ON variants.id = vid", | ||
# order: 'COALESCE(cnt, 0) DESC' | ||
add_search_scope :descend_by_popularity do | ||
joins(:master). | ||
order(%Q{ | ||
COALESCE(( | ||
SELECT | ||
COUNT(#{LineItem.quoted_table_name}.id) | ||
FROM | ||
#{LineItem.quoted_table_name} | ||
JOIN | ||
#{Variant.quoted_table_name} AS popular_variants | ||
ON | ||
popular_variants.id = #{LineItem.quoted_table_name}.variant_id | ||
WHERE | ||
popular_variants.product_id = #{Product.quoted_table_name}.id | ||
), 0) DESC | ||
}) | ||
end | ||
|
||
add_search_scope :not_deleted do | ||
where("#{Product.quoted_table_name}.deleted_at IS NULL or #{Product.quoted_table_name}.deleted_at >= ?", Time.zone.now) | ||
end | ||
|
||
def self.not_discontinued(only_not_discontinued = true) | ||
if only_not_discontinued != '0' && only_not_discontinued | ||
where("#{Product.quoted_table_name}.discontinue_on IS NULL or #{Product.quoted_table_name}.discontinue_on >= ?", Time.zone.now) | ||
else | ||
all | ||
end | ||
end | ||
search_scopes << :not_discontinued | ||
|
||
def self.with_currency(currency) | ||
joins(variants_including_master: :prices). | ||
where(Price.table_name => { currency: currency.upcase }). | ||
where.not(Price.table_name => { amount: nil }). | ||
distinct | ||
end | ||
search_scopes << :with_currency | ||
|
||
# Can't use add_search_scope for this as it needs a default argument | ||
def self.available(available_on = nil, currency = nil) | ||
available_on ||= Time.current | ||
|
||
scope = not_discontinued.where("#{Product.quoted_table_name}.available_on <= ?", available_on) | ||
|
||
unless Spree::Config.show_products_without_price | ||
currency ||= Spree::Config[:currency] | ||
scope = scope.with_currency(currency) | ||
end | ||
|
||
scope | ||
end | ||
search_scopes << :available | ||
|
||
def self.active(currency = nil) | ||
available(nil, currency) | ||
end | ||
search_scopes << :active | ||
|
||
def self.for_filters(currency, taxon: nil) | ||
scope = active(currency) | ||
scope = scope.in_taxon(taxon) if taxon.present? | ||
scope | ||
end | ||
search_scopes << :for_filters | ||
|
||
def self.for_user(user = nil) | ||
if user.try(:has_spree_role?, 'admin') | ||
with_deleted | ||
else | ||
not_deleted.not_discontinued.where("#{Product.quoted_table_name}.available_on <= ?", Time.current) | ||
end | ||
end | ||
|
||
add_search_scope :taxons_name_eq do |name| | ||
group('spree_products.id').joins(:taxons).where(Taxon.arel_table[:name].eq(name)) | ||
end | ||
|
||
def self.price_table_name | ||
Price.quoted_table_name | ||
end | ||
private_class_method :price_table_name | ||
|
||
# specifically avoid having an order for taxon search (conflicts with main order) | ||
def self.prepare_taxon_conditions(taxons) | ||
ids = taxons.map(&:cached_self_and_descendants_ids).flatten.uniq | ||
joins(:classifications).where(Classification.table_name => { taxon_id: ids }) | ||
end | ||
private_class_method :prepare_taxon_conditions | ||
|
||
# Produce an array of keywords for use in scopes. | ||
# Always return array with at least an empty string to avoid SQL errors | ||
def self.prepare_words(words) | ||
return [''] if words.blank? | ||
|
||
a = words.split(/[,\s]/).map(&:strip) | ||
a.any? ? a : [''] | ||
end | ||
private_class_method :prepare_words | ||
|
||
def self.get_taxons(*ids_or_records_or_names) | ||
taxons = Taxon.table_name | ||
ids_or_records_or_names.flatten.map do |t| | ||
case t | ||
when Integer then Taxon.find_by(id: t) | ||
when ApplicationRecord then t | ||
when String | ||
Taxon.find_by(name: t) || | ||
Taxon.where("#{taxons}.permalink LIKE ? OR #{taxons}.permalink = ?", "%/#{t}/", "#{t}/").first | ||
end | ||
end.compact.flatten.uniq | ||
end | ||
private_class_method :get_taxons | ||
end | ||
end | ||
end | ||
|
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Imho would be better to open Spree::Product class instead of ProductScopes based on https://dev-docs.spreecommerce.org/customization/logic or simple class_eval