Rails 2 - named_scope :include, :joins and selecting specific columns - mysql

I was wondering if it's possible to use :include in named_scope but to specify only specific columns to :include?
Currently I use the following:
class ProductOverwrite < ActiveRecord::Base
belongs_to :product
named_scope :with_name, :include => :product
def name
produt.name
end
end
But i'm wondering if I can select specific columns from the product table instead of selecting the entire set of columns which I obviously don't need.

This isn't something rails does out of the box.
You could 'piggy back' the attribute
named_scope :with_product_name, :joins => :product, :select => 'product_overwrites.*, products.name as piggy_backed_name'
def product_name
read_attribute(:piggy_backed_name) || product.name
end
If it is possible for a ProductOverwrite to have no product, then you'd need a left join rather than the default inner join.

Related

Rails query user first name on join model

this situation that find the user on a join model is seems tricky than usual
class ConversationParticipant < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
attr_accessible :user_id
end
class User < ActiveRecord::Base
has_many :conversation_participants
end
I'm trying to find the conversation_participant with the fisrt_name with this query
user = User.where('conversation_participant like ? or first_name like ?', query, query)
but the query did not return the user_participant and the user first_name either.
someone can spare a hint please!
You have to include the % for the sql LIKE operator
query = params[:query] # or something ...
User.where('conversation_participant LIKE :query OR first_name LIKE :query', query: "%#{query}%")

Rails 5 - Activerecord association query

I have two models associated with each other as follows.
class Comment < ApplicationRecord
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
end
Following record query
comments_list = Comment.where(:post_id => post_id, :is_delete => false).joins(:user).select('comments.*,users.*')
Generates the following mysql query in logger
SELECT comments.*,users.* FROM `comments` INNER JOIN `users` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = '81' AND `comments`.`is_delete` = 0.
This seems generating very ligitimate query, but comments_list object contain columns only from comments table.
Thanks
It depends on what you want to do, if you want to display the username next to the comment, Mert B.'s answer is fine, all you have to do is include(:user) and the users from the comment list will be fetched along when you do something like this:
comments_list = Comment.where(:post_id => post_id, :is_delete => false).joins(:user).select('comments.*,users.*')
comments_list.each do |comment|
puts "#{comment.text} by #{comment.user.name}"
end
Or maybe if you want only users who have at least one comment, you can always select users from the user_ids on the comments table:
User.where(id: Comment.select(:user_id))

Rails search through `has_many :through` text fields

Let say I have a two models with association has_many :through between them.
class Category < ActiveRecord::Base
has_many :category_recipes
has_many :categories, through: :category_recipes
validates :name, presence: true
class Recipe < ActiveRecord::Base
has_many :category_recipes
has_many :categories, through: :category_recipes
validates :title, presence: true
I want to create search functionality using ActiveRecord for mySQL database, which allow users to implement text search on Recipe title and Category name.
Now I have just:
#recipes = Recipe.where('title LIKE ?', "%#{params[:query]}%")
How can I modify this query to search through both title of recipe and it's category names?
Recipe.includes(:categories).where('recipes.title LIKE :query or categories.name like :query', query: "%#{params[:query]}%").references(:categories)
See: Specifying Conditions on Eager Loaded Associations
Hey try in this way
#recipes = Recipe.includes(:categories).where('title LIKE ? or categories.name LIKE ?', "%#{params[:query]}%","%#{params[:query]}%")
You need to left join to the associated table and query the name on either table with OR:
Recipe.joins("LEFT OUTER JOIN category_recipes ON category_recipes.recipe_id = recipes.id")
.joins("LEFT OUTER JOIN categories ON categories.id = category_recipes.id")
.where("recipes.title LIKE :q OR categories.name LIKE :q", q: "%#{params[:query]}%").uniq

Custom Search in Ruby on Rails

