Class: Spree::Product
- Extended by:
- FriendlyId
- Defined in:
- app/models/spree/product.rb,
app/models/spree/product/scopes.rb
Overview
this model uses paranoia. #destroy will only soft-destroy records and the default scope hides soft-destroyed records using WHERE deleted_at IS NULL.
Products represent an entity for sale in a store. Products can have variations, called variants. Product properties include description, permalink, availability, shipping category, etc. that do not change by variant.
Constant Summary collapse
- MASTER_ATTRIBUTES =
[ :rebuild_vat_prices, :sku, :price, :currency, :display_amount, :display_price, :weight, :height, :width, :depth, :cost_currency, :price_in, :price_for, :amount_in, :cost_price ]
Instance Attribute Summary collapse
-
#option_values_hash ⇒ Object
Returns the value of attribute option_values_hash.
-
#prototype_id ⇒ Object
Overrides the prototype_id setter in order to ensure it is cast to an integer.
Class Method Summary collapse
- .active(currency = nil) ⇒ Object
- .add_search_scope(name, &block) ⇒ Object
-
.available(available_on = nil, currency = nil) ⇒ Object
Can’t use add_search_scope for this as it needs a default argument.
- .distinct_by_product_ids(sort_order = nil) ⇒ Object
-
.like_any(fields, values) ⇒ ActiveRecord::Relation
Poor man’s full text search.
- .property_conditions(property) ⇒ Object
Instance Method Summary collapse
-
#available? ⇒ Boolean
Determines if product is available.
-
#categorise_variants_from_option(opt_type, pricing_options = Spree::Config.default_pricing_options) ⇒ Hash
deprecated
Deprecated.
This method is not called in the Solidus codebase
-
#deleted? ⇒ Boolean
Use for checking whether this product has been deleted.
-
#display_image ⇒ Spree::Image
Image that can be used for the product.
-
#duplicate ⇒ Spree::Product
Creates a new product with the same attributes, variants, etc.
-
#empty_option_values? ⇒ Boolean
True if there are no option values.
-
#ensure_option_types_exist_for_values_hash ⇒ Array
Ensures option_types and product_option_types exist for keys in option_values_hash.
- #find_or_build_master ⇒ Object
-
#find_variant_property_rule(option_value_ids) ⇒ Spree::VariantPropertyRule
Finds the variant property rule that matches the provided option value ids.
-
#has_variants? ⇒ Boolean
True if there are any variants.
-
#possible_promotions ⇒ Array
All advertised and not-rejected promotions.
-
#property(property_name) ⇒ String
The value of the given property.
-
#set_property(property_name, property_value) ⇒ Object
Assigns the given value to the given property.
-
#tax_category ⇒ Spree::TaxCategory
Tax category for this product, or the default tax category.
-
#total_on_hand ⇒ Fixnum, Infinity
The number of on-hand stock items; Infinity if any variant does not track inventory.
-
#variant_option_values_by_option_type(variant_scope = nil) ⇒ Hash<Spree::OptionType, Array<Spree::OptionValue>>
Groups all of the option values that are associated to the product’s variants, grouped by option type.
-
#variants_and_option_values(current_currency = nil) ⇒ Array<Spree::Variant>
deprecated
Deprecated.
This method can only handle prices for currencies
-
#variants_and_option_values_for(pricing_options = Spree::Config.default_pricing_options) ⇒ Array<Spree::Variant>
All variants with at least one option value.
Methods inherited from Base
display_includes, #initialize_preference_defaults, page, preference
Methods included from Spree::Preferences::Preferable
#default_preferences, #defined_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_type, #set_preference
Instance Attribute Details
#option_values_hash ⇒ Object
Returns the value of attribute option_values_hash.
97 98 99 |
# File 'app/models/spree/product.rb', line 97 def option_values_hash @option_values_hash end |
#prototype_id ⇒ Object
Overrides the prototype_id setter in order to ensure it is cast to an integer.
123 124 125 |
# File 'app/models/spree/product.rb', line 123 def prototype_id @prototype_id end |
Class Method Details
.active(currency = nil) ⇒ Object
181 182 183 184 |
# File 'app/models/spree/product/scopes.rb', line 181 def self.active(currency = nil) Spree::Deprecation.warn("This scope is deprecated, please use .available instead", caller) not_deleted.available(nil, currency) end |
.add_search_scope(name, &block) ⇒ Object
7 8 9 10 |
# File 'app/models/spree/product/scopes.rb', line 7 def self.add_search_scope(name, &block) singleton_class.send(:define_method, name.to_sym, &block) search_scopes << name.to_sym end |
.available(available_on = nil, currency = nil) ⇒ Object
Can’t use add_search_scope for this as it needs a default argument
175 176 177 178 |
# File 'app/models/spree/product/scopes.rb', line 175 def self.available(available_on = nil, currency = nil) Spree::Deprecation.warn("The second currency argument on Product.available has no effect, and is deprecated", caller) if currency joins(master: :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.current) end |
.distinct_by_product_ids(sort_order = nil) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'app/models/spree/product/scopes.rb', line 191 def self.distinct_by_product_ids(sort_order = nil) sort_column = sort_order.split(" ").first # Postgres will complain when using ordering by expressions not present in # SELECT DISTINCT. e.g. # # PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY # expressions must appear in select list. e.g. # # SELECT DISTINCT "spree_products".* FROM "spree_products" LEFT OUTER JOIN # "spree_variants" ON "spree_variants"."product_id" = "spree_products"."id" AND "spree_variants"."is_master" = 't' # AND "spree_variants"."deleted_at" IS NULL LEFT OUTER JOIN "spree_prices" ON # "spree_prices"."variant_id" = "spree_variants"."id" AND "spree_prices"."currency" = 'USD' # AND "spree_prices"."deleted_at" IS NULL WHERE "spree_products"."deleted_at" IS NULL AND ('t'='t') # ORDER BY "spree_prices"."amount" ASC LIMIT 10 OFFSET 0 # # Don't allow sort_column, a variable coming from params, # to be anything but a column in the database if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && !column_names.include?(sort_column) all else distinct end end |
.like_any(fields, values) ⇒ ActiveRecord::Relation
Poor man’s full text search.
Filters products to those which have any of the strings in values in any of the fields in fields.
183 184 185 186 187 188 |
# File 'app/models/spree/product.rb', line 183 def self.like_any(fields, values) conditions = fields.product(values).map do |(field, value)| arel_table[field].matches("%#{value}%") end where conditions.inject(:or) end |
.property_conditions(property) ⇒ Object
12 13 14 15 16 17 18 19 |
# File 'app/models/spree/product/scopes.rb', line 12 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 |
Instance Method Details
#available? ⇒ Boolean
Determines if product is available. A product is available if it has not been deleted and the available_on date is in the past.
158 159 160 |
# File 'app/models/spree/product.rb', line 158 def available? !(available_on.nil? || available_on.future?) && !deleted? end |
#categorise_variants_from_option(opt_type, pricing_options = Spree::Config.default_pricing_options) ⇒ Hash
This method is not called in the Solidus codebase
Groups variants by the specified option type.
169 170 171 172 |
# File 'app/models/spree/product.rb', line 169 def categorise_variants_from_option(opt_type, = Spree::Config.) return {} unless option_types.include?(opt_type) variants.with_prices().group_by { |v| v.option_values.detect { |o| o.option_type == opt_type } } end |
#deleted? ⇒ Boolean
Use for checking whether this product has been deleted. Provided for overriding the logic for determining if a product is deleted.
150 151 152 |
# File 'app/models/spree/product.rb', line 150 def deleted? !!deleted_at end |
#display_image ⇒ Spree::Image
Image that can be used for the product.
Will first search for images on the product, then those belonging to the variants. If all else fails, will return a new image object.
279 280 281 |
# File 'app/models/spree/product.rb', line 279 def display_image images.first || variant_images.first || Spree::Image.new end |
#duplicate ⇒ Spree::Product
Creates a new product with the same attributes, variants, etc.
141 142 143 144 |
# File 'app/models/spree/product.rb', line 141 def duplicate duplicator = ProductDuplicator.new(self) duplicator.duplicate end |
#empty_option_values? ⇒ Boolean
Returns true if there are no option values.
229 230 231 232 233 |
# File 'app/models/spree/product.rb', line 229 def empty_option_values? .empty? || .any? do |opt| opt.option_type.option_values.empty? end end |
#ensure_option_types_exist_for_values_hash ⇒ Array
Ensures option_types and product_option_types exist for keys in option_values_hash.
132 133 134 135 136 |
# File 'app/models/spree/product.rb', line 132 def ensure_option_types_exist_for_values_hash return if option_values_hash.nil? required_option_type_ids = option_values_hash.keys.map(&:to_i) self.option_type_ids |= required_option_type_ids end |
#find_or_build_master ⇒ Object
58 59 60 |
# File 'app/models/spree/product.rb', line 58 def find_or_build_master master || build_master end |
#find_variant_property_rule(option_value_ids) ⇒ Spree::VariantPropertyRule
Finds the variant property rule that matches the provided option value ids.
287 288 289 290 291 |
# File 'app/models/spree/product.rb', line 287 def find_variant_property_rule(option_value_ids) variant_property_rules.find do |rule| rule.matches_option_value_ids?(option_value_ids) end end |
#has_variants? ⇒ Boolean
Returns true if there are any variants.
108 109 110 |
# File 'app/models/spree/product.rb', line 108 def has_variants? variants.any? end |
#possible_promotions ⇒ Array
Returns all advertised and not-rejected promotions.
257 258 259 260 |
# File 'app/models/spree/product.rb', line 257 def possible_promotions promotion_ids = promotion_rules.map(&:promotion_id).uniq Spree::Promotion.advertised.where(id: promotion_ids).reject(&:inactive?) end |
#property(property_name) ⇒ String
Returns the value of the given property. nil if property is undefined on this product.
237 238 239 240 |
# File 'app/models/spree/product.rb', line 237 def property(property_name) return nil unless prop = properties.find_by(name: property_name) product_properties.find_by(property: prop).try(:value) end |
#set_property(property_name, property_value) ⇒ Object
Assigns the given value to the given property.
246 247 248 249 250 251 252 253 254 |
# File 'app/models/spree/product.rb', line 246 def set_property(property_name, property_value) ActiveRecord::Base.transaction do # Works around spree_i18n https://github.com/spree/spree/issues/301 property = Property.create_with(presentation: property_name).find_or_create_by(name: property_name) product_property = ProductProperty.where(product: self, property: property).first_or_initialize product_property.value = property_value product_property.save! end end |
#tax_category ⇒ Spree::TaxCategory
Returns tax category for this product, or the default tax category.
113 114 115 |
# File 'app/models/spree/product.rb', line 113 def tax_category super || TaxCategory.find_by(is_default: true) end |
#total_on_hand ⇒ Fixnum, Infinity
The number of on-hand stock items; Infinity if any variant does not track inventory.
266 267 268 269 270 271 272 |
# File 'app/models/spree/product.rb', line 266 def total_on_hand if any_variants_not_track_inventory? Float::INFINITY else stock_items.sum(:count_on_hand) end end |
#variant_option_values_by_option_type(variant_scope = nil) ⇒ Hash<Spree::OptionType, Array<Spree::OptionValue>>
Groups all of the option values that are associated to the product’s variants, grouped by option type.
used to determine the applied option_types associated with the products variants grouped by option type
217 218 219 220 221 222 223 224 225 226 |
# File 'app/models/spree/product.rb', line 217 def variant_option_values_by_option_type(variant_scope = nil) option_value_scope = Spree::OptionValuesVariant.joins(:variant) .where(spree_variants: { product_id: id }) option_value_scope = option_value_scope.merge(variant_scope) if variant_scope option_value_ids = option_value_scope.distinct.pluck(:option_value_id) Spree::OptionValue.where(id: option_value_ids). includes(:option_type). order("#{Spree::OptionType.table_name}.position, #{Spree::OptionValue.table_name}.position"). group_by(&:option_type) end |
#variants_and_option_values(current_currency = nil) ⇒ Array<Spree::Variant>
This method can only handle prices for currencies
Returns all variants with at least one option value.
193 194 195 196 197 |
# File 'app/models/spree/product.rb', line 193 def variants_and_option_values(current_currency = nil) variants.includes(:option_values).active(current_currency).select do |variant| variant.option_values.any? end end |
#variants_and_option_values_for(pricing_options = Spree::Config.default_pricing_options) ⇒ Array<Spree::Variant>
Returns all variants with at least one option value.
204 205 206 207 208 |
# File 'app/models/spree/product.rb', line 204 def variants_and_option_values_for( = Spree::Config.) variants.includes(:option_values).with_prices().select do |variant| variant.option_values.any? end end |