Class: Spree::Product
- Extended by:
- FriendlyId
- Defined in:
- app/models/spree/product.rb,
app/models/spree/product/scopes.rb
Overview
this model uses paranoia. #destroy will only soft-destroy records and the default scope hides soft-destroyed records using WHERE deleted_at IS NULL.
Products represent an entity for sale in a store. Products can have variations, called variants. Product properties include description, permalink, availability, shipping category, etc. that do not change by variant.
Instance Attribute Summary collapse
-
#option_values_hash ⇒ Object
Returns the value of attribute option_values_hash.
-
#prototype_id ⇒ Object
Overrides the prototype_id setter in order to ensure it is cast to an integer.
Class Method Summary collapse
- .active(currency = nil) ⇒ Object
- .add_search_scope(name, &block) ⇒ Object
- .add_simple_scopes(scopes) ⇒ Object
-
.available(available_on = nil, currency = nil) ⇒ Object
Can’t use add_search_scope for this as it needs a default argument.
- .distinct_by_product_ids(sort_order = nil) ⇒ Object
-
.like_any(fields, values) ⇒ ActiveRecord::Relation
Poor man’s full text search.
- .property_conditions(property) ⇒ Object
- .simple_scopes ⇒ Object
Instance Method Summary collapse
-
#available? ⇒ Boolean
Determines if product is available.
-
#categorise_variants_from_option(opt_type) ⇒ Hash
Groups variants by the specified option type.
-
#deleted? ⇒ Boolean
Use for checking whether this product has been deleted.
-
#display_image ⇒ Spree::Image
Image that can be used for the product.
-
#duplicate ⇒ Spree::Product
Creates a new product with the same attributes, variants, etc.
-
#empty_option_values? ⇒ Boolean
True if there are no option values.
-
#ensure_option_types_exist_for_values_hash ⇒ Array
Ensures option_types and product_option_types exist for keys in option_values_hash.
-
#has_variants? ⇒ Boolean
True if there are any variants.
-
#master ⇒ Spree::Variant
Override so if the master variant is deleted, we can still find it.
-
#possible_promotions ⇒ Array
All advertised and not-rejected promotions.
-
#property(property_name) ⇒ String
The value of the given property.
-
#set_property(property_name, property_value) ⇒ Object
Assigns the given value to the given property.
-
#tax_category ⇒ Spree::TaxCategory
Tax category for this product, or the default tax category.
-
#total_on_hand ⇒ Fixnum, Infinity
The number of on-hand stock items; Infinity if any variant does not track inventory.
-
#variants_and_option_values(current_currency = nil) ⇒ Array<Spree::Variant>
All variants with at least one option value.
Methods inherited from Base
Methods included from Spree::Preferences::Preferable
#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.
83 84 85 |
# File 'app/models/spree/product.rb', line 83 def option_values_hash @option_values_hash end |
#prototype_id ⇒ Object
Overrides the prototype_id setter in order to ensure it is cast to an integer.
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 219 220 221 222 223 224 225 226 227 |
# File 'app/models/spree/product/scopes.rb', line 204 def self.distinct_by_product_ids(sort_order = nil) sort_column = sort_order.split(" ").first # Postgres will complain when using ordering by expressions not present in # SELECT DISTINCT. e.g. # # PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY # expressions must appear in select list. e.g. # # SELECT DISTINCT "spree_products".* FROM "spree_products" LEFT OUTER JOIN # "spree_variants" ON "spree_variants"."product_id" = "spree_products"."id" AND "spree_variants"."is_master" = 't' # AND "spree_variants"."deleted_at" IS NULL LEFT OUTER JOIN "spree_prices" ON # "spree_prices"."variant_id" = "spree_variants"."id" AND "spree_prices"."currency" = 'USD' # AND "spree_prices"."deleted_at" IS NULL WHERE "spree_products"."deleted_at" IS NULL AND ('t'='t') # ORDER BY "spree_prices"."amount" ASC LIMIT 10 OFFSET 0 # # Don't allow sort_column, a variable coming from params, # to be anything but a column in the database if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && !column_names.include?(sort_column) all else distinct end end |
.like_any(fields, values) ⇒ ActiveRecord::Relation
Poor man’s full text search.
Filters products to those which have any of the strings in values in any of the fields in fields.
170 171 172 173 174 175 176 |
# File 'app/models/spree/product.rb', line 170 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_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
Determines if product is available. A product is available if it has not been deleted and the available_on date is in the past.
149 150 151 |
# File 'app/models/spree/product.rb', line 149 def available? !(available_on.nil? || available_on.future?) && !deleted? end |
#categorise_variants_from_option(opt_type) ⇒ Hash
Groups variants by the specified option type.
157 158 159 160 |
# File 'app/models/spree/product.rb', line 157 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 for checking whether this product has been deleted. Provided for overriding the logic for determining if a product is deleted.
141 142 143 |
# File 'app/models/spree/product.rb', line 141 def deleted? !!deleted_at end |
#display_image ⇒ Spree::Image
Image that can be used for the product.
Will first search for images on the product, then those belonging to the variants. If all else fails, will return a new image object.
248 249 250 |
# File 'app/models/spree/product.rb', line 248 def display_image images.first || variant_images.first || Spree::Image.new end |
#duplicate ⇒ Spree::Product
Creates a new product with the same attributes, variants, etc.
132 133 134 135 |
# File 'app/models/spree/product.rb', line 132 def duplicate duplicator = ProductDuplicator.new(self) duplicator.duplicate end |
#empty_option_values? ⇒ Boolean
187 188 189 190 191 |
# File 'app/models/spree/product.rb', line 187 def empty_option_values? .empty? || .any? do |opt| opt.option_type.option_values.empty? end end |
#ensure_option_types_exist_for_values_hash ⇒ Array
Ensures option_types and product_option_types exist for keys in option_values_hash.
121 122 123 124 125 126 127 |
# File 'app/models/spree/product.rb', line 121 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
93 94 95 |
# File 'app/models/spree/product.rb', line 93 def has_variants? variants.any? end |
#master ⇒ Spree::Variant
Override so if the master variant is deleted, we can still find it.
239 240 241 |
# File 'app/models/spree/product.rb', line 239 def master super || variants_including_master.with_deleted.where(is_master: true).first end |
#possible_promotions ⇒ Array
219 220 221 222 |
# File 'app/models/spree/product.rb', line 219 def possible_promotions promotion_ids = promotion_rules.map(&:promotion_id).uniq Spree::Promotion.advertised.where(id: promotion_ids).reject(&:expired?) end |
#property(property_name) ⇒ String
195 196 197 198 |
# File 'app/models/spree/product.rb', line 195 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
Assigns the given value to the given property.
204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'app/models/spree/product.rb', line 204 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_category ⇒ Spree::TaxCategory
98 99 100 101 102 103 104 |
# File 'app/models/spree/product.rb', line 98 def tax_category if self[:tax_category_id].nil? TaxCategory.where(is_default: true).first else TaxCategory.find(self[:tax_category_id]) end end |
#total_on_hand ⇒ Fixnum, Infinity
The number of on-hand stock items; Infinity if any variant does not track inventory.
228 229 230 231 232 233 234 |
# File 'app/models/spree/product.rb', line 228 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) ⇒ Array<Spree::Variant>
180 181 182 183 184 |
# File 'app/models/spree/product.rb', line 180 def variants_and_option_values(current_currency = nil) variants.includes(:option_values).active(current_currency).select do |variant| variant.option_values.any? end end |