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 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

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

#option_values_hashObject

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

.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) ⇒ Object

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

.distinct_by_product_ids(sort_order = nil) ⇒ Object



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

.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.



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

.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.



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

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

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

Deprecated.

This method is not called in the Solidus codebase

Groups variants by the specified option type.



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

#deleted?Boolean

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



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

def deleted?
  !!deleted_at
end

#display_imageSpree::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.



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

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

#duplicateSpree::Product

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



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

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

#empty_option_values?Boolean



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

#ensure_option_types_exist_for_values_hashArray

Ensures option_types and product_option_types exist for keys in option_values_hash.



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

#find_or_build_masterObject



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.



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

#has_variants?Boolean



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

def has_variants?
  variants.any?
end

#possible_promotionsArray



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

#property(property_name) ⇒ String



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

#set_property(property_name, property_value) ⇒ Object

Assigns the given value to the given property.



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

#tax_categorySpree::TaxCategory



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

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

#total_on_handFixnum, Infinity

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



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

#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



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

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

Deprecated.

This method can only handle prices for currencies

Returns 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

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



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