Class: Spree::Product
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Methods inherited from Base
belongs_to_required_by_default, page, spree_base_scopes
#clear_preferences, #default_preferences, #defined_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_type, #set_preference
Instance Attribute Details
#option_values_hash ⇒ Object
Returns the value of attribute option_values_hash.
106
107
108
|
# File 'app/models/spree/product.rb', line 106
def option_values_hash
@option_values_hash
end
|
#prototype_id ⇒ Object
Adding properties and option types on creation based on a chosen prototype
165
166
167
|
# File 'app/models/spree/product.rb', line 165
def prototype_id
@prototype_id
end
|
Class Method Details
.active(currency = nil) ⇒ Object
205
206
207
|
# File 'app/models/spree/product/scopes.rb', line 205
def self.active(currency = nil)
available(nil, currency)
end
|
.add_search_scope(name, &block) ⇒ Object
7
8
9
10
|
# File 'app/models/spree/product/scopes.rb', line 7
def self.add_search_scope(name, &block)
singleton_class.send(:define_method, name.to_sym, &block)
search_scopes << name.to_sym
end
|
.add_simple_scopes(scopes) ⇒ Object
21
22
23
24
25
26
27
28
29
|
# File 'app/models/spree/product/scopes.rb', line 21
def self.add_simple_scopes(scopes)
scopes.each do |name|
next if name.to_s.include?('master_price')
parts = name.to_s.match(/(.*)_by_(.*)/)
scope(name.to_s, -> { order(Arel.sql("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? 'ASC' : 'DESC'}")) })
end
end
|
.available(available_on = nil, _currency = nil) ⇒ Object
Can’t use add_search_scope for this as it needs a default argument
199
200
201
202
|
# File 'app/models/spree/product/scopes.rb', line 199
def self.available(available_on = nil, _currency = nil)
available_on ||= Time.current
not_discontinued.joins(master: :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on)
end
|
.like_any(fields, values) ⇒ Object
228
229
230
231
232
233
|
# File 'app/models/spree/product.rb', line 228
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
|
.not_discontinued(only_not_discontinued = true) ⇒ Object
190
191
192
193
194
195
196
|
# File 'app/models/spree/product/scopes.rb', line 190
def self.not_discontinued(only_not_discontinued = true)
if only_not_discontinued != '0' && only_not_discontinued
where("#{Product.quoted_table_name}.discontinue_on IS NULL or #{Product.quoted_table_name}.discontinue_on >= ?", Time.zone.now)
else
all
end
end
|
.property_conditions(property) ⇒ Object
31
32
33
34
35
36
37
38
|
# File 'app/models/spree/product/scopes.rb', line 31
def self.property_conditions(property)
properties = Property.table_name
case property
when String then { "#{properties}.name" => property }
when Property then { "#{properties}.id" => property.id }
else { "#{properties}.id" => property.to_i }
end
end
|
.simple_scopes ⇒ Object
12
13
14
15
16
17
18
19
|
# File 'app/models/spree/product/scopes.rb', line 12
def self.simple_scopes
[
:ascend_by_updated_at,
:descend_by_updated_at,
:ascend_by_name,
:descend_by_name
]
end
|
Instance Method Details
#available? ⇒ Boolean
determine if product is available. deleted products and products with nil or future available_on date are not available
198
199
200
|
# File 'app/models/spree/product.rb', line 198
def available?
!(available_on.nil? || available_on.future?) && !deleted? && !discontinued?
end
|
#backorderable? ⇒ Boolean
139
140
141
|
# File 'app/models/spree/product.rb', line 139
def backorderable?
variants_including_master.any?(&:backorderable?)
end
|
#backordered? ⇒ Boolean
determine if any variant (including master) is out of stock and backorderable
216
217
218
|
# File 'app/models/spree/product.rb', line 216
def backordered?
variants_including_master.any?(&:backordered?)
end
|
#brand ⇒ Object
281
282
283
|
# File 'app/models/spree/product.rb', line 281
def brand
taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) })
end
|
#can_supply? ⇒ Boolean
determine if any variant (including master) can be supplied
211
212
213
|
# File 'app/models/spree/product.rb', line 211
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” -> […]
222
223
224
225
226
|
# File 'app/models/spree/product.rb', line 222
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
285
286
287
|
# File 'app/models/spree/product.rb', line 285
def category
taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) })
end
|
#default_variant ⇒ Object
152
153
154
|
# File 'app/models/spree/product.rb', line 152
def default_variant
has_variants? ? variants.first : master
end
|
#default_variant_id ⇒ Object
156
157
158
|
# File 'app/models/spree/product.rb', line 156
def 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.
191
192
193
|
# File 'app/models/spree/product.rb', line 191
def deleted?
!!deleted_at
end
|
#discontinue! ⇒ Object
202
203
204
|
# File 'app/models/spree/product.rb', line 202
def discontinue!
update_attribute(:discontinue_on, Time.current)
end
|
#discontinued? ⇒ Boolean
206
207
208
|
# File 'app/models/spree/product.rb', line 206
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
183
184
185
186
|
# File 'app/models/spree/product.rb', line 183
def duplicate
duplicator = ProductDuplicator.new(self)
duplicator.duplicate
end
|
#empty_option_values? ⇒ Boolean
246
247
248
249
250
|
# File 'app/models/spree/product.rb', line 246
def empty_option_values?
options.empty? || options.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
171
172
173
174
175
176
177
178
179
|
# File 'app/models/spree/product.rb', line 171
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
143
144
145
|
# File 'app/models/spree/product.rb', line 143
def find_or_build_master
master || build_master
end
|
#has_variants? ⇒ Boolean
the master variant is not a member of the variants array
148
149
150
|
# File 'app/models/spree/product.rb', line 148
def has_variants?
variants.any?
end
|
#in_stock? ⇒ Boolean
134
135
136
|
# File 'app/models/spree/product.rb', line 134
def in_stock?
variants_including_master.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.
277
278
279
|
# File 'app/models/spree/product.rb', line 277
def master
super || variants_including_master.with_deleted.find_by(is_master: true)
end
|
#property(property_name) ⇒ Object
252
253
254
|
# File 'app/models/spree/product.rb', line 252
def property(property_name)
product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value)
end
|
#purchasable? ⇒ Boolean
129
130
131
|
# File 'app/models/spree/product.rb', line 129
def purchasable?
variants_including_master.any?(&:purchasable?)
end
|
#set_property(property_name, property_value, property_presentation = property_name) ⇒ Object
256
257
258
259
260
261
262
263
264
|
# File 'app/models/spree/product.rb', line 256
def set_property(property_name, property_value, property_presentation = property_name)
ApplicationRecord.transaction do
property = Property.create_with(presentation: property_presentation).find_or_create_by(name: property_name)
product_property = ProductProperty.where(product: self, property: property).first_or_initialize
product_property.value = property_value
product_property.save!
end
end
|
#tax_category ⇒ Object
160
161
162
|
# File 'app/models/spree/product.rb', line 160
def tax_category
super || TaxCategory.find_by(is_default: true)
end
|
#total_on_hand ⇒ Object
266
267
268
269
270
271
272
|
# File 'app/models/spree/product.rb', line 266
def total_on_hand
if any_variants_not_track_inventory?
Float::INFINITY
else
stock_items.sum(:count_on_hand)
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
240
241
242
243
244
|
# File 'app/models/spree/product.rb', line 240
def variants_and_option_values(current_currency = nil)
variants.includes(:option_values).active(current_currency).select do |variant|
variant.option_values.any?
end
end
|