Class: Spree::Variant
- Inherits:
-
Object
- Object
- Spree::Variant
- Includes:
- PgSearch::Model, DefaultPrice, MemoizedData, Metadata, Metafields, Webhooks
- Defined in:
- app/models/spree/variant.rb,
app/models/spree/variant/webhooks.rb
Defined Under Namespace
Modules: Webhooks
Constant Summary collapse
- MEMOIZED_METHODS =
%w(purchasable in_stock on_sale backorderable tax_category options_text compare_at_price)
- DIMENSION_UNITS =
%w[mm cm in ft]
- WEIGHT_UNITS =
%w[g kg lb oz]
- 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
-
#additional_images ⇒ Array<Spree::Image>
Returns all images except the default image, combining variant and product images.
-
#amount_in(currency) ⇒ BigDecimal
Returns the amount for the given currency.
-
#available? ⇒ Boolean
Returns true if the variant is available.
-
#backorderable? ⇒ Boolean
(also: #is_backorderable?)
Returns true if the variant is backorderable.
- #backordered? ⇒ Boolean
- #clear_in_stock_cache ⇒ Object
-
#compare_at_amount_in(currency) ⇒ BigDecimal
Returns the compare at amount for the given currency.
-
#compare_at_price ⇒ BigDecimal
Returns the compare at price of the variant.
-
#default_image ⇒ Spree::Image?
Returns default Image for Variant, falling back to product’s default image.
-
#deleted? ⇒ Boolean
use deleted? rather than checking the attribute directly.
-
#descriptive_name ⇒ String
Returns the descriptive name of the variant.
-
#digital? ⇒ Boolean
Is this variant purely digital? (no physical product).
- #dimension ⇒ Object
- #discontinue! ⇒ Object
- #discontinued? ⇒ Boolean
-
#exchange_name ⇒ String
Returns the exchange name of the variant.
-
#find_option_value(opt_name) ⇒ Spree::OptionValue
Returns the option value for the given option name.
-
#has_images? ⇒ Boolean
Returns true if the variant has images.
-
#human_name ⇒ String
Returns the human name of the variant.
-
#in_stock? ⇒ Boolean
Returns true if the variant is in stock.
-
#in_stock_or_backorderable? ⇒ Boolean
Returns true if the variant is in stock or backorderable.
-
#name_and_sku ⇒ String
Returns the name and sku of the variant.
- #on_sale?(currency) ⇒ Boolean
-
#option_value(option_type) ⇒ String
Returns the presentation of the option value for the given option type.
-
#options ⇒ Array<Hash>
Returns an array of hashes with the option type name, value and presentation.
-
#options=(options = {}) ⇒ void
Sets the option values for the variant.
-
#options_text ⇒ String
Returns the options text of the variant.
-
#price_for(context_or_options) ⇒ Spree::Price
Returns the price for the given context or options.
-
#price_in(currency) ⇒ Spree::Price
Returns the base price (global price, not from a price list) for the given currency.
-
#price_modifier_amount(options = {}) ⇒ BigDecimal
Returns the price modifier amount of the variant.
- #price_modifier_amount_in(currency, options = {}) ⇒ Object
-
#primary_image ⇒ Spree::Image?
Returns first Image for Variant.
- #purchasable? ⇒ Boolean
-
#secondary_image ⇒ Spree::Image?
Returns second Image for Variant (for hover effects).
-
#set_option_value(opt_name, opt_value, opt_type_position = nil) ⇒ void
Sets the option value for the given option name.
-
#set_price(currency, amount, compare_at_amount = nil) ⇒ void
Sets the base price (global price, not for a price list) for the given currency.
-
#set_stock(count_on_hand, backorderable = nil, stock_location = nil) ⇒ void
Sets the stock for the variant.
-
#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.
-
#sku_and_options_text ⇒ String
Returns the sku and options text of the variant.
-
#tax_category ⇒ Spree::TaxCategory
Returns tax category for Variant.
-
#tax_category_id ⇒ Integer
Returns tax category ID for Variant.
- #volume ⇒ Object
-
#weight_unit ⇒ String
Returns the weight unit for the variant.
- #with_digital_assets? ⇒ Boolean
Class Method Details
.product_name_or_sku_cont(query) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'app/models/spree/variant.rb', line 191 def self.product_name_or_sku_cont(query) sanitized_query = ActiveRecord::Base.sanitize_sql_like(query.to_s.downcase.strip) query_pattern = "%#{sanitized_query}%" sku_condition = arel_table[:sku].lower.matches(query_pattern) if Spree.use_translations? translation_arel_table = Product::Translation.arel_table.alias(Product.translation_table_alias)[:name] product_name_condition = translation_arel_table.lower.matches(query_pattern) joins(:product). join_translation_table(Product). where(product_name_condition.or(sku_condition)) else product_name_condition = Product.arel_table[:name].lower.matches(query_pattern) joins(:product).where(product_name_condition.or(sku_condition)) end end |
.search_by_product_name_or_sku(query) ⇒ Object
209 210 211 |
# File 'app/models/spree/variant.rb', line 209 def self.search_by_product_name_or_sku(query) product_name_or_sku_cont(query) end |
Instance Method Details
#additional_images ⇒ Array<Spree::Image>
Returns all images except the default image, combining variant and product images.
318 319 320 |
# File 'app/models/spree/variant.rb', line 318 def additional_images @additional_images ||= (images + product.images).uniq.reject { |image| image.id == default_image&.id } end |
#amount_in(currency) ⇒ BigDecimal
Returns the amount for the given currency.
439 440 441 |
# File 'app/models/spree/variant.rb', line 439 def amount_in(currency) price_in(currency).try(:amount) end |
#available? ⇒ Boolean
Returns true if the variant is available.
225 226 227 |
# File 'app/models/spree/variant.rb', line 225 def available? !discontinued? && product.available? end |
#backorderable? ⇒ Boolean Also known as: is_backorderable?
Returns true if the variant is backorderable.
551 552 553 554 555 |
# File 'app/models/spree/variant.rb', line 551 def backorderable? @backorderable ||= Rails.cache.fetch(['variant-backorderable', cache_key_with_version]) do quantifier.backorderable? end end |
#backordered? ⇒ Boolean
597 598 599 |
# File 'app/models/spree/variant.rb', line 597 def backordered? @backordered ||= !in_stock? && stock_items.exists?(backorderable: true) end |
#clear_in_stock_cache ⇒ Object
612 613 614 |
# File 'app/models/spree/variant.rb', line 612 def clear_in_stock_cache Rails.cache.delete(in_stock_cache_key) end |
#compare_at_amount_in(currency) ⇒ BigDecimal
Returns the compare at amount for the given currency.
446 447 448 |
# File 'app/models/spree/variant.rb', line 446 def compare_at_amount_in(currency) price_in(currency).try(:compare_at_amount) end |
#compare_at_price ⇒ BigDecimal
Returns the compare at price of the variant.
521 522 523 |
# File 'app/models/spree/variant.rb', line 521 def compare_at_price @compare_at_price ||= price_in(cost_currency).try(:compare_at_amount) end |
#default_image ⇒ Spree::Image?
Returns default Image for Variant, falling back to product’s default image.
300 301 302 |
# File 'app/models/spree/variant.rb', line 300 def default_image @default_image ||= has_images? ? images.first : product.default_image 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.
285 286 287 |
# File 'app/models/spree/variant.rb', line 285 def deleted? !!deleted_at end |
#descriptive_name ⇒ String
Returns the descriptive name of the variant.
277 278 279 |
# File 'app/models/spree/variant.rb', line 277 def descriptive_name is_master? ? "#{name} - Master" : "#{name} - #{}" end |
#digital? ⇒ Boolean
Is this variant purely digital? (no physical product)
604 605 606 |
# File 'app/models/spree/variant.rb', line 604 def digital? product.digital? end |
#dimension ⇒ Object
579 580 581 |
# File 'app/models/spree/variant.rb', line 579 def dimension (width || 0) + (height || 0) + (depth || 0) end |
#discontinue! ⇒ Object
589 590 591 |
# File 'app/models/spree/variant.rb', line 589 def discontinue! update_attribute(:discontinue_on, Time.current) end |
#discontinued? ⇒ Boolean
593 594 595 |
# File 'app/models/spree/variant.rb', line 593 def discontinued? !!discontinue_on && discontinue_on <= Time.current end |
#exchange_name ⇒ String
Returns the exchange name of the variant.
271 272 273 |
# File 'app/models/spree/variant.rb', line 271 def exchange_name is_master? ? name : end |
#find_option_value(opt_name) ⇒ Spree::OptionValue
Returns the option value for the given option name.
393 394 395 |
# File 'app/models/spree/variant.rb', line 393 def find_option_value(opt_name) option_values.includes(:option_type).detect { |o| o.option_type.name.parameterize == opt_name.parameterize } end |
#has_images? ⇒ Boolean
Returns true if the variant has images. Uses loaded association when available, otherwise falls back to counter cache.
292 293 294 295 296 |
# File 'app/models/spree/variant.rb', line 292 def has_images? return images.any? if images.loaded? image_count.positive? end |
#human_name ⇒ String
Returns the human name of the variant.
215 216 217 218 219 220 221 |
# File 'app/models/spree/variant.rb', line 215 def human_name @human_name ||= option_values. joins(option_type: :product_option_types). merge(product.product_option_types). reorder('spree_product_option_types.position'). pluck(:presentation).join('/') end |
#in_stock? ⇒ Boolean
Returns true if the variant is in stock.
539 540 541 542 543 544 545 546 547 |
# File 'app/models/spree/variant.rb', line 539 def in_stock? @in_stock ||= if association(:stock_items).loaded? && association(:stock_locations).loaded? total_on_hand.positive? else Rails.cache.fetch(in_stock_cache_key, version: cache_version) do total_on_hand.positive? end end end |
#in_stock_or_backorderable? ⇒ Boolean
Returns true if the variant is in stock or backorderable.
231 232 233 |
# File 'app/models/spree/variant.rb', line 231 def in_stock_or_backorderable? self.class.in_stock_or_backorderable.exists?(id: id) end |
#name_and_sku ⇒ String
Returns the name and sku of the variant.
527 528 529 |
# File 'app/models/spree/variant.rb', line 527 def name_and_sku "#{name} - #{sku}" end |
#on_sale?(currency) ⇒ Boolean
557 558 559 |
# File 'app/models/spree/variant.rb', line 557 def on_sale?(currency) @on_sale ||= price_in(currency)&.discounted? end |
#option_value(option_type) ⇒ String
Returns the presentation of the option value for the given option type.
400 401 402 403 404 405 406 |
# File 'app/models/spree/variant.rb', line 400 def option_value(option_type) if option_type.is_a?(Spree::OptionType) option_values.detect { |o| o.option_type_id == option_type.id }.try(:presentation) else find_option_value(option_type).try(:presentation) end end |
#options ⇒ Array<Hash>
Returns an array of hashes with the option type name, value and presentation
324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'app/models/spree/variant.rb', line 324 def @options ||= option_values. includes(option_type: :product_option_types). merge(product.product_option_types). reorder('spree_product_option_types.position'). map do |option_value| { name: option_value.option_type.name, value: option_value.name, presentation: option_value.presentation } end end |
#options=(options = {}) ⇒ void
This method returns an undefined value.
Sets the option values for the variant
341 342 343 344 345 346 347 |
# File 'app/models/spree/variant.rb', line 341 def ( = {}) .each do |option| next if option[:name].blank? || option[:value].blank? set_option_value(option[:name], option[:value], option[:position]) end end |
#options_text ⇒ String
Returns the options text of the variant.
257 258 259 260 261 262 263 264 265 266 267 |
# File 'app/models/spree/variant.rb', line 257 def @options_text ||= if option_values.loaded? option_values.sort_by do |ov| ov.option_type.position end.map { |ov| "#{ov.option_type.presentation}: #{ov.presentation}" }.to_sentence(words_connector: ', ', two_words_connector: ', ') else option_values.includes(:option_type).joins(:option_type).order("#{Spree::OptionType.table_name}.position").map do |ov| "#{ov.option_type.presentation}: #{ov.presentation}" end.to_sentence(words_connector: ', ', two_words_connector: ', ') end end |
#price_for(context_or_options) ⇒ Spree::Price
Returns the price for the given context or options.
465 466 467 468 469 470 471 472 473 474 475 |
# File 'app/models/spree/variant.rb', line 465 def price_for() context = if .is_a?(Spree::Pricing::Context) elsif .is_a?(Hash) Spree::Pricing::Context.new(**.merge(variant: self)) else raise ArgumentError, 'Must provide a Pricing::Context or options hash' end Spree::Pricing::Resolver.new(context).resolve end |
#price_in(currency) ⇒ Spree::Price
Returns the base price (global price, not from a price list) for the given currency. Use price_for(context) when you need to resolve prices including price lists.
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'app/models/spree/variant.rb', line 412 def price_in(currency) currency = currency&.upcase price = if prices.loaded? && prices.any? prices.detect { |p| p.currency == currency && p.price_list_id.nil? } else prices.base_prices.find_by(currency: currency) end if price.nil? return Spree::Price.new( currency: currency, variant_id: id ) end price rescue TypeError Spree::Price.new( currency: currency, variant_id: id ) end |
#price_modifier_amount(options = {}) ⇒ BigDecimal
Returns the price modifier amount of the variant.
506 507 508 509 510 511 512 513 514 515 516 517 |
# File 'app/models/spree/variant.rb', line 506 def price_modifier_amount( = {}) return 0 unless .present? .keys.map do |key| m = "#{key}_price_modifier_amount".to_sym if respond_to? m send(m, [key]) else 0 end end.sum end |
#price_modifier_amount_in(currency, options = {}) ⇒ Object
490 491 492 493 494 495 496 497 498 499 500 501 |
# File 'app/models/spree/variant.rb', line 490 def price_modifier_amount_in(currency, = {}) return 0 unless .present? .keys.map do |key| m = "#{key}_price_modifier_amount_in".to_sym if respond_to? m send(m, currency, [key]) else 0 end end.sum end |
#primary_image ⇒ Spree::Image?
Returns first Image for Variant.
306 307 308 |
# File 'app/models/spree/variant.rb', line 306 def primary_image images.first end |
#purchasable? ⇒ Boolean
565 566 567 |
# File 'app/models/spree/variant.rb', line 565 def purchasable? @purchasable ||= in_stock? || backorderable? end |
#secondary_image ⇒ Spree::Image?
Returns second Image for Variant (for hover effects).
312 313 314 |
# File 'app/models/spree/variant.rb', line 312 def secondary_image images.second end |
#set_option_value(opt_name, opt_value, opt_type_position = nil) ⇒ void
This method returns an undefined value.
Sets the option value for the given option name.
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'app/models/spree/variant.rb', line 354 def set_option_value(opt_name, opt_value, opt_type_position = nil) # no option values on master return if is_master option_type = Spree::OptionType.where(name: opt_name.parameterize).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 product_option_type = if (existing_prod_ot = product.product_option_types.find { |ot| ot.option_type_id == option_type.id }) existing_prod_ot else product_option_type = product.product_option_types.new product_option_type.option_type = option_type end product_option_type.position = opt_type_position if opt_type_position product_option_type.save! if product_option_type.new_record? || product_option_type.changed? else return if current_value.name.parameterize == opt_value.parameterize option_values.delete(current_value) end option_value = option_type.option_values.where(name: opt_value.parameterize).first_or_initialize do |o| o.name = o.presentation = opt_value o.save! end option_values << option_value save end |
#set_price(currency, amount, compare_at_amount = nil) ⇒ void
This method returns an undefined value.
Sets the base price (global price, not for a price list) for the given currency.
455 456 457 458 459 460 |
# File 'app/models/spree/variant.rb', line 455 def set_price(currency, amount, compare_at_amount = nil) price = prices.base_prices.find_or_initialize_by(currency: currency) price.amount = amount price.compare_at_amount = compare_at_amount if compare_at_amount.present? price.save! end |
#set_stock(count_on_hand, backorderable = nil, stock_location = nil) ⇒ void
This method returns an undefined value.
Sets the stock for the variant
482 483 484 485 486 487 488 |
# File 'app/models/spree/variant.rb', line 482 def set_stock(count_on_hand, backorderable = nil, stock_location = nil) stock_location ||= Spree::Store.current.default_stock_location stock_item = stock_items.find_or_initialize_by(stock_location: stock_location) stock_item.count_on_hand = count_on_hand stock_item.backorderable = backorderable if backorderable.present? stock_item.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
571 572 573 |
# File 'app/models/spree/variant.rb', line 571 def should_track_inventory? track_inventory? && Spree::Config.track_inventory_levels end |
#sku_and_options_text ⇒ String
Returns the sku and options text of the variant.
533 534 535 |
# File 'app/models/spree/variant.rb', line 533 def "#{sku} #{}".strip end |
#tax_category ⇒ Spree::TaxCategory
Returns tax category for Variant
237 238 239 240 241 242 243 |
# File 'app/models/spree/variant.rb', line 237 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 |
#tax_category_id ⇒ Integer
Returns tax category ID for Variant
247 248 249 250 251 252 253 |
# File 'app/models/spree/variant.rb', line 247 def tax_category_id @tax_category_id ||= if self[:tax_category_id].nil? product.tax_category_id else self[:tax_category_id] end end |
#volume ⇒ Object
575 576 577 |
# File 'app/models/spree/variant.rb', line 575 def volume (width || 0) * (height || 0) * (depth || 0) end |
#weight_unit ⇒ String
Returns the weight unit for the variant
585 586 587 |
# File 'app/models/spree/variant.rb', line 585 def weight_unit attributes['weight_unit'] || Spree::Store.default.preferred_weight_unit end |
#with_digital_assets? ⇒ Boolean
608 609 610 |
# File 'app/models/spree/variant.rb', line 608 def with_digital_assets? digitals.any? end |