Primary key is null when left join is used - ruby-on-rails

When I use this request
#questions = Question.joins("left join answers as a on a.user_id = #{current_user.id} and a.question_id = questions.id").select('questions.*, a.*')
Question.id is null. Does anyone know why? Maybe it needs an alias or something like that.
My schema:
class Answer:
belong_to: User
belongs_to: Question
end
class User:
has_many: answers
end
class Question:
has_one: answer
end

The problem is that the answers table probably also has a column named id, which is shadowing the question's id. Although in the result set one column is called questions.id and the other a.id, active record just looks at the last part ('id')
There's no good way around this that I am aware of other than aliasing problematic columns from the answers table, which unfortunately means explicitly naming all of them (although you can of course automate this to an extent)

This is probably because of the ID columns of two tables conflicting with one another.
Update:
Simply changing the order of select columns will help. So instead of .select("questions.*", "answers.*"), try .select("answers.*", "questions.*").
Old answer:
Try the following:
# Assuming the following:
# Question.table_name == "questions"
# Answer.table_name == "answers"
question_columns = Question.column_names # or %w{questions.*}
answer_columns = Answer.column_names.map { |c| "answer_#{c}" }
columns = question_columns + answer_columns
#questions =
Question.
joins("LEFT OUTER JOIN answers ON (
answers.question_id = questions.id
AND
answers.user_id = #{current_user.id}
)").
select(*columns)
#questions.first.id #=> should return some integer
#questions.first.answer_id #=> may or may not return something
However, unless you absolutely need a LEFT JOIN along with those select-columns, it would be much cleaner to accomplish your task using the following:
class Answer
belongs_to :question
end
class User
has_many :answers
has_many :questions, through: :answers
end
current_user.questions

Related

Find records where association didnt already exists

how to get only records that isn't associated in Ebook model? Simply, i want to offer only ebooks that user didnt already have. I tried much solutions, one of best is:
Magazine.left_joins(:ebooks).where(ebooks: { id: nil })
but i dont know where to specify user_id
model Magazine
has_many :ebooks
has_many :users, :through => :ebook
model Ebook
belongs_to :user
belongs_to :magazine
model User
has_many :ebooks
Im new in rails, sorry for stupid question maybe.
#Hass
thank you, but this generate sql like this:
SELECT "magazines".* FROM "magazines"
INNER JOIN "ebooks" ON "magazines"."id" = "ebooks"."magazine_id"
LEFT OUTER JOIN "ebooks" "ebooks_magazines" ON "ebooks_magazines"."magazine_id" = "magazines"."id"
WHERE "ebooks"."user_id" = 1 AND "ebooks"."id" IS NULL
(WHERE "ebooks"."user_id" = 1 => this returns 0 records, because where filter doesnt find any row in table ebooks)
but i need something like this:
SELECT "magazines".*, ebooks.user_id FROM "magazines"
LEFT JOIN "ebooks" ON "magazines"."id" = "ebooks"."magazine_id" AND ebooks.user_id = 1
LEFT OUTER JOIN "ebooks" "ebooks_magazines" ON "ebooks_magazines"."magazine_id" = "magazines"."id"
WHERE ebooks.user_id IS NULL
this returns rows that im looking for but i dont know how to do this with rais and associations
You want to use a left_outer_join instead.
Magazine.left_outer_joins(:ebooks).where(ebooks: { id: nil })

How to write complex query in Ruby

