Class: Spree::Variant

Inherits:
Base
  • Object
show all
Includes:
DefaultPrice
Defined in:
app/models/spree/variant.rb,
app/models/spree/variant/scopes.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.

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

page

Methods included from Preferences::Preferable

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

Class Method Details

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

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)


77
78
79
# File 'app/models/spree/variant.rb', line 77

def self.active(currency = nil)
  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 = 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_conditions = option_values.each do |option_value|
    option_value_conditions = case option_value
    when OptionValue then { "#{OptionValue.table_name}.name" => option_value.name }
    when String      then { "#{OptionValue.table_name}.name" => option_value }
    else                  { "#{OptionValue.table_name}.id"   => option_value }
    end
    relation = relation.where(option_value_conditions)
  end

  relation
end

Instance Method Details

#amount_in(currency) ⇒ Float

Fetches the price amount in the specified currency.

Returns:

  • (Float)

    the amount in the specified currency.



227
228
229
# File 'app/models/spree/variant.rb', line 227

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



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

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)


94
95
96
# File 'app/models/spree/variant.rb', line 94

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



152
153
154
# File 'app/models/spree/variant.rb', line 152

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



144
145
146
# File 'app/models/spree/variant.rb', line 144

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) (defaults to: true)

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

Returns:



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

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.



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

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.



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

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



114
115
116
# File 'app/models/spree/variant.rb', line 114

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

#name_and_skuString

Generates a friendly name and sku string.

Returns:

  • (String)


270
271
272
# File 'app/models/spree/variant.rb', line 270

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

#on_backorderFixnum

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

Returns:

  • (Fixnum)


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

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



211
212
213
# File 'app/models/spree/variant.rb', line 211

def option_value(opt_name)
  self.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.



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

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.



121
122
123
124
125
126
127
128
129
130
131
# File 'app/models/spree/variant.rb', line 121

def options_text
  values = self.option_values.sort do |a, b|
    a.option_type.position <=> b.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_in(currency) ⇒ Spree::Price

Converts the variant’s price to the given currency.

Parameters:

  • currency (String)

    the desired currency

Returns:



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

def price_in(currency)
  prices.detect{ |price| price.currency == currency && price.is_default } || Spree::Price.new(variant_id: self.id, currency: currency)
end

#price_modifier_amount(options = {}) ⇒ Fixnum

Calculates the sum of the specified price modifiers.

Parameters:

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

    for specifying keys, eg: ‘[’key_1’, ‘key_2’]‘

Returns:

  • (Fixnum)

    the sum



254
255
256
257
258
259
260
261
262
263
264
265
# File 'app/models/spree/variant.rb', line 254

def price_modifier_amount(options = {})
  return 0 unless options.present?

  options.keys.map { |key|
    m = "#{options[key]}_price_modifier_amount".to_sym
    if self.respond_to? m
      self.send(m, options[key])
    else
      0
    end
  }.sum
end

#price_modifier_amount_in(currency, options = {}) ⇒ Fixnum

Calculates the sum of the specified price modifiers in the specified currency.

Parameters:

  • currency (String)

    (see #price)

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

    for specifying keys, eg: ‘[’key_1’, ‘key_2’]‘

Returns:

  • (Fixnum)

    the sum



237
238
239
240
241
242
243
244
245
246
247
248
# File 'app/models/spree/variant.rb', line 237

def price_modifier_amount_in(currency, options = {})
  return 0 unless options.present?

  options.keys.map { |key|
    m = "#{key}_price_modifier_amount_in".to_sym
    if self.respond_to? m
      self.send(m, currency, options[key])
    else
      0
    end
  }.sum
end

#productSpree::Product

Override ActiveRecord finder to function even if the product has been deleted.

Returns:



160
161
162
# File 'app/models/spree/variant.rb', line 160

def product
  Spree::Product.unscoped { super }
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



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'app/models/spree/variant.rb', line 177

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

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

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

  unless current_value.nil?
    return if current_value.name == opt_value
    self.option_values.delete(current_value)
  else
    # then we have to check to make sure that the product has the option type
    unless self.product.option_types.include? option_type
      self.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

  self.option_values << option_value
  self.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



306
307
308
# File 'app/models/spree/variant.rb', line 306

def should_track_inventory?
  self.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)


277
278
279
# File 'app/models/spree/variant.rb', line 277

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

#tax_categorySpree::TaxCategory

Returns the variant’s tax category.

Returns:



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

def tax_category
  if self[:tax_category_id].nil?
    product.tax_category
  else
    TaxCategory.find(self[:tax_category_id])
  end
end

#total_on_handFixnum

Fetches the on-hand quantity of the variant.

Returns:

  • (Fixnum)

    the number currently on-hand



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

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

#weight=(weight) ⇒ Bignum

Sets the weight for the variant.

Parameters:

  • weight (Any)

    the weight to set

Returns:

  • (Bignum)


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

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