Class: Product

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
Scopes::Product
Defined in:
app/models/product.rb

Overview

PRODUCTS Products represent an entity for sale in a store. Products can have variations, called variants Products properties include description, permalink, availability,

shipping category, etc. that do not change by variant.

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. Price, SKU, size, weight, etc. are all delegated to the master variant. 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.

Constant Summary

Constants included from Scopes::Product

Scopes::Product::ATTRIBUTE_HELPER_METHODS, Scopes::Product::ORDERING, Scopes::Product::SCOPES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Scopes::Product

arguments_for_scope_name, get_taxons, prepare_taxon_conditions, prepare_words

Instance Attribute Details

#prototype_idObject

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



178
179
180
# File 'app/models/product.rb', line 178

def prototype_id
  @prototype_id
end

Class Method Details

.activeObject

RAILS 3 TODO - this scope doesn’t match the original 2.3.x version, needs attention (but it works)



88
89
90
# File 'app/models/product.rb', line 88

def active
  not_deleted.available
end

.available(available_on = nil) ⇒ Object



83
84
85
# File 'app/models/product.rb', line 83

def available(available_on = nil)
  where(Product.arel_table[:available_on].lteq(available_on || Time.zone.now ))
end

.id_equals(input_id) ⇒ Object



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

def id_equals(input_id)
  where(Product.arel_table[:id].eq(input_id))
end

.like_any(fields, values) ⇒ Object



245
246
247
248
# File 'app/models/product.rb', line 245

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

.not_deletedObject



79
80
81
# File 'app/models/product.rb', line 79

def not_deleted
  where(Product.arel_table[:deleted_at].eq(nil))
end

.on_handObject



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

def on_hand
  where(Product.arel_table[:count_on_hand].gteq(0))
end

.taxons_name_eq(name) ⇒ Object



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

def taxons_name_eq(name)
  joins(:taxons).where(Taxon.arel_table[:name].eq(name))
end

Instance Method Details

#add_properties_and_option_types_from_prototypeObject



183
184
185
186
187
188
189
190
# File 'app/models/product.rb', line 183

def add_properties_and_option_types_from_prototype
  if prototype_id && prototype = Prototype.find_by_id(prototype_id)
    prototype.properties.each do |property|
      product_properties.create(:property => property)
    end
    self.option_types = prototype.option_types
  end
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” -> […]



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

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

#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)


226
227
228
# File 'app/models/product.rb', line 226

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



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'app/models/product.rb', line 194

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

  p.product_properties = self.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}
  p.images = self.images.map {|i| image_dup.call i}

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

  if self.has_variants?
    # don't dup the actual variants, just the characterising types
    p.option_types = self.option_types
  else
  end
  # allow site to do some customization
  p.send(:duplicate_extra, self) if p.respond_to?(:duplicate_extra)
  p.save!
  p
end

#effective_tax_rateObject



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

def effective_tax_rate
  if self.tax_category
    tax_category.effective_amount
  else
    TaxRate.default
  end
end

#has_stock?Boolean

Returns true if there are inventory units (any variant) with “on_hand” state for this product

Returns:

  • (Boolean)


165
166
167
# File 'app/models/product.rb', line 165

def has_stock?
  master.in_stock? || variants.any?(&: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)


149
150
151
# File 'app/models/product.rb', line 149

def has_variants?
  variants.any?
end

#master_priceObject


The following methods are deprecated and will be removed in a future version of Spree




119
120
121
122
# File 'app/models/product.rb', line 119

def master_price
  warn "[DEPRECATION] `Product.master_price` is deprecated.  Please use `Product.price` instead. (called from #{caller[0]})"
  self.price
end

#master_price=(value) ⇒ Object



124
125
126
127
# File 'app/models/product.rb', line 124

def master_price=(value)
  warn "[DEPRECATION] `Product.master_price=` is deprecated.  Please use `Product.price=` instead. (called from #{caller[0]})"
  self.price = value
end

#on_handObject

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



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

def on_hand
  has_variants? ? variants.inject(0){|sum, v| sum + v.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



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

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

#tax_categoryObject



169
170
171
172
173
174
175
# File 'app/models/product.rb', line 169

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

#to_paramObject


end deprecation region




143
144
145
146
# File 'app/models/product.rb', line 143

def to_param
  return permalink if permalink.present?
  name.to_url
end

#variantObject



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

def variant
  warn "[DEPRECATION] `Product.variant` is deprecated.  Please use `Product.master` instead. (called from #{caller[0]})"
  self.master
end

#variant_imagesObject



61
62
63
# File 'app/models/product.rb', line 61

def variant_images
  Image.find_by_sql("SELECT assets.* FROM assets LEFT JOIN variants ON (variants.id = assets.viewable_id) WHERE (variants.product_id = #{self.id})")
end

#variants?Boolean

Returns:

  • (Boolean)


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

def variants?
  warn "[DEPRECATION] `Product.variants?` is deprecated.  Please use `Product.has_variants?` instead. (called from #{caller[0]})"
  self.has_variants?
end