Class: Spree::Product
- Extended by:
- FriendlyId
- Includes:
- MemoizedData, Metadata, MultiStoreResource, ProductScopes, TranslatableResource, TranslatableResourceSlug, VendorConcern, Webhooks::HasWebhooks
- Defined in:
- app/models/spree/product.rb
Constant Summary collapse
- MEMOIZED_METHODS =
%w[total_on_hand taxonomy_ids taxon_and_ancestors category default_variant_id tax_category default_variant purchasable? in_stock? backorderable?]
- TRANSLATABLE_FIELDS =
%i[name description slug meta_description meta_keywords meta_title].freeze
Instance Attribute Summary collapse
-
#option_values_hash ⇒ Object
Returns the value of attribute option_values_hash.
-
#prototype_id ⇒ Object
Adding properties and option types on creation based on a chosen prototype.
Class Method Summary collapse
Instance Method Summary collapse
- #any_variant_in_stock_or_backorderable? ⇒ Boolean
-
#available? ⇒ Boolean
determine if product is available.
-
#backorderable? ⇒ Boolean
Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259.
-
#backordered? ⇒ Boolean
determine if any variant (including master) is out of stock and backorderable.
- #brand ⇒ Object
-
#can_supply? ⇒ Boolean
determine if any variant (including master) can be supplied.
-
#categorise_variants_from_option(opt_type) ⇒ Object
split variants list into hash which shows mapping of opt value onto matching variants eg categorise_variants_from_option(color) => -> […], “blue” -> […].
- #category ⇒ Object
-
#default_variant ⇒ Spree::Variant
Returns default Variant for Product If ‘track_inventory_levels` is enabled it will try to find the first Variant in stock or backorderable, if there’s none it will return first Variant sorted by ‘position` attribute If `track_inventory_levels` is disabled it will return first Variant sorted by `position` attribute.
-
#default_variant_id ⇒ Integer
Returns default Variant ID for Product.
-
#deleted? ⇒ Boolean
use deleted? rather than checking the attribute directly.
- #digital? ⇒ Boolean
- #discontinue! ⇒ Object
- #discontinued? ⇒ Boolean
-
#duplicate ⇒ Object
for adding products which are closely related to existing ones define “duplicate_extra” for site-specific actions, eg for additional fields.
- #empty_option_values? ⇒ Boolean
-
#ensure_option_types_exist_for_values_hash ⇒ Object
Ensures option_types and product_option_types exist for keys in option_values_hash.
- #find_or_build_master ⇒ Object
-
#has_variants? ⇒ Boolean
the master variant is not a member of the variants array.
-
#in_stock? ⇒ Boolean
Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259.
-
#master ⇒ Object
Master variant may be deleted (i.e. when the product is deleted) which would make AR’s default finder return nil.
- #property(property_name) ⇒ Object
-
#purchasable? ⇒ Boolean
Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259.
- #set_property(property_name, property_value, property_presentation = property_name) ⇒ Object
- #tax_category ⇒ Object
- #taxons_for_store(store) ⇒ Object
- #total_on_hand ⇒ Object
-
#variants_and_option_values(current_currency = nil) ⇒ Object
Suitable for displaying only variants that has at least one option value.
Methods inherited from Base
belongs_to_required_by_default, for_store, has_many_inversing, json_api_columns, json_api_permitted_attributes, json_api_type, page, spree_base_scopes, spree_base_uniqueness_scope
Methods included from Spree::Preferences::Preferable
#clear_preferences, #default_preferences, #defined_preferences, #deprecated_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_deprecated, #preference_type, #set_preference
Instance Attribute Details
#option_values_hash ⇒ Object
Returns the value of attribute option_values_hash.
142 143 144 |
# File 'app/models/spree/product.rb', line 142 def option_values_hash @option_values_hash end |
#prototype_id ⇒ Object
Adding properties and option types on creation based on a chosen prototype
234 235 236 |
# File 'app/models/spree/product.rb', line 234 def prototype_id @prototype_id end |
Class Method Details
.like_any(fields, values) ⇒ Object
296 297 298 299 300 301 |
# File 'app/models/spree/product.rb', line 296 def self.like_any(fields, values) conditions = fields.product(values).map do |(field, value)| arel_table[field].matches("%#{value}%") end where conditions.inject(:or) end |
Instance Method Details
#any_variant_in_stock_or_backorderable? ⇒ Boolean
380 381 382 383 384 385 386 |
# File 'app/models/spree/product.rb', line 380 def any_variant_in_stock_or_backorderable? if variants.any? variants_including_master.in_stock_or_backorderable.exists? else master.in_stock_or_backorderable? end end |
#available? ⇒ Boolean
determine if product is available. deleted products and products with status different than active are not available
264 265 266 |
# File 'app/models/spree/product.rb', line 264 def available? active? && !deleted? end |
#backorderable? ⇒ Boolean
Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259
192 193 194 |
# File 'app/models/spree/product.rb', line 192 def backorderable? default_variant.backorderable? || variants.any?(&:backorderable?) end |
#backordered? ⇒ Boolean
determine if any variant (including master) is out of stock and backorderable
284 285 286 |
# File 'app/models/spree/product.rb', line 284 def backordered? variants_including_master.any?(&:backordered?) end |
#brand ⇒ Object
361 362 363 364 365 |
# File 'app/models/spree/product.rb', line 361 def brand @brand ||= taxons.joins(:taxonomy). join_translation_table(Taxonomy). find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_brands_name) }) end |
#can_supply? ⇒ Boolean
determine if any variant (including master) can be supplied
279 280 281 |
# File 'app/models/spree/product.rb', line 279 def can_supply? variants_including_master.any?(&:can_supply?) end |
#categorise_variants_from_option(opt_type) ⇒ Object
split variants list into hash which shows mapping of opt value onto matching variants eg categorise_variants_from_option(color) => -> […], “blue” -> […]
290 291 292 293 294 |
# File 'app/models/spree/product.rb', line 290 def categorise_variants_from_option(opt_type) return {} unless option_types.include?(opt_type) variants.active.group_by { |v| v.option_values.detect { |o| o.option_type == opt_type } } end |
#category ⇒ Object
367 368 369 370 371 372 |
# File 'app/models/spree/product.rb', line 367 def category @category ||= taxons.joins(:taxonomy). join_translation_table(Taxonomy). order(depth: :desc). find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_categories_name) }) end |
#default_variant ⇒ Spree::Variant
Returns default Variant for Product If ‘track_inventory_levels` is enabled it will try to find the first Variant in stock or backorderable, if there’s none it will return first Variant sorted by ‘position` attribute If `track_inventory_levels` is disabled it will return first Variant sorted by `position` attribute
213 214 215 216 217 218 219 220 221 |
# File 'app/models/spree/product.rb', line 213 def default_variant @default_variant ||= Rails.cache.fetch(default_variant_cache_key) do if Spree::Config[:track_inventory_levels] && available_variant = variants.detect(&:purchasable?) available_variant else has_variants? ? variants.first : master end end end |
#default_variant_id ⇒ Integer
Returns default Variant ID for Product
225 226 227 |
# File 'app/models/spree/product.rb', line 225 def default_variant_id @default_variant_id ||= default_variant.id 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.
257 258 259 |
# File 'app/models/spree/product.rb', line 257 def deleted? !!deleted_at end |
#digital? ⇒ Boolean
388 389 390 |
# File 'app/models/spree/product.rb', line 388 def digital? shipping_category&.name == I18n.t('spree.seed.shipping.categories.digital') end |
#discontinue! ⇒ Object
268 269 270 271 272 |
# File 'app/models/spree/product.rb', line 268 def discontinue! self.discontinue_on = Time.current self.status = 'archived' save(validate: false) end |
#discontinued? ⇒ Boolean
274 275 276 |
# File 'app/models/spree/product.rb', line 274 def discontinued? !!discontinue_on && discontinue_on <= Time.current end |
#duplicate ⇒ Object
for adding products which are closely related to existing ones define “duplicate_extra” for site-specific actions, eg for additional fields
249 250 251 252 |
# File 'app/models/spree/product.rb', line 249 def duplicate duplicator = ProductDuplicator.new(self) duplicator.duplicate end |
#empty_option_values? ⇒ Boolean
312 313 314 315 316 |
# File 'app/models/spree/product.rb', line 312 def empty_option_values? .empty? || .any? do |opt| opt.option_type.option_values.empty? end end |
#ensure_option_types_exist_for_values_hash ⇒ Object
Ensures option_types and product_option_types exist for keys in option_values_hash
237 238 239 240 241 242 243 244 245 |
# File 'app/models/spree/product.rb', line 237 def ensure_option_types_exist_for_values_hash return if option_values_hash.nil? required_option_type_ids = option_values_hash.keys.map(&:to_i) missing_option_type_ids = required_option_type_ids - option_type_ids missing_option_type_ids.each do |id| product_option_types.create(option_type_id: id) end end |
#find_or_build_master ⇒ Object
196 197 198 |
# File 'app/models/spree/product.rb', line 196 def find_or_build_master master || build_master end |
#has_variants? ⇒ Boolean
the master variant is not a member of the variants array
201 202 203 |
# File 'app/models/spree/product.rb', line 201 def has_variants? variants.any? end |
#in_stock? ⇒ Boolean
Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259
187 188 189 |
# File 'app/models/spree/product.rb', line 187 def in_stock? default_variant.in_stock? || variants.any?(&:in_stock?) end |
#master ⇒ Object
Master variant may be deleted (i.e. when the product is deleted) which would make AR’s default finder return nil. This is a stopgap for that little problem.
357 358 359 |
# File 'app/models/spree/product.rb', line 357 def master super || variants_including_master.with_deleted.find_by(is_master: true) end |
#property(property_name) ⇒ Object
318 319 320 321 322 |
# File 'app/models/spree/product.rb', line 318 def property(property_name) product_properties.joins(:property). join_translation_table(Property). find_by(Property.translation_table_alias => { name: property_name }).try(:value) end |
#purchasable? ⇒ Boolean
Can’t use short form block syntax due to github.com/Netflix/fast_jsonapi/issues/259
182 183 184 |
# File 'app/models/spree/product.rb', line 182 def purchasable? default_variant.purchasable? || variants.any?(&:purchasable?) end |
#set_property(property_name, property_value, property_presentation = property_name) ⇒ Object
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'app/models/spree/product.rb', line 324 def set_property(property_name, property_value, property_presentation = property_name) ApplicationRecord.transaction do # Manual first_or_create to work around Mobility bug property = if Property.where(name: property_name).exists? Property.where(name: property_name).first else Property.create(name: property_name, presentation: property_presentation) end product_property = if ProductProperty.where(product: self, property: property).exists? ProductProperty.where(product: self, property: property).first else ProductProperty.create(product: self, property: property) end product_property.value = property_value product_property.save! end end |
#tax_category ⇒ Object
229 230 231 |
# File 'app/models/spree/product.rb', line 229 def tax_category @tax_category ||= super || TaxCategory.find_by(is_default: true) end |
#taxons_for_store(store) ⇒ Object
374 375 376 377 378 |
# File 'app/models/spree/product.rb', line 374 def taxons_for_store(store) Rails.cache.fetch("#{cache_key_with_version}/taxons-per-store/#{store.id}") do taxons.for_store(store) end end |
#total_on_hand ⇒ Object
344 345 346 347 348 349 350 351 352 |
# File 'app/models/spree/product.rb', line 344 def total_on_hand @total_on_hand ||= Rails.cache.fetch(['product-total-on-hand', cache_key_with_version]) do if any_variants_not_track_inventory? BigDecimal::INFINITY else stock_items.sum(:count_on_hand) end end end |
#variants_and_option_values(current_currency = nil) ⇒ Object
Suitable for displaying only variants that has at least one option value. There may be scenarios where an option type is removed and along with it all option values. At that point all variants associated with only those values should not be displayed to frontend users. Otherwise it breaks the idea of having variants
308 309 310 |
# File 'app/models/spree/product.rb', line 308 def variants_and_option_values(current_currency = nil) variants.active(current_currency).joins(:option_value_variants) end |