I really don't understand some tutorial code despite running the equivalents of it on a console (which don't work) and there's no explanation.
This comes from the Rails guides and I really like to understand everything that I read
Article.where(author: author)
Author.joins(:articles).where(articles: { author: author })
The (author: author) part is where I get lost. I mean does it do a self join? If that's the case I can't do it on my console with the same syntax.
And if author: author means articles.author and Author.author (which would be weird because ambiguity.
Thanks, sorry if this has been posted before.
Article.where(author: author)
^- Select Article records by the given author.
Select Article records where the author_id (probably) column equals the "id of author". Now author (after the :) could be a local variable or it could be a method call returning an 'author' object. Either way, it's probably an instance of an Author model class that responds to an id method call.
Author.joins(:articles).where(articles: { author: author })
^- Select Author records for authors that have an Article. Again the author here (after the :) is a local variable or a method call. The articles: { author: author } is just a convenient way to put some criteria on the join.
Once you get the correct author variable sorted, add a .to_sql to the end of the method call chain in your console to see what SQL is being generated. That should help you understand what's going on.
Related
I am pretty new in rails and honestly, I am struggling with queries even after multiple researchs.
Here is my simple schema:
So basically, a question has many options, an option belongs to a question and has many answer, and an answer belongs to an option and has many users.
I don't think it s necessary to post the models code since it is just like i mentioned above.
What i would like to do is given a question option, see if a particular user already checked it (so look in the answer table if there is a row matching a given id_option, user_id and user_type). So in my haml loop, when displaying the different question option, i'm calling a method of my question_option model just like this :
- question.question_option.all.each do |option|
#{option.title}
.check
- if option.selected_by(current_actor)
= check_box_tag(option.id, "checked",true, class: 'styled-checkbox')
- else
= check_box_tag(option.id, "checked",false, class: 'styled-checkbox')
and the method called :
def selected_by(answerer)
answer_match =
::Vacancies::QuestionOption
.joins(:answers)
.where(answerer_id: answerer.id, answerer_type:answerer.type )
response = answer_match.find(self.id)
return response
end
This method is located in my QuestionOption model and leads to no errors but it s not working ever.
Can you help me transform this query to make it work with ActiveRecord ? Thanks
Try the below code. It checks if there are any answers by the user passed in the params on the question. I think this is what you intended to do as well-
def selected_by(answerer)
answers =
Answer.where(answerer_id: answerer.id, answerer_type:answerer.type, id_option: self.id)
answers.exists?
end
I have a model (News) associated with another model (Category), so in News model i have:
has_and_belong_to_many :news_categories, join:table: 'news_categories_news'
I want to take all news with own categories, so:
News.find(/*conditions*/).includes(:news_categories)
If I check in console I see the right inner join query, but when I call
#news.news_categories
(Where news is a single news in the result array) if I check in console I see another query to take the categories for the current news, how can I avoid this redundant query?
p.s: sorry for my english...
First of all, .includes can't work when chained after .find. Reason - find will not return the ActiveRecord::Relation which is necessary for relational chaining; it will rather return the matching News object or error.
You should do:
#all_news = News.includes(:news_categories).where(id: 1)
#news = #all_news.first
#news.news_categories # shouldn't invoke new query
Thank you to all but i resolved with eager_load()
It's generate just once query!
Scenario: I have a quiz type setup where Questions have many Answers and also have a Response provided by the user (omitted). A response has a selected attribute to indicate the user's choice and a correct? method that compares selected with the correct_answer.
Code: is here in this GitHub repo, along with seed data. Quick links to:
Question.rb
Answer.rb
Response.rb
Problem: I want to return all responses for a question that are correct however, unsaved records are not included.
I've tried a couple of different ways, as you'll see in the code including scope, question.correct_responses and also inverse_of (which I've read is supposed to be automatic now) - but to no avail.
Basically, I'm expecting following code to return 1, not 0.
q=Question.first
r=q.responses.build
r.selected = q.correct_answer
q.responses.correct.size # => 0??! wtf man!?
Your assistance is greatly appreciated.
When you use a scope you're going to the database.
Since you aren't saving the response, you don't want to go to the database. Instead, you should use something like the line below, which will select all of the question's "correct" responses and then count them.
q.responses.select { |r| r.correct? }.size
EDIT: or the short syntax for select:
q.responses.select(&:correct?).size
I am working on an app that allows Members to take a survey (Member has a one to many relationship with Response). Response holds the member_id, question_id, and their answer.
The survey is submitted all or nothing, so if there are any records in the Response table for that Member they have completed the survey.
My question is, how do I re-write the query below so that it actually works? In SQL this would be a prime candidate for the EXISTS keyword.
def surveys_completed
members.where(responses: !nil ).count
end
You can use includes and then test if the related response(s) exists like this:
def surveys_completed
members.includes(:responses).where('responses.id IS NOT NULL')
end
Here is an alternative, with joins:
def surveys_completed
members.joins(:responses)
end
The solution using Rails 4:
def surveys_completed
members.includes(:responses).where.not(responses: { id: nil })
end
Alternative solution using activerecord_where_assoc:
This gem does exactly what is asked here: use EXISTS to to do a condition.
It works with Rails 4.1 to the most recent.
members.where_assoc_exists(:responses)
It can also do much more!
Similar questions:
How to query a model based on attribute of another model which belongs to the first model?
association named not found perhaps misspelled issue in rails association
Rails 3, has_one / has_many with lambda condition
Rails 4 scope to find parents with no children
Join multiple tables with active records
You can use SQL EXISTS keyword in elegant Rails-ish manner using Where Exists gem:
members.where_exists(:responses).count
Of course you can use raw SQL as well:
members.where("EXISTS" \
"(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
count
You can also use a subquery:
members.where(id: Response.select(:member_id))
In comparison to something with includes it will not load the associated models (which is a performance benefit if you do not need them).
If you are on Rails 5 and above you should use left_joins. Otherwise a manual "LEFT OUTER JOINS" will also work. This is more performant than using includes mentioned in https://stackoverflow.com/a/18234998/3788753. includes will attempt to load the related objects into memory, whereas left_joins will build a "LEFT OUTER JOINS" query.
def surveys_completed
members.left_joins(:responses).where.not(responses: { id: nil })
end
Even if there are no related records (like the query above where you are finding by nil) includes still uses more memory. In my testing I found includes uses ~33x more memory on Rails 5.2.1. On Rails 4.2.x it was ~44x more memory compared to doing the joins manually.
See this gist for the test:
https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1
where.missing (Rails 6.1+)
Rails 6.1 introduces a new way to check for the absence of an association - where.missing.
Please, have a look at the following code snippet:
# Before:
Post.left_joins(:author).where(authors: { id: nil })
# After:
Post.where.missing(:author)
And this is an example of SQL query that is used under the hood:
Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL
As a result, your particular case can be rewritten as follows:
def surveys_completed
members.where.missing(:response).count
end
Thanks.
Sources:
where.missing official docs.
Pull request.
Article from the Saeloun blog.
Notes:
where.associated - a counterpart for checking for the presence of an association is also available starting from Rails 7.
See offical docs and this answer.
I am trying to return all groups created by a user. All of the groups are associated with the user id. When I run a find_by query it only returns the first result. Is there a way for it to return multiple?
Thanks in advance
I'm writing a separate answer because I can't comment on James Lowrey's answer as I don't have 50 points.
find_all_by is deprecated (Ruby 4.2).
To get a list of active records from models, do:
Model.where(attribute_name: val)
For example, to find all records in Vehicle table (having column name "model_name") such that the value of model_name is "Audi", do
#vehicles = Vehicle.where(model_name: "Audi")
You can iterate through these like so:
<% #vehicles.each do |vehicle| %>
Change find_by to find_all_by and it will return all matching results.
I think find_all_by may be deprecated now (at least, I couldn't get it to work). I believe the 'where' function is now recommended
where("name LIKE ?","%#{search}%")
where(instructor_id: params[:choosen_instructor_id])
where ("id = ?",id_val)
In case you didn't know, the ? and parameter list is to prevent SQL injections
User class has id as primary key.
which is stored as user_id in Group class
Then to find all groups associated with that user could be found as.
#groups=Group.find_all_by_user_id(user_id)