Class: Spree::Variant

Inherits:
Base
  • Object
show all
Includes:
DefaultPrice
Defined in:
app/models/spree/variant.rb,
app/models/spree/variant/scopes.rb,
app/models/spree/variant/price_selector.rb,
app/models/spree/variant/pricing_options.rb,
app/models/spree/variant/vat_price_generator.rb

Overview

Master Variant

Every product has one master variant, which stores master price and SKU, size and weight, etc. The master variant does not have option values associated with it. Contains on_hand inventory levels only when there are no variants for the product.

Variants

All variants can access the product properties directly (via reverse delegation). Inventory units are tied to Variant. The master variant can have inventory units, but not option values. All other variants have option values and may have inventory units. Sum of on_hand each variant's inventory level determine “on_hand” level for the product.

Defined Under Namespace

Classes: PriceSelector, PricingOptions, VatPriceGenerator

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DefaultPrice

#find_or_build_default_price, #has_default_price?

Methods inherited from Base

display_includes, #initialize_preference_defaults, page, preference

Methods included from Preferences::Preferable

#default_preferences, #defined_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_type, #set_preference

Instance Attribute Details

#rebuild_vat_prices=(value) ⇒ Object (writeonly)

Sets the attribute rebuild_vat_prices

Parameters:

  • value

    the value to set the attribute rebuild_vat_prices to.



20
21
22
# File 'app/models/spree/variant.rb', line 20

def rebuild_vat_prices=(value)
  @rebuild_vat_prices = value
end

Class Method Details

.active(currency = nil) ⇒ ActiveRecord::Relation

Deprecated.

Please use the .with_prices scope instead

Returns variants that are not deleted and have a price in the given currency.

Parameters:

  • currency (String) (defaults to: nil)

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

Returns:

  • (ActiveRecord::Relation)


101
102
103
104
# File 'app/models/spree/variant.rb', line 101

def self.active(currency = nil)
  Spree::Deprecation.warn("`Variant.active(currency)` is deprecated. Please use `Variant.with_prices(pricing_options)` instead.", caller)
  joins(:prices).where(deleted_at: nil).where('spree_prices.currency' => currency || Spree::Config[:currency]).where('spree_prices.amount IS NOT NULL')
end

.has_option(option_type, *option_values) ⇒ Object Also known as: has_options

Returns variants that match a given option value

Example:

product.variants_including_master.has_option(OptionType.find_by(name: 'shoe-size'), OptionValue.find_by(name: '8'))



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/models/spree/variant/scopes.rb', line 14

def has_option(option_type, *option_values)
  option_types = Spree::OptionType.table_name

  option_type_conditions = case option_type
                           when OptionType then { "#{option_types}.name" => option_type.name }
                           when String     then { "#{option_types}.name" => option_type }
  else { "#{option_types}.id" => option_type }
  end

  relation = joins(option_values: :option_type).where(option_type_conditions)

  option_values.each do |option_value|
    option_value_conditions = case option_value
                              when OptionValue then { "#{Spree::OptionValue.table_name}.name" => option_value.name }
                              when String      then { "#{Spree::OptionValue.table_name}.name" => option_value }
    else { "#{Spree::OptionValue.table_name}.id" => option_value }
    end
    relation = relation.where(option_value_conditions)
  end

  relation
end

.in_stock(stock_locations = nil) ⇒ ActiveRecord::Relation

Returns variants that are in stock. When stock locations are provided as a parameter, the scope is limited to variants that are in stock in the provided stock locations.

Parameters:

Returns:

  • (ActiveRecord::Relation)


83
84
85
86
87
88
89
90
# File 'app/models/spree/variant.rb', line 83

def self.in_stock(stock_locations = nil)
  return all unless Spree::Config.track_inventory_levels
  in_stock_variants = joins(:stock_items).where(Spree::StockItem.arel_table[:count_on_hand].gt(0).or(arel_table[:track_inventory].eq(false)))
  if stock_locations.present?
    in_stock_variants = in_stock_variants.where(spree_stock_items: { stock_location_id: stock_locations.map(&:id) })
  end
  in_stock_variants
end

.with_prices(pricing_options = Spree::Config.default_pricing_options) ⇒ ActiveRecord::Relation

Returns variants that have a price for the given pricing options

Parameters:

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

    A Pricing Options object as defined on the price selector class

Returns:

  • (ActiveRecord::Relation)


110
111
112
# File 'app/models/spree/variant.rb', line 110

def self.with_prices(pricing_options = Spree::Config.default_pricing_options)
  joins(:prices).merge(Spree::Price.currently_valid.where(pricing_options.search_arguments))
