Find all associated objects by specific condition - ruby-on-rails

class QuestionGroup < ActiveRecord::Base
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :question_group
has_many :question_answers
has_many :question_users_answers, :through => :question_answers, :source => :user_question_answers
def self.questions_without_answers(user_id)
select {|q| q.question_users_answers.where(:user_id=>user_id).empty?}
end
end
class QuestionAnswer < ActiveRecord::Base
belongs_to :question
has_many :user_question_answers
end
I need find all Questions if they have no user answers I did it by class method self.questions_without_answers(user_id)
But how can I find all QuestionGroups where present questions_without_answers and for particular user?
P.S: I need to find all unanswered questions and all groups that own these questions, can I do it by find or named-scope?
UPDATED:
def self.groups_without_answers(user_id)
questions_ids = Question.questions_without_answers(user_id).map {|q| q.id}
all(:conditions => "id in (select distinct question_group_id from questions where id in (#{questions_ids.join(',')}))")
end
But I think it is not good or maybe I wrong?

class QuestionGroup < ActiveRecord::Base
has_many :questions
def self.without_answers(user_id)
joins(%"inner join questions on question_groups.id = questions.question_group_id
inner join question_answers
on question_answers.question_id = questions.id
inner join question_groups
on question_answers.question_users_answers_id = question_users_answers.id").where("user_question_answers.user_id" => user_id).select { |qq| ... }
end
end
end
You can change some of the inner joins to left out join to pick up records where the table you are joining to doesn't have a match, for instance where there is no answer. The fields of the table you are joining to will have NULL values for all the fields. Adding a where id is null will even filter to just the questions with no answers.
Keep in mind that this is just an alternative technique. You could programmatically solve the problem simple with:
class QuestionGroup
def self.question_groups_without_answers(user_id)
select {|qq| qq.question_users_answers.where(:user_id=>user_id).empty?}.map{ |qq| qq.question_group }
end
end
An advantage of doing the joins is that the database does all the work, and you don't send several SQL queries to the database, so it can be much faster.

Related

Rails, active record. Joining four tables together

I am learning ruby on rails and I've run into an active record problem I have been unable to solve. I am trying to inner join four tables together and display data from them.
I have confirmed that my database schema is correct by running the following SQL style query. It returns the data I expect it to.
select * from sailings
INNER JOIN travelling_parties on sailings.id = travelling_parties.sailing_id
INNER JOIN party_registers on travelling_parties.id = party_registers.travelling_party_id
inner JOIN users on party_registers.user_id = users.id
where users.id = 8
After reading through the great docs on guides.rubyonrails.org, I am still unable to find the solution. The below query is what I've come up with in my controller, but it doesn't seem to be return what I need it to.
#sailing = Sailing.joins(:travelling_parties => [{:party_registers => :user}])
I would appreciate anyone who could steer me in the right direction.
Thanks
EDIT 1: Here are my models. I am attempting to check user.id = current_user.id and display data based on that.
class Sailing
has_many :travelling_parties
end
class TravelingParty
has_many :party_registers
has_many :users, through: :party_registers
end
class PartyRegister
belongs_to :user
belongs_to :travelling_party
end
class User
has_many :party_registers
has_many :travelling_parties, through: :party_registers
end
EDIT 2: The following query gave me my intended results
#sailing = Sailing.joins(:travelling_parties => {:party_registers => :user}).where("users.id" => current_user.id)
Thanks for the responses.
Your answer is close to correct assuming that you have defined the associations.
class Sailing
belongs_to :traveling_party
end
class TravelingParty
belongs_to :party_register
end
class PartyRegister
belongs_to :user
end
The
#sailing = Sailing.joins(:travelling_parties => {:party_registers => :user})

Selecting from joined tables in ActiveRecord

I have the following models
class Forum < ActiveRecord::Base
has_many :topics
end
class Topic < ActiveRecord::Base
belongs_to :forum
has_many :posts
end
class Posts < ActiveRecord::Base
belongs_to :topic
belongs_to :user
end
I would like to join these associations (with some conditions on both topics and posts), so I did Topic.joins(:forum, :posts).where(some condition), but I would like to access the attributes in all three tables. How can I achieve this?
You can do a multiple join like that. All three tables are available, you just have to specify in the wheres.
Topic.joins(:forum, :posts).where('topics.foo' => 'bar').where('posts.whizz' => 'bang)
The two caveats are that that is a multiple inner join so the topic will need to have both forum and post to show up at all and that the where's are anded together. For outer joins or 'or' logic in the where's you will have to get fancier. But you can write sql for both .joins and .where to make that happen. Using .includes instead of .joins is another way to get outer join behavior--but of course you are then maybe loading a lot of records.
To clarify the comment below, and show the improved (more rails-y) syntax where syntax suggested by another user:
topics = Topic.joins(:forum, :posts).where(topic: {foo: 'bar'}).where(posts: {whizz: 'bang'})
topics.each do |t|
puts "Topic #{t.name} has these posts:"
t.posts.each do |p|
puts p.text
end
end

Difficulty sorting entries based on attributes of related models

I am having trouble implementing a feature that allows users to see questions (my model) sorted or filtered based on various attributes belonging to it (i.e. was the question answered? how many answered per question, etc), which would be based on the Question model's attributes, or that of attributes of related models to Question.
I have the following models:
class Question < ActiveRecord::Base
belongs_to :course
belongs_to :user
has_many :answers, inverse_of: :question
belongs_to :accepted_answer, class_name: :answer, foreign_key: :accepted_answer_id
default_scope order: 'questions.created_at DESC'
end
class Answer < ActiveRecord::Base
belongs_to :user
belongs_to :question, inverse_of: :answers
has_many :a_votes
default_scope order: 'answers.created_at DESC'
def accepted?
return false if new_record?
question.try( :accepted_answer_id ) == id
# an alternative is to use question.try( :accepted_answer ) == self
end
end
What I would be add are sorts or filters in the controller such as "See only answered questions", where only questions that has question.accepted_answer == true. What would be the best way to achieve this? And are there any guides that I should consult on ActiveRecord filtering/sorting that I could use for future reference?
Thanks!!
Addendum
I am displaying the questions as rendered _question.html.erb and calling it via the show function of question's parent, Group (so, each Group will has_many questions)
class CoursesController < ApplicationController
def show
#course = Course.find(params[:id])
# #questions = #course.questions.all this is the default selection
#questions = #course.questions.by_answer_count #maybe Im not calling the scope correctly (and sorry if I am making a noob mistake..)
end
#There are other methods not shown
end
I achieve this sort of thing by defining a scope on the parent model using joins and grouping.
For example, this scope would order the questions by number of answers (descending).
class Question
scope :by_answer_count, -> {
joins(:answers).reorder("count(answers.id) DESC").group(:id)
}
end
I hope that helps.

Rails active record query: Find records on one side of a has_many :through that haven't been used with a particular record on the other side

I have a has_many :through relationship set up like so
class Situation < ActiveRecord::Base
has_many :notifications
has_many :notiftypes, through: :notifications
end
class Notification < ActiveRecord::Base
belongs_to :situation
belongs_to :notiftype
end
class Notiftype < ActiveRecord::Base
has_many :notifications
has_many :situations, through: :notifications
end
So, a Situation has many Notifications, which can be of many types (Notiftype).
My problem is trying to query for the notiftypes that have not been set for a particular situation.
Want to find records with no associated records in Rails 3
The answers in that question get me close, but only to the point of finding Notiftypes that have not been set AT ALL.
If this were the standard :situation has_many :notiftypes I could just do a Left Outer Join like so
myquery = Notiftype.joins('LEFT OUTER JOIN situations ON situations.notiftype_id = notiftype.id').where('notiftype_id IS NULL')
but I'm really not sure how to do this with the intermediate table between them.
I have been trying consecutive joins but it's not working. I'm not sure how to join the two separated tables.
Can anyone explain the right way to query the db? I am using SQLite, Rails 3.1, Ruby 1.9.2 right now, but likely Postgresql in the future.
Try this:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= proxy_owner.notiftype_ids.empty? ?
Notiftype.all :
Notiftype.where("id NOT IN (?)", proxy_owner.notiftype_ids)
end
end
end
Now to get the missing Notiftype
situation.notiftypes.missing
If you want to further optimize this to use one SQL rather than two you can do the following:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= Notiftype.joins("
LEFT OUTER JOIN (#{proxy_owner.notiftypes.to_sql}) A
ON A.id = notiftypes.id").
where("A.id IS NULL")
end
end
end
You can access the missing Notifytypes as:
situation.notiftypes.missing

How to filter by more than 1 habtm association

I'm pretty new at Rails, so don't kill me if this a stupid question =P
I have the following models:
class Profile < ActiveRecord::Base
has_and_belongs_to_many :sectors
has_and_belongs_to_many :languages
class Sector < ActiveRecord::Base
has_and_belongs_to_many :profiles
end
class Language < ActiveRecord::Base
has_and_belongs_to_many :profiles
end
I'm looking for an elegant way (without writing sql joins or anything, if possible) to get all the profiles that have a particular sector and a particular language.
I've googled but all I could find is how to do it for 1 habtm, but I need it for 2.
All I have is the following:
def some_method(sector_id, language_id)
Sector.find(sector_id).profiles
end
But I don't know then how to add the filter by language_id without messing with joins conditions or writing sql, and of course, all in one query... Is there a clean/elegant way to do this?
Thanks!
In your example above you've already generated 2 sql requests,
first Sector.find(#id) (select on
sectors table to get record
with id == #id)
second .profiles (select on profiles
table to get all profiles with
following sector - in this select
you already have inner join
profiles_selectors on
profiles_selectors.profile_id =
profiles.id generated automatically by rails)
I hope this is what you are looking for: (but I use :joins key)
class Profile < ActiveRecord::Base
has_and_belongs_to_many :sectors
has_and_belongs_to_many :languages
def self.some_method(language_id, sector_id)
all(:conditions => ["languages.id = ? and sectors.id = ?", language_id, sector_id], :joins => [:languages, :sectors])
end
end
Result of this method is 1 sql query and you get profiles filtered by language and sector.
Best regards
Mateusz Juraszek
Try this:
Profile.all(:joins => [:sectors, :languages],
:conditions => ["sectors.id = ? AND languages.id ?", sector_id, language_id])

Resources