1 class Product < StoreItem
2 # Conditions that the product is in stock, or available
3 # and just out of stock.
4 CONDITIONS_AVAILABLE = %Q/
5 CURRENT_DATE() >= DATE(items.date_available)
6 AND items.is_discontinued = 0
7 OR (items.is_discontinued = 1 AND (items.quantity > 0 OR items.variation_quantity > 0))
10 has_many :product_images
11 has_many :variations, :dependent => :destroy, :order => 'name ASC'
13 :through => :product_images, :order => "-product_images.rank DESC",
14 :dependent => :destroy
16 # Join with related items...
17 has_and_belongs_to_many :related_products,
18 :class_name => 'Product',
19 :join_table => 'related_products',
20 :association_foreign_key => 'related_id',
21 :foreign_key => 'product_id',
22 :after_add => :add_return_relation,
23 :after_remove => :remove_return_relation
25 def add_return_relation(relative)
26 relative.related_products << self unless relative.related_products.include?(self)
28 def remove_return_relation(relative)
29 sql = "DELETE FROM related_products "
30 sql << "WHERE product_id = #{relative.id} AND related_id = #{self.id}"
35 has_and_belongs_to_many :tags
37 #############################################################################
39 #############################################################################
41 after_create :add_cached_related_products
43 # Related products (associations) freak out if this
44 # object isn't saved before adding relatives.
46 # If there's a cached @related_list we try to add em.
48 def add_cached_related_products
49 self.related_product_ids=@cached_related_list if @cached_related_list
52 #############################################################################
54 #############################################################################
56 # Admin search for products
57 # Uses product name, code, or description
58 def self.search(search_term, count=false, limit_sql=nil)
59 if (count == true) then
60 sql = "SELECT COUNT(*) "
62 sql = "SELECT DISTINCT * "
66 sql << " name LIKE ? "
67 sql << " OR items.description LIKE ? "
68 sql << " OR items.code LIKE ? "
69 sql << ") AND items.type = 'Product' "
70 sql << "ORDER BY date_available DESC "
71 sql << "LIMIT #{limit_sql}" if limit_sql
72 arg_arr = [sql, "%#{search_term}%", "%#{search_term}%", "%#{search_term}%"]
73 if (count == true) then
80 # Finds products by list of tag ids passed in
82 # We could JOIN multiple times, but selecting IN grabs us the products
83 # and using GROUP BY & COUNT with the number of tag id's given
84 # is a faster approach according to freenode #mysql
85 def self.find_by_tags(tag_ids, find_available=false, order_by="items.date_available DESC")
88 sql << "JOIN products_tags on items.id = products_tags.product_id "
89 sql << "WHERE products_tags.tag_id IN (#{tag_ids.join(",")}) "
90 sql << "AND #{CONDITIONS_AVAILABLE}" if find_available==true
91 sql << "GROUP BY items.id HAVING COUNT(*)=#{tag_ids.length} "
92 sql << "ORDER BY #{order_by};"
96 #############################################################################
98 #############################################################################
100 # Defined to save tags from product edit view
104 tags << Tag[id] if !id.empty?
108 # Calculated based on variations
111 variations = self.variations.all # :order => "price ASC")
112 if variations.size == 0
115 low_price = variations[0].price
116 high_price = variations[variations.size-1].price
117 if low_price == high_price
120 return [low_price, high_price]
126 if self.variations.count == 0
127 return self.attributes['quantity']
129 return self.variation_quantity
133 # Is the item active on the site? Is it listed in the store?
136 !self.is_discontinued? || self.quantity > 0
139 # Is this product new?
144 self.date_available >= 2.weeks.ago
148 # Is this product on sale?
153 !self.tags.find_by_name('On Sale').nil?
157 # Adds related products from list.
158 # Used in UI for auto-completion
160 # See callback for why we return if this is a new record.
162 def related_product_ids=(list)
164 @cached_related_list = list
168 # If self is a new record shit fails
169 self.related_products.clear
171 p = Product.find_by_code(name.split(':')[0])
172 next if !p || p == self
173 self.related_products << p