Class: Spree::Variant
- Includes:
- DefaultPrice, SoftDeletable, Scopes
- Defined in:
- app/models/spree/variant.rb,
app/models/spree/variant/scopes.rb,
app/models/spree/variant/price_selector.rb,
app/models/spree/variant/pricing_options.rb,
app/models/spree/variant/vat_price_generator.rb
Overview
Master Variant
Every product has one master variant, which stores master price and SKU, size and weight, etc. The master variant does not have option values associated with it. Contains on_hand inventory levels only when there are no variants for the product.
Variants
All variants can access the product properties directly (via reverse delegation). Inventory units are tied to Variant. The master variant can have inventory units, but not option values. All other variants have option values and may have inventory units. Sum of on_hand each variant’s inventory level determine “on_hand” level for the product.
Defined Under Namespace
Modules: Scopes Classes: PriceSelector, PricingOptions, VatPriceGenerator
Instance Attribute Summary collapse
-
#rebuild_vat_prices ⇒ Object
writeonly
Sets the attribute rebuild_vat_prices.
Class Method Summary collapse
-
.in_stock(stock_locations = nil) ⇒ ActiveRecord::Relation
Returns variants that are in stock.
-
.suppliable ⇒ ActiveRecord::Relation
Returns a scope of Variants which are suppliable.
-
.with_prices(pricing_options = Spree::Config.default_pricing_options) ⇒ ActiveRecord::Relation
Returns variants that have a price for the given pricing options If you have modified the pricing options class, you might want to modify this scope too.
Instance Method Summary collapse
-
#can_supply?(quantity = 1, stock_location = nil) ⇒ Boolean
True if the desired quantity can be supplied.
-
#cost_price=(price) ⇒ Bignum
Sets the cost_price for the variant.
-
#deleted? ⇒ Boolean
Returns whether this variant has been deleted.
-
#descriptive_name ⇒ String
Generates a verbose name for the variant, appending ‘Master’ if it is a master variant, otherwise a list of its option values.
-
#exchange_name ⇒ String
Determines the name of an Exchange variant.
-
#gallery ⇒ Spree::Gallery
The gallery for the variant, which represents all the images associated with it.
-
#in_stock? ⇒ Boolean
True if there is stock on-hand for the variant.
-
#is_backorderable? ⇒ Boolean
True if this variant can be backordered.
-
#name_and_sku ⇒ String
Generates a friendly name and sku string.
-
#on_backorder ⇒ Fixnum
Counts the number of units currently on backorder for this variant.
-
#option_value(opt_name) ⇒ String
Fetches the option value for the given option name.
-
#options=(options = []) ⇒ Object
Assign given options hash to option values.
-
#options_text ⇒ String
Creates a sentence out of the variant’s (sorted) option values.
-
#price_difference_from_master(pricing_options = Spree::Config.default_pricing_options) ⇒ Object
Returns the difference in price from the master variant.
- #price_same_as_master?(pricing_options = Spree::Config.default_pricing_options) ⇒ Boolean
-
#price_selector ⇒ Spree::Variant::PriceSelector
Returns an instance of the globally configured variant price selector class for this variant.
-
#set_option_value(opt_name, opt_value) ⇒ Object
Sets an option type and value for the given name and value.
-
#shipping_category ⇒ Spree::ShippingCategory
This returns the product’s shipping category if the shipping category ID on the variant is nil.
-
#shipping_category_id ⇒ Integer
This returns the product’s shipping category if if the shipping category ID on the variant is nil.
-
#should_track_inventory? ⇒ Boolean
Shortcut method to determine if inventory tracking is enabled for this variant.
-
#sku_and_options_text ⇒ String
Generates a string of the SKU and a list of all the option values.
-
#tax_category ⇒ Spree::TaxCategory
This returns the product’s tax category if the tax category ID on the variant is nil.
-
#tax_category_id ⇒ Integer
This returns the product’s tax category ID if the tax category ID on the variant is nil.
-
#total_on_hand(stock_location = nil) ⇒ Fixnum
Fetches the on-hand quantity of the variant.
-
#variant_properties ⇒ Array<Spree::VariantPropertyRuleValue>
Determines the variant’s property values by verifying which of the product’s variant property rules apply to itself.
-
#weight=(weight) ⇒ Bignum
Sets the weight for the variant.
Methods included from DefaultPrice
#default_price, #default_price_or_build, #has_default_price?
Methods included from Scopes
Methods inherited from Base
Methods included from Core::Permalinks
#generate_permalink, #save_permalink
Instance Attribute Details
#rebuild_vat_prices=(value) ⇒ Object (writeonly)
Sets the attribute rebuild_vat_prices
28 29 30 |
# File 'app/models/spree/variant.rb', line 28 def rebuild_vat_prices=(value) @rebuild_vat_prices = value end |
Class Method Details
.in_stock(stock_locations = nil) ⇒ ActiveRecord::Relation
Returns variants that are in stock. When stock locations are provided as a parameter, the scope is limited to variants that are in stock in the provided stock locations.
If you want to also include backorderable variants see suppliable
100 101 102 103 104 105 106 107 |
# File 'app/models/spree/variant.rb', line 100 def self.in_stock(stock_locations = nil) return all unless Spree::Config.track_inventory_levels in_stock_variants = joins(:stock_items).where(Spree::StockItem.arel_table[:count_on_hand].gt(0).or(arel_table[:track_inventory].eq(false))) if stock_locations.present? in_stock_variants = in_stock_variants.where(spree_stock_items: { stock_location_id: stock_locations.map(&:id) }) end in_stock_variants.distinct end |
.suppliable ⇒ ActiveRecord::Relation
Returns a scope of Variants which are suppliable. This includes:
-
in_stock variants
-
backorderable variants
-
variants which do not track stock
115 116 117 118 119 120 121 122 123 |
# File 'app/models/spree/variant.rb', line 115 def self.suppliable return all unless Spree::Config.track_inventory_levels arel_conditions = [ arel_table[:track_inventory].eq(false), Spree::StockItem.arel_table[:count_on_hand].gt(0), Spree::StockItem.arel_table[:backorderable].eq(true) ] joins(:stock_items).where(arel_conditions.inject(:or)).distinct end |
.with_prices(pricing_options = Spree::Config.default_pricing_options) ⇒ ActiveRecord::Relation
Returns variants that have a price for the given pricing options If you have modified the pricing options class, you might want to modify this scope too.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'app/models/spree/variant.rb', line 133 def self.with_prices( = Spree::Config.) where( Spree::Price. where(Spree::Variant.arel_table[:id].eq(Spree::Price.arel_table[:variant_id])). # This next clause should just be `where(pricing_options.search_arguments)`, but ActiveRecord # generates invalid SQL, so the SQL here is written manually. where( "spree_prices.currency = ? AND (spree_prices.country_iso IS NULL OR spree_prices.country_iso = ?)", .search_arguments[:currency], .search_arguments[:country_iso].compact ). arel.exists ) end |
Instance Method Details
#can_supply?(quantity = 1, stock_location = nil) ⇒ Boolean
347 348 349 |
# File 'app/models/spree/variant.rb', line 347 def can_supply?(quantity = 1, stock_location = nil) Spree::Stock::Quantifier.new(self, stock_location).can_supply?(quantity) end |
#cost_price=(price) ⇒ Bignum
Sets the cost_price for the variant.
187 188 189 |
# File 'app/models/spree/variant.rb', line 187 def cost_price=(price) self[:cost_price] = Spree::LocalizedNumber.parse(price) if price.present? end |
#deleted? ⇒ Boolean
Returns whether this variant has been deleted. Provided as a method of overriding the logic for determining if a variant is deleted.
245 246 247 |
# File 'app/models/spree/variant.rb', line 245 def deleted? !!deleted_at end |
#descriptive_name ⇒ String
Generates a verbose name for the variant, appending ‘Master’ if it is a master variant, otherwise a list of its option values.
237 238 239 |
# File 'app/models/spree/variant.rb', line 237 def descriptive_name is_master? ? name + ' - Master' : name + ' - ' + end |
#exchange_name ⇒ String
Determines the name of an Exchange variant.
229 230 231 |
# File 'app/models/spree/variant.rb', line 229 def exchange_name is_master? ? name : end |
#gallery ⇒ Spree::Gallery
The gallery for the variant, which represents all the images associated with it
384 385 386 |
# File 'app/models/spree/variant.rb', line 384 def gallery @gallery ||= Spree::Config.variant_gallery_class.new(self) end |
#in_stock? ⇒ Boolean
336 337 338 339 340 |
# File 'app/models/spree/variant.rb', line 336 def in_stock? Rails.cache.fetch(in_stock_cache_key) do total_on_hand > 0 end end |
#is_backorderable? ⇒ Boolean
207 208 209 |
# File 'app/models/spree/variant.rb', line 207 def is_backorderable? Spree::Stock::Quantifier.new(self).backorderable? end |
#name_and_sku ⇒ String
Generates a friendly name and sku string.
324 325 326 |
# File 'app/models/spree/variant.rb', line 324 def name_and_sku "#{name} - #{sku}" end |
#on_backorder ⇒ Fixnum
Counts the number of units currently on backorder for this variant.
202 203 204 |
# File 'app/models/spree/variant.rb', line 202 def on_backorder inventory_units.with_state('backordered').size end |
#option_value(opt_name) ⇒ String
Fetches the option value for the given option name.
296 297 298 |
# File 'app/models/spree/variant.rb', line 296 def option_value(opt_name) option_values.detect { |option| option.option_type.name == opt_name }.try(:presentation) end |
#options=(options = []) ⇒ Object
Assign given options hash to option values.
252 253 254 255 256 |
# File 'app/models/spree/variant.rb', line 252 def ( = []) .each do |option| set_option_value(option[:name], option[:value]) end end |
#options_text ⇒ String
Creates a sentence out of the variant’s (sorted) option values.
214 215 216 217 218 219 220 221 222 223 224 |
# File 'app/models/spree/variant.rb', line 214 def values = option_values.sort_by do |option_value| option_value.option_type.position end values.to_a.map! do |ov| "#{ov.option_type.presentation}: #{ov.presentation}" end values.to_sentence({ words_connector: ", ", two_words_connector: ", " }) end |
#price_difference_from_master(pricing_options = Spree::Config.default_pricing_options) ⇒ Object
Returns the difference in price from the master variant
309 310 311 312 313 314 |
# File 'app/models/spree/variant.rb', line 309 def price_difference_from_master( = Spree::Config.) master_price = product.master.() variant_price = () return unless master_price && variant_price Spree::Money.new(variant_price.amount - master_price.amount, currency: .currency) end |
#price_same_as_master?(pricing_options = Spree::Config.default_pricing_options) ⇒ Boolean
316 317 318 319 |
# File 'app/models/spree/variant.rb', line 316 def price_same_as_master?( = Spree::Config.) diff = price_difference_from_master() diff && diff.zero? end |
#price_selector ⇒ Spree::Variant::PriceSelector
Returns an instance of the globally configured variant price selector class for this variant. It’s cached so we don’t create too many objects.
304 305 306 |
# File 'app/models/spree/variant.rb', line 304 def price_selector @price_selector ||= Spree::Config.variant_price_selector_class.new(self) end |
#set_option_value(opt_name, opt_value) ⇒ Object
Sets an option type and value for the given name and value.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'app/models/spree/variant.rb', line 262 def set_option_value(opt_name, opt_value) # no option values on master return if is_master option_type = Spree::OptionType.where(name: opt_name).first_or_initialize do |option| option.presentation = opt_name option.save! end current_value = option_values.detect { |option| option.option_type.name == opt_name } if current_value return if current_value.name == opt_value option_values.delete(current_value) else # 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 end option_value = Spree::OptionValue.where(option_type_id: option_type.id, name: opt_value).first_or_initialize do |option| option.presentation = opt_value option.save! end option_values << option_value save end |
#shipping_category ⇒ Spree::ShippingCategory
This returns the product’s shipping category if the shipping category ID on the variant is nil. It looks like an association, but really is an override.
171 172 173 |
# File 'app/models/spree/variant.rb', line 171 def shipping_category super || product_shipping_category end |
#shipping_category_id ⇒ Integer
This returns the product’s shipping category if if the shipping category ID on the variant is nil.
179 180 181 |
# File 'app/models/spree/variant.rb', line 179 def shipping_category_id super || product_shipping_category_id 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.
366 367 368 |
# File 'app/models/spree/variant.rb', line 366 def should_track_inventory? track_inventory? && Spree::Config.track_inventory_levels end |
#sku_and_options_text ⇒ String
Generates a string of the SKU and a list of all the option values.
331 332 333 |
# File 'app/models/spree/variant.rb', line 331 def "#{sku} #{}".strip end |
#tax_category ⇒ Spree::TaxCategory
This returns the product’s tax category if the tax category ID on the variant is nil. It looks like an association, but really is an override.
153 154 155 |
# File 'app/models/spree/variant.rb', line 153 def tax_category super || product_tax_category end |
#tax_category_id ⇒ Integer
This returns the product’s tax category ID if the tax category ID on the variant is nil. It looks like an association, but really is an override.
162 163 164 |
# File 'app/models/spree/variant.rb', line 162 def tax_category_id super || product_tax_category_id end |
#total_on_hand(stock_location = nil) ⇒ Fixnum
Fetches the on-hand quantity of the variant.
357 358 359 |
# File 'app/models/spree/variant.rb', line 357 def total_on_hand(stock_location = nil) Spree::Stock::Quantifier.new(self, stock_location).total_on_hand end |
#variant_properties ⇒ Array<Spree::VariantPropertyRuleValue>
Determines the variant’s property values by verifying which of the product’s variant property rules apply to itself.
374 375 376 377 378 |
# File 'app/models/spree/variant.rb', line 374 def variant_properties product.variant_property_rules.flat_map do |rule| rule.values if rule.applies_to_variant?(self) end.compact end |
#weight=(weight) ⇒ Bignum
Sets the weight for the variant.
195 196 197 |
# File 'app/models/spree/variant.rb', line 195 def weight=(weight) self[:weight] = Spree::LocalizedNumber.parse(weight) if weight.present? end |