Class: Spree::Product

Inherits:
Base
  • Object
show all
Extended by:
FriendlyId
Includes:
MemoizedData, Metadata, MultiStoreResource, ProductScopes, VendorConcern, Webhooks::HasWebhooks
Defined in:
app/models/spree/product.rb

Constant Summary collapse

MEMOIZED_METHODS =
%w(total_on_hand taxonomy_ids taxon_and_ancestors category
default_variant_id tax_category default_variant
purchasable? in_stock? backorderable?)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

belongs_to_required_by_default, for_store, has_many_inversing, json_api_columns, json_api_permitted_attributes, json_api_type, page, spree_base_scopes, spree_base_uniqueness_scope

Methods included from Spree::Preferences::Preferable

#clear_preferences, #default_preferences, #defined_preferences, #deprecated_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_deprecated, #preference_type, #set_preference

Instance Attribute Details

#option_values_hashObject

Returns the value of attribute option_values_hash.



132
133
134
# File 'app/models/spree/product.rb', line 132

def option_values_hash
  @option_values_hash
end

#prototype_idObject

Adding properties and option types on creation based on a chosen prototype



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

def prototype_id
  @prototype_id
end

Class Method Details

.like_any(fields, values) ⇒ Object



286
287
288
289
290
291
# File 'app/models/spree/product.rb', line 286

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

Instance Method Details

#any_variant_in_stock_or_backorderable?Boolean

Returns:

  • (Boolean)


353
354
355
356
357
358
359
# File 'app/models/spree/product.rb', line 353

def any_variant_in_stock_or_backorderable?
  if variants.any?
    variants_including_master.in_stock_or_backorderable.exists?
  else
    master.in_stock_or_backorderable?
  end
end

#available?Boolean

determine if product is available. deleted products and products with status different than active are not available

Returns:

  • (Boolean)


254
255
256
# File 'app/models/spree/product.rb', line 254

def available?
  active? && !deleted?
end

#backorderable?Boolean

Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259

Returns:

  • (Boolean)


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

def backorderable?
  default_variant.backorderable? || variants.any?(&:backorderable?)
end

#backordered?Boolean

determine if any variant (including master) is out of stock and backorderable

Returns:

  • (Boolean)


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

def backordered?
  variants_including_master.any?(&:backordered?)
end

#brandObject



339
340
341
# File 'app/models/spree/product.rb', line 339

def brand
  @brand ||= taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) })
end

#can_supply?Boolean

determine if any variant (including master) can be supplied

Returns:

  • (Boolean)


269
270
271
# File 'app/models/spree/product.rb', line 269

def can_supply?
  variants_including_master.any?(&:can_supply?)
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” -> […]



280
281
282
283
284
# File 'app/models/spree/product.rb', line 280

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

#categoryObject



343
344
345
# File 'app/models/spree/product.rb', line 343

def category
  @category ||= taxons.joins(:taxonomy).order(depth: :desc).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) })
end

#default_variantSpree::Variant

Returns default Variant for Product If ‘track_inventory_levels` is enabled it will try to find the first Variant in stock or backorderable, if there’s none it will return first Variant sorted by ‘position` attribute If `track_inventory_levels` is disabled it will return first Variant sorted by `position` attribute

Returns:



203
204
205
206
207
208
209
210
211
# File 'app/models/spree/product.rb', line 203

def default_variant
  @default_variant ||= Rails.cache.fetch(default_variant_cache_key) do
    if Spree::Config[:track_inventory_levels] && available_variant = variants.detect(&:purchasable?)
      available_variant
    else
      has_variants? ? variants.first : master
    end
  end
end

#default_variant_idInteger

Returns default Variant ID for Product

Returns:

  • (Integer)


215
216
217
# File 'app/models/spree/product.rb', line 215

def default_variant_id
  @default_variant_id ||= default_variant.id
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.

Returns:

  • (Boolean)


247
248
249
# File 'app/models/spree/product.rb', line 247

def deleted?
  !!deleted_at
end

#digital?Boolean

Returns:

  • (Boolean)


361
362
363
# File 'app/models/spree/product.rb', line 361

