Class: Spree::Product

Inherits:
Base
  • Object
show all
Extended by:
FriendlyId
Defined in:
app/models/spree/product.rb,
app/models/spree/product/scopes.rb

Overview

Note:

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

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)

Class Method Summary (collapse)

Instance Method Summary (collapse)

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

- (Object) option_values_hash

Returns the value of attribute option_values_hash



96
97
98
# File 'app/models/spree/product.rb', line 96

def option_values_hash
  @option_values_hash
end

Class Method Details

+ (Object) add_search_scope(name, &block)



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

+ (Object) available(available_on = nil)

Can't use add_search_scope for this as it needs a default argument



175
176
177
# File 'app/models/spree/product/scopes.rb', line 175

def self.available(available_on = nil)
  joins(master: :prices).where("#{Spree::Product.quoted_table_name}.available_on <= ?", available_on || Time.current)
end

+ (Object) distinct_by_product_ids(sort_order = nil)



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'app/models/spree/product/scopes.rb', line 184

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

+ (ActiveRecord::Relation) like_any(fields, values)

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.

Parameters:

  • fields (Array{String,Symbol})

    columns of the products table to search for values

  • values (Array{String})

    strings to search through fields for

Returns:

  • (ActiveRecord::Relation)

    scope with WHERE clause for search applied



171
172
173
174
175
176
# File 'app/models/spree/product.rb', line 171

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

+ (Object) property_conditions(property)



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

- (Boolean) available?

Determines if product is available. A product is available if it has not been deleted and the available_on date is in the past.

Returns:

  • (Boolean)

    true if this product is available



146
147
148
# File 'app/models/spree/product.rb', line 146

def available?
  !(available_on.nil? || available_on.future?) && !deleted?
end

- (Hash) categorise_variants_from_option(opt_type, pricing_options = Spree::Config.default_pricing_options)

Deprecated.

This method is not called in the Solidus codebase

Groups variants by the specified option type.

Parameters:

  • opt_type (String)

    the name of the option type to group by

  • pricing_options (Spree::Config.pricing_options_class) (defaults to: Spree::Config.default_pricing_options)

    the pricing options to search for, default: the default pricing options

Returns:

  • (Hash)

    option_type as keys, array of variants as values.



157
158
159
160
# File 'app/models/spree/product.rb', line 157

def categorise_variants_from_option(opt_type, pricing_options = Spree::Config.default_pricing_options)
  return {} unless option_types.include?(opt_type)
  variants.with_prices(pricing_options).group_by { |v| v.option_values.detect { |o| o.option_type == opt_type } }
end

- (Boolean) deleted?

Use for checking whether this product has been deleted. Provided for overriding the logic for determining if a product is deleted.

Returns:

  • (Boolean)

    true if this product is deleted



138
139
140
# File 'app/models/spree/product.rb', line 138

def deleted?
  !!deleted_at
end

- (Spree::Image) display_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.

Returns:



265
266
267
# File 'app/models/spree/product.rb', line 265

def display_image
  images.first || variant_images.first || Spree::Image.new
end

- (Spree::Product) duplicate

Creates a new product with the same attributes, variants, etc.

Returns:



129
130
131
132
# File 'app/models/spree/product.rb', line 129

def duplicate
  duplicator = ProductDuplicator.new(self)
  duplicator.duplicate
end

- (Boolean) empty_option_values?

Returns true if there are no option values

Returns:

  • (Boolean)

    true if there are no option values



217
218
219
# File 'app/models/spree/product.rb', line 217

def empty_option_values?
  options.empty? || !option_types.left_joins(:option_values).where('spree_option_values.id IS NULL').empty?
end

- (Array) ensure_option_types_exist_for_values_hash

Ensures option_types and product_option_types exist for keys in option_values_hash.

Returns:

  • (Array)

    the option_values



120
121
122
123
124
# File 'app/models/spree/product.rb', line 120

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

- (Object) find_or_build_master



58
59
60
# File 'app/models/spree/product.rb', line 58

def find_or_build_master
  master || build_master
end

- (Spree::VariantPropertyRule) find_variant_property_rule(option_value_ids)

Finds the variant property rule that matches the provided option value ids.

Parameters:

  • list (Array<Integer>)

    of option value ids

Returns:



