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

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

.search_by_product_name_or_sku(query) ⇒ Object



128
129
130
131
132
133
134
135
# File 'app/models/spree/variant.rb', line 128

def self.search_by_product_name_or_sku(query)
  if defined?(SpreeGlobalize)
    joins(product: :translations).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)",
                                        query: "%#{query}%")
  else
    product_name_or_sku_cont(query)
  end
end

Instance Method Details

#amount_in(currency) ⇒ Object



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

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

#available?Boolean

Returns:

  • (Boolean)


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

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

#backorderable?Boolean Also known as: is_backorderable?

Returns:

  • (Boolean)


293
294
295
296
297
# File 'app/models/spree/variant.rb', line 293

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

#backordered?Boolean

Returns:

  • (Boolean)


329
330
331
# File 'app/models/spree/variant.rb', line 329

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

#compare_at_amount_in(currency) ⇒ Object



241
242
243
# File 'app/models/spree/variant.rb', line 241

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

#compare_at_priceObject



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

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)


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

def deleted?
  !!deleted_at
end

#descriptive_nameObject



162
163
164
# File 'app/models/spree/variant.rb', line 162

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

#digital?Boolean

Is this variant to be downloaded by the customer?

Returns:

  • (Boolean)


334
335
336
# File 'app/models/spree/variant.rb', line 334

def digital?
  digitals.present?
end

#dimensionObject



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

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

#discontinue!Object



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

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

#discontinued?Boolean

Returns:

  • (Boolean)


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

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

#exchange_nameObject

Default to master name



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

def exchange_name
  is_master? ? name : options_text
end

#find_option_value(opt_name) ⇒ Object



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

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)


283
284
285
286
287
288
289
290
291
# File 'app/models/spree/variant.rb', line 283

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)


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

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

#name_and_skuObject



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

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

#option_value(opt_name) ⇒ Object



216
217
218
# File 'app/models/spree/variant.rb', line 216

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

#options=(options = {}) ⇒ Object



173
174
175
176
177
178
179
# File 'app/models/spree/variant.rb', line 173

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



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

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

#price_in(currency) ⇒ Object



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

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



258
259
260
261
262
263
264
265
266
267
268
269
# File 'app/models/spree/variant.rb', line 258

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



245
246
247
248
249
250
251
252
253
254
255
256
# File 'app/models/spree/variant.rb', line 245

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)


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

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

#set_option_value(opt_name, opt_value) ⇒ Object



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
208
209
210
# File 'app/models/spree/variant.rb', line 181

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)


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

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

#sku_and_options_textObject



279
280
281
# File 'app/models/spree/variant.rb', line 279

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

#tax_categoryObject



145
146
147
148
149
150
151
# File 'app/models/spree/variant.rb', line 145

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



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

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