end

Instance Method Details

#amount_in(currency) ⇒ Float

Deprecated.

Please use #price_for instead and use a money object rathern than a BigDecimal.

Fetches the price amount in the specified currency.

Returns:

  • (Float)

    the amount in the specified currency.



283
284
285
# File 'app/models/spree/variant.rb', line 283

def amount_in(currency)
  price_in(currency).try(:amount)
end

#can_supply?(quantity = 1) ⇒ Boolean

Returns true if the desired quantity can be supplied

Parameters:

  • quantity (Fixnum) (defaults to: 1)

    how many are desired

Returns:

  • (Boolean)

    true if the desired quantity can be supplied



311
312
313
# File 'app/models/spree/variant.rb', line 311

def can_supply?(quantity = 1)
  Spree::Stock::Quantifier.new(self).can_supply?(quantity)
end

#cost_price=(price) ⇒ Bignum

Sets the cost_price for the variant.

Parameters:

  • price (Any)

    the price to set

Returns:

  • (Bignum)


127
128
129
# File 'app/models/spree/variant.rb', line 127

def cost_price=(price)
  self[:cost_price] = Spree::LocalizedNumber.parse(price) if price.present?
end

#deleted?Boolean

Returns whether this variant has been deleted. Provided as a method of overriding the logic for determining if a variant is deleted.

Returns:

  • (Boolean)

    true if this variant has been deleted



185
186
187
# File 'app/models/spree/variant.rb', line 185

def deleted?
  !!deleted_at
end

#descriptive_nameString

Generates a verbose name for the variant, appending 'Master' if it is a master variant, otherwise a list of its option values.

Returns:

  • (String)

    the generated name



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

def descriptive_name
  is_master? ? name + ' - Master' : name + ' - ' + options_text
end

#display_image(fallback: true) ⇒ Spree::Image

Image that can be used for the variant.

Will first search for images on the variant. If it doesn't find any, it'll fallback to any variant image (unless fallback is false) or to a new Image.

Parameters:

  • fallback (Boolean)

    whether or not we should fallback to an image not from this variant

Returns:



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

def display_image(fallback: true)
  images.first || (fallback && product.variant_images.first) || Spree::Image.new
end

#exchange_nameString

Determines the name of an Exchange variant.

Returns:

  • (String)

    the master variant name, if it is a master; or a comma-separated list of all option values.



169
170
171
# File 'app/models/spree/variant.rb', line 169

def exchange_name
  is_master? ? name : options_text
end

#in_stock?Boolean

Returns true if there is stock on-hand for the variant.

Returns:

  • (Boolean)

    true if there is stock on-hand for the variant.



303
304
305
306
307
# File 'app/models/spree/variant.rb', line 303

def in_stock?
  Rails.cache.fetch(in_stock_cache_key) do
    total_on_hand > 0
  end
end

#is_backorderable?Boolean

Returns true if this variant can be backordered

Returns:

  • (Boolean)

    true if this variant can be backordered



147
148
149
# File 'app/models/spree/variant.rb', line 147

def is_backorderable?
  Spree::Stock::Quantifier.new(self).backorderable?
end

#name_and_skuString

Generates a friendly name and sku string.

Returns:

  • (String)


291
292
293
# File 'app/models/spree/variant.rb', line 291

def name_and_sku
  "#{name} - #{sku}"
end

#on_backorderFixnum

Counts the number of units currently on backorder for this variant.

Returns:

  • (Fixnum)


142
143
144
# File 'app/models/spree/variant.rb', line 142

def on_backorder
  inventory_units.with_state('backordered').size
end

#option_value(opt_name) ⇒ String

Fetches the option value for the given option name.

Parameters:

  • opt_name (String)

    the name of the option whose value you want

Returns:

  • (String)

    the option value



236
237
238
# File 'app/models/spree/variant.rb', line 236

def option_value(opt_name)
  option_values.detect { |o| o.option_type.name == opt_name }.try(:presentation)
end

#options=(options = {}) ⇒ Object

Assign given options hash to option values.

Parameters:

  • options (Array) (defaults to: {})

    array of hashes with a name and value.



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

def options=(options = {})
  options.each do |option|
    set_option_value(option[:name], option[:value])
  end
end

#options_textString

Creates a sentence out of the variant's (sorted) option values.

Returns:

  • (String)

    a sentence-ified string of option values.



154
155
156
157
158
159
160
161
162
163
164
# File 'app/models/spree/variant.rb', line 154

