Rails, active record. Joining four tables together - ruby-on-rails

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})

Related

RoR Activerecord: joining query with two associations that share a model

I have a model called flightleg:
class FlightLeg < ActiveRecord::Base
....
belongs_to :departure_airport, :class_name => "Airport"
belongs_to :arrival_airport, :class_name => "Airport"
end
And I want to do a query on it like this:
Flight.joins(:airline, flight_legs: [:departure_airport, :arrival_airport]).where('departure_airport.icao_code = YBBN')
Of course, this doesn't work. Here is a gist of the error message:
https://gist.github.com/emilevictor/b1b7d18d5cede597c6be
I am trying to figure out how to get it all to work nicely, and be able to refer to fields of the departure and arrival airports in my query.
There's no table 'departure_airports', but in the gist we see
INNER JOIN "airports" ON "airports"."id" = "flight_legs"."departure_airport_id"
so can you try ('airports.icao_code = YBBN') ?

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

Retrieve data from join table

I am new in RoR and I am trying to write a query on a join table that retrieve all the data I need
class User < ActiveRecord::Base
has_many :forms, :through => :user_forms
end
class Form < ActiveRecord::Base
has_many :users, :through => :user_forms
end
In my controller I can successfully retrieve all the forms of a user like this :
User.find(params[:u]).forms
Which gives me all the Form objects
But, I would like to add a new column in my join table (user_forms) that tells the status of the form (close, already filled, etc).
Is it possible to modify my query so that it can also retrieve columns from the user_forms table ?
it is possible. Once you've added the status column to user_forms, try the following
>> user = User.first
>> closed_forms = user.forms.where(user_forms: { status: 'closed' })
Take note that you don't need to add a joins because that's taken care of when you called user.forms.
UPDATE: to add an attribute from the user_forms table to the forms, try the following
>> closed_forms = user.forms.select('forms.*, user_forms.status as status')
>> closed_forms.first.status # should return the status of the form that is in the user_forms table
It is possible to do this using find_by_sql and literal sql. I do not know of a way to properly chain together rails query methods to create the same query, however.
But here's a modified example that I put together for a friend previously:
#user = User.find(params[:u])
#forms = #user.forms.find_by_sql("SELECT forms.*, user_forms.status as status FROM forms INNER JOIN user_forms ON forms.id = user_forms.form_id WHERE (user_forms.user_id = #{#user.id});")
And then you'll be able to do
#forms.first.status
and it'll act like status is just an attribute of the Form model.
First, I think you made a mistake.
When you have 2 models having has_many relations, you should set an has_and_belongs_to_many relation.
In most cases, 2 models are joined by
has_many - belongs_to
has_one - belongs_to
has_and_belongs_to_many - has_and_belongs_to_many
has_and_belongs_to_many is one of the solutions. But, if you choose it, you must create a join table named forms_users. Choose an has_and_belongs_to_many implies you can not set a status on the join table.
For it, you have to add a join table, with a form_id, a user_id and a status.
class User < ActiveRecord::Base
has_many :user_forms
has_many :forms, :through => :user_forms
end
class UserForm < ActiveRecord::Base
belongs_to :user
belongs_to :form
end
class Form < ActiveRecord::Base
has_many :user_forms
has_many :users, :through => :user_forms
end
Then, you can get
User.find(params[:u]).user_forms
Or
UserForm.find(:all,
:conditions => ["user_forms.user_id = ? AND user_forms.status = ?",
params[:u],
'close'
)
)
Given that status is really a property of Form, you probably want to add the status to the Forms table rather than the join table.
Then when you retrieve forms using your query, they will already have the status information retrieved with them i.e.
User.find(params[:u]).forms.each{ |form| puts form.status }
Additionally, if you wanted to find all the forms for a given user with a particular status, you can use queries like:
User.find(params[:u]).forms.where(status: 'closed')

Find all associated objects by specific condition

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.

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