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 additional Images for Variant.
- #amount_in(currency) ⇒ Object
- #available? ⇒ Boolean
- #backorderable? ⇒ Boolean (also: #is_backorderable?)
- #backordered? ⇒ Boolean
- #clear_in_stock_cache ⇒ Object
- #compare_at_amount_in(currency) ⇒ Object
- #compare_at_price ⇒ Object
-
#default_image ⇒ Spree::Image
Returns default Image for Variant.
-
#deleted? ⇒ Boolean
use deleted? rather than checking the attribute directly.
- #descriptive_name ⇒ Object
-
#digital? ⇒ Boolean
Is this variant purely digital? (no physical product).
- #dimension ⇒ Object
- #discontinue! ⇒ Object
- #discontinued? ⇒ Boolean
-
#exchange_name ⇒ Object
Default to master name.
- #find_option_value(opt_name) ⇒ Object
- #human_name ⇒ Object
- #in_stock? ⇒ Boolean
- #in_stock_or_backorderable? ⇒ Boolean
- #name_and_sku ⇒ Object
- #on_sale?(currency) ⇒ Boolean
- #option_value(option_type) ⇒ Object
-
#options ⇒ Array<Hash>
Returns an array of hashes with the option type name, value and presentation.
- #options=(options = {}) ⇒ Object
- #options_text ⇒ Object
- #price_in(currency) ⇒ Object
- #price_modifier_amount(options = {}) ⇒ Object
- #price_modifier_amount_in(currency, options = {}) ⇒ Object
- #purchasable? ⇒ Boolean
-
#secondary_image ⇒ Spree::Image
Returns secondary Image for Variant.
- #set_option_value(opt_name, opt_value, opt_type_position = nil) ⇒ Object
- #set_price(currency, amount, compare_at_amount = nil) ⇒ Object
- #set_stock(count_on_hand, backorderable = nil, stock_location = nil) ⇒ Object
-
#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 ⇒ Object
-
#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
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'app/models/spree/variant.rb', line 189 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
207 208 209 |
# File 'app/models/spree/variant.rb', line 207 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 additional Images for Variant
293 294 295 |
# File 'app/models/spree/variant.rb', line 293 def additional_images @additional_images ||= (images + product.images).uniq.find_all { |image| image.id != default_image&.id } end |
#amount_in(currency) ⇒ Object
393 394 395 |
# File 'app/models/spree/variant.rb', line 393 def amount_in(currency) price_in(currency).try(:amount) end |
#available? ⇒ Boolean
219 220 221 |
# File 'app/models/spree/variant.rb', line 219 def available? !discontinued? && product.available? end |
#backorderable? ⇒ Boolean Also known as: is_backorderable?
464 465 466 467 468 |
# File 'app/models/spree/variant.rb', line 464 def backorderable? @backorderable ||= Rails.cache.fetch(['variant-backorderable', cache_key_with_version]) do quantifier.backorderable? end end |
#backordered? ⇒ Boolean
510 511 512 |
# File 'app/models/spree/variant.rb', line 510 def backordered? @backordered ||= !in_stock? && stock_items.exists?(backorderable: true) end |
#clear_in_stock_cache ⇒ Object
525 526 527 |
# File 'app/models/spree/variant.rb', line 525 def clear_in_stock_cache Rails.cache.delete(in_stock_cache_key) end |
#compare_at_amount_in(currency) ⇒ Object
397 398 399 |
# File 'app/models/spree/variant.rb', line 397 def compare_at_amount_in(currency) price_in(currency).try(:compare_at_amount) end |
#compare_at_price ⇒ Object
442 443 444 |
# File 'app/models/spree/variant.rb', line 442 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
273 274 275 276 277 278 279 |
# File 'app/models/spree/variant.rb', line 273 def default_image @default_image ||= if images.any? images.first else product.default_image end 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.
267 268 269 |
# File 'app/models/spree/variant.rb', line 267 def deleted? !!deleted_at end |
#descriptive_name ⇒ Object
260 261 262 |
# File 'app/models/spree/variant.rb', line 260 def descriptive_name is_master? ? name + ' - Master' : name + ' - ' + end |
#digital? ⇒ Boolean
Is this variant purely digital? (no physical product)
517 518 519 |
# File 'app/models/spree/variant.rb', line 517 def digital? product.digital? end |
#dimension ⇒ Object
492 493 494 |
# File 'app/models/spree/variant.rb', line 492 def dimension (width || 0) + (height || 0) + (depth || 0) end |
#discontinue! ⇒ Object
502 503 504 |
# File 'app/models/spree/variant.rb', line 502 def discontinue! update_attribute(:discontinue_on, Time.current) end |
#discontinued? ⇒ Boolean
506 507 508 |
# File 'app/models/spree/variant.rb', line 506 def discontinued? !!discontinue_on && discontinue_on <= Time.current end |
#exchange_name ⇒ Object
Default to master name
256 257 258 |
# File 'app/models/spree/variant.rb', line 256 def exchange_name is_master? ? name : end |
#find_option_value(opt_name) ⇒ Object
357 358 359 |
# File 'app/models/spree/variant.rb', line 357 def find_option_value(opt_name) option_values.includes(:option_type).detect { |o| o.option_type.name.parameterize == opt_name.parameterize } end |
#human_name ⇒ Object
211 212 213 214 215 216 217 |
# File 'app/models/spree/variant.rb', line 211 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
454 455 456 457 458 459 460 461 462 |
# File 'app/models/spree/variant.rb', line 454 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
223 224 225 |
# File 'app/models/spree/variant.rb', line 223 def in_stock_or_backorderable? self.class.in_stock_or_backorderable.exists?(id: id) end |
#name_and_sku ⇒ Object
446 447 448 |
# File 'app/models/spree/variant.rb', line 446 def name_and_sku "#{name} - #{sku}" end |
#on_sale?(currency) ⇒ Boolean
470 471 472 |
# File 'app/models/spree/variant.rb', line 470 def on_sale?(currency) @on_sale ||= price_in(currency)&.discounted? end |
#option_value(option_type) ⇒ Object
361 362 363 364 365 366 367 |
# File 'app/models/spree/variant.rb', line 361 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
299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'app/models/spree/variant.rb', line 299 def ||= 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 = {}) ⇒ Object
313 314 315 316 317 318 319 |
# File 'app/models/spree/variant.rb', line 313 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 ⇒ Object
247 248 249 250 251 252 253 |
# File 'app/models/spree/variant.rb', line 247 def ||= if option_values.loaded? option_values.sort_by { |ov| ov.option_type.position }.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 { |ov| "#{ov.option_type.presentation}: #{ov.presentation}" }.to_sentence(words_connector: ', ', two_words_connector: ', ') end end |
#price_in(currency) ⇒ Object
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'app/models/spree/variant.rb', line 369 def price_in(currency) currency = currency&.upcase price = if prices.loaded? && prices.any? prices.detect { |p| p.currency == currency } else 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 = {}) ⇒ Object
429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'app/models/spree/variant.rb', line 429 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
416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'app/models/spree/variant.rb', line 416 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 |
#purchasable? ⇒ Boolean
478 479 480 |
# File 'app/models/spree/variant.rb', line 478 def purchasable? @purchasable ||= in_stock? || backorderable? end |
#secondary_image ⇒ Spree::Image
Returns secondary Image for Variant
283 284 285 286 287 288 289 |
# File 'app/models/spree/variant.rb', line 283 def secondary_image @secondary_image ||= if images.size > 1 images.second else product.secondary_image end end |
#set_option_value(opt_name, opt_value, opt_type_position = nil) ⇒ Object
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'app/models/spree/variant.rb', line 321 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) ⇒ Object
401 402 403 404 405 406 |
# File 'app/models/spree/variant.rb', line 401 def set_price(currency, amount, compare_at_amount = nil) price = 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) ⇒ Object
408 409 410 411 412 413 414 |
# File 'app/models/spree/variant.rb', line 408 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
484 485 486 |
# File 'app/models/spree/variant.rb', line 484 def should_track_inventory? track_inventory? && Spree::Config.track_inventory_levels end |
#sku_and_options_text ⇒ Object
450 451 452 |
# File 'app/models/spree/variant.rb', line 450 def "#{sku} #{options_text}".strip end |
#tax_category ⇒ Spree::TaxCategory
Returns tax category for Variant
229 230 231 232 233 234 235 |
# File 'app/models/spree/variant.rb', line 229 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
239 240 241 242 243 244 245 |
# File 'app/models/spree/variant.rb', line 239 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
488 489 490 |
# File 'app/models/spree/variant.rb', line 488 def volume (width || 0) * (height || 0) * (depth || 0) end |
#weight_unit ⇒ String
Returns the weight unit for the variant
498 499 500 |
# File 'app/models/spree/variant.rb', line 498 def weight_unit attributes['weight_unit'] || Spree::Store.default.preferred_weight_unit end |
#with_digital_assets? ⇒ Boolean
521 522 523 |
# File 'app/models/spree/variant.rb', line 521 def with_digital_assets? digitals.any? end |