I am developing a small user application in Rails 3 and is stuck on a search feature. I got a table called Profile which holds Firstname and Lastname. When searching this table I use:
Profile.find_all_by_firstname(params[:keyword])
The thing is that I want the user to be able to search a table that holds contacts (profile_id and friend_id only) but I want them to be able to seach by name here too. How is this done?
For example:
John searches for the name Ann which is a contact of his. Since the Contact table does not store names the search must include the Profile table in the query. How can I do this?
UPDATE
This join query fetches everyone not only the contacts, can anyone spot a problem with it?
Profile.find_all_by_firstname(params[:keyword],
:select => "firstname, lastname, user_id",
:joins => "left outer join contacts on profiles.id = contacts.profile_id")
Profile.find_all_by_firstname(params[:keyword],
:select => "firstname, lastname, user_id",
:joins => "left outer join contacts on profiles.id = contacts.profile_id")
this query fetches everyone because you serch only by the firstname If you whant select contacts that are friends of the particular user firstly you must have his id Then you should add this to your conditions
current_user = Profile.first
Profile.find(:all,
:conditions => ["profiles.firstname = ? AND contacts.friend_id = ?", params[:keyword], current_user.id],
:joins => :contacts)
or make join conditional
current_user = Profile.first
Profile.find_all_by_firstname(params[:keyword],
:select => "firstname, lastname, user_id",
:joins => "INNER JOIN contacts on (profiles.id = contacts.profile_id AND contacts.friend_id = #{current_user.id})")
but I'm not quite sure about syntax
Related
In an ecommerce shop application I would like to retrieve all orders that match a first_name that was entered via a search form and where paid == true. The search form submits the search term via params Parameters: {"utf8"=>"✓", "search"=>"john", "commit"=>"Search"}. In the controller
#users = User.search(params[:search]) #returns all users with the matching first_name, e.g. 'john'
#order = Order.where('user_id = ? AND paid = ?', #users.ids, true )
The query in #order works just fine, if only one user is returned, e.g. only one user is named john. But if multiple users are named John, multiple user ids are returned and the error message ActiveRecord::StatementInvalid is returned. My understanding is that the query stops working once `#users.ids is an array with more than one value.
How do I structure the following query: for each user_id return all orders (user.orders) where paid equals true.
Models
user.rb
has_many :orders
order.rb
belongs_to :users
There are various ways to go about this.
You can use a JOIN as Nic Nilov suggests but this can be difficult if your #users query is built using scopes and you don't want to manually inline those scopes.
You could also use a subquery since ActiveRecord in Rails4 is smart enough do The Right Thing when you use a relation in a where, you just have to use the hash form of where:
#users = User.search(params[:search])
# #users should be a User::ActiveRecord_Relation now.
#orders = Order.where(:user_id => #users, :paid => true)
This will end up with SQL like:
select *
from orders
where paid = 't'
and user_id in (
select id
from users
where /* whatever `search` does... */
)
The advantage here is that you don't need to know what User.search does as long as it is returning an ActiveRecord relation.
If your #users is actually an array (of ids or whole User instances) then you'd do it exactly the same way:
# Suppose #users is an array of Users or an array of User ids...
#orders = Order.where(:user_id => #users, :paid => true)
and ActiveRecord will figure out what to do with your #users array without you having to do anything extra.
Instead of two queries, you should use a nested query with a WHERE IN clause
SELECT * from Order WHERE user_id IN (SELECT user_id FROM users WHERE first_name LIKE ?) AND paid = true
This should do:
Order.joins(:user).where(users: { name: params[:search] }, paid: true)
It generates a single query with an INNER JOIN:
SELECT "orders".*
FROM "orders"
INNER JOIN "users" ON "users"."id" = "orders"."user_id"
WHERE "users"."name" = 'Test User' AND "orders"."paid" = 't'
I have three tables: users, locations, locations_users, The associations are as follows :
user has_many locations_users
user has_many locations :through => locations_users
location has_many locations_users
location has_many users :through => locations_users
How would I find all the users who are joined to location_id = 5 in one query?
Any help would be appreciated.
You can use LEFT OUTER JOIN. It fetches all data from the left table with matching data from right, if present (if not present, join column is null):
User
.joins('LEFT OUTER JOIN locations_users ON locations_users.user_id = users.id')
.where('locations_users.id IS NULL OR locations_users.location_id = ?', 5)
.all
Hovewer:
this query uses join, which's performance can be poor on big tables (maybe two queries will perform faster - test it!)
for better performance, make sure, that indexes are set on joined columns
I don't know 1 query way but the solution below is efficient.
a = User.where("id NOT IN (?)", LocationUser.pluck("DISTINCT user_id"))
b = LocationUser.where(location_id: 5).pluck("DISTINCT user_id")
result = User.where(id: a+b)
I have 2 models
class User < AR
has_many :friends
end
class Friend < AR
# has a name column
end
I need to find all Users who are Friends with both 'Joe' and 'Jack'
Any idea how i can do this in rails?
One option is to put each of the names as arguments for individual INNER JOINS. In SQL it would be something like this:
SELECT users.* FROM users
INNER JOIN friends AS f1
ON users.id = f1.user_id
AND f1.name = 'Joe'
INNER JOIN friends AS f2
ON users.id = f2.user_id
AND f2.name = 'Jack'
Since it is INNER JOINS, it will only display results where the users table can be joined with both f1 and f2.
And to use it in Rails, maybe do it something like this:
class User < AR
has_many :friends
def self.who_knows(*friend_names)
joins((1..friend_names.length).map{ |n|
"INNER JOIN friends AS f#{n} ON users.id = f#{n}.user_id AND f#{n}.name = ?" }.join(" "),
*friend_names)
})
end
end
Which you then can call like this:
#users = User.who_knows("Joe", "Jack")
Possible way: User.all(:joins => :friends, :conditions => ["friends.name IN (?,?)", "Joe", "Jack"], :group => "users.id") and then iterate over the array to find users with 2 friends.
This is the best solution i got when tried to solve similar problem for myself. If you find the way to do it in pure sql or ActiveRecord – let me know please!
Although using hard-coded SQL as suggested by DanneManne will most often work, and is probably the way you'd want to go, it is not necessarily composable. As soon as you have hard-coded a table name, you can run into problems combining that into other queries where ActiveRecord may decide to alias the table.
So, at the cost of some extra complexity, we can solve this using some ARel as follows:
f = Friend.arel_table
User.
where(:id=>f.project(:user_id).where(f[:name].eq('Joe'))).
where(:id=>f.project(:user_id).where(f[:name].eq('Jack')))
This will use a pair of subqueries to do the job.
I'm fairly certain there's an ARel solution using joins as well, but and I can figure out how to compose that query in ARel, just not how to then use that query as the basis for an ActiveRecord query to get back User model instances.
Let's say that Person has_many Addresses, and with this query I can go through the loop and use addresses for that peoples.
#people = Person.find(:all, :include => :addresses)
But, I want to include ONLY addresses where the user_id = 1 for example.
How to build query to do that?
P.S. New to RoR
#people = Person.find(:all, :include => :addresses, :conditions => ['user_id=?',params[:user_id]])
Make note that when you include you can only have one column of each or rails will give you an error about ambiguous find conditions.
I've got two models, joined by a Has and Belongs To Many join table. Lets call these models User and Event. The majority of Users have 0 events, while few have one or more. I want to do something like:
User.find(:all, :joins => :events, :conditions => ["'Something that count the events' > ?"], 0)
The problem is, I'm not sure how to select only users that have 1 or more associated events.
I've found the answer:
User.find(:all, :joins => :events, :select => 'DISTINCT `users`.*')
Basically, the users.* restricts the result set to just the users table, and the DISTINCT keyword makes sure each user is only returned once.