Class: Spree::Variant
- Inherits:
-
Object
- Object
- Spree::Variant
- Includes:
- 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)
Constants included from DefaultPrice
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
-
#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 ⇒ Object
deprecated
Deprecated.
Use #primary_media instead.
- #default_stock_location ⇒ Object
-
#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.
-
#gallery_media ⇒ ActiveRecord::Relation
Returns the variant’s media gallery.
-
#has_media? ⇒ Boolean
(also: #has_images?)
Returns true if the variant has media.
-
#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.
- #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?
deprecated
Deprecated.
Use #primary_media instead.
- #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) ⇒ 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.
-
#tax_category ⇒ Spree::TaxCategory
Returns tax category for Variant.
-
#tax_category_id ⇒ Integer
Returns tax category ID for Variant.
-
#update_thumbnail! ⇒ Object
Updates primary_media_id to the first media item by position.
- #volume ⇒ Object
-
#weight_unit ⇒ String
Returns the weight unit for the variant.
- #with_digital_assets? ⇒ Boolean
Methods included from Metadata
#metadata, #metadata=, #public_metadata=
Class Method Details
.product_name_or_sku_cont(query) ⇒ Object
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'app/models/spree/variant.rb', line 172 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
190 191 192 |
# File 'app/models/spree/variant.rb', line 190 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.
309 310 311 |
# File 'app/models/spree/variant.rb', line 309 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.
430 431 432 |
# File 'app/models/spree/variant.rb', line 430 def amount_in(currency) price_in(currency).try(:amount) end |
#available? ⇒ Boolean
Returns true if the variant is available.
206 207 208 |
# File 'app/models/spree/variant.rb', line 206 def available? !discontinued? && product.available? end |
#backorderable? ⇒ Boolean Also known as: is_backorderable?
Returns true if the variant is backorderable.
527 528 529 |
# File 'app/models/spree/variant.rb', line 527 def backorderable? @backorderable ||= quantifier.backorderable? end |
#backordered? ⇒ Boolean
571 572 573 |
# File 'app/models/spree/variant.rb', line 571 def backordered? @backordered ||= !in_stock? && stock_items.exists?(backorderable: true) end |
#compare_at_amount_in(currency) ⇒ BigDecimal
Returns the compare at amount for the given currency.
437 438 439 |
# File 'app/models/spree/variant.rb', line 437 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.
515 516 517 |
# File 'app/models/spree/variant.rb', line 515 def compare_at_price @compare_at_price ||= price_in(cost_currency).try(:compare_at_amount) end |
#default_image ⇒ Object
Use #primary_media instead.
281 282 283 284 |
# File 'app/models/spree/variant.rb', line 281 def default_image Spree::Deprecation.warn('Spree::Variant#default_image is deprecated and will be removed in Spree 6.0. Please use Spree::Variant#primary_media instead.') primary_media end |
#default_stock_location ⇒ Object
480 481 482 |
# File 'app/models/spree/variant.rb', line 480 def default_stock_location Spree::Store.current.default_stock_location end |
#descriptive_name ⇒ String
Returns the descriptive name of the variant.
258 259 260 |
# File 'app/models/spree/variant.rb', line 258 def descriptive_name is_master? ? name + ' - Master' : name + ' - ' + end |
#digital? ⇒ Boolean
Is this variant purely digital? (no physical product)
578 579 580 |
# File 'app/models/spree/variant.rb', line 578 def digital? product.digital? end |
#dimension ⇒ Object
553 554 555 |
# File 'app/models/spree/variant.rb', line 553 def dimension (width || 0) + (height || 0) + (depth || 0) end |
#discontinue! ⇒ Object
563 564 565 |
# File 'app/models/spree/variant.rb', line 563 def discontinue! update_attribute(:discontinue_on, Time.current) end |
#discontinued? ⇒ Boolean
567 568 569 |
# File 'app/models/spree/variant.rb', line 567 def discontinued? !!discontinue_on && discontinue_on <= Time.current end |
#exchange_name ⇒ String
Returns the exchange name of the variant.
252 253 254 |
# File 'app/models/spree/variant.rb', line 252 def exchange_name is_master? ? name : end |
#find_option_value(opt_name) ⇒ Spree::OptionValue
Returns the option value for the given option name.
384 385 386 |
# File 'app/models/spree/variant.rb', line 384 def find_option_value(opt_name) option_values.includes(:option_type).detect { |o| o.option_type.name.parameterize == opt_name.parameterize } end |
#gallery_media ⇒ ActiveRecord::Relation
Returns the variant’s media gallery. Currently returns direct images. In 6.0 will use variant_media join table.
265 266 267 |
# File 'app/models/spree/variant.rb', line 265 def gallery_media images end |
#has_media? ⇒ Boolean Also known as: has_images?
Returns true if the variant has media. Uses loaded association when available, otherwise falls back to counter cache.
272 273 274 275 276 |
# File 'app/models/spree/variant.rb', line 272 def has_media? return images.any? if images.loaded? media_count.positive? end |
#human_name ⇒ String
Returns the human name of the variant.
196 197 198 199 200 201 202 |
# File 'app/models/spree/variant.rb', line 196 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.
521 522 523 |
# File 'app/models/spree/variant.rb', line 521 def in_stock? @in_stock ||= total_on_hand.positive? end |
#in_stock_or_backorderable? ⇒ Boolean
Returns true if the variant is in stock or backorderable.
212 213 214 |
# File 'app/models/spree/variant.rb', line 212 def in_stock_or_backorderable? self.class.in_stock_or_backorderable.exists?(id: id) end |
#on_sale?(currency) ⇒ Boolean
531 532 533 |
# File 'app/models/spree/variant.rb', line 531 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.
391 392 393 394 395 396 397 |
# File 'app/models/spree/variant.rb', line 391 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
315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'app/models/spree/variant.rb', line 315 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
332 333 334 335 336 337 338 |
# File 'app/models/spree/variant.rb', line 332 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.
238 239 240 241 242 243 244 245 246 247 248 |
# File 'app/models/spree/variant.rb', line 238 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.
456 457 458 459 460 461 462 463 464 465 466 |
# File 'app/models/spree/variant.rb', line 456 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.
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'app/models/spree/variant.rb', line 403 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.
500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'app/models/spree/variant.rb', line 500 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
484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'app/models/spree/variant.rb', line 484 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?
Use #primary_media instead.
Returns first Image for Variant.
296 297 298 299 |
# File 'app/models/spree/variant.rb', line 296 def primary_image Spree::Deprecation.warn('Spree::Variant#primary_image is deprecated and will be removed in Spree 6.0. Please use Spree::Variant#primary_media instead.') primary_media end |
#purchasable? ⇒ Boolean
539 540 541 |
# File 'app/models/spree/variant.rb', line 539 def purchasable? @purchasable ||= in_stock? || backorderable? end |
#secondary_image ⇒ Spree::Image?
Returns second Image for Variant (for hover effects).
303 304 305 |
# File 'app/models/spree/variant.rb', line 303 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.
345 346 347 348 349 350 351 352 353 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 |
# File 'app/models/spree/variant.rb', line 345 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.
446 447 448 449 450 451 |
# File 'app/models/spree/variant.rb', line 446 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 price.save! if persisted? end |
#set_stock(count_on_hand, backorderable = nil) ⇒ void
This method returns an undefined value.
Sets the stock for the variant
473 474 475 476 477 478 |
# File 'app/models/spree/variant.rb', line 473 def set_stock(count_on_hand, backorderable = nil) stock_item = stock_items.find_or_initialize_by(stock_location: default_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
545 546 547 |
# File 'app/models/spree/variant.rb', line 545 def should_track_inventory? track_inventory? && Spree::Config.track_inventory_levels end |
#tax_category ⇒ Spree::TaxCategory
Returns tax category for Variant
218 219 220 221 222 223 224 |
# File 'app/models/spree/variant.rb', line 218 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
228 229 230 231 232 233 234 |
# File 'app/models/spree/variant.rb', line 228 def tax_category_id @tax_category_id ||= if self[:tax_category_id].nil? product.tax_category_id else self[:tax_category_id] end end |
#update_thumbnail! ⇒ Object
Updates primary_media_id to the first media item by position. Called when media is added, removed, or reordered.
288 289 290 291 |
# File 'app/models/spree/variant.rb', line 288 def update_thumbnail! first_media = images.order(:position).first update_column(:primary_media_id, first_media&.id) end |
#volume ⇒ Object
549 550 551 |
# File 'app/models/spree/variant.rb', line 549 def volume (width || 0) * (height || 0) * (depth || 0) end |
#weight_unit ⇒ String
Returns the weight unit for the variant
559 560 561 |
# File 'app/models/spree/variant.rb', line 559 def weight_unit attributes['weight_unit'] || Spree::Store.default.preferred_weight_unit end |
#with_digital_assets? ⇒ Boolean
582 583 584 |
# File 'app/models/spree/variant.rb', line 582 def with_digital_assets? digitals.any? end |