Rails: find_by_sql uses the wrong ID - sql

I am using the following Ruby code:
sql = "SELECT * FROM
conversations
INNER JOIN member_users ON member_users.conversation_id = conversations.id
WHERE conversations.id = #{conversation.id}
AND member_users.user_id = #{user.id}"
cs = Conversation.find_by_sql(sql)
The query returns a single row. But, find_by_sql is returning the Conversation with the ID of the member_user, not the ID of the Conversation. This is because there are two "id" columns in the result, and find_by_sql seems to be using the wrong one.
Is there any way to prevent this without using SELECT conversations.* instead of SELECT *?
Why does this happen - why doesn't Rails use the first ID column it comes across? Is it because the row is returned in a hash?
More generally, in SQL - is there a way to differentiate between different columns with the same name? Or are these meant to be equivalent? I often get confused when doing joins which result in several "id" cols.
Thanks,
Louise

If you do a join in a find_by_sql query, ActiveRecord sometimes mixes up identically-named columns from the tables. You can hint ActiveRecord to correctly map by changing the SELECT clause to only include the table columns you are interested in.
So to fix your problem, change your query to begin with
"SELECT conversations.* FROM conversations ..."

I would find it like so
class MemberUser < ActiveRecord::Base
belongs_to :conversation_user
scope :users, ->(*u) {
where(user_id: u.flatten.compact.uniq)
}
scope :conversations, -> (*c) {
where(conversation_id: c.flatten.compact.uniq)
}
end
Conversation.joins(:member_users).merge(MemberUser.users(user))
As for your direct question, why are you trying to find the conversation when you already have it?

Related

Complex SQL query in Rails?

I need to select groups from all user's groups that do not belong to projects of the current user.
groups habtm projects
projects belongs_to user
groups habtm users
The problem is that a group can exist without a project. I understand how to make separate simple queries:
#groups = current_user.groups.includes(:groups_projects).where.('groups_projects.project_id' => nil)
#groups = current_user.groups.includes(:groups_projects).where.not('groups_projects.project_id' => current_user.projects.ids)
How to make it in one query?
UPDATE
According the answer I make the query:
#groups = current_user.groups.includes(:groups_projects).where("'groups_projects.project_id' = ? AND 'groups_projects.project_id' NOT IN (?)", nil, current_user.project_ids)
But now here is an error:
PG::InvalidTextRepresentation: ERROR: invalid input syntax for integer: "groups_projects.project_id"
UPDATE 2
Answer below is good, but it doesn't do the job for me, so I make it by different way:
#groups = current_user.groups - current_user.groups.joins(:projects).merge(current_user.projects)
Maybe it's no right way, but it works.
You can use SQL in where clauses representing the WHERE part of the underlying SQL statement. In your case it might look like
.includes(:groups_projects).where('groups_projects.project_id = ? OR groups_projects.project_id NOT IN (?)', nil, current_user.projects.ids).references(:groups_projects)

Query for Joining Multiple Associations

