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
Returns true if there are no option values.
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
Returns true if there are any variants.
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
Returns all advertised and not-rejected promotions.
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
Returns the value of the given property. nil if property is undefined on this product.
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
Returns tax category for this product, or the default tax category.
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>
Returns all variants with at least one option value.
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 |