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

#prototype_idObject

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



114
115
116
# File 'app/models/spree/product.rb', line 114

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)



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

def self.active
  not_deleted.available
end

.ascend_by_master_priceObject



25
26
27
# File 'app/models/spree/product/scopes.rb', line 25

def self.ascend_by_master_price
  joins(:variants_with_only_master).order("#{variant_table_name}.price ASC")
end

.available(available_on = nil) ⇒ Object



192
193
194
# File 'app/models/spree/product/scopes.rb', line 192

def self.available(available_on = nil)
  where('available_on <= ?', available_on || Time.now)
end

.descend_by_master_priceObject



29
30
31
# File 'app/models/spree/product/scopes.rb', line 29

def self.descend_by_master_price
  joins(:variants_with_only_master).order("#{variant_table_name}.price DESC")
end

.descend_by_popularityObject

Sorts products from most popular (popularity is extracted from how many times use has put product in cart, not completed orders)

there is alternative faster and more elegant solution, it has small drawback though, it doesn stack with other scopes :/

:joins => “LEFT OUTER JOIN (SELECT line_items.variant_id as vid, COUNT(*) as cnt FROM line_items GROUP BY line_items.variant_id) AS popularity_count ON variants.id = vid”, :order => ‘COALESCE(cnt, 0) DESC’



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'app/models/spree/product/scopes.rb', line 170

def self.descend_by_popularity
  joins(:master).
  order(%Q{
       COALESCE((
         SELECT
           COUNT(#{LineItem.quoted_table_name}.id)
         FROM
           #{LineItem.quoted_table_name}
         JOIN
           #{Variant.quoted_table_name} AS popular_variants
         ON
           popular_variants.id = #{LineItem.quoted_table_name}.variant_id
         WHERE
           popular_variants.product_id = #{Product.quoted_table_name}.id
       ), 0) DESC
    })
end

.in_cached_group(product_group) ⇒ Object



77
78
79
# File 'app/models/spree/product/scopes.rb', line 77

def self.in_cached_group(product_group)
  joins(:product_groups).where('spree_product_groups_products.product_group_id' => product_group)
end

.in_name(words) ⇒ Object

Finds all products that have a name containing the given words.



142
143
144
# File 'app/models/spree/product/scopes.rb', line 142

def self.in_name(words)
  like_any([:name], prepare_words(words))
end

.in_name_or_description(words) ⇒ Object

Finds all products that have a name, description, meta_description or meta_keywords containing the given keywords.



152
153
154
# File 'app/models/spree/product/scopes.rb', line 152

def self.in_name_or_description(words)
  like_any([:name, :description, :meta_description, :meta_keywords], prepare_words(words))
end

.in_name_or_keywords(words) ⇒ Object

Finds all products that have a name or meta_keywords containing the given words.



147
148
149
# File 'app/models/spree/product/scopes.rb', line 147

def self.in_name_or_keywords(words)
  like_any([:name, :meta_keywords], prepare_words(words))
end

.in_taxon(taxon) ⇒ Object

This scope selects products in taxon AND all its descendants If you need products only within one taxon use

Spree::Product.taxons_id_eq(x)


63
64
65
# File 'app/models/spree/product/scopes.rb', line 63

def self.in_taxon(taxon)
  joins(:taxons).where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) })
end

.in_taxons(*taxons) ⇒ Object

This scope selects products in all taxons AND all its descendants If you need products only within one taxon use

Spree::Product.taxons_id_eq([x,y])


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

def self.in_taxons(*taxons)
  taxons = get_taxons(taxons)
  taxons.first ? prepare_taxon_conditions(taxons) : scoped
end

.like_any(fields, values) ⇒ Object



182
183
184
185
# File 'app/models/spree/product.rb', line 182

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

.master_price_gte(price) ⇒ Object



55
56
57
# File 'app/models/spree/product/scopes.rb', line 55

def self.master_price_gte(price)
  joins(:master).where("#{variant_table_name}.price >= ?", price)
end

.master_price_lte(price) ⇒ Object



51
52
53
# File 'app/models/spree/product/scopes.rb', line 51

def self.master_price_lte(price)
  joins(:master).where("#{variant_table_name}.price <= ?", price)
end

.not_deletedObject



188
189
190
# File 'app/models/spree/product/scopes.rb', line 188

def self.not_deleted
  where(:deleted_at => nil)
end

.on_handObject



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

def self.on_hand
  where("spree_products.id in (select product_id from spree_variants group by product_id having sum(count_on_hand) > 0)")
end

.price_between(low, high) ⇒ Object



47
48
49
# File 'app/models/spree/product/scopes.rb', line 47

def self.price_between(low, high)
  joins(:master).where(Variant.table_name => { :price => low..high })
end

.simple_scopesObject



3
4
5
6
7
8
9
10
11
12
13
14
15
# File 'app/models/spree/product/scopes.rb', line 3

def self.simple_scopes
  [
    :ascend_by_updated_at,
    :descend_by_updated_at,
    :ascend_by_name,
    :descend_by_name,
    # Need to have master price scopes here
    # This makes them appear in admin/product_groups/edit
    :ascend_by_master_price,
    :descend_by_master_price,
    :descend_by_popularity
  ]
end

.taxons_name_eq(name) ⇒ Object



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

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

.with(value) ⇒ Object

Finds all products which have either: 1) have an option value with the name matching the one given 2) have a product property with a value matching the one given



135
136
137
138
139
# File 'app/models/spree/product/scopes.rb', line 135

def self.with(value)
  includes(:variants_including_master => :option_values).
  includes(:product_properties).
  where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value)
end

.with_ids(*ids) ⇒ Object

Finds all products that have the ids matching the given collection of ids. Alternatively, you could use find(collection_of_ids), but that would raise an exception if one product couldn’t be found



158
159
160
# File 'app/models/spree/product/scopes.rb', line 158

def self.with_ids(*ids)
  where(:id => ids)
end

.with_option(option) ⇒ Object

a scope that finds all products having an option_type specified by name, object or id



108
109
110
111
112
113
114
115
116
117
# File 'app/models/spree/product/scopes.rb', line 108

def self.with_option(option)
  option_types = OptionType.table_name
  conditions = case option
  when String     then { "#{option_types}.name" => option }
  when OptionType then { "#{option_types}.id" => option.id }
  else                 { "#{option_types}.id" => option.to_i }
  end

  joins(:option_types).where(conditions)
end

.with_option_value(option, value) ⇒ Object

a scope that finds all products having an option value specified by name, object or id



120
121
122
123
124
125
126
127
128
129
130
# File 'app/models/spree/product/scopes.rb', line 120

def self.with_option_value(option, value)
  option_values = OptionValue.table_name
  option_type_id = case option
    when String then OptionType.find_by_name(option) || option.to_i
    when OptionType then option.id
    else option.to_i
  end

  conditions = "#{option_values}.name = ? AND #{option_values}.option_type_id = ?", value, option_type_id
  joins(:variants_including_master => :option_values).where(conditions)
end

.with_property(property) ⇒ Object

a scope that finds all products having property specified by name, object or id



82
83
84
85
86
87
88
89
90
91
# File 'app/models/spree/product/scopes.rb', line 82

def self.with_property(property)
  properties = Property.table_name
  conditions = case property
  when String   then { "#{properties}.name" => property }
  when Property then { "#{properties}.id" => property.id }
  else               { "#{properties}.id" => property.to_i }
  end

  joins(:properties).where(conditions)
end

.with_property_value(property, value) ⇒ Object

a simple test for product with a certain property-value pairing note that it can test for properties with NULL values, but not for absent values



95
96
97
98
99
100
101
102
103
104
105
# File 'app/models/spree/product/scopes.rb', line 95

def self.with_property_value(property, value)
  properties = Spree::Property.table_name
  conditions = case property
  when String   then ["#{properties}.name = ?", property]
  when Property then ["#{properties}.id = ?", property.id]
  else               ["#{properties}.id = ?", property.to_i]
  end
  conditions = ["#{ProductProperty.table_name}.value = ? AND #{conditions[0]}", value, conditions[1]]

  joins(:properties).where(conditions)
end

Instance Method Details

#add_properties_and_option_types_from_prototypeObject



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

def add_properties_and_option_types_from_prototype
  if prototype_id && prototype = Spree::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” -> […]



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

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)


163
164
165
# File 'app/models/spree/product.rb', line 163

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



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'app/models/spree/product.rb', line 130

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 }

  master = Spree::Variant.find_by_product_id_and_is_master(self.id, true)
  variant = master.dup
  variant.sku = 'COPY OF ' + master.sku
  variant.deleted_at = nil
  variant.images = 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



174
175
176
177
178
179
180
# File 'app/models/spree/product.rb', line 174

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

#empty_option_values?Boolean

Returns:

  • (Boolean)


187
188
189
190
191
# File 'app/models/spree/product.rb', line 187

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

#ensure_masterObject



75
76
77
78
# File 'app/models/spree/product.rb', line 75

def ensure_master
  return unless self.new_record?
  self.master ||= Variant.new
end

#has_stock?Boolean

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

Returns:

  • (Boolean)


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

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)


85
86
87
# File 'app/models/spree/product.rb', line 85

def has_variants?
  variants.any?
end

#on_handObject

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



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

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



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

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



105
106
107
108
109
110
111
# File 'app/models/spree/product.rb', line 105

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



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

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

#variant_imagesObject



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

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