Class: Spree::Variant
- Includes:
- Discard::Model, DefaultPrice, ParanoiaDeprecations
- 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
Classes: PriceSelector, PricingOptions, VatPriceGenerator
Instance Attribute Summary collapse
-
#rebuild_vat_prices ⇒ Object
writeonly
Sets the attribute rebuild_vat_prices.
Class Method Summary collapse
-
.active(currency = nil) ⇒ ActiveRecord::Relation
deprecated
Deprecated.
Please use the .with_prices scope instead
-
.has_option(option_type, *option_values) ⇒ Object
(also: has_options)
Returns variants that match a given option value.
-
.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.
Instance Method Summary collapse
-
#amount_in(currency) ⇒ Float
deprecated
Deprecated.
Please use #price_for instead and use a money object rathern than a BigDecimal.
-
#can_supply?(quantity = 1) ⇒ 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.
-
#display_image(fallback: true) ⇒ Spree::Image
Image that can be used for the variant.
-
#exchange_name ⇒ String
Determines the name of an Exchange variant.
-
#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_for ⇒ Spree::Money
Chooses an appropriate price for the given pricing options.
-
#price_in(currency) ⇒ Spree::Price
deprecated
Deprecated.
Please use #price_for(pricing_options) instead
- #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.
-
#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.
-
#total_on_hand ⇒ 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
#find_or_build_default_price, #has_default_price?
Methods included from ParanoiaDeprecations
#paranoia_delete, #paranoia_destroy
Methods inherited from Base
display_includes, #initialize_preference_defaults, page, preference
Methods included from Preferences::Preferable
#admin_form_preference_names, #default_preferences, #defined_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_type, #set_preference
Instance Attribute Details
#rebuild_vat_prices=(value) ⇒ Object (writeonly)
Sets the attribute rebuild_vat_prices
34 35 36 |
# File 'app/models/spree/variant.rb', line 34 def rebuild_vat_prices=(value) @rebuild_vat_prices = value end |
Class Method Details
.active(currency = nil) ⇒ ActiveRecord::Relation
Please use the .with_prices scope instead
Returns variants that are not deleted and have a price in the given currency.
133 134 135 136 |
# File 'app/models/spree/variant.rb', line 133 def self.active(currency = nil) Spree::Deprecation.warn("`Variant.active(currency)` is deprecated. Please use `Variant.with_prices(pricing_options)` instead.", caller) joins(:prices).where(deleted_at: nil).where('spree_prices.currency' => currency || Spree::Config[:currency]).where('spree_prices.amount IS NOT NULL') end |
.has_option(option_type, *option_values) ⇒ Object Also known as: has_options
Returns variants that match a given option value
Example:
product.variants_including_master.has_option(OptionType.find_by(name: ‘shoe-size’), OptionValue.find_by(name: ‘8’))
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'app/models/spree/variant/scopes.rb', line 14 def has_option(option_type, *option_values) option_types = Spree::OptionType.table_name option_type_conditions = case option_type when OptionType then { "#{option_types}.name" => option_type.name } when String then { "#{option_types}.name" => option_type } else { "#{option_types}.id" => option_type } end relation = joins(option_values: :option_type).where(option_type_conditions) option_values.each do |option_value| option_value_conditions = case option_value when OptionValue then { "#{Spree::OptionValue.table_name}.name" => option_value.name } when String then { "#{Spree::OptionValue.table_name}.name" => option_value } else { "#{Spree::OptionValue.table_name}.id" => option_value } end relation = relation.where(option_value_conditions) end relation end |
.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}
99 100 101 102 103 104 105 106 |
# File 'app/models/spree/variant.rb', line 99 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 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
114 115 116 117 118 119 120 121 122 |
# File 'app/models/spree/variant.rb', line 114 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)) end |
.with_prices(pricing_options = Spree::Config.default_pricing_options) ⇒ ActiveRecord::Relation
Returns variants that have a price for the given pricing options
142 143 144 |
# File 'app/models/spree/variant.rb', line 142 def self.with_prices( = Spree::Config.) joins(:prices).merge(Spree::Price.currently_valid.where(.search_arguments)) end |
Instance Method Details
#amount_in(currency) ⇒ Float
Please use #price_for instead and use a money object rathern than a BigDecimal.
Fetches the price amount in the specified currency.
315 316 317 |
# File 'app/models/spree/variant.rb', line 315 def amount_in(currency) price_in(currency).try(:amount) end |
#can_supply?(quantity = 1) ⇒ Boolean
Returns true if the desired quantity can be supplied.
343 344 345 |
# File 'app/models/spree/variant.rb', line 343 def can_supply?(quantity = 1) Spree::Stock::Quantifier.new(self).can_supply?(quantity) end |
#cost_price=(price) ⇒ Bignum
Sets the cost_price for the variant.
159 160 161 |
# File 'app/models/spree/variant.rb', line 159 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.
217 218 219 |
# File 'app/models/spree/variant.rb', line 217 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.
209 210 211 |
# File 'app/models/spree/variant.rb', line 209 def descriptive_name is_master? ? name + ' - Master' : name + ' - ' + end |
#display_image(fallback: true) ⇒ Spree::Image
Image that can be used for the variant.
Will first search for images on the variant. If it doesn’t find any, it’ll fallback to any variant image (unless fallback is false) or to a new Image.
371 372 373 |
# File 'app/models/spree/variant.rb', line 371 def display_image(fallback: true) images.first || (fallback && product.variant_images.first) || Spree::Image.new end |
#exchange_name ⇒ String
Determines the name of an Exchange variant.
201 202 203 |
# File 'app/models/spree/variant.rb', line 201 def exchange_name is_master? ? name : end |
#in_stock? ⇒ Boolean
Returns true if there is stock on-hand for the variant.
335 336 337 338 339 |
# File 'app/models/spree/variant.rb', line 335 def in_stock? Rails.cache.fetch(in_stock_cache_key) do total_on_hand > 0 end end |
#is_backorderable? ⇒ Boolean
Returns true if this variant can be backordered.
179 180 181 |
# File 'app/models/spree/variant.rb', line 179 def is_backorderable? Spree::Stock::Quantifier.new(self).backorderable? end |
#name_and_sku ⇒ String
Generates a friendly name and sku string.
323 324 325 |
# File 'app/models/spree/variant.rb', line 323 def name_and_sku "#{name} - #{sku}" end |
#on_backorder ⇒ Fixnum
Counts the number of units currently on backorder for this variant.
174 175 176 |
# File 'app/models/spree/variant.rb', line 174 def on_backorder inventory_units.with_state('backordered').size end |
#option_value(opt_name) ⇒ String
Fetches the option value for the given option name.
268 269 270 |
# File 'app/models/spree/variant.rb', line 268 def option_value(opt_name) option_values.detect { |o| o.option_type.name == opt_name }.try(:presentation) end |
#options=(options = {}) ⇒ Object
Assign given options hash to option values.
224 225 226 227 228 |
# File 'app/models/spree/variant.rb', line 224 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.
186 187 188 189 190 191 192 193 194 195 196 |
# File 'app/models/spree/variant.rb', line 186 def values = option_values.includes(:option_type).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
288 289 290 291 292 293 |
# File 'app/models/spree/variant.rb', line 288 def price_difference_from_master( = Spree::Config.) master_price = product.master.price_for() variant_price = price_for() return unless master_price && variant_price variant_price - master_price end |
#price_for ⇒ Spree::Money
Chooses an appropriate price for the given pricing options
285 |
# File 'app/models/spree/variant.rb', line 285 delegate :price_for, to: :price_selector |
#price_in(currency) ⇒ Spree::Price
Please use #price_for(pricing_options) instead
Converts the variant’s price to the given currency.
305 306 307 |
# File 'app/models/spree/variant.rb', line 305 def price_in(currency) prices.currently_valid.find_by(currency: currency) end |
#price_same_as_master?(pricing_options = Spree::Config.default_pricing_options) ⇒ Boolean
295 296 297 298 |
# File 'app/models/spree/variant.rb', line 295 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.
276 277 278 |
# File 'app/models/spree/variant.rb', line 276 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.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'app/models/spree/variant.rb', line 234 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 |o| o.presentation = opt_name o.save! end current_value = option_values.detect { |o| o.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 |o| 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.
359 360 361 |
# File 'app/models/spree/variant.rb', line 359 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.
330 331 332 |
# File 'app/models/spree/variant.rb', line 330 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.
151 152 153 |
# File 'app/models/spree/variant.rb', line 151 def tax_category super || product_tax_category end |
#total_on_hand ⇒ Fixnum
Fetches the on-hand quantity of the variant.
350 351 352 |
# File 'app/models/spree/variant.rb', line 350 def total_on_hand Spree::Stock::Quantifier.new(self).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.
379 380 381 382 383 |
# File 'app/models/spree/variant.rb', line 379 def variant_properties product.variant_property_rules.map do |rule| rule.values if rule.applies_to_variant?(self) end.flatten.compact end |
#weight=(weight) ⇒ Bignum
Sets the weight for the variant.
167 168 169 |
# File 'app/models/spree/variant.rb', line 167 def weight=(weight) self[:weight] = Spree::LocalizedNumber.parse(weight) if weight.present? end |