Need advice, how to write complex query in Ruby.
Query in PHP project:
$get_trustee = db_query("SELECT t.trustee_name,t.secret_key,t.trustee_status,t.created,t.user_id,ui.image from trustees t
left join users u on u.id = t.trustees_id
left join user_info ui on ui.user_id = t.trustees_id
WHERE t.user_id='$user_id' AND trustee_status ='pending'
group by secret_key
ORDER BY t.created DESC")
My guess in Ruby:
get_trustee = Trustee.find_by_sql('SELECT t.trustee_name, t.secret_key, t.trustee_status, t.created, t.user_id, ui.image FROM trustees t
LEFT JOIN users u ON u.id = t.trustees_id
LEFT JOIN user_info ui ON ui.user_id = t.trustees_id
WHERE t.user_id = ? AND
t.trustee_status = ?
GROUP BY secret_key
ORDER BY t.created DESC',
[user_id, 'pending'])
Option 1 (Okay)
Do you mean Ruby with ActiveRecord? Are you using ActiveRecord and/or Rails? #find_by_sql is a method that exists within ActiveRecord. Also it seems like the user table isn't really needed in this query, but maybe you left something out? Either way, I'll included it in my examples. This query would work if you haven't set up your relationships right:
users_trustees = Trustee.
select('trustees.*, ui.image').
joins('LEFT OUTER JOIN users u ON u.id = trustees.trustees_id').
joins('LEFT OUTER JOIN user_info ui ON ui.user_id = t.trustees_id').
where(user_id: user_id, trustee_status: 'pending').
order('t.created DESC')
Also, be aware of a few things with this solution:
I have not found a super elegant way to get the columns from the join tables out of the ActiveRecord objects that get returned. You can access them by users_trustees.each { |u| u['image'] }
This query isn't really THAT complex and ActiveRecord relationships make it much easier to understand and maintain.
I'm assuming you're using a legacy database and that's why your columns are named this way. If I'm wrong and you created these tables for this app, then your life would be much easier (and conventional) with your primary keys being called id and your timestamps being called created_at and updated_at.
Option 2 (Better)
If you set up your ActiveRecord relationships and classes properly, then this query is much easier:
class Trustee < ActiveRecord::Base
self.primary_key = 'trustees_id' # wouldn't be needed if the column was id
has_one :user
has_one :user_info
end
class User < ActiveRecord::Base
belongs_to :trustee, foreign_key: 'trustees_id' # relationship can also go the other way
end
class UserInfo < ActiveRecord::Base
self.table_name = 'user_info'
belongs_to :trustee
end
Your "query" can now be ActiveRecord goodness if performance isn't paramount. The Ruby convention is readability first, reorganizing code later if stuff starts to scale.
Let's say you want to get a trustee's image:
trustee = Trustee.where(trustees_id: 5).first
if trustee
image = trustee.user_info.image
..
end
Or if you want to get all trustee's images:
Trustee.all.collect { |t| t.user_info.try(:image) } # using a #try in case user_info is nil
Option 3 (Best)
It seems like trustee is just a special-case user of some sort. You can use STI if you don't mind restructuring you tables to simplify even further.
This is probably outside of the scope of this question so I'll just link you to the docs on this: http://api.rubyonrails.org/classes/ActiveRecord/Base.html see "Single Table Inheritance". Also see the article that they link to from Martin Fowler (http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html)
Resources
http://guides.rubyonrails.org/association_basics.html
http://guides.rubyonrails.org/active_record_querying.html
Yes, find_by_sql will work, you can try this also:
Trustee.connection.execute('...')
or for generic queries:
ActiveRecord::Base.connection.execute('...')

Conditional has_many in rails 2

My app has applicants with many question_sections with many questions with many answers.
Here I am returning these for an applicant
#question_sections = QuestionSection.find(
:all, :include => {:questions => :answers},
:conditions => ['answers.application_form_id is NULL OR answers.application_form_id = ?', #application_form.id],
:order => 'question_sections.list_index ASC, questions.list_index ASC'
)
What I'd like to do is return a row even if the answer row is null (ie, a left join on answer) so we can identify questions that have not been answered rather than omitting them entirely (which is what happens currently.)
I think the issue might be that the answer belongs to both the question and the applicant;
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :application_form, :touch => true
So, is pseudo code I'd like 'belongs_to :application_form IF :application_form is not null' - to retain any potential associations.
While I can write all this with SQL fairly easily, I'd like to let rails handle that and fix the model.
In SQL I want to go from this
FROM `question_sections`
LEFT OUTER JOIN `questions` ON questions.question_section_id = question_sections.id
LEFT OUTER JOIN `answers` ON answers.question_id = questions.id
WHERE ((answers.application_form_id IS NULL
OR answers.application_form_id = 656))
to this
FROM `question_sections`
LEFT OUTER JOIN `questions` ON questions.question_section_id = question_sections.id
LEFT JOIN `answers` ON answers.question_id = questions.id AND answers.application_form_id = 656
// No WHERE
Thanks.
EDIT
What I need, I think, is a lambda on the has_many association. Something like;
has_many :answers_and_null_answers, :whatever => lambda ( a = Answer.find(n); if a.nil? a = Answer.new; )
Obviously, thats just messy pseudo - but is this possible?
EDIT #2
Aha! first_or_create does what I want, but seems you can'd do it on :includes. I'm assuming there is something I can do to the model to allow this?
The solution I went with was to add raw SQL to find() call. Wrote my joins manually and then edited the view to suit the different output.
Not what I would have preferred to do, but it works.

JOIN statement in Rails with OR condition across 2 tables

UPDATE 2:
This looks much better:
Comp.includes(:members).where('members.member_email = ? OR comps.user_id = ?', current_user.email,current_user.id)
UPDATE:
This seems to work but is there a more elegant way to do this in Rails? I feel like there must be.
#my_comps = Comp.joins('LEFT OUTER JOIN teams ON teams.comp_id = comps.id LEFT OUTER JOIN members ON members.team_id = teams.id').where('members.member_email = ? OR comps.user_id = ?', current_user.email,current_user.id).group('comps.id')
ORIGINAL:
My model associations are:
Comp.rb
has_many :teams
has_many :members, :through => :teams
Team.rb
belongs_to :comp
has_many :members
Member.rb
belongs_to :team
I want to write a query that finds all of the Comps where comps.user_id equals a particular value OR members.member_email equals a particular value for any of the members of that Comp.
I unsuccessfully tried this:
#my_comps = Comp.joins(:members).where('members.member_email = ? OR comps.user_id = ?', email, id)
There are 2 issues with the results returned: 1) it returns duplicate Comps where member_email is equal to the condition and 2) it does NOT return the Comps where the user_id is equal to the condition. I solved problem 1 by adding .group('id') to the end of this code but I feel like there is likely a better way to do it, and more importantly it doesn't solve problem 2.
Any advice on how to approach this differently? Thanks so much.
changed the suggestion, didn't know that .join only uses "INNER JOIN" in newer Rails (having an old version).
The final suggestions was: use .include instead of .join

Arel: order by association count

Here is what I'm trying to do
class Question
has_many :votes
end
class Vote
belongs_to :question
end
I want to find all questions ordered by the number of votes they have. I want to express this in Arel (in Rails 3) without using any counter caches.
Is there any way of doing this ?
Thanks.
Try next one:
Question.joins(:votes).select("questions.id, *other question coulmns*, count(votes.id) as vote_count").order("vote_count DESC").group("questions.id")
Try this:
Question.select("questions.*, a.vote_count AS vote_count").
joins("LEFT OUTER JOIN (
SELECT b.question_id, COUNT(b.id) AS vote_count
FROM votes b
GROUP BY b.question_id
) a ON a.question_id = questions.id")
Solution is DB agnostic. Make sure you add an index on the question_id column in the votes table( you should add the index even if you don't use this solution).

Resources