273
274
275
276
277
# File 'app/models/spree/product.rb', line 273

def find_variant_property_rule(option_value_ids)
  variant_property_rules.find do |rule|
    rule.matches_option_value_ids?(option_value_ids)
  end
end

- (Boolean) has_variants?

Returns true if there are any variants

Returns:

  • (Boolean)

    true if there are any variants



107
108
109
# File 'app/models/spree/product.rb', line 107

def has_variants?
  variants.any?
end

- (Array) possible_promotions

Returns all advertised and not-rejected promotions

Returns:

  • (Array)

    all advertised and not-rejected promotions



243
244
245
246
# File 'app/models/spree/product.rb', line 243

def possible_promotions
  promotion_ids = promotion_rules.map(&:promotion_id).uniq
  Spree::Promotion.advertised.where(id: promotion_ids).reject(&:inactive?)
end

- (String) property(property_name)

Returns the value of the given property. nil if property is undefined on this product

Parameters:

  • property_name (String)

    the name of the property to find

Returns:

  • (String)

    the value of the given property. nil if property is undefined on this product



223
224
225
226
# File 'app/models/spree/product.rb', line 223

def property(property_name)
  return nil unless prop = properties.find_by(name: property_name)
  product_properties.find_by(property: prop).try(:value)
end

- (Object) set_property(property_name, property_value)

Assigns the given value to the given property.

Parameters:

  • property_name (String)

    the name of the property

  • property_value (String)

    the property value



232
233
234
235
236
237
238
239
240
# File 'app/models/spree/product.rb', line 232

def set_property(property_name, property_value)
  ActiveRecord::Base.transaction do
    # Works around spree_i18n https://github.com/spree/spree/issues/301
    property = Spree::Property.create_with(presentation: property_name).find_or_create_by(name: property_name)
    product_property = Spree::ProductProperty.where(product: self, property: property).first_or_initialize
    product_property.value = property_value
    product_property.save!
  end
end

- (Spree::TaxCategory) tax_category

Returns tax category for this product, or the default tax category

Returns:



112
113
114
# File 'app/models/spree/product.rb', line 112

def tax_category
  super || Spree::TaxCategory.find_by(is_default: true)
end

- (Fixnum, Infinity) total_on_hand

The number of on-hand stock items; Infinity if any variant does not track inventory.

Returns:

  • (Fixnum, Infinity)


252
253
254
255
256
257
258
# File 'app/models/spree/product.rb', line 252

def total_on_hand
  if any_variants_not_track_inventory?
    Float::INFINITY
  else
    stock_items.sum(:count_on_hand)
  end
end

- (Hash<Spree::OptionType, Array<Spree::OptionValue>>) variant_option_values_by_option_type(variant_scope = nil)

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

Parameters:

  • variant_scope (ActiveRecord_Associations_CollectionProxy) (defaults to: nil)

    scope to filter the variants

Returns:



205
206
207
208
209
210
211
212
213
214
# File 'app/models/spree/product.rb', line 205

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

- (Array<Spree::Variant>) variants_and_option_values(current_currency = nil)

Deprecated.

This method can only handle prices for currencies

Returns all variants with at least one option value

Parameters:

  • current_currency (String) (defaults to: nil)

    currency to filter variants by; defaults to Spree's default

Returns:

  • (Array<Spree::Variant>)

    all variants with at least one option value



181
182
183
184
185
# File 'app/models/spree/product.rb', line 181

def variants_and_option_values(current_currency = nil)
  variants.includes(:option_values).active(current_currency).select do |variant|
    variant.option_values.any?
  end
end

- (Array<Spree::Variant>) variants_and_option_values_for(pricing_options = Spree::Config.default_pricing_options)

Returns all variants with at least one option value

Parameters:

  • pricing_options (Spree::Variant::PricingOptions) (defaults to: Spree::Config.default_pricing_options)

    the pricing options to search for, default: the default pricing options

Returns:

  • (Array<Spree::Variant>)

    all variants with at least one option value



192
193
194
195
196
# File 'app/models/spree/product.rb', line 192

def variants_and_option_values_for(pricing_options = Spree::Config.default_pricing_options)
  variants.includes(:option_values).with_prices(pricing_options).select do |variant|
    variant.option_values.any?
  end
end