Rails - Find similar Products - ruby-on-rails

I have two models
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
has_many :photos
belongs_to :category
def self.similar(product)
where("name LIKE ?", "#{product.name}%").limit(4)
end
end
I would like the self.similar method to product products which are similar to the argument passed to it(product).
The trouble Im having is that this method only returns products which match the argument perfectly. Im wondering if its possible to set the tolerance of this method to product a broader range of products rather than ones which match the argument perfectly? Or perhaps another superior way to produce an array of similar products.

You must simple change your where query to following:
def self.similar(product)
where("name LIKE ?", "%#{product.name}%").limit(4)
end
and your LIKE query should work..with the % at the start and the end also not exactly matching results will be returned..

If you don't want to/can't install a gem, then you can use Postgres' levenshtein (if it's installed):
Model.select("*, levenshtein(LOWER(name) , '#{thing_name.downcase}') AS fuzzydist").order('fuzzydist ASC').first

I had a very good experience using fuzzy_match. There is no built in ActiveRecord nor Rails nor Ruby support for it otherwise.
So your code would then become more or less
require 'fuzzy_match'
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
has_many :photos
belongs_to :category
def self.similar(product)
fz = FuzzyMatch.new(Product.all, :read => :name)
return fz.find(product.name).limit(4)
end
end

Related

Order objects by invoice number using Rails

In Ruby I can sort some objects by invoice number in descending order like:
#app.sample_sales.sort_by {|sale| -sale.payload['invoice_number'].to_i }
However I can't quite get it to work using Rails:
#app.sample_sales.order("payload->'invoice_number' desc")
I've also tried:
#app.sample_sales.order("payload->'invoice_number::integer' desc")
Models
#sample_app.rb
class SampleApp < ApplicationRecord
has_many :sample_sales, dependent: :destroy
end
#sample_sale.rb
class SampleSale < ApplicationRecord
belongs_to :sample_app
end
Your syntax is not correct. Here is a good one:
#app.sample_sales.joins(:payload).order('payload.invoice_number desc')
I might update this answer when you've shared your model's associations. 😉

Create Rails scope comparing fields on two tables

I have a number of associated tables in an application
class Listing < ActiveRecord::Base
belongs_to :house
belongs_to :multiple_listing_service
end
class House < ActiveRecord::Base
has_one :zip_code
has_one :primary_mls, through: :zip_code
end
I wanted to create a scope that produces all the Listings that are related to the Primary MLS for the associated House. Put another way, the scope should produce all the Listings where the multiple_listing_service_id = primary_mls.id for the associated house.
I've tried dozens of nested joins scopes, and none seem to work. At best they just return all the Listings, and normally they fail out.
Any ideas?
If I understand correctly, I'm not sure a pure scope would be the way to go. Assuming you have:
class MultipleListingService < ActiveRecord::Base
has_many :listings
has_many :zip_codes
end
I would go for something like:
class House < ActiveRecord::Base
...
def associated_listings
primary_mls.listings
end
end
Update 1
If your goal is to just get the primary listing then I would add an is_primary field to the Listing. This would be the most efficient. The alternative is a 3 table join which can work but is hard to optimize well:
class Listing < ActiveRecord::Base
...
scope :primary, -> { joins(:houses => [:zip_codes])
.where('zip_codes.multiple_listing_service_id = listings.multiple_listing_service_id') }

In Rails 5 what is the best way to show duplicate records across a has_and_belongs_to_many and a belongs to relationship

