Class: Spree::Variant

Inherits:
Base
  • Object
show all
Includes:
DefaultPrice, MemoizedData, Metadata, Webhooks::HasWebhooks
Defined in:
app/models/spree/variant.rb

Constant Summary collapse

MEMOIZED_METHODS =
%w(purchasable in_stock backorderable tax_category options_text compare_at_price)
LOCALIZED_NUMBERS =

FIXME: cost price should be represented with DisplayMoney class

%w(cost_price weight depth width height)

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 Preferences::Preferable

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

Class Method Details

.product_name_or_sku_cont(query) ⇒ Object



124
125
126
127
128
# File 'app/models/spree/variant.rb', line 124

def self.product_name_or_sku_cont(query)
  joins(:product).join_translation_table(Product).
    where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)
           OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%")
end

.search_by_product_name_or_sku(query) ⇒ Object



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

def self.search_by_product_name_or_sku(query)
  product_name_or_sku_cont(query)
end

Instance Method Details

#amount_in(currency) ⇒ Object



234
235
236
# File 'app/models/spree/variant.rb', line 234

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

#available?Boolean

Returns:

  • (Boolean)


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

def available?
  !discontinued? && product.available?
end

#backorderable?Boolean Also known as: is_backorderable?

Returns:

  • (Boolean)


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

def backorderable?
  @backorderable ||= Rails.cache.fetch(['variant-backorderable', cache_key_with_version]) do
    quantifier.backorderable?
  end
end

#backordered?Boolean

Returns:

  • (Boolean)


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

def backordered?
  @backordered ||= !in_stock? && stock_items.exists?(backorderable: true)
end

#compare_at_amount_in(currency) ⇒ Object



238
239
240
# File 'app/models/spree/variant.rb', line 238

def compare_at_amount_in(currency)
  price_in(currency).try(:compare_at_amount)
end

#compare_at_priceObject



268
269
270
# File 'app/models/spree/variant.rb', line 268

def compare_at_price
  @compare_at_price ||= price_in(cost_currency).try(:compare_at_amount)
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)


166
167
168
# File 'app/models/spree/variant.rb', line 166

def deleted?
  !!deleted_at
end

#descriptive_nameObject



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

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

#digital?Boolean

Is this variant to be downloaded by the customer?

Returns:

  • (Boolean)


331
332
333
# File 'app/models/spree/variant.rb', line 331

def digital?
  digitals.present?
end

#dimensionObject



314
315
316
# File 'app/models/spree/variant.rb', line 314

def dimension
  (width || 0) + (height || 0) + (depth || 0)
end

#discontinue!Object



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

def discontinue!
  update_attribute(:discontinue_on, Time.current)
end

#discontinued?Boolean

Returns:

  • (Boolean)


322
323
324
# File 'app/models/spree/variant.rb', line 322

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

#exchange_nameObject

Default to master name



155
156
157
# File 'app/models/spree/variant.rb', line 155

def exchange_name
  is_master? ? name : options_text
end

#find_option_value(opt_name) ⇒ Object



209
210
211
# File 'app/models/spree/variant.rb', line 209

def find_option_value(opt_name)
  option_values.detect { |o| o.option_type.name.downcase.strip == opt_name.downcase.strip }
end

#in_stock?Boolean

Returns:

  • (Boolean)


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

def in_stock?
  # Issue 10280
  # Check if model responds to cache version and fall back to updated_at for older rails versions
  # This makes sure a version is supplied when recyclable cache keys are disabled.
  version = respond_to?(:cache_version) ? cache_version : updated_at.to_i
  @in_stock ||= Rails.cache.fetch(in_stock_cache_key, version: version) do
    total_on_hand > 0
  end
end

#in_stock_or_backorderable?Boolean

Returns:

  • (Boolean)


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

def in_stock_or_backorderable?
  self.class.in_stock_or_backorderable.exists?(id: id)
end

#name_and_skuObject



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

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

#option_value(opt_name) ⇒ Object



213
214
215
# File 'app/models/spree/variant.rb', line 213

def option_value(opt_name)
  find_option_value(opt_name).try(:presentation)
end

#options=(options = {}) ⇒ Object



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

def options=(options = {})
  options.each do |option|
    next if option[:name].blank? || option[:value].blank?

    set_option_value(option[:name], option[:value])
  end
end

#options_textObject



150
151
152
# File 'app/models/spree/variant.rb', line 150

def options_text
  @options_text ||= Spree::Variants::OptionsPresenter.new(self).to_sentence
end

#price_in(currency) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'app/models/spree/variant.rb', line 217

def price_in(currency)
  currency = currency&.upcase
  find_or_build_price = lambda do
    if prices.loaded?
      prices.detect { |price| price.currency == currency } || prices.build(currency: currency)
    else
      prices.find_or_initialize_by(currency: currency)
    end
  end

  Rails.cache.fetch("spree/prices/#{cache_key_with_version}/price_in/#{currency}") do
    find_or_build_price.call
  end
rescue TypeError
  find_or_build_price.call
end

#price_modifier_amount(options = {}) ⇒ Object



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

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

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

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



242
243
244
245
246
247
248
249
250
251
252
253
# File 'app/models/spree/variant.rb', line 242

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

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

#purchasable?Boolean

Returns:

  • (Boolean)


300
301
302
# File 'app/models/spree/variant.rb', line 300

def purchasable?
  @purchasable ||= in_stock? || backorderable?
end

#set_option_value(opt_name, opt_value) ⇒ Object



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
206
207
# File 'app/models/spree/variant.rb', line 178

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

  option_type = Spree::OptionType.where(['LOWER(name) = ?', opt_name.downcase.strip]).first_or_initialize do |o|
    o.name = o.presentation = opt_name
    o.save!
  end

  current_value = find_option_value(opt_name)

  if current_value.nil?
    # 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
  else
    return if current_value.name.downcase.strip == opt_value.downcase.strip

    option_values.delete(current_value)
  end

  option_value = option_type.option_values.where(['LOWER(name) = ?', opt_value.downcase.strip]).first_or_initialize do |o|
    o.name = 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)


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

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

#sku_and_options_textObject



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

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

#tax_categoryObject



142
143
144
145
146
147
148
# File 'app/models/spree/variant.rb', line 142

def tax_category
  @tax_category ||= if self[:tax_category_id].nil?
                      product.tax_category
                    else
                      Spree::TaxCategory.find_by(id: self[:tax_category_id]) || product.tax_category
                    end
end

#volumeObject



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

def volume
  (width || 0) * (height || 0) * (depth || 0)
end