Class: Spree::Product

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/spree/product.rb,
app/models/spree/product/scopes.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#option_values_hashObject

Returns the value of attribute option_values_hash.



73
74
75
# File 'app/models/spree/product.rb', line 73

def option_values_hash
  @option_values_hash
end

#prototype_idObject

Adding properties and option types on creation based on a chosen prototype



160
161
162
# File 'app/models/spree/product.rb', line 160

def prototype_id
  @prototype_id
end

Class Method Details

.active(currency = nil) ⇒ Object



206
207
208
# File 'app/models/spree/product/scopes.rb', line 206

def self.active(currency = nil)
  not_deleted.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)
  self.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|
    # We should not define price scopes here, as they require something slightly different
    next if name.to_s.include?("master_price")
    parts = name.to_s.match(/(.*)_by_(.*)/)
    order_text = "#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ?  "ASC" : "DESC"}"
    self.scope(name.to_s, relation.order(order_text))
  end
end

.available(available_on = nil, currency = nil) ⇒ Object

Can’t use add_search_scope for this as it needs a default argument



197
198
199
200
201
202
203
# File 'app/models/spree/product/scopes.rb', line 197

def self.available(available_on = nil, currency = nil)
  scope = joins(:master => :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.now)
  unless Spree::Config.show_products_without_price
    scope = scope.where('spree_prices.currency' => currency || Spree::Config[:currency]).where('spree_prices.amount IS NOT NULL')
  end
  scope
end

.group_by_products_idObject

This method needs to be defined *as a method*, otherwise it will cause the problem shown in #1247.



222
223
224
225
226
227
228
229
230
231
# File 'app/models/spree/product/scopes.rb', line 222