Given the following model structures;
class Project < ApplicationRecord
has_many :leads
has_and_belonds_to_many :clients
end
class Lead < ApplicationRecord
belongs_to :project
end
class Client < ApplicationRecord
has_and_belongs_to_many :projects
end
How you would suggest reporting on duplicate leads across a Client?
Right now I am doing something very gnarly with flattens and counts, it feels like there should be a 'Ruby way'.
Ideally I would like the interface to be able to say either Client.first.duplicate_leads or Lead.first.duplicate?.
Current (terrible) solution
#duplicate_leads = Client.all.map(&:duplicate_leads).flatten
Class Client
def duplicate_leads
leads = projects.includes(:leads).map(&:leads).flatten
grouped_leads = leads.group_by(&:email)
grouped_leads.select { |_, v| v.size > 1 }.map { |a| a[1][0] }.flatten
end
end
Environment
Rails 5
Ruby 2.3.1
You could try this.
class Client < ApplicationRecord
has_and_belongs_to_many :projects
has_many :leads, through: :projects
def duplicate_leads
duplicate_ids = leads.group(:email).having("count(email) > 1").count.keys
Lead.where(id: duplicate_ids)
end
end
You could try creating a has_many association from Lead through Project back to Lead, in which you use a lambda to dynamically join based on a match on email between the two records, and on the id not matching. This would mark both records as a duplicate -- if you wanted to mark only one then you can require that the id of one is less than the id of the other.
Some hints: Rails has_many with dynamic conditions

How to build a has_many-alike association containing a list of various Models?

I have an Order which has many line_items. Only this is not a LineItem module, but a list of "things that act Orderable". E.g. Addon or Site.
class Order
attr_accessor :line_items
before_save :persist_line_items
private
def persist_line_items
#line_items.each {|li| li.save }
end
end
class Addon
belongs_to: order
end
class Site
belongs_to: order
end
Which can be used as:
order = Order.new
order.line_items << Addon.new(order: order)
order.line_items << Site.new(order: order)
But, now I want to load an Order and join the "associated" line_items. I
could load them in an after_initialize hook, and do an
Addon.find_by(order_id: self.id) but that quickly leads to a lot of
queries; where a JOIN would be more appropriate. In addition, I
currently miss the validations trickling up: when a normal has_many
related item is invalid the containing model will not be valid either:
order = Order.new(line_items: [an_invalid_line_item])
order.valid? #=> false
I am wondering if there is a way
to leverage ActiveRecords' has_many-relation to be used with a list of
different models.
I think that a polymorphic association should do the trick.
Would look like this:
class Order < ActiveRecord::Base
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :orderable, polymorphic: true
end
class Site < ActiveRecord::Base
has_many :line_items, as: :orderable
end
class Addon < ActiveRecord::Base
has_many :line_items, as: :orderable
end
It would use a join table, but i think this is actually a good thing. Otherwise you could use STI for your Addon and Site models, but that would not make a lot of sense in my regard.

Specifying the default ordering for a has_many association using a join (Rails)?

I have a simple Customer model with a has many relationship with a Purchase model.
class Customer < ActiveRecord::Base
has_many :purchases
end
I am repeatedly finding that I need to order Customer.purchases in my views in the following way:
#customer.purchases.joins(:shop).order("shops.position").order(:position) #yes, two orders chained
In the interest of keeping things DRY, I'd like to put this somewhere centralized so I don't have to repeatedly do it. Ideally, I'd like to make it the default ordering for Customer.purchases. For example:
class Customer < ActiveRecord::Base
has_many :purchases, :order => joins(:shop).order("shops.position").order(:position)
end
Obviously the above doesn't work. How should I do this?
In your customer model you specified joins(:shop) is the value for the key :order. I think here is the problem, So you can use the joins as a key instead of order like below,
class Customer < ActiveRecord::Base
has_many :purchases, :joins => [:shop], :order => "shops.position"
end
I think it may work.
In your purchases model, you can create a class method:
Purchase.rb:
def self.order_by_position
joins(:shop).order("shops.position").order(:position)
end
Then you can say things like:
#customer.purchases.order_by_position
Purchase.order_by_position
You could create a method on Customer that returns ordered purchases:
class Customer < ActiveRecord::Base
has_many :purchases
def ordered_purchases
purchases.joins(:shop).order("shops.position").order(:position)
end
end
and call #customer.ordered_purchases from your views.

Resources