def digital?
  shipping_category&.name == I18n.t('spree.seed.shipping.categories.digital')
end

#discontinue!Object



258
259
260
261
262
# File 'app/models/spree/product.rb', line 258

def discontinue!
  self.discontinue_on = Time.current
  self.status = 'archived'
  save(validate: false)
end

#discontinued?Boolean

Returns:

  • (Boolean)


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

def discontinued?
  !!discontinue_on && discontinue_on <= Time.current
end

#duplicateObject

for adding products which are closely related to existing ones define “duplicate_extra” for site-specific actions, eg for additional fields



239
240
241
242
# File 'app/models/spree/product.rb', line 239

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

#empty_option_values?Boolean

Returns:

  • (Boolean)


302
303
304
305
306
# File 'app/models/spree/product.rb', line 302

def empty_option_values?
  options.empty? || options.any? do |opt|
    opt.option_type.option_values.empty?
  end
end

#ensure_option_types_exist_for_values_hashObject

Ensures option_types and product_option_types exist for keys in option_values_hash



227
228
229
230
231
232
233
234
235
# File 'app/models/spree/product.rb', line 227

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)
  missing_option_type_ids = required_option_type_ids - option_type_ids
  missing_option_type_ids.each do |id|
    product_option_types.create(option_type_id: id)
  end
end

#find_or_build_masterObject



186
187
188
# File 'app/models/spree/product.rb', line 186

def find_or_build_master
  master || build_master
end

#has_variants?Boolean

the master variant is not a member of the variants array

Returns:

  • (Boolean)


191
192
193
# File 'app/models/spree/product.rb', line 191

def has_variants?
  variants.any?
end

#in_stock?Boolean

Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259

Returns:

  • (Boolean)


177
178
179
# File 'app/models/spree/product.rb', line 177

def in_stock?
  default_variant.in_stock? || variants.any?(&:in_stock?)
end

#masterObject

Master variant may be deleted (i.e. when the product is deleted) which would make AR’s default finder return nil. This is a stopgap for that little problem.



335
336
337
# File 'app/models/spree/product.rb', line 335

def master
  super || variants_including_master.with_deleted.find_by(is_master: true)
end

#property(property_name) ⇒ Object



308
309
310
# File 'app/models/spree/product.rb', line 308

def property(property_name)
  product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value)
end

#purchasable?Boolean

Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259

Returns:

  • (Boolean)


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

def purchasable?
  default_variant.purchasable? || variants.any?(&:purchasable?)
end

#set_property(property_name, property_value, property_presentation = property_name) ⇒ Object



312
313
314
315
316
317
318
319
320
# File 'app/models/spree/product.rb', line 312

def set_property(property_name, property_value, property_presentation = property_name)
  ApplicationRecord.transaction do
    # Works around spree_i18n #301
    property = Property.create_with(presentation: property_presentation).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_categoryObject



219
220
221
# File 'app/models/spree/product.rb', line 219

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

#taxons_for_store(store) ⇒ Object



347
348
349
350
351
# File 'app/models/spree/product.rb', line 347

def taxons_for_store(store)
  Rails.cache.fetch("#{cache_key_with_version}/taxons-per-store/#{store.id}") do
    taxons.for_store(store)
  end
end

#total_on_handObject



322
323
324
325
326
327
328
329
330
# File 'app/models/spree/product.rb', line 322

def total_on_hand
  @total_on_hand ||= Rails.cache.fetch(['product-total-on-hand', cache_key_with_version]) do
    if any_variants_not_track_inventory?
      BigDecimal::INFINITY
    else
      stock_items.sum(:count_on_hand)
    end
  end
end

#variants_and_option_values(current_currency = nil) ⇒ Object

Suitable for displaying only variants that has at least one option value. There may be scenarios where an option type is removed and along with it all option values. At that point all variants associated with only those values should not be displayed to frontend users. Otherwise it breaks the idea of having variants



298
299
300
# File 'app/models/spree/product.rb', line 298

def variants_and_option_values(current_currency = nil)
  variants.active(current_currency).joins(:option_value_variants)
end