Similarity in rails - ruby-on-rails

I am trying to implement a similar posts functionality in my rails app for this i have this code in my posts controller
#related_Posts = Post
.where('posts.id != ?', #post.id)
.where(:post_title=>#post.post_title)
.where(:category_id=>#post.category).limit(5)
this work fine, but I'm wondering if there is a way to consider a post is similar to the other when only 50% of title are similar instead of the full title

Using a full-text search engine like solr/sunspot is probably what you want. It can be configured to find related posts that have similar words in the title field.

Simply you can use sql LIKE function, in rails (matches) it will seems as following:
class Post < ActiveRecord::Base
scope :by_not_ids, ->(ids = []) { where.not id: ids }
scope :by_title_like, ->(title = '') {
arel_table[:title].matches("%#{title}%")
}
scope :by_category_ids, ->(category_ids = []) {
where category_id: category_ids
}
end
class PostsController < ApplicationController
def index
#related_posts = related_scoping
end
private
def related_scoping
Post.by_not_ids([#post.id])
.by_title_like(#post.title)
.by_category_ids([#post.category_id])
.limit(5)
end
end

Related

Chaining ActiveRecord_Relation in PORO

In a Rails 5.1 app, I have a query object (PORO) named CoolProducts.
class CoolProducts
def self.call(relation = Product.all)
...
# return an instance of Product::ActiveRecord_Relation
end
end
Now I need to limit the found Products based on the fact the name matches a string.
The following works
CoolProducts.call.where("name ILIKE ?", "%#{string}%")
However, I'd like to encapsulate the matching login within the CoolProducts class allowing to do something like
CoolProducts.call.including_in_name(string)
But I'm not sure where to start from.
Any ideas?
It will be difficult if you want any of your methods to be chainable or return ActiveRecord::Relation.
If you consider explicitly fetching the records when you're done chaining being ok, this should work:
class CoolProducts
def initialize(relation)
#relation = relation
end
def self.call(relation = Product.all)
new(relation).apply_scopes
end
attr_reader :relation
alias_method :fetch, :relation
def including_in_name(string)
tap { #relation = relation.where("name ILIKE ?", string) }
end
def apply_scopes
tap { #relation = relation.where(price: 123) }
end
end
Usage:
CoolProducts.call.including_in_name(string).fetch

Calling a ActiveRecord class method for ActiveRecord_Relation as a receiver

I want to create a class method for a class inherits ActiveRecord:Base.
What the method need to do is add where clauses based on the options and it works well.
class Article < ActiveRecord::Base
def self.list_by_params(params={})
articles = self
articles = articles.where(author_id: params[:author_id]) unless params[:author_id].blank?
articles = articles.where(category_id: params[:category_id]) unless params[:category_id].blank?
articles = articles.where("created_at > ?", params[:created_at].to_date) unless params[:created_at].blank?
articles
end
end
This code works fine in case of the call such as:
articles = Article.list_by_params({author_id: 700})
#=> Works fine as I expected.
articles = Article.joins(:authors).list_by_params({author_id: 700})
#=> Works fine as I expected.
However, the problem is that, if I want to call the list_by_params without filtering params, then it lose its former relations. For example:
articles = Article.joins(:authors).list_by_params({})
#=> articles is just a `Article` (not an ActiveRecord_Relation) class itself without joining :authors.
Is there any chance that I made a mistake?
Thanks in advance.
What you are looking for is a scope.
I would do something like this
scope :for_author, lambda { |author| where(author_id: author) unless author.blank? }
scope :in_category, lambda { |category| where(category_id: category) unless category.blank? }
scope :created_after, lambda { |date| where('created_at > ?', date.to_date) unless date.blank? }
scope :list_by_params, lambda do |params|
for_author(params[:author_id])
.in_category(params[:category_id])
.created_after(params[:created_at])
end
Now you can reuse the components of your query. Everything has a names and it gets easier to read the code.
For the self explanation, I've solved the problems by using where(nil).
Actually, Model.scoped returned anonymous scope but the method has been deprecated since Rails version 4. Now, where(nil) can replace the functionality.
class Article < ActiveRecord::Base
def self.list_by_params(params={})
articles = where(nil) # <-- HERE IS THE PART THAT I CHANGED.
articles = articles.where(author_id: params[:author_id]) unless params[:author_id].blank?
articles = articles.where(category_id: params[:category_id]) unless params[:category_id].blank?
articles = articles.where("created_at > ?", params[:created_at].to_date) unless params[:created_at].blank?
articles
end
end

Method to return a collection - Ruby

Say, I have a method called posted_listings, which is supposed to run an ActiveRecord query and return a collection of User.listings where posted: true, with posted? being a Listing class method. So far I have been doing:
class Bar < ActiveRecord::Base
def posted_listings
posted_listings = []
listings.each { |listing| posted_listings << listing if listing.posted? }
posted_listing
end
end
but each time this query runs I start feeling really bad about my skills (or lack of thereof). What is the most efficient way to return a collection of posted listings?
Edit:
posted? is not an attribute, its a class method:
class Listing < ActiveRecord::Base
def posted?
true if quantity >= 1 && has_sellers?
end
end
def has_sellers?
sellers.count >=1 #association to Seller
end
I would recommend adding a scope to your Listing model like this:
scope :posted, -> { where(posted: true) }
Then you can get all posted listings like this:
#user.listings.posted
You can learn more about scopes here if you are interested.
UPDATE
Try this scope instead:
def self.posted
joins(:sellers)
.where('posted = ? AND quantity > ?', true, 0)
.group('listings.id')
.having('COUNT(sellers.id) > ?', 0)
end
Your question is not so clear for me.
You may try:
User.listings.where(posted: true)
to get all users' posted Listings.
Or, saying #useris an User instance:
#user.listings.where(posted: true)
to get posted Listings from an specific user.

How can I get all records that return True in a model function in a rails app?

I have the following model:
class AuthorizedDriver < ActiveRecord::Base
belongs_to :car
def authorized?
!self.authorized_until.nil? && self.authorized_until.to_date >= Time.current.to_date
end
end
I would like to be able to do:
def show_authorized_drivers
#car = Car.find(params[:id])
#authorized_drivers = #car.authorized_drivers.where(authorized?: true)
end
I know I can do this with a specific field, but I would like to use the authorized? function (or another function at a later time) above.
Any guidance on this would be much appreciated, thanks!
I am using Rails 4.1.4 and Ruby 2.1.2.
You can do it like so:
#authorized_drivers = #car.authorized_drivers.to_a.select(&:authorized?)
Note that this fetches all the authorized_drivers for that car, then filters them by calling the #authorized? method.
Also note that
.select(&:authorized?)
is shortcut notation for
.select {|it| it.authorized? }
Add the following scope to your Driver model:
scope :authorized, -> { where('authorized_until >= ?', Time.current) }
Then you can query authorized drivers for a car like this:
#authorized_drivers = #car.authorized_drivers.authorized
Update (to answer your comment): You have two options if you need to add another criteria.
You could combine both conditions into one scope:
scope :authorized, -> {
where('authorized_until >= ?', Time.current).where(status: 'Active')
}
Or you could add another scope:
scope :active, -> { where(status: 'Active') }
and just chain the scopes:
#authorized_drivers = #car.authorized_drivers.authorized.active

Rails 3 multiple parameter filtering using scopes

Trying to do a basic filter in rails 3 using the url params. I'd like to have a white list of params that can be filtered by, and return all the items that match. I've set up some scopes (with many more to come):
# in the model:
scope :budget_min, lambda {|min| where("budget > ?", min)}
scope :budget_max, lambda {|max| where("budget < ?", max)}
...but what's the best way to use some, none, or all of these scopes based on the present params[]? I've gotten this far, but it doesn't extend to multiple options. Looking for a sort of "chain if present" type operation.
#jobs = Job.all
#jobs = Job.budget_min(params[:budget_min]) if params[:budget_min]
I think you are close. Something like this won't extend to multiple options?
query = Job.scoped
query = query.budget_min(params[:budget_min]) if params[:budget_min]
query = query.budget_max(params[:budget_max]) if params[:budget_max]
#jobs = query.all
Generally, I'd prefer hand-made solutions but, for this kind of problem, a code base could become a mess very quickly. So I would go for a gem like meta_search.
One way would be to put your conditionals into the scopes:
scope :budget_max, lambda { |max| where("budget < ?", max) unless max.nil? }
That would still become rather cumbersome since you'd end up with:
Job.budget_min(params[:budget_min]).budget_max(params[:budget_max]) ...
A slightly different approach would be using something like the following inside your model (based on code from here:
class << self
def search(q)
whitelisted_params = {
:budget_max => "budget > ?",
:budget_min => "budget < ?"
}
whitelisted_params.keys.inject(scoped) do |combined_scope, param|
if q[param].nil?
combined_scope
else
combined_scope.where(whitelisted_params[param], q[param])
end
end
end
end
You can then use that method as follows and it should use the whitelisted filters if they're present in params:
MyModel.search(params)

Resources