Class: Spree::Product
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Spree::Product
- Defined in:
- app/models/spree/product.rb,
app/models/spree/product/scopes.rb
Instance Attribute Summary collapse
-
#prototype_id ⇒ Object
Adding properties and option types on creation based on a chosen prototype.
Class Method Summary collapse
-
.active ⇒ Object
RAILS 3 TODO - this scope doesn’t match the original 2.3.x version, needs attention (but it works).
- .ascend_by_master_price ⇒ Object
- .available(available_on = nil) ⇒ Object
- .descend_by_master_price ⇒ Object
-
.descend_by_popularity ⇒ Object
Sorts products from most popular (popularity is extracted from how many times use has put product in cart, not completed orders).
- .in_cached_group(product_group) ⇒ Object
-
.in_name(words) ⇒ Object
Finds all products that have a name containing the given words.
-
.in_name_or_description(words) ⇒ Object
Finds all products that have a name, description, meta_description or meta_keywords containing the given keywords.
-
.in_name_or_keywords(words) ⇒ Object
Finds all products that have a name or meta_keywords containing the given words.
-
.in_taxon(taxon) ⇒ Object
This scope selects products in taxon AND all its descendants If you need products only within one taxon use.
-
.in_taxons(*taxons) ⇒ Object
This scope selects products in all taxons AND all its descendants If you need products only within one taxon use.
- .like_any(fields, values) ⇒ Object
- .master_price_gte(price) ⇒ Object
- .master_price_lte(price) ⇒ Object
- .not_deleted ⇒ Object
- .on_hand ⇒ Object
- .price_between(low, high) ⇒ Object
- .simple_scopes ⇒ Object
- .taxons_name_eq(name) ⇒ Object
-
.with(value) ⇒ Object
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.
-
.with_ids(*ids) ⇒ Object
Finds all products that have the ids matching the given collection of ids.
-
.with_option(option) ⇒ Object
a scope that finds all products having an option_type specified by name, object or id.
-
.with_option_value(option, value) ⇒ Object
a scope that finds all products having an option value specified by name, object or id.
-
.with_property(property) ⇒ Object
a scope that finds all products having property specified by name, object or id.
-
.with_property_value(property, value) ⇒ Object
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.
Instance Method Summary collapse
- #add_properties_and_option_types_from_prototype ⇒ Object
-
#categorise_variants_from_option(opt_type) ⇒ Object
split variants list into hash which shows mapping of opt value onto matching variants eg categorise_variants_from_option(color) => -> […], “blue” -> […].
-
#deleted? ⇒ Boolean
use deleted? rather than checking the attribute directly.
-
#duplicate ⇒ Object
for adding products which are closely related to existing ones define “duplicate_extra” for site-specific actions, eg for additional fields.
- #effective_tax_rate ⇒ Object
- #empty_option_values? ⇒ Boolean
- #ensure_master ⇒ Object
-
#has_stock? ⇒ Boolean
Returns true if there are inventory units (any variant) with “on_hand” state for this product.
-
#has_variants? ⇒ Boolean
returns true if the product has any variants (the master variant is not a member of the variants array).
-
#on_hand ⇒ Object
returns the number of inventory units “on_hand” for this product.
-
#on_hand=(new_level) ⇒ Object
adjusts the “on_hand” inventory level for the product up or down to match the given new_level.
- #tax_category ⇒ Object
- #to_param ⇒ Object
- #variant_images ⇒ Object
Instance Attribute Details
#prototype_id ⇒ Object
Adding properties and option types on creation based on a chosen prototype
114 115 116 |
# File 'app/models/spree/product.rb', line 114 def prototype_id @prototype_id end |
Class Method Details
.active ⇒ Object
RAILS 3 TODO - this scope doesn’t match the original 2.3.x version, needs attention (but it works)
197 198 199 |
# File 'app/models/spree/product/scopes.rb', line 197 def self.active not_deleted.available end |
.ascend_by_master_price ⇒ Object
25 26 27 |
# File 'app/models/spree/product/scopes.rb', line 25 def self.ascend_by_master_price joins(:variants_with_only_master).order("#{variant_table_name}.price ASC") end |
.available(available_on = nil) ⇒ Object
192 193 194 |
# File 'app/models/spree/product/scopes.rb', line 192 def self.available(available_on = nil) where('available_on <= ?', available_on || Time.now) end |
.descend_by_master_price ⇒ Object
29 30 31 |
# File 'app/models/spree/product/scopes.rb', line 29 def self.descend_by_master_price joins(:variants_with_only_master).order("#{variant_table_name}.price DESC") end |
.descend_by_popularity ⇒ Object
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’
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'app/models/spree/product/scopes.rb', line 170 def self.descend_by_popularity 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 |
.in_cached_group(product_group) ⇒ Object
77 78 79 |
# File 'app/models/spree/product/scopes.rb', line 77 def self.in_cached_group(product_group) joins(:product_groups).where('spree_product_groups_products.product_group_id' => product_group) end |
.in_name(words) ⇒ Object
Finds all products that have a name containing the given words.
142 143 144 |
# File 'app/models/spree/product/scopes.rb', line 142 def self.in_name(words) like_any([:name], prepare_words(words)) end |
.in_name_or_description(words) ⇒ Object
Finds all products that have a name, description, meta_description or meta_keywords containing the given keywords.
152 153 154 |
# File 'app/models/spree/product/scopes.rb', line 152 def self.in_name_or_description(words) like_any([:name, :description, :meta_description, :meta_keywords], prepare_words(words)) end |
.in_name_or_keywords(words) ⇒ Object
Finds all products that have a name or meta_keywords containing the given words.
147 148 149 |
# File 'app/models/spree/product/scopes.rb', line 147 def self.in_name_or_keywords(words) like_any([:name, :meta_keywords], prepare_words(words)) end |
.in_taxon(taxon) ⇒ Object
63 64 65 |
# File 'app/models/spree/product/scopes.rb', line 63 def self.in_taxon(taxon) joins(:taxons).where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) }) end |
.in_taxons(*taxons) ⇒ Object
72 73 74 75 |
# File 'app/models/spree/product/scopes.rb', line 72 def self.in_taxons(*taxons) taxons = get_taxons(taxons) taxons.first ? prepare_taxon_conditions(taxons) : scoped end |
.like_any(fields, values) ⇒ Object
182 183 184 185 |
# File 'app/models/spree/product.rb', line 182 def self.like_any(fields, values) where_str = fields.map { |field| Array.new(values.size, "#{self.quoted_table_name}.#{field} #{LIKE} ?").join(' OR ') }.join(' OR ') self.where([where_str, values.map { |value| "%#{value}%" } * fields.size].flatten) end |
.master_price_gte(price) ⇒ Object
55 56 57 |
# File 'app/models/spree/product/scopes.rb', line 55 def self.master_price_gte(price) joins(:master).where("#{variant_table_name}.price >= ?", price) end |
.master_price_lte(price) ⇒ Object
51 52 53 |
# File 'app/models/spree/product/scopes.rb', line 51 def self.master_price_lte(price) joins(:master).where("#{variant_table_name}.price <= ?", price) end |
.not_deleted ⇒ Object
188 189 190 |
# File 'app/models/spree/product/scopes.rb', line 188 def self.not_deleted where(:deleted_at => nil) end |
.on_hand ⇒ Object
201 202 203 |
# File 'app/models/spree/product/scopes.rb', line 201 def self.on_hand where("spree_products.id in (select product_id from spree_variants group by product_id having sum(count_on_hand) > 0)") end |
.price_between(low, high) ⇒ Object
47 48 49 |
# File 'app/models/spree/product/scopes.rb', line 47 def self.price_between(low, high) joins(:master).where(Variant.table_name => { :price => low..high }) end |
.simple_scopes ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 |
# File 'app/models/spree/product/scopes.rb', line 3 def self.simple_scopes [ :ascend_by_updated_at, :descend_by_updated_at, :ascend_by_name, :descend_by_name, # Need to have master price scopes here # This makes them appear in admin/product_groups/edit :ascend_by_master_price, :descend_by_master_price, :descend_by_popularity ] end |
.taxons_name_eq(name) ⇒ Object
205 206 207 |
# File 'app/models/spree/product/scopes.rb', line 205 def self.taxons_name_eq(name) joins(:taxons).where(Taxon.arel_table[:name].eq(name)) end |
.with(value) ⇒ Object
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
135 136 137 138 139 |
# File 'app/models/spree/product/scopes.rb', line 135 def self.with(value) includes(:variants_including_master => :option_values). includes(:product_properties). where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value) end |
.with_ids(*ids) ⇒ Object
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
158 159 160 |
# File 'app/models/spree/product/scopes.rb', line 158 def self.with_ids(*ids) where(:id => ids) end |
.with_option(option) ⇒ Object
a scope that finds all products having an option_type specified by name, object or id
108 109 110 111 112 113 114 115 116 117 |
# File 'app/models/spree/product/scopes.rb', line 108 def self.with_option(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 |
.with_option_value(option, value) ⇒ Object
a scope that finds all products having an option value specified by name, object or id
120 121 122 123 124 125 126 127 128 129 130 |
# File 'app/models/spree/product/scopes.rb', line 120 def self.with_option_value(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 joins(:variants_including_master => :option_values).where(conditions) end |
.with_property(property) ⇒ Object
a scope that finds all products having property specified by name, object or id
82 83 84 85 86 87 88 89 90 91 |
# File 'app/models/spree/product/scopes.rb', line 82 def self.with_property(property) properties = Property.table_name conditions = case property when String then { "#{properties}.name" => property } when Property then { "#{properties}.id" => property.id } else { "#{properties}.id" => property.to_i } end joins(:properties).where(conditions) end |
.with_property_value(property, value) ⇒ Object
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
95 96 97 98 99 100 101 102 103 104 105 |
# File 'app/models/spree/product/scopes.rb', line 95 def self.with_property_value(property, value) properties = Spree::Property.table_name conditions = case property when String then ["#{properties}.name = ?", property] when Property then ["#{properties}.id = ?", property.id] else ["#{properties}.id = ?", property.to_i] end conditions = ["#{ProductProperty.table_name}.value = ? AND #{conditions[0]}", value, conditions[1]] joins(:properties).where(conditions) end |
Instance Method Details
#add_properties_and_option_types_from_prototype ⇒ Object
119 120 121 122 123 124 125 126 |
# File 'app/models/spree/product.rb', line 119 def add_properties_and_option_types_from_prototype if prototype_id && prototype = Spree::Prototype.find_by_id(prototype_id) prototype.properties.each do |property| product_properties.create(:property => property) end self.option_types = prototype.option_types end end |
#categorise_variants_from_option(opt_type) ⇒ Object
split variants list into hash which shows mapping of opt value onto matching variants eg categorise_variants_from_option(color) => -> […], “blue” -> […]
169 170 171 172 |
# File 'app/models/spree/product.rb', line 169 def categorise_variants_from_option(opt_type) return {} unless option_types.include?(opt_type) variants.active.group_by { |v| v.option_values.detect { |o| o.option_type == opt_type} } end |
#deleted? ⇒ Boolean
use deleted? rather than checking the attribute directly. this allows extensions to override deleted? if they want to provide their own definition.
163 164 165 |
# File 'app/models/spree/product.rb', line 163 def deleted? !!deleted_at end |
#duplicate ⇒ Object
for adding products which are closely related to existing ones define “duplicate_extra” for site-specific actions, eg for additional fields
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'app/models/spree/product.rb', line 130 def duplicate p = self.dup p.name = 'COPY OF ' + self.name p.deleted_at = nil p.created_at = p.updated_at = nil p.taxons = self.taxons p.product_properties = self.product_properties.map { |q| r = q.dup; r.created_at = r.updated_at = nil; r } image_dup = lambda { |i| j = i.dup; j. = i..clone; j } p.images = self.images.map { |i| image_dup.call i } master = Spree::Variant.find_by_product_id_and_is_master(self.id, true) variant = master.dup variant.sku = 'COPY OF ' + master.sku variant.deleted_at = nil variant.images = master.images.map { |i| image_dup.call i } p.master = variant if self.has_variants? # don't dup the actual variants, just the characterising types p.option_types = self.option_types else end # allow site to do some customization p.send(:duplicate_extra, self) if p.respond_to?(:duplicate_extra) p.save! p end |
#effective_tax_rate ⇒ Object
174 175 176 177 178 179 180 |
# File 'app/models/spree/product.rb', line 174 def effective_tax_rate if self.tax_category tax_category.effective_amount else TaxRate.default end end |
#empty_option_values? ⇒ Boolean
187 188 189 190 191 |
# File 'app/models/spree/product.rb', line 187 def empty_option_values? .empty? || .any? do |opt| opt.option_type.option_values.empty? end end |
#ensure_master ⇒ Object
75 76 77 78 |
# File 'app/models/spree/product.rb', line 75 def ensure_master return unless self.new_record? self.master ||= Variant.new end |
#has_stock? ⇒ Boolean
Returns true if there are inventory units (any variant) with “on_hand” state for this product
101 102 103 |
# File 'app/models/spree/product.rb', line 101 def has_stock? master.in_stock? || variants.any?(&:in_stock?) end |
#has_variants? ⇒ Boolean
returns true if the product has any variants (the master variant is not a member of the variants array)
85 86 87 |
# File 'app/models/spree/product.rb', line 85 def has_variants? variants.any? end |
#on_hand ⇒ Object
returns the number of inventory units “on_hand” for this product
90 91 92 |
# File 'app/models/spree/product.rb', line 90 def on_hand has_variants? ? variants.inject(0) { |sum, v| sum + v.on_hand } : master.on_hand end |
#on_hand=(new_level) ⇒ Object
adjusts the “on_hand” inventory level for the product up or down to match the given new_level
95 96 97 98 |
# File 'app/models/spree/product.rb', line 95 def on_hand=(new_level) raise 'cannot set on_hand of product with variants' if has_variants? && Spree::Config[:track_inventory_levels] master.on_hand = new_level end |
#tax_category ⇒ Object
105 106 107 108 109 110 111 |
# File 'app/models/spree/product.rb', line 105 def tax_category if self[:tax_category_id].nil? TaxCategory.where(:is_default => true).first else TaxCategory.find(self[:tax_category_id]) end end |
#to_param ⇒ Object
80 81 82 |
# File 'app/models/spree/product.rb', line 80 def to_param permalink.present? ? permalink : (permalink_was || name.to_s.to_url) end |
#variant_images ⇒ Object
61 62 63 |
# File 'app/models/spree/product.rb', line 61 def variant_images Image.find_by_sql("SELECT #{Asset.quoted_table_name}.* FROM #{Asset.quoted_table_name} LEFT JOIN #{Variant.quoted_table_name} ON (#{Variant.quoted_table_name}.id = #{Asset.quoted_table_name}.viewable_id) WHERE (#{Variant.quoted_table_name}.product_id = #{self.id})") end |