Rails - Search for object based on association's value - sql

I've got two models Company and InsuredObject. Company has_many InsuredObjects and the reverse belongs_to. Currently, I have a functioning search for InsuredObject(s) that returns all objects that contain the search input as shown below:
# /models/insured_objects.rb
def self.search(search)
query = "%#{search}%"
if search
where("object LIKE ? OR insurance_type LIKE ? OR class_code LIKE ? OR information LIKE ?",
query, query, query, query)
end
end
and:
# /controllers/insured_objects_controller.rb
def index
#insured_objects = InsuredObject.search(params[:search])
end
Each Company has a is_active attribute. I'm trying to think of a way to search for the same thing but only return InsuredObject(s) that their Company's is_active attrutbute is true. Any thoughts?

Get all entries of InsuredObject from active companies:
InsuredObject.joins(:company).where(companies: {is_active: true})
In joins(:company), :company is a name of association (in InsuredObject you should have belongs_to :company)
In where(companies: ..., :companies is a table name for model Company

Related

How do I search an id in a string attribute (SQL)

In my chat app I want to calculate the response rate for student model.
I track all conversations with a slug attribute. It's a string like this: 270-77, which means that this is a conversation between student 270 and recruiter 77.
Now I want to check how many conversations one student has. Here is my code:
def calculate_number_of_conversations(#student)
#conversations = Conversation.where("slug LIKE ?", "%#{params[#student]}")
end
Important is that it should only search in the first part of the string because the first number in slug is always a student's id.
I'm not sure what #student is. I will write my examples as if it's a record.
You could use - to make sure it's looking for students only:
#conversations = Conversation.where('slug LIKE ?', "#{#student.id}-%")
But I think it's better to have explicit relationships:
class Conversation < ApplicationRecord
belongs_to :student
belongs_to :recruiter
end
#conversations = #student.conversations
You can add the '-' to the WHERE LIKE clause:
def calculate_number_of_conversations(#student)
#conversations = Conversation.where("slug LIKE ?", "%#{params[#student]}-")
end

Query for all of a specific record where its related resource all have the same value for a single attribute

class User < ActiveRecord::Base
has_many :memberships
# included columns
# id: integer
---------------------
Membership < ActiveRecord::Base
belongs_to :user
# included columns
# user_id: integer
# active: boolean
I'd like to be able to grab all users where all their memberships have 'active = false' in a single query. So far the best that I've been able to come up with is:
#grab possibles
users = User.joins(:memberships).where('memberships.active = false')
#select ones that satisfy condition
users.select{ |user| user.memberships.pluck(&:active).uniq == [false] }
which is not that great since I have to use ruby to pluck out the valid ones.
This can do the trick:
users_with_active_membership = User.joins(:memberships).where(memberships: { active: true })
users = User.where( 'users.id NOT IN (?)', users_with_active_membership.pluck(:id) )
I am not sure of the result but I expect it to be 2 nested queries, one selecting the User ids having an active membership, the other query to select the User not in this previous ids list.
I can't test it since I don't have an environment with these relationships. Can you try it and post the SQL query generated? (add .to_sql to see it)
Another way, I don't know which could be the most efficient:
User.where( 'users.id NOT IN (?)', Membership.where(active: true).group(:user_id).pluck(:user_id) )

Selecting models through Rails relations?

I have these (simplified) models:
Address
public (Boolean)
has_one :group_member
Group
has_many :Group_Members
belongs_to :User
Group_Member
belongs_to :group
belongs_to :address
User
has_many :groups
I want to select all addresses where public is true and all the addresses which User can access through Groups.
I assume it's something along the lines of:
Address.where(public: TRUE).joins(:group_member)
Am I somewhere close?
I'm using Rails 4 and PostgreSQL as my database if it helps anyone.
I think this will work:
Address.joins(group_member: :group).where(is_public: true, groups: {user_id: 12345})
Let's break this down.
First we are calling on the Address model, b/c that is what we want to return.
.joins(:group_member) will join addresses to group_members via the Address has_one :group_member relation.
However, we actually want to go further, and join the group connected with the group_member, so we use a nested join, which is why it looks like this joins(group_member: :group) to indicate that we join address -> group_member, then group_member -> group.
Next the where clause. There are 2 conditions, we want:
Public addresses only, which is indicated as a column on the address, so we add this:
.where(is_public: true).
We want only where a particular user is connected with a group (and so its group_members and addresses). For this we need to add a nested where clause, like so groups: {user_id: 12345}.
The result of combining these is:
where(is_public: true, groups: {user_id: 12345})
So all together, the line of code above should get what you want.
Try the following -- it is not a single query but I think it is better than a single query with big join table:
public_addresses = Address.where(is_public: true)
# => get all public addresses
user_addresses = current_user.groups.includes(:group_members => :address).
# includes is to eager load records to avoid N+1 queries
flat_map{|g| g.group_members.map(&:address)}
# inner block returns array of addresses for each group
# flat_map converts the array of arrays to single level
# => get all addresses associated with the user
all_addresses = (public_addresses + user_addresses).uniq
# => remove duplicates
To speed up the query add indices for slower queries. For e.g
add_index :groups, :user_id
# this speeds up finding groups for a given user
add_index :group_members, :group_id
# this speeds up finding group_members for a given group
add_index :addresses, :group_member_id
# this speeds up finding addresses for a given group_member
Other Options is to get user_addresses using join tables
user_addresses = Address.joins(group_member: group).where(groups: {user_id: current_user.id} )

has_one association selecting specific field

I have user model which has_one association with user_profile
And I want to select name field from user_profile instead of user_profile.*
I have tried,
user = User.first
user.user_profile.select(:name)
But this is not working.any way?
UPDATED:
It seems, that rails handles the other direction and one-to-one connections differently. You have two options:
1) Define the selected attributes in the association, it will always select those
has_one :user_profile, :select => [:name, :id]
2) Define a specific find in your model, where you can add select, like this:
def my_profile
UserProfile.find(self.user_profile_id)
end
....
my_profile.select(:name)
ORIGINAL:
In case of has_many direction, it works:
I've tried your method in my code, like:
= User.find("admin").article.select(:title).to_sql
It returns the correct sql:
SELECT title
FROM "articles"
WHERE "articles"."user_id" = 1364

Using where clause with a foreign key

In my app I have a search field to find football visits. I want it to be able to search by the club of the visit. In my model visit belongs_to club, so visit has a field "club_id". So when I search with an ID, it finds the club, but I want to be able to find it by club name. "club_id" should be replaces by something like "visit.club.name", but how can I achieve this? This is my current query:
# Find visits
def find_anything(find_phrase)
unless find_phrase.blank?
#visits = Visit.where('address LIKE ? OR ground LIKE ? OR club_id LIKE ?',
"%#{find_phrase}%",
"%#{find_phrase}%",
"%#{find_phrase}%")
end
end
that should do the trick:
Visit.where(club_id: Club.where(name: "ClubName"))
Club.joins(:visits).where('visits.club_id' => club_id).first.name
*this assumes the Club model has_many :visits
or
Club.find(Visit.find_by_club_id(club_id)).name