def self.group_by_products_id
  if (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL')
    # Need to check, otherwise `column_names` will fail
    if table_exists?
      group(column_names.map { |col_name| "#{table_name}.#{col_name}"})
    end
  else
    group("#{self.quoted_table_name}.id")
  end
end

.like_any(fields, values) ⇒ Object



222
223
224
225
# File 'app/models/spree/product.rb', line 222

def self.like_any(fields, values)
  where_str = fields.map { |field| Array.new(values.size, "#{self.quoted_table_name}.#{field} #{LIKE} ?").join(' OR ') }.join(' OR ')
  self.where([where_str, values.map { |value| "%#{value}%" } * fields.size].flatten)
end

.simple_scopesObject



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

Returns:

  • (Boolean)


211
212
213
# File 'app/models/spree/product.rb', line 211

def available?
  !(available_on.nil? || available_on.future?)
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” -> […]



217
218
219
220
# File 'app/models/spree/product.rb', line 217

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

#count_on_hand=(value) ⇒ Object



134
135
136
# File 'app/models/spree/product.rb', line 134

def count_on_hand=(value)
  raise I18n.t('exceptions.count_on_hand_setter')
end

#deleteObject

override the delete method to set deleted_at value instead of actually deleting the product.



154
155
156
157
# File 'app/models/spree/product.rb', line 154

def delete
  self.update_column(:deleted_at, Time.now)
  variants_including_master.update_all(:deleted_at => Time.now)
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.

Returns:

  • (Boolean)


207
208
209
# File 'app/models/spree/product.rb', line 207

def deleted?
  !!deleted_at
end

#duplicateObject

for adding products which are closely related to existing ones define “duplicate_extra” for site-specific actions, eg for additional fields



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'app/models/spree/product.rb', line 176

def duplicate
  p = self.dup
  p.name = 'COPY OF ' + name
  p.deleted_at = nil
  p.created_at = p.updated_at = nil
  p.taxons = taxons

  p.product_properties = product_properties.map { |q| r = q.dup; r.created_at = r.updated_at = nil; r }

  image_dup = lambda { |i| j = i.dup; j.attachment = i.attachment.clone; j }

  variant = master.dup
  variant.sku = 'COPY OF ' + master.sku
  variant.deleted_at = nil
  variant.images = master.images.map { |i| image_dup.call i }
  variant.price = master.price
  variant.currency = master.currency
  p.master = variant

  # don't dup the actual variants, just the characterising types
  p.option_types = option_types if has_variants?

  # allow site to do some customization
  p.send(:duplicate_extra, self) if p.respond_to?(:duplicate_extra)
  p.save!
  p
end

#empty_option_values?Boolean

Returns:

  • (Boolean)


227
228
229
230
231
# File 'app/models/spree/product.rb', line 227

def empty_option_values?
  options.empty? || options.any? do |opt|
    opt.option_type.option_values.empty?
  end
end

#ensure_option_types_exist_for_values_hashObject

Ensures option_types and product_option_types exist for keys in option_values_hash



166
167
168
169
170
171
172
# File 'app/models/spree/product.rb', line 166

def ensure_option_types_exist_for_values_hash
  return if option_values_hash.nil?
  option_values_hash.keys.map(&:to_i).each do |id|
    self.option_type_ids << id unless option_type_ids.include?(id)
    product_option_types.create({:option_type_id => id}, :without_protection => true) unless product_option_types.pluck(:option_type_id).include?(id)
  end
end

#has_stock?Boolean

Returns true if there are inventory units (any variant) with “on_hand” state for this product Variants take precedence over master

Returns:

  • (Boolean)


140
141
142
# File 'app/models/spree/product.rb', line 140

def has_stock?
  has_variants? ? variants.any?(&:in_stock?) : master.in_stock?
end

#has_variants?Boolean

returns true if the product has any variants (the master variant is not a member of the variants array)

Returns:

  • (Boolean)


101
102
103
# File 'app/models/spree/product.rb', line 101

def has_variants?
  variants.any?
end

#on_demand=(new_on_demand) ⇒ Object



128
129
130
131
132
# File 'app/models/spree/product.rb', line 128

def on_demand=(new_on_demand)
  raise 'cannot set on_demand of product with variants' if has_variants? && Spree::Config[:track_inventory_levels]
  master.on_demand = on_demand
  self[:on_demand] = new_on_demand
end

#on_display?Boolean

should product be displayed on products pages and search

Returns:

  • (Boolean)


106
107
108
# File 'app/models/spree/product.rb', line 106

def on_display?
  has_stock? || Spree::Config[:show_zero_stock_products]
end

#on_handObject

returns the number of inventory units “on_hand” for this product



116
117
118
# File 'app/models/spree/product.rb', line 116

def on_hand
  has_variants? ? variants.sum(&:on_hand) : master.on_hand
end

#on_hand=(new_level) ⇒ Object

adjusts the “on_hand” inventory level for the product up or down to match the given new_level



121
122
123
124
125
126
# File 'app/models/spree/product.rb', line 121

def on_hand=(new_level)
  unless self.on_demand
    raise 'cannot set on_hand of product with variants' if has_variants? && Spree::Config[:track_inventory_levels] 
    master.on_hand = new_level
  end
end

#on_sale?Boolean

is this product actually available for purchase

Returns:

  • (Boolean)


111
112
113
# File 'app/models/spree/product.rb', line 111

def on_sale?
  has_stock? || Spree::Config[:allow_backorders]
end

#property(property_name) ⇒ Object



233
234
235
236
# File 'app/models/spree/product.rb', line 233

def property(property_name)
  return nil unless prop = properties.find_by_name(property_name)
  product_properties.find_by_property_id(prop.id).try(:value)
end

#set_property(property_name, property_value) ⇒ Object



238
239
240
241
242
243
244
245
# File 'app/models/spree/product.rb', line 238

def set_property(property_name, property_value)
  ActiveRecord::Base.transaction do
    property = Property.where(:name => property_name).first_or_create!(:presentation => property_name)
    product_property = ProductProperty.where(:product_id => id, :property_id => property.id).first_or_initialize
    product_property.value = property_value
    product_property.save!
  end
end

#tax_categoryObject



144
145
146
147
148
149
150
# File 'app/models/spree/product.rb', line 144

def tax_category
  if self[:tax_category_id].nil?
    TaxCategory.where(:is_default => true).first
  else
    TaxCategory.find(self[:tax_category_id])
  end
end

#to_paramObject



96
97
98
# File 'app/models/spree/product.rb', line 96

def to_param
  permalink.present? ? permalink : (permalink_was || name.to_s.to_url)
end

#variants_with_only_masterObject



91
92
93
94
# File 'app/models/spree/product.rb', line 91

def variants_with_only_master
  ActiveSupport::Deprecation.warn("[SPREE] Spree::Product#variants_with_only_master will be deprecated in Spree 1.3. Please use Spree::Product#master instead.")
  master
end