Query optimization in associated models - ruby-on-rails

I have a User model
class User < ActiveRecord::Base
has_many :skills
has_one :profile
end
Profile table has two columns named, age & experience
Now, I've a search form where the parameters are passed are:
params[:skill_ids] = [273,122,233]
params[:age] = "23"
params[:experience] = "2"
I've to search through all the users where user's skills meet any of the params[:skill_ids] and also from the user's profile, their age and experience.
Do I have to go through a loop like:
users = []
User.all.each do |user|
if (user.skills.collect{|s| s.id} & params[:skill_ids] ) > 0
// skip other parts
users << user
end
end
or, any of you have any better solution?

Because your skills belong to exactly one user, you could first fetch all users belonging to the skill ids provided and filter them by the other criteria:
matching_users = User.includes(:skills, :profile)
.where(["skills.id in (?) AND profile.age = ? AND profile.experience = ?",
params[:skill_ids],
params[:age].to_i,
params[:experience].to_i]
)

Try this:
#users = User.includes(:skills).where(["skills.id in (?)", params[:skill_ids]]).all

Related

How to use `join` method to access columns from multiple tables in rails

I have two tables, and I want to display all these columns on page.
Tables:
1.Users:
name, email, sex_id
abc, abc#q.com, 0
2. Masters:
type, sex, sexn
8, 0, female
I want to display:
name, email, sex
abc, abc#q.com, femail
Models' definition:
class Master < ApplicationRecord
has_many :users
end
class User < ApplicationRecord
belongs_to :master
def self.search(search)
where("name LIKE ?", "%#{search}%")
end
end
using #users = User.joins("INNER JOIN masters ON masters.sex = users.sex_id AND masters.type = 8"), I can only access columns from Users.
I want to access data from Masters. Using #users.first.master, I just get nil.
using#users = User.find_by_sql("SELECT * FROM users INNER JOIN masters ON masters.sex = users.sex_id AND masters.type = 8"), I can access columns from these two tables. So there's no problem with my data.
How do I use join method to access columns from multiple tables?
You have missed the select statement, try this one.
#users = User.select("users.*, masters.*").joins("INNER JOIN masters ON masters.sex = users.sex_id AND masters.type = 8")
You're almost there! You can have Rails do the join for you with the .includes() method:
def self.search(search)
# :master is singular
includes(:master).where("name LIKE ?", "%#{search}%")
end
Then:
#users.each do |user|
# singular again
puts user.master.sexn
end

Postgresql cross-database references in Rails

I'm building a search for and got stuck on a cross-database references error
activities = Activity.order(:name).includes([:profile => :country])
activities = activities.where("lower(activities.city) like ?", "%#{params[:activity_search][:city].downcase}%") unless params[:activity_search][:city] == ""
activities = activities.where("activities.sport_id =?", params[:activity_search][:sport_id])
I'm trying to add something like this:
activities = activities.where("activities.profiles.country.id =?", params[:activity_search][:country_id])
an activity's country is the same as the activity creator's country.
How can I add this constraint in my query?
Thanks for your help
You need to use joins here to include the associations:
activities.joins(profiles: :country).where('countries.id = ?', params[:whatever])
This assumes Activity has_many :profiles and Profile has_one :country and countries is your table name, but I think all that is true based on your post. This will include the associated profiles and country with activities and allow you to use their attributes in the where method call.

Self-referential find in controller count relations