I managed to make query with joined table. I wonder how could I add another join table (e.g. publication_comments), count how many comments publication have, sum publication_comments_count with reviews_count and order in DESC order.
I have read rails guide and many example on stackoverflow, however still need somebody to assist on syntax and how exactly should I gather all pieces in one place. Thanks.
#publication = Publication.all(
joins: :reviews,
select: '"publications".*, count("reviews".id) as reviews_count',
group: '"publications".id',
order: "reviews_count DESC")
ActiveRecord allows you to just supply a string to the joins method. So you can just write raw SQL join conditions:
Publication.select("..., COUNT(publication_comments.id) as pub_count")
.join('JOIN reviews on (..) JOIN publication_comments')
.group('publications.id'

How to Sanitize the SQL in Rails?

I'm new to this RoR world,
I've many SELECT sql queries in my RoR Application, something like this
#replies = Offerreply.find_by_sql ("SELECT * FROM offerreplies WHERE
offer_id="+params [:offer_id])
Some are very simple like above and some are very complex JOINS. most of them are suffering from SQL Injection problem. So., How to Sanitize such SQL statements in RoR?
Edit: How to Handle same in SQL statements which has JOINS and Sub-queries? something like this
#to_be_approved=Beneficiary.find_by_sql("SELECT * FROM beneficiaries WHERE project_id="+params[:id]+" AND NOT id IN (SELECT beneficiaries.id FROM beneficiaries INNER JOIN beneficiaryloans ON beneficiaryloans.beneficiary_id=beneficiaries.id AND beneficiaryloans.hfi_id="+session[:id].to_s+" AND beneficiaries.status_id=4) AND cso_id IN(SELECT user_id FROM user_projects INNER JOIN users ON user_projects.user_id=users.id AND users.user_type_id=2)")
If you're using Rails 3 (as your tag says), you can do it like this.
#replies = Offerreply.where("offer_id = ?", params[:offer_id])
You can find more information at the Rails site.
edit: If you have more than one condition, you can do it like this.
#replies = Offerreply.where("offer_id = ? AND second = ?", params[:offer_id], params[:second])
edit2: And see Micha's answer for multiple joins.
Waynn Lue's answer is fine, but it doesn't show how to query on joined tables. You can do it like this:
Offerreply.joins(:offers).where('offers.id', params[:offer_id])
Or:
Offerreply.joins(:offers).where(:offers => { :id => params[:offer_id] })
Again: if you want to use Rails you really have to learn the Active Record Query Interface. Here's the paragraph on joins. Only use find_by_sql if there is no way of doing it via the "normal" interface.

Rails Inner Join not working but the SQL looks right

So I have 2 tables that are joined by an ID. I'm in rails console and I type:
Programmer.all(:joins=>:assignment)
the sql that is generated is:
SELECT `programmers`.* FROM `programmers` INNER JOIN `assignments` ON `assignments`.`programmer_id` = `programmers`.`id`
The output that is generated is the same as Programmer.all. Why doesn't it include the assignments data?
I believe I majorly overanalyzed your question. If you just want to join any available assignments to programmers, you're looking for:
Programmer.all(:include => :assignment)
Rails is designed so that :joins is used to perform things like sorting and grabbing certain records but still keep the query result to a minimum size -- meaning, :joins never actually includes the results from the joined table in the result.
Now here's my previous answer that assumes you want to perform an INNER JOIN to get only the programmers with assignments, but you also want that data. In that case, you have two options:
#1 - Use :select
Programmer.all(:select => '*', :joins => :assignment)
That will change the SQL to:
SELECT * FROM `programmers` INNER JOIN `assignments` ON `assignments`.`programmer_id` = `programmers`.`id`
Upside: You get the query you want and all the data is somewhere, at least.
Downside: assignments is assigned directly to the Programmer object and not to the proper place at Programmer.assignment.
#2 - Use a combination of :joins and :includes
Programmer.all(:joins => :assignment, :include => :assignment)
Which produces the SQL:
SELECT `programmers`.* FROM `programmers` INNER JOIN `assignments` ON `assignments`.`id` = `programmers`.`assignment_id`
SELECT `assignments`.* FROM `assignments` WHERE (`assignments`.`id` IN (?) )
Upside: All your data is in the right place now. You can refer to programmer.assignment without another query.
Downside: You are running that extra query in a lot of instances. I am fairly sure that Rails tries to optimize this when it needs to, though, and if not, it shouldn't cause you too much overhead.
Simply you can do like
Programmer.includes(:assignment)

Cannot get data from both tables in a join query using Rails 3

I am using Rail 3 and I am finding it very hard to do a join of two tables AND get access to data from both of them in the view. I only get access to one of the two. What is wrong with the below code? How should it look like?
Please note that I am using Rails 3.
#contacts = Profile.where("profiles.id = ?", #profile).includes(:contacts).order("lastname ASC")
I have also tried something like this
#contacts = Profile.joins('LEFT OUTER JOIN contacts ON contacts.friend_id = profiles.id').where("profiles.firstname LIKE :input OR profiles.lastname LIKE :input",{:input => "#{params[:keyword]}%"}).where("contacts.profile_id = #{params[:profile_id]}")
You are missing the select method
See this question solution: Rails 3 - select with Include? It works similar with a JOIN, allowing you to select fields from both tables but the results will be in a Profile object with Contact fields as virtual columns.