def options_text
  values = option_values.includes(:option_type).sort_by do |option_value|
    option_value.option_type.position
  end

  values.to_a.map! do |ov|
    "#{ov.option_type.presentation}: #{ov.presentation}"
  end

  values.to_sentence({ words_connector: ", ", two_words_connector: ", " })
end

#price_difference_from_master(pricing_options = Spree::Config.default_pricing_options) ⇒ Object

Returns the difference in price from the master variant



256
257
258
259
260
261
# File 'app/models/spree/variant.rb', line 256

def price_difference_from_master(pricing_options = Spree::Config.default_pricing_options)
  master_price = product.master.price_for(pricing_options)
  variant_price = price_for(pricing_options)
  return unless master_price && variant_price
  variant_price - master_price
end

#price_forSpree::Money

Chooses an appropriate price for the given pricing options

Parameters:

  • An (Spree::Config.pricing_options_class)

    instance of pricing options

Returns:

See Also:



253
# File 'app/models/spree/variant.rb', line 253

delegate :price_for, to: :price_selector

#price_in(currency) ⇒ Spree::Price

Deprecated.

Please use #price_for(pricing_options) instead

Converts the variant's price to the given currency.

Parameters:

  • currency (String)

    the desired currency

Returns:



273
274
275
# File 'app/models/spree/variant.rb', line 273

def price_in(currency)
  prices.currently_valid.find_by(currency: currency)
end

#price_same_as_master?(pricing_options = Spree::Config.default_pricing_options) ⇒ Boolean

Returns:

  • (Boolean)


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

def price_same_as_master?(pricing_options = Spree::Config.default_pricing_options)
  diff = price_difference_from_master(pricing_options)
  diff && diff.zero?
end

#price_selectorSpree::Variant::PriceSelector

Returns an instance of the globally configured variant price selector class for this variant. It's cached so we don't create too many objects.

Returns:



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

def price_selector
  @price_selector ||= Spree::Config.variant_price_selector_class.new(self)
end

#set_option_value(opt_name, opt_value) ⇒ Object

Sets an option type and value for the given name and value.

Parameters:

  • opt_name (String)

    the name of the option

  • opt_value (String)

    the value to set to the option



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/models/spree/variant.rb', line 202

def set_option_value(opt_name, opt_value)
  # no option values on master
  return if is_master

  option_type = Spree::OptionType.where(name: opt_name).first_or_initialize do |o|
    o.presentation = opt_name
    o.save!
  end

  current_value = option_values.detect { |o| o.option_type.name == opt_name }

  if current_value
    return if current_value.name == opt_value
    option_values.delete(current_value)
  else
    # then we have to check to make sure that the product has the option type
    unless product.option_types.include? option_type
      product.option_types << option_type
    end
  end

  option_value = Spree::OptionValue.where(option_type_id: option_type.id, name: opt_value).first_or_initialize do |o|
    o.presentation = opt_value
    o.save!
  end

  option_values << option_value
  save
end

#should_track_inventory?Boolean

Shortcut method to determine if inventory tracking is enabled for this variant. This considers both variant tracking flag and site-wide inventory tracking settings.

Returns:

  • (Boolean)

    true if inventory tracking is enabled



327
328
329
# File 'app/models/spree/variant.rb', line 327

def should_track_inventory?
  track_inventory? && Spree::Config.track_inventory_levels
end

#sku_and_options_textString

Generates a string of the SKU and a list of all the option values.

Returns:

  • (String)


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

def sku_and_options_text
  "#{sku} #{options_text}".strip
end

#tax_categorySpree::TaxCategory

This returns the product's tax category if the tax category ID on the variant is nil. It looks like an association, but really is an override.

Returns:



119
120
121
# File 'app/models/spree/variant.rb', line 119

def tax_category
  super || product_tax_category
end

#total_on_handFixnum

Fetches the on-hand quantity of the variant.

Returns:

  • (Fixnum)

    the number currently on-hand



318
319
320
# File 'app/models/spree/variant.rb', line 318

def total_on_hand
  Spree::Stock::Quantifier.new(self).total_on_hand
end

#variant_propertiesArray<Spree::VariantPropertyRuleValue>

Determines the variant's property values by verifying which of the product's variant property rules apply to itself.

Returns:



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

def variant_properties
  product.variant_property_rules.map do |rule|
    rule.values if rule.applies_to_variant?(self)
  end.flatten.compact
end

#weight=(weight) ⇒ Bignum

Sets the weight for the variant.

Parameters:

  • weight (Any)

    the weight to set

Returns:

  • (Bignum)


135
136
137
# File 'app/models/spree/variant.rb', line 135

def weight=(weight)
  self[:weight] = Spree::LocalizedNumber.parse(weight) if weight.present?
end