roseweixel/lacquer-lover

View on GitHub
app/models/lacquer.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# == Schema Information
#
# Table name: lacquers
#
#  id               :integer          not null, primary key
#  name             :string(255)
#  created_at       :datetime
#  updated_at       :datetime
#  brand_id         :integer
#  user_added_by_id :integer
#  default_picture  :string(255)
#  item_url         :string(255)
#

class Lacquer < ActiveRecord::Base
  belongs_to :brand
  has_many :user_lacquers, dependent: :destroy
  has_many :colors, through: :user_lacquers
  has_many :finishes, through: :user_lacquers
  has_many :users, through: :user_lacquers
  has_many :swatches, dependent: :destroy
  has_many :favorites, dependent: :destroy
  has_many :reviews, -> { order(:updated_at => :desc)}, dependent: :destroy 
  has_many :lacquer_words, dependent: :destroy
  has_many :words, through: :lacquer_words

  validates :name, :brand, presence: true, :on => :create
  validates_length_of :name, :minimum => 1
  validates_uniqueness_of :name, scope: :brand

  accepts_nested_attributes_for :user_lacquers
  accepts_nested_attributes_for :swatches, :reject_if => proc { |attributes| attributes['image'].blank? }
  accepts_nested_attributes_for :reviews, :reject_if => proc { |attributes| attributes['comments'].blank? }

  before_validation :set_buy_url
  after_save :create_words

  def set_buy_url
    base_url = "http://www.amazon.com/s/ref=nb_sb_noss_2?url=search-alias%3Dbeauty&field-keywords="
    query_string = "#{self.brand.name.gsub(' ', '+')}+#{self.name.gsub(' ', '+')}"
    self.buy_url = base_url + query_string
  end

  def url_for_buy_it_link
    if item_url && Brand::BRANDS_WITH_ITEM_URL_AS_BUY_IT_URL.include?(brand.name) && actually_works?(item_url)
      return item_url
    else
      return buy_url
    end
  end

  def actually_works?(link)
    begin
      if link.start_with?("lacquers/")
        return false
      elsif link.class == Paperclip::Attachment || !link.start_with?("http")
        return true
      else
        uri = Addressable::URI.parse(link)

        request = Net::HTTP.new uri.host

        # rescue SocketError that occurs when offline
        response= request.request_head uri.path

        response.code.to_i == 200
      end
    rescue SocketError
      return false
    end
  end

  def users_who_are_friends_with(user)
    users.where(id: user.accepted_friends.pluck(:id))
  end

  def users_who_friend_can_borrow_from(friend)
    users_who_are_friends_with(friend).includes(:user_lacquers).where(user_lacquers: {lacquer_id: self.id, loanable: true, on_loan: false})
  end

  def picture
    default_picture == "" ? nil : default_picture
  end

  def average_rating
    ratings = reviews.pluck(:rating)
    if ratings.any?
      ratings.inject(:+)/ratings.count.to_f
    else
      nil
    end
  end

  def create_words
    words_in_name = (name.split(" ") + name_with_no_non_word_chars.split(" ")).uniq

    words_in_name.each do |word|
      new_word = Word.find_or_create_by(text: word.downcase)
      LacquerWord.create(lacquer_id: self.id, word_id: new_word.id)
    end
  end

  # Potentially use this kind of method when generating Words and LacquerWords (to count "car" and "pool" as words as well as or instead of "car-pool")
  def name_with_no_non_word_chars
    name.gsub(/\W/, " ")
  end

  # def self.fuzzy_find_by_name(search_term)
  #   where( "lower(name) REGEXP ?", '\b' + search_term + '\b' )
  # end

  def self.lacquers_matching_all_words(search_term)
    search_words = search_term.downcase.split(" ")
    search_word_ids = Word.where(text: search_words).pluck(:id)
    lacquer_ids = LacquerWord.where(word_id: search_word_ids).pluck(:lacquer_id)

    Lacquer.where(id: lacquer_ids.select{|id| lacquer_ids.count(id) == search_word_ids.count})
  end

  def self.lacquers_matching_most_words(search_term)
    search_words = search_term.downcase.split(" ")
    search_word_ids = Word.where(text: search_words).pluck(:id)
    lacquers_matching_most_words = []
    max = search_word_ids.count - 1
    while max > 0 && lacquers_matching_most_words.empty?
      lacquers_matching_most_words = Lacquer.where(id: lacquer_ids.select{|id| lacquer_ids.count(id) == max})
      max -= 1
    end

    lacquers_matching_most_words
  end

  def self.closest_lacquers(search_term)
    search_words = search_term.downcase.split(" ")
    closest_lacquers = []
    search_words.each do |search_word|
      closest_lacquers << Word.find_closest_lacquers(search_word).uniq
    end

    closest_lacquers.uniq.flatten
  end

  def self.fuzzy_find_by_name(search_term)
    search_words = search_term.downcase.split(" ")
    search_word_ids = Word.where(text: search_words).pluck(:id)
    lacquer_ids = LacquerWord.where(word_id: search_word_ids).pluck(:lacquer_id)

    lacquers_matching_all_words = Lacquer.where(id: lacquer_ids.select{|id| lacquer_ids.count(id) == search_word_ids.count})

    if lacquers_matching_all_words.empty?
      lacquers_matching_most_words = []
      max = search_word_ids.count - 1
      while max > 0 && lacquers_matching_most_words.empty?
        lacquers_matching_most_words = Lacquer.where(id: lacquer_ids.select{|id| lacquer_ids.count(id) == max})
        max -= 1
      end

      if lacquers_matching_most_words.any?
        return lacquers_matching_most_words
      else
        closest_lacquers = []
        search_words.each do |search_word|
          closest_lacquers << Word.find_closest_lacquers(search_word)
        end
        return closest_lacquers.flatten
      end
    end

    lacquers_matching_all_words
  end

  def levenshtein_distance(string)
    string.downcase!
    text = name.downcase
    m = string.length
    n = text.length
    return m if n == 0
    return n if m == 0
    d = Array.new(m+1) {Array.new(n+1)}

    (0..m).each {|i| d[i][0] = i}
    (0..n).each {|j| d[0][j] = j}
    (1..n).each do |j|
      (1..m).each do |i|
        d[i][j] = if string[i-1] == text[j-1]  # adjust index into string
                    d[i-1][j-1]       # no operation required
                  else
                    [ d[i-1][j]+1,    # deletion
                      d[i][j-1]+1,    # insertion
                      d[i-1][j-1]+1,  # substitution
                    ].min
                  end
      end
    end
    d[m][n]
  end

  def self.find_closest(string)
    results = []
    closest_distance = 3
    Lacquer.all.each do |lacquer|
      distance = lacquer.levenshtein_distance(string)
      if distance <= closest_distance
        results << lacquer
      end
    end
    results
  end

  def color_tags
    colors = []
    @user_lacquers = UserLacquer.where(:lacquer_id => self.id)
    @user_lacquers.each do |user_lacquer|
      user_lacquer.colors.each do |color|
        colors << color.name
      end
    end
    colors
  end

  def formatted_name
    shortened = name

    # for Butter lacquers, since many of them end in 'Nail Lacquer'
    if name.end_with?('Nail Lacquer')
      shortened = name.gsub('Nail Lacquer', '')
    end

    if shortened.length > 17
      shortened = "#{shortened.slice(0..14)}..."
    end
    
    shortened
  end

  def finish_tags
    finishes = []
    @user_lacquers = UserLacquer.where(:lacquer_id => self.id)
    @user_lacquers.each do |user_lacquer|
      user_lacquer.finishes.each do |finish|
        finishes << finish.description
      end
    end
    finishes
  end

  def color_string
    string_array = []
    colors.each do |color|
      string_array << color.name
    end
    string_array.join(", ")
  end

  def finish_string
    string_array = []
    finishes.each do |finish|
      string_array << finish.description
    end
    string_array.join(", ")
  end

  def self.user_added
    Lacquer.where.not(user_added_by_id: nil)
  end

  def user_who_added_me
    User.where(id: user_added_by_id).first
  end

  def favorited_by?(user)
    Favorite.find_by(lacquer_id: self.id, user_id: user.id)
  end
end