I'm trying to implement a custom search feature in my Rails app.
I have 3 tables User Company and CompanyUser
My relations declared are as follows
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :user
...
end
In Company.rb
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
In User.rb
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
and in my view page I'm listing all companies using company_users table
Now i have a search field where I can type a name in it which filters this list of company users.
I'm calling a method from my CompanyUsers table to filter the search result like this
#company_users = CompanyUser.all
if params[:search_data].present?
#company_users = #company_users.filter_company_users(params[:search_data])
end
where params[:search_data] is passed to the same method when search data is entered in the search field.
this is the method that do the search filtering.
def self.filter_company_users(search_data)
where('company_id like (?)', "%#{search_data}%")
end
Right now I can get the result only if I type the correct id of CompanyUser table in that search field. What I'm trying to do is to search using fields from User table and Company Table. For eg email from User and name from Company.
class CompanyUser
def self.search(search_data)
search = self.joins(:company, :user)
search.where!('company_users.company_id LIKE :query OR users.email LIKE :query OR companies.name LIKE :query', query: "%#{search_data}%")
search
end
end
With the join statement you join the CompanyUser table with the company and the user, therefore you will be able to apply filters on that tables too.
The where condition reuses the same query string and applies the search in OR to all the columns you want to search for.
#company_users = CompanyUser.all
#copany_users.all(:conditions => ["company_id LIKE ? OR company.email like ? OR user.name LIKE ?","%#{params[:search_data]}%","%#{params[:search_data]}%", "%#{params[:search_data]}%" ])
or
def self.filter_company_users(search_data)
where("company_id LIKE ? OR company.email like ? OR user.name LIKE ?" ,"%#{search_data}%", "%#{search_data}%","%#{search_data}%")
end

Rails query on multiple primary keys with conditions on association

Is there a way in Active Record to construct a single query that will do a conditional join for multiple primary keys?
Say I have the following models:
Class Athlete < ActiveRecord::Base
has_many :workouts
end
Class Workout < ActiveRecord::Base
belongs_to :athlete
named_scope :run, :conditions => {:type => "run"}
named_scope :best, :order => "time", :limit => 1
end
With that, I could generate a query to get the best run time for an athlete:
Athlete.find(1).workouts.run.best
How can I get the best run time for each athlete in a group, using a single query?
The following does not work, because it applies the named scopes just once to the whole array, returning the single best time for all athletes:
Athlete.find([1,2,3]).workouts.run.best
The following works. However, it is not scalable for larger numbers of Athletes, since it generates a separate query for each Athlete:
[1,2,3].collect {|id| Athlete.find(id).workouts.run.best}
Is there a way to generate a single query using the Active Record query interface and associations?
If not, can anyone suggest a SQL query pattern that I can use for find_by_SQL? I must confess I am not very strong at SQL, but if someone will point me in the right direction I can probably figure it out.
To get the Workout objects with the best time:
athlete_ids = [1,2,3]
# Sanitize the SQL as we need to substitute the bind variable
# this query will give duplicates
join_sql = Workout.send(:santize_sql, [
"JOIN (
SELECT a.athlete_id, max(a.time) time
FROM workouts a
WHERE a.athlete_id IN (?)
GROUP BY a.athlete_id
) b ON b.athlete_id = workouts.athlete_id AND b.time = workouts.time",
athlete_ids])
Workout.all(:joins => join_sql, :conditions => {:athlete_id => })
If you require just the best workout time per user then:
Athlete.max("workouts.time", :include => :workouts, :group => "athletes.id",
:conditions => {:athlete_id => [1,2,3]}))
This will return a OrderedHash
{1 => 300, 2 => 60, 3 => 120}
Edit 1
The solution below avoids returning multiple workouts with same best time. This solution is very efficient if athlete_id and time columns are indexed.
Workout.all(:joins => "LEFT OUTER JOIN workouts a
ON workouts.athlete_id = a.athlete_id AND
(workouts.time < b.time OR workouts.id < b.id)",
:conditions => ["workouts.athlete_id = ? AND b.id IS NULL", athlete_ids]
)
Read this article to understand how this query works. Last check (workouts.id < b.id) in the JOIN ensures only one row is returned when there are more than one matches for the best time. When there are more than one match to the best time for an athlete, the workout with the highest id is returned(i.e. the last workout).
Certainly following will not work
Athlete.find([1,2,3]).workouts.run.best
Because Athlete.find([1,2,3]) returns an array and you can't call Array.workouts
You can try something like this:
Workout.find(:first, :joins => [:athlete], :conditions => "athletes.id IN (1,2,3)", :order => 'workouts.time DESC')
You can edit the conditions according to your need.