Created
October 21, 2013 19:55
-
-
Save jakecraige/7089865 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Product < ActiveRecord::Base | |
belongs_to :category | |
has_many :trackings | |
has_many :site_product_prices, -> { order(:id => :asc) } | |
has_many :user_price_alerts | |
after_create :extract_color_from_name, :update_brand_and_model_number_if_blank, :remove_false_values, :run_delayed_jobs | |
validates_presence_of :name | |
include Tire::Model::Search | |
index_name("worthit#{Rails.env}products") | |
mapping do | |
indexes :name, analyzer: 'snowball' | |
indexes :manufacturer, analyzer: 'snowball' | |
indexes :brand, analyzer: 'snowball' | |
end | |
after_commit { tire.delay.update_index } | |
after_destroy { tire.delay.update_index } | |
INVALID_TITLE_WORDS = %w( | |
size sz men mens men's man mans man's women womens women's woman womans | |
woman's unisex unisex's red black orange gray white yellow green blue | |
) | |
def to_s | |
name | |
end | |
def self.match_to_existing_product_or_initalize_new(source, filtered_product_attributes, api_product_attributes) | |
# Create the product attributes that combines what was returned from | |
# the api and the tracking | |
product_attributes = {} | |
# Loop the product columns and to populate the product_attributes hash | |
Product.column_names.each do |column_name| | |
if filtered_product_attributes[column_name].present? | |
# Don't call .to_sym on the column name because the | |
# filtered_product_attributes needs this format: filtered_product_attributes['attribute'] | |
product_attributes[column_name.to_sym] = filtered_product_attributes[column_name] | |
end | |
# Overwrite the attribute if the api has info | |
if api_product_attributes.present? | |
if api_product_attributes[column_name.to_sym].present? | |
product_attributes[column_name.to_sym] = api_product_attributes[column_name.to_sym] | |
end | |
end # if api_product_attributes.present? | |
end | |
product = Product.where(product_attributes).first_or_initialize | |
return product# MATCHING TURNED OFF | |
# Search for existing product | |
# First, search only by UPC | |
not_found_product = true | |
if product_attributes[:upc].present? && product = Product.find_by(upc: product_attributes[:upc]) | |
not_found_product = false | |
::Rails.logger.info '-------------- Product Match 1 --------' | |
end | |
# Search by Model number and Brand / Manufacturer & color and size | |
# Have to add color and size because some items have generic model numbers | |
# Make sure that these fields have info | |
search_hash = {} | |
search_hash[:color] = product_attributes[:color] if product_attributes[:color].present? | |
search_hash[:size] = product_attributes[:size] if product_attributes[:size].present? | |
search_hash[:model_number] = product_attributes[:model_number] if product_attributes[:model_number].present? | |
search_hash[:brand] = product_attributes[:manufacturer] if product_attributes[:manufacturer].present? | |
search_hash[:brand] = product_attributes[:brand] if product_attributes[:brand].present? # Overwrite manufacturer | |
# if not_found_product && product = Product.where(model_number: product_attributes[:model_number], color: product_attributes[:color], size: product_attributes[:size]).where("brand IN (:brand, :manufacturer) OR manufacturer IN (:brand, :manufacturer)", brand: product_attributes[:brand], manufacturer: product_attributes[:manufacturer]).first | |
# not_found_product = false | |
# ::Rails.logger.info '-------------- Product Match 2 --------' | |
# end | |
# | |
if not_found_product && search_hash.any? | |
if product = Product.where(search_hash).first | |
not_found_product = false | |
::Rails.logger.info '-------------- Product Match 2 --------' | |
end | |
end | |
# Search by name and model number | |
if not_found_product && product_attributes[:model_number].present? && product = Product.where(model_number: product_attributes[:model_number], name: product_attributes[:name]).first | |
not_found_product = false | |
::Rails.logger.info '-------------- Product Match 3 --------' | |
end | |
# Search by name, size and color and brand | |
search_hash = {} | |
search_hash[:color] = product_attributes[:color] if product_attributes[:color].present? | |
search_hash[:size] = product_attributes[:size] if product_attributes[:size].present? | |
search_hash[:brand] = product_attributes[:manufacturer] if product_attributes[:manufacturer].present? | |
search_hash[:brand] = product_attributes[:brand] if product_attributes[:brand].present? # Overwrite manufacturer | |
# if not_found_product && product = Product.find_by(name: product_attributes[:name], size: product_attributes[:size], color: product_attributes[:color], brand: product_attributes[:brand]) | |
# not_found_product = false | |
# ::Rails.logger.info '-------------- Product Match 4 --------' | |
# end | |
# | |
if not_found_product && search_hash.any? | |
if product = Product.where(name: product_attributes[:name]).where(search_hash).first | |
not_found_product = false | |
::Rails.logger.info '-------------- Product Match 4 --------' | |
end | |
end | |
# If no product is found yet, | |
# intialize a product based on the tracking info | |
if not_found_product | |
product = Product.where(product_attributes).first_or_initialize | |
::Rails.logger.info '-------------- Product Match 5 --------' | |
else | |
# Update the product with the product_params | |
product.update_attributes_with_found_attributes(product_attributes) | |
::Rails.logger.info '-------------- Product Match 6 --------' | |
::Rails.logger.info ' product_attributes ' | |
::Rails.logger.info product_attributes | |
end | |
# unless product = Product.find_by(upc: product_attributes[:upc]) | |
# | |
# # Search by Model number and Brand / Manufacturer | |
# unless product = Product.where(model_number: product_attributes[:model_number]).where("brand IN (:brand, :manufacturer) OR manufacturer IN (:brand, :manufacturer)", brand: product_attributes[:brand], manufacturer: product_attributes[:manufacturer]).first | |
# | |
# # If no product is found yet, | |
# # intialize a product based on the tracking info | |
# product = Product.where(product_attributes).first_or_initialize | |
# | |
# end # model number | |
# | |
# end # unless product = Product.find_by(upc: upc) | |
product | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
def update_attributes_with_found_attributes(product_attributes) | |
# Loop the product columns and update teh columns that are blank | |
Product.column_names.each do |column_name| | |
self[column_name.to_sym] = product_attributes[column_name.to_sym] if self[column_name.to_sym].blank? | |
end | |
self.save | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
def check_current_price | |
# Lookup product at all places we know | |
# Always look for the asin | |
self.look_for_asin | |
# Amazon ---- | |
if asin | |
::Rails.logger.info "------------ Starting to look up products on Amazon via ASIN lookup -----------" | |
asin_product_lookup = Amazon.asin_product_lookup(asin) | |
if asin_product_lookup | |
api_product_attributes = Amazon.product_attributes(asin_product_lookup) | |
SiteProductPrice.create_new(self, Site.amazon, api_product_attributes, 'amazon') if api_product_attributes | |
end | |
end | |
if upc.present? && upc != 'undefined' && upc != 'false' && upc != '' | |
# Shopzilla ----- | |
product_lookup = Shopzilla.product_lookup(upc) | |
if product_lookup | |
::Rails.logger.info "------------ Starting to look up products on Shopzilla -----------" | |
Shopzilla.find_product_prices(self, product_lookup) | |
end | |
# Commission Junction ----- | |
product_lookup = Commissionjunction.product_lookup(upc) | |
if product_lookup | |
::Rails.logger.info "------------ Starting to look up products on Commission Junction -----------" | |
Commissionjunction.find_product_prices(self, product_lookup) | |
end | |
# Become | |
product_lookup = Become.product_lookup(upc) | |
if product_lookup | |
::Rails.logger.info "------------ Starting to look up products on Become -----------" | |
Become.find_product_prices(self, product_lookup) | |
end | |
# Search UPC | |
product_lookup = Searchupc.product_lookup(upc) | |
if product_lookup | |
::Rails.logger.info "------------ Starting to look up products on Search UPC -----------" | |
Searchupc.find_product_prices(self, product_lookup) | |
end | |
# # PriceSpin | |
# product_lookup = Pricespin.product_lookup(upc) | |
# if product_lookup | |
# ::Rails.logger.info "------------ Starting to look up products on Pricespin -----------" | |
# Pricespin.delay.find_product_prices(self, product_lookup) | |
# end | |
end # if upc && upc != 'undefined' | |
# Start checking to see if this price matches a site product price | |
# TODO: make sure this runs after the above jobs are done | |
trackings.each do |tracking| | |
tracking.delay.match_price_to_tracking | |
end | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
def self.create_product_without_api(product_params, site, user = nil) | |
product_attributes = {:name => product_params[:title]} | |
%w(manufacturer brand size color material gender width model_number upc ean asin manufacturer_part_number).each do |field| | |
product_attributes[field.to_sym] = product_params[field.to_sym] if product_params[field.to_sym].present? | |
end | |
product = Product.find_or_create_by(product_attributes) | |
# product.search_for_product_upc | |
product | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
def search_for_prices | |
# Start by searching on amazon | |
# identifiers.each do |identifier| | |
# Amazon.offers_lookup(identifier.unique_id) | |
# end | |
# identifiers.each do |identifier| | |
# Amazon.search(identifier.unique_id) | |
# end | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
def search_for_product_upc | |
# Search Become, Amazon, Semantics, etc for product UPC | |
# ---- | |
# results = Amazon.keyword_search(self.search_name, 1) | |
# | |
# matched = self.find_product_match(results) | |
# | |
# unless matched | |
# page = 2 | |
# total_pages = 2 | |
# while page <= total_pages | |
# results = Amazon.keyword_search(self.search_name, page) | |
# | |
# matched = self.find_product_match(results) | |
# | |
# page = matched ? 1000 : page + 1 | |
# end | |
# end | |
# | |
# return true if matched | |
# ---- | |
# If not matched, try semantics 3 | |
results = Semantics.keyword_search(self.search_name, 1) | |
matched = self.find_product_match(results) | |
# return true if matched | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
def find_product_match(results) | |
this_products_brand = self.brand | |
this_products_manufacturer = self.manufacturer | |
this_products_size = self.size | |
this_products_color = self.color | |
this_products_compare_title = Product.compare_title(self.name, this_products_brand, this_products_manufacturer, this_products_size, this_products_color) | |
# Compare Brand, Manufacturer, Size, Color, and Title | |
results.each do |result| | |
match = false | |
result_title = result[:title] | |
compare_title = Product.compare_title(result_title, this_products_brand, this_products_manufacturer, this_products_size, this_products_color) | |
::Rails.logger.info "------------ Compare title -----------" | |
::Rails.logger.info this_products_compare_title | |
::Rails.logger.info compare_title | |
::Rails.logger.info " " | |
if this_products_compare_title == compare_title | |
result_size = result[:size] | |
result_color = result[:color] | |
# compare the rest of the attributes | |
if result_brand == self.brand && result_manufacturer == self.manufacturer | |
match = true | |
if self.size.present? && result_size != self.size | |
# size: 11.5 D(M) US | |
match = false | |
end | |
if match && self.color.present? && result_color != self.color | |
match = false | |
end | |
end | |
# matches << result if match | |
if match | |
self.update_column(:upc, result[:upc]) | |
self.update_column(:ean, result[:ean]) | |
return true | |
end | |
end # if this_products_compare_title == compare_title | |
end # results | |
false | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
# Returns a title with invalid attributes removed for comparison | |
def self.compare_title(title, brand, manufacturer, size, color) | |
regex = /[^0-9A-Za-z]/ | |
remove_invalid_title_words!(title) | |
title = title.downcase.gsub(regex, '') | |
brand = brand.to_s.downcase.gsub(regex, '') | |
manufacturer = manufacturer.to_s.downcase.gsub(regex, '') | |
size = size.to_s.downcase.gsub(regex, '') | |
color = color.to_s.downcase.gsub(regex, '') | |
title.gsub(brand, '').gsub(manufacturer,'').gsub(size,'').gsub(color,'') | |
end | |
# ------------------------------------------------------------------------------------------------------------ | |
def set_search_name | |
this_search_name = name.gsub(/[^0-9A-Za-z 9 \/\()-]/, '') | |
self.update_column(:search_name, this_search_name) | |
end | |
def self.clean_price(price) | |
return price if price.blank? | |
price.to_s.strip.gsub('$','').gsub(',','').to_f | |
end | |
def self.clear_all_products | |
# [email protected] | |
# Product.destroy_all | |
# Tracking.destroy_all | |
# SiteProductPrice.destroy_all | |
# UserPriceAlert.destroy_all | |
# Delayed::Job.destroy_all | |
# Tag.destroy_all | |
end | |
def best_site_product_price | |
best_price_cache.best_site_product_price | |
end | |
def best_price | |
best_price_cache.best_price | |
end | |
# TODO: refactor to make better | |
def image_path | |
if image_url.present? | |
# Ensure http:// is on the image | |
full_image_url = self.image_url | |
full_image_url = 'http://' + full_image_url if full_image_url.exclude?('http://') | |
return full_image_url | |
end | |
self.site_product_prices.each do |site_product| | |
return site_product.image_url if site_product.has_image? | |
end | |
nil # return nil and let calling function handle default image | |
end | |
def self.search(search_string, user) | |
# map out this users products so we are able to exclude products that don't | |
# match from the results | |
tracked_product_ids = user.trackings.select(:product_id).map(&:product_id) | |
results = tire.search(load: true) do | |
query { string search_string.match(/\w+/).to_s } | |
end | |
results.select { |product| tracked_product_ids.include? product.id } | |
end | |
def extract_color_from_name | |
return # MATCHING TURNED OFF | |
return if color? | |
# TODO: refactor - this is ugly | |
downcase_name = name.downcase | |
colors = ["grey", "espresso", "alice blue", "antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanched almond","blue","blue violet","brown","burlywood","cadet blue","chartreuse","chocolate","coral","cornflower blue","cornsilk","crimson","cyan","dark blue","dark cyan","dark goldenrod","dark gray","dark green","dark khaki","dark magenta","dark olive green","dark orange","dark orchid","dark red","dark salmon","dark sea green","dark slate blue","dark slate gray","dark turquoise","dark violet","deep pink","deep sky blue","dim gray","dodger blue","firebrick","floral white","forest green","fuchsia","gainsboro","ghost white","gold","goldenrod","gray","green","green yellow","honeydew","hot pink","indian red","indigo","ivory","khaki","lavender","lavender blush","lawn green","lemon chiffon","light blue","light coral","light cyan","light goldenrod yellow","light gray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium aquamarine","medium blue","medium orchid","medium purple","medium seag reen","medium slate blue","medium sprin ggreen","medium turquoise","medium violetred","midnight blue","mint cream","misty rose","moccasin","navajo white","navy","oldlace","olive","olive drab","orange","orange red","orchid","pale goldenrod","pale green","pale turquoise","pale violet red","papaya whip","peach puff","peru","pink","plum","powderblue","purple","red","rosy brown","royal blue","saddle brown","salmon","sandy brown","sea green","seashell","sienna","silver","sky blue","slate blue","slate gray","snow","spring green","steel blue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","white smoke","yellow","yellow green"] | |
colors.each do |this_color| | |
if downcase_name.include?(' ' + this_color + ' ') | |
self.update_column(:color, this_color.titleize) | |
return | |
end | |
end | |
end | |
def update_brand_and_model_number_if_blank | |
# Manufacturer are usually the same thing | |
# Populate brand if manufacturer is set | |
self.update_column(:brand, manufacturer) if brand.blank? && manufacturer? | |
self.update_column(:model_number, manufacturer_part_number) if model_number.blank? && manufacturer_part_number? | |
end | |
def remove_false_values | |
# TODO: find why "false" is being entered and remove it | |
self.update_column(:model_number, nil) if model_number == 'false' | |
end | |
def run_delayed_jobs | |
# Run these as delayed jobs so we cut down on live api calls | |
self.delay.get_upc_from_amazon_by_model_number | |
self.delay.look_for_asin | |
end | |
def get_upc_from_amazon_by_model_number | |
return # MATCHING TURNED OFF | |
return if upc? | |
if Amazon.model_number_lookup(self) | |
self.look_for_asin | |
end | |
end | |
def look_for_asin | |
return if asin? || !upc? | |
if asin = Amazon.asin_lookup_by_upc(upc) | |
self.update_column(:asin, asin) | |
# Search for the product on Amazon | |
asin_product_lookup = Amazon.asin_product_lookup(asin) | |
if asin_product_lookup | |
api_product_attributes = Amazon.product_attributes(asin_product_lookup) | |
SiteProductPrice.create_new(self, Site.amazon, api_product_attributes, 'amazon') if api_product_attributes | |
end | |
end | |
end | |
def top_retailers | |
retailers = self.site_product_prices.last_24_hours.trusted.by_price | |
retailers = retailers.group_by { |retailer| retailer.site } | |
.map(&:last) | |
.each{ |sites_array| sites_array.sort_by! { |site| site.weight }.reverse! } | |
.map(&:first).reverse | |
.sort_by { |x| x.price } | |
if retailers.count > 1 | |
# remove best price from results | |
retailers.shift | |
elsif retailers.count == 1 | |
# If the first retailor is the same as the best price, | |
# don't use it | |
if retailers.first.site == self.best_site_product_price.site | |
retailers.shift | |
end | |
end | |
# If the top retailers is blank | |
# and the origiinal site product price is not the same as the best price | |
# add the originail site product price | |
if retailers.blank? | |
first_site_product_price = self.site_product_prices.first | |
if first_site_product_price.site != self.best_site_product_price.site | |
retailers = [first_site_product_price] | |
end | |
end | |
retailers[0..5] # return max of 6 results | |
end | |
def self.remove_invalid_title_words!(title) | |
INVALID_TITLE_WORDS.each do |word| | |
title.gsub!(/\b(#{word}|'s)\b/i, '') | |
end | |
end | |
private | |
def best_price_cache | |
@best_price_cache ||= ProductBestPriceCache.new(self) | |
end | |
def group_by_site! retailers | |
retailers = retailers.group_by { |retailer| retailer.site } | |
end | |
def sort_by_weight! retailers | |
retailers = retailers.map(&:last).map(&:first) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment