I have a question and answer model, answers belong to questions and a question has many answers. Answers have a boolean :correct column so answers can be marked as correct.
The code below checks whether a question has any correct answers and then displays a different div accordingly. The code works however it performs a laborious count query on the database which I would like to avoid.
Is there a way to rewrite this query or is it best to have a column in the questions table which is toggled to true when an answer is marked as correct?
<div class="<%= question.answers.count(conditions: ['correct = ?', true]) == 1 ? 'correct-answer' : 'no-correct-answer' %>">
<%= question.answers_count %>
</div>
EDIT
Thanks to the guys below who posted answers, however even with an questions_id index on the answers table, both queries were using a count query which i want to avoid, as there is likely to be quite a few answers to have to loop through.
My solution was to create a :correct boolean column on the question table, and when an answer is marked/toggled as correct it toggled this column as well, so when rendering a view i didnt have to perform any dynamic SQL queries.
answer.rb
def toggle_correct(attribute)
toggle(attribute).update_attributes({attribute => self[attribute]})
end
def toggle_question_correct
self.question.toggle_correct(:correct)
end
You're counting how many correct answers there is for the question (i.e. check every answer), whereas you only need to check if a correct answer exists (i.e. stop as soon as the correct answer is spotted). This could be written with exists?
question.answers.exists?(:correct => true)
Unless you have a lot of answers for each question, it should not change significantly the response time. You said this part of code was 'laborious', you should check that you created an index on the column question_id of the table answers. Without it, question.answers generate a full scan of the answers table.
I would suggest that you create two scopes on Answer model
scope :correct, -> { where(correct: true) }
scope :correct, -> { where(correct: false) }
then you may select count correnct answers for question like that:
question.answers.correct.count
You might also want to create a method on Question
def has_correct_answer?
! answers.correct.count.zero?
end
If you're showing many questions and answers on single page I would suggest going for AR#includes with where clause so it will make less SQL queries (http://guides.rubyonrails.org/active_record_querying.html#eager-loading-multiple-associations)
Related
I have two tables :
Question table where contains :
And then I have another table that is called
UserAnswerQuestion that contains :
That has reference to the question via question_id
The thing is that every time user answers a question I create a row in db saying :
question_id user_id has answered it and then I check if the answer is correct or not, if it's correct I put passed as a True, otherwise I put passed as a false.
Ok, from now is all ok, the thing that I can not get is I'm creating a method that returns to me the nextQuestion not answered, but I'm not able to do this method correctly.
What I'd like is to have a query that first return all questions that user have not answered yet, and then another query that returns just a question that user has not answered yet.
Note: This shown attribute was added because I want first to iterate over other questions instead of showing the ones that user has failed recently.
Is the question clear?
What I've tried?
I've tried to get the questions List<UserAnswerQuestion> findAllByUserEmailAndPassedFalseAndShownFalse(String email); but it doesn't work because with this table I can know every answer in every try that user does, so for example if I have a question that have 4 answers and 1 correct, this table can contain 4 row with the same question_question_id because the user perhaps has failed 3 times and then at the fourth it answers correctly.
Edit
I have already the questions that user can answer with this method :
public List<Question> getAllQuestionsThatUserCanAnswer(String email, Long topic){
List<Question> mList = this.questionRepository.findAllByTopicParentId(topic);
if(mList==null) return null;
List<UserAnswerQuestion> userAnswerQuestionList = this.userAnswerQuestionRepository.findAllByUserEmail(email);
for (UserAnswerQuestion userAnswerQuestion : userAnswerQuestionList) {
if(mList.contains(userAnswerQuestion.getQuestion())){
if(userAnswerQuestion.getPassed()){
mList.remove(userAnswerQuestion.getQuestion());
}
}
}
return mList;
}
Now I'm trying to do the getNext of this method but I do not know how to iterate over it.
Focus on SQL query that solve your problem
select * from Question q
left join Answer a on q.id = a.question_id
where a.id is null OR (a.passed != 'true' AND a.user_id = 1)
order by a.passed
Check scratch here
In my controller method for the the index view I have the following line.
#students_instance = Student.includes(:memo_tests => {:memo_target => :memo_level})
So for each Student I eager-load all necessary info.
Later on in a .map block, I call the .where() method on one of the relations as shown below.
#all_students = #students_instance.map do |student|
...
last_pass = student.memo_tests.where(:result => true).last.created_at.utc
difference_in_weeks = ((last_pass.to_i - current_date.to_i) / 1.week).round
...
end
This leads to a single SQL query for each student. And since I have over 300+ students, leads to very slow load times and over 300+ SQL queries.
Am I right in thinking that this is caused by the .where() method. I think this because I have checked everything else and these are the two lines that cause all of the queries.
More importantly, is there a better way to do this that reduces these queries to a single query?
The moment you ask where, the statement is translated to a query. Normally, the result should be sql-cached...
Anyway, in order to be sure, you can instead add programming logic to your statement. That way, you are not requesting a NEW sql statement.
last_pass = student.memo_tests.map {|m| m.created_at if m.result}.compact.sort.last
EDIT
I see the OP's question does not require sorting... So, leaving the sorting out:
last_pass = student.memo_tests.map {|m| m.created_at if m.result}.compact.last
compact is required to remove nil results from the array.
I'm wondering if anyone has experience using Ransack with HABTM relationships. My app has photos which have a habtm relationship with terms (terms are like tags). Here's a simplified explanation of what I'm experiencing:
I have two photos: Photo 1 and Photo 2. They have the following terms:
Photo 1: A, B, C
Photo 2: A, B, D
I built a ransack form, and I make checkboxes in the search form for all the terms, like so:
- terms.each do |t|
= check_box_tag 'q[terms_id_in][]', t.id
If I use: q[terms_id_in][] and I check "A, C" my results are Photo 1 and Photo 2. I only want Photo 1, because I asked for A and C, in this query I don't care about B or D but I want both A and C to be present on a given result.
If I use q[terms_id_in_all][] my results are nil, because neither photo includes only A and C. Or, perhaps, because there's only one term per join, so no join matches both A and C. Regardless, I want just Photo 1 to be returned.
If I use any variety of q[terms_id_eq][] I never get any results, so I don't think that works in this case.
So, given a habtm join, how do you search for models that match the given values while ignoring not given values?
Or, for any rails/sql gurus not familiar with Ransack, how else might you go about creating a search form like I'm describing for a model with a habtm join?
Update: per the answer to related question, I've now gotten as far as constructing an Arel query that correctly matches this. Somehow you're supposed to be able to use Arel nodes as ransackers, or as cdesrosiers pointed out, as custom predicates, but thus far I haven't gotten that working.
Per that answer, I setup the following ransack initializer:
Ransack.configure do |config|
config.add_predicate 'has_terms',
:arel_predicate => 'in',
:formatter => proc {|term_ids| Photo.terms_subquery(term_ids)},
:validator => proc {|v| v.present?},
:compounds => true
end
... and then setup the following method on Photo:
def self.terms_subquery(term_ids)
photos = Arel::Table.new(:photos)
terms = Arel::Table.new(:terms)
photos_terms = Arel::Table.new(:photos_terms)
photos[:id].in(
photos.project(photos[:id])
.join(photos_terms).on(photos[:id].eq(photos_terms[:photo_id]))
.join(terms).on(photos_terms[:term_id].eq(terms[:id]))
.where(terms[:id].in(term_ids))
.group(photos.columns)
.having(terms[:id].count.eq(term_ids.length))
).to_sql
end
Unfortunately this doesn't seem to work. While terms_subquery produces the correct SQL, the result of Photo.search(:has_terms => [2,5]).result.to_sql is just "SELECT \"photos\".* FROM \"photos\" "
With a custom ransack predicate defined as in my answer to your related question, this should work with a simple change to your markup:
- terms.each do |t|
= check_box_tag 'q[id_has_terms][]', t.id
UPDATE
The :formatter doesn't do what I thought, and seeing as how the Ransack repo makes not a single mention of "subquery," you may not be able to use it for what you're trying to do, after all. All available options seem to be exhausted, so there would be nothing left to do but monkey patch.
Why not just skip ransack and query the "photos" table as you normally would with active record (or even with the Arel query you now have)? You already know the query works. Is there a specific benefit you hoped to reap from using Ransack?
In rails 3, I would like to do the following:
SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id")
This works, but i get the following from the DB:
[{"some_other_connection_id":254},{"some_other_connection_id":315}]
Now, those id-s are the ones I need, but I am uncapable of making a query that only gives me the ids. I do not want to have to itterate over the resulst, only to get those numbers out. Are there any way for me to do this with something like :
SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id").values()
Or something of that nautre?
I have been trying with the ".select_values()" found at Git-hub, but it only returns "some_other_connection_id".
I am not an expert in rails, so this info might be helpful also:
The "SomeModel" is a connecting table, for a many-to-many relation in one of my other models. So, accually what I am trying to do is to, from the array of IDs, get all the entries from the other side of the connection. Basicly I have the source ids, and i want to get the data from the models with all the target ids. If there is a magic way of getting these without me having to do all the sql myself (with some help from active record) it would be really nice!
Thanks :)
Try pluck method
SomeModel.where(:some => condition).pluck("some_field")
it works like
SomeModel.where(:some => condition).select("some_field").map(&:some_field)
SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id").map &:some_other_connection_id
This is essentially a shorthand for:
results = SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id")
results.map {|row| row.some_other_connection_id}
Look at Array#map for details on map method.
Beware that there is no lazy loading here, as it iterates over the results, but it shouldn't be a problem, unless you want to add more constructs to you query or retrieve some associated objects(which should not be the case as you haven't got the ids for loading the associated objects).
I previously asked a question regarding pulling specific items out of a database if they contained a specific word in their string, someone kindly offered the following which did just the job:
def SomeModel < ActiveRecord::Base
scope :contains_city,
lambda { |city| where("some_models.address LIKE ?","%"+city+"%" ) }
end
However, I have some instances where I would like to do the opposite, i.e. pull out all the items which do not have the specified word in their string. Is there a way to do a NOT LIKE function? I have prevously seen people use '!=' for a NOT EQUALS, but have had no success along these lines for the LIKE function. Is there an equivalent or is it best to iterate through the database putting items in 2 separate databases based on whether they satisfy the LIKE condition?
You could try NOT LIKE in your query; MySQL supports this.
http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html