I'm having real trouble pulling out a set of records that are self-referentially related to a user in order to show these on a user's 'show' page.
Here's the idea:
Users (current_user) rate the compatibility between two other users (user_a and user_b). They can rate compatibility either positively or negatively: rating two users "compatible" creates a positive_connection between user_a and user_b, and rating them "incompatible" creates a negative_connection. So there are models for positive_connection, negative_connection and user.
Now I need to display only users that are overall_positively_connected_to(#user) (i.e. where positive_connections_to(#user).count > negative_connections_to(#user).count).
This is where I've got to, but I can't get any further:
User model:
def overall_positive_connected_to(user)
positive_connections_to(user).count > negative_connections_to(user).count
end
def positive_connections_to(user)
positive_connections.where("user_b_id = ?", user)
end
def negative_connections_to(user)
negative_connections.where("user_b_id = ?", user)
end
Controller
#user.user_bs.each do |user_b|
if user_b.overall_pos_connected_to(#user)
#compatibles = user_b
end
end
The code in the controller is clearly wrong, but how should I go about doing this? I'm completely new to rails (and sql), so may have done something naive.
Any help would be great.
So am I right in saying you have 3 models
User (id, name)
PositiveConnection (user_a_id, user_b_id)
NegativeConnection (user_a_id, user_b_id)
Or something of that sort.
I think you just want 2 models
and for convenience I'm going to rename the relations as "from_user" and "to_user"
User (id, name)
Connection (value:integer, from_user_id, to_user_id)
Where value is -1 for a negative
and +1 for a positive.
Now we can have do something like
(note: you need to sort out the exact syntax, like :foreign_key, and :source, and stuff)
class User
has_many :connections, :foreign_key => "from_user_id"
has_many :connected_users, :through => :connections, :source => :to_user
def positive_connections
connections.where(:value => 1)
end
def negative_connections
...
end
end
But we also now have a framework to create a complex sql query
(again you need to fill in the blanks... but something like)
class User
def positive_connected_users
connected_users.joins(:connections).group("from_user_id").having("SUM(connections.value) > 0")
end
end
this isn't quite going to work
but is kind of pseudo code for a real solution
(it might be better to think in pure sql terms)
SELECT users.* FROM users
INNER JOIN connections ON to_user_id = users.id
WHERE from_user_id = #{user.id}
HAVING SUM(connections.value) > 0

Could I add an association based on another association?

My User model looks like:
User
habtm :Roles
Role
habtm :Users
RoleExtension
belongs_to :Role
mysql tables:
users
id
..
roles
id
..
roles_users
user_id
role_id
role_extensions
id
role_id
feature_id
..
..
Now everything seems to be working fine so far.
Now I want the User model to have a collection of RoleExtensions, based on the habtm Roles collection.
example:
user = User.find(1)
user.Roles (returns roles with id's of 1,2,3)
So I want:
user.RoleExtensions
to return all Role extensions that have role_id in (1,2,3)
Normally you'd use a has_many, :through association, but that doesn't apply to has_and_belongs_to_many relations.
So, instead, in your User model:
def role_extensions
return roles.inject([]) do |array, role|
role.role_extensions do |re|
array.include?(re) ? array << re : array
end
end
end
Then my_user.role_extensions should return an array of all role extensions belonging to all the user's roles.
Note: I haven't tested this, but it should work
UPDATE: I like this better
def role_extensions
return roles.inject([]) { |array, role| array << role.role_extensions }.flatten!.uniq
end
user = User.find(1)
RoleExtension.find(:all, :conditions => ["role_id IN (?)", user.role_ids])
Otherwise you can use nested joins.
Try this -
# Fetch user object
user = User.first
# If you want roles of that user try this
roles = user.roles
# You can map all the role extensions of that user by
role_extensions = user.roles.map(&:role_extensions).uniq
Be aware that this will be extremely slow for large number of roles. In that case better write your own query method. Something like
role_extensions = RoleExtension.where("role_id in (?)", user.role_ids).all
#user.role_extensions.where(:joins => :roles)

Difference Between find and Where with Relationships

I wouldn't think there is a difference when it comes to active record and finding data.
Here are my models
class User < ActiveRecord::Base
has_many :shows
end
class Show < ActiveRecord::Base
belongs_to :user
end
When I use the rails console I can do the following and it works.
u = User.find(1)
u.shows
It gives me all the shows for that user.
However when I do
u = User.where("username = ?", "percent20")
u.shows # this is doesn't work gives me a now instance error
I get the same user and relevant information, but not the relationship. The only problem I can see is maybe I am doing something wrong because there is some difference between where and find.
Any help is appreciated.
The problem is not the relationship.
u = User.find(1)
returns one User
#return a Set of users. In your case its only one user.
u = User.where("username = ?", "percent20")
The result type is ActiveRecord::Relation --> [User, User, User]
use e.g. first to get the first User
#returns the first user
u = User.where("username = ?", "percent20").first
u.class.name
=> "User"
User.find(1) is retrieving a specific record with its ID, whereas User.where("username = ?", "percent20") is retrieving the set of records that match the condition.
Try:
u = User.where("username = ?", "percent20").first
u.shows
The where is method that returns an array of objects. So, in your case try
u.each { |user| user.shows }

Resources