Class: Spree::Product

Inherits:
ActiveRecord::Base
  • Object
show all
Extended by:
FriendlyId
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.



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

def option_values_hash
  @option_values_hash
end

#prototype_idObject

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



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

def prototype_id
  @prototype_id
end

Class Method Details

.active(currency = nil) ⇒ Object



195
196
197
# File 'app/models/spree/product/scopes.rb', line 195

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
# 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_(.*)/)
    self.scope(name.to_s, -> { order("#{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



190
191
192
# File 'app/models/spree/product/scopes.rb', line 190

def self.available(available_on = nil, currency = nil)
  joins(:master => :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.now)
end

.distinct_by_product_ids(sort_order = nil) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'app/models/spree/product/scopes.rb', line 204

def self.distinct_by_product_ids(sort_order=nil)
  if (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL')
    sort_column = sort_order.split(" ").first
    # Don't allow sort_column, a variable coming from params,
    # to be anything but a column in the database
    if column_names.include?(sort_column)
      distinct_fields = ["id", sort_column].compact.join(",")
      select("DISTINCT ON(#{distinct_fields}) spree_products.*")
    else
      all
    end
  else
    select("DISTINCT spree_products.*")
  end
end

.like_any(fields, values) ⇒ Object



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

def self.like_any(fields, values)
  where fields.map { |field|
    values.map { |value|
      arel_table[field].matches("%#{value}%")
    }.inject(:or)
  }.inject(:or)
end

.property_conditions(property) ⇒ Object



30
31
32
33
34
35
36
37
# File 'app/models/spree/product/scopes.rb', line 30

def self.property_conditions(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
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

determine if product is available. deleted products and products with nil or future available_on date are not available

Returns:

  • (Boolean)


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

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



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

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)


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

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



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

def duplicate
  duplicator = ProductDuplicator.new(self)
  duplicator.duplicate
end

#empty_option_values?Boolean

Returns:

  • (Boolean)


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

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



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

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) unless product_option_types.pluck(:option_type_id).include?(id)
  end
end

#has_variants?Boolean

the master variant is not a member of the variants array

Returns:

  • (Boolean)


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

def has_variants?
  variants.any?
end

#masterObject

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.



214
215
216
# File 'app/models/spree/product.rb', line 214

def master
  super || variants_including_master.with_deleted.where(is_master: true).first
end

#possible_promotionsObject



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

def possible_promotions
  promotion_ids = promotion_rules.map(&:promotion_id).uniq
  Spree::Promotion.advertised.where(id: promotion_ids).reject(&:expired?)
end

#property(property_name) ⇒ Object



179
180
181
182
# File 'app/models/spree/product.rb', line 179

def property(property_name)
  return nil unless prop = properties.find_by(name: property_name)
  product_properties.find_by(property: prop).try(:value)
end

#set_property(property_name, property_value) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'app/models/spree/product.rb', line 184

def set_property(property_name, property_value)
  ActiveRecord::Base.transaction do
    # Works around spree_i18n #301
    property = if Property.exists?(name: property_name)
      Property.where(name: property_name).first
    else
      Property.create(name: property_name, presentation: property_name)
    end
    product_property = ProductProperty.where(product: self, property: property).first_or_initialize
    product_property.value = property_value
    product_property.save!
  end
end

#tax_categoryObject



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

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



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

def to_param
  slug
end

#total_on_handObject



203
204
205
206
207
208
209
# File 'app/models/spree/product.rb', line 203

def total_on_hand
  if self.variants_including_master.any? { |v| !v.should_track_inventory? }
    Float::INFINITY
  else
    self.stock_items.to_a.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



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

def variants_and_option_values(current_currency = nil)
  variants.includes(:option_values).active(current_currency).select do |variant|
    variant.option_values.any?
  end
end