Rails Active Record: How to join three tables? - mysql

I've three tables as shown below:
Advertiser model:
class Advertiser < ActiveRecord::Base
has_many :advertisers_account_groups
AdvertisersAccountGroup model
class AdvertisersAccountGroup < ActiveRecord::Base
belongs_to :advertiser
belongs_to :v2_account_account_group, class_name: 'V2Account::AccountGroup', foreign_key: 'account_group_id'
I wanna know which advertiser belongs to v2_account_account_groups
and wanna get v2_account_account_groups.name
Desired Output:
What I tried;
Advertiser.where(media_type: "line").joins(advertisers_account_groups,v2_account_account_groups)
But it doesn't work

It looks to me that your current setup uses AdvertisersAccountGroup as a join table; therefore, I'd suggest using a has_many :through association.
To do this, you'd just need to switch up the models as follows:
class Advertiser < ActiveRecord::Base
has_many :v2_account_account_groups, through: :advertisers_account_groups
has_many :advertisers_account_groups
...
end
class V2Account::AccountGroup < ActiveRecord::Base
has_many :advertisers, through: :advertisers_account_groups
has_many :advertisers_account_groups
...
end
class AdvertisersAccountGroup < ActiveRecord::Base
belongs_to :advertiser
belongs_to :v2_account_account_group, class_name: 'V2Account::AccountGroup', foreign_key: 'account_group_id'
...
end
This will allow you to query against the advertiser as desired, i.e. advertiser.v2_account_account_groups.
However, this association is a many-to-many between advertisers and v2 account groups as is - therefore, you won't be able to call advertiser.v2_account_account_groups.name as advertiser.v2_account_account_groups returns a collection rather than a single record.
You could use advertiser.v2_account_account_groups.map(&:name) (to get an array of all groups' names) or advertiser.v2_account_account_groups.first&.name, but it sounds as if you might need to restructure the data if an advertiser should have just the one v2 account group.
Does that make sense and sound like what you're looking for? Let me know if you've any questions.
Edit:
Based on your comment, I think you should be able to construct a query as follows:
Advertiser.includes(advertiser_account_groups: : v2_account_account_group)
.where(advertiser_account_groups: { v2_account_groups: { name: "something" } })
Does that sound like what you're looking for?
A couple of things to note:
when referencing the associations in the includes, you want to use the association name
however, when plugging these into the where clause, you need to use the full table names, as they are in the databases (searchable via Model.table_name)
Also, in your comment, you reference adding media_type: "line", which the below also includes:
Advertiser.includes(advertiser_account_groups: : v2_account_account_group)
.where(media_type: "line", advertiser_account_groups: { v2_account_account_groups: { name: "something" } })
Probably the best way to structure this in your code is as a scope in your advertiser model, such as:
scope :by_v2_group_name, -> (name) { includes(advertiser_account_groups: :v2_account_account_group)
.where(media_type: "line", advertiser_account_groups: { v2_account_account_groups: { name: "something" } }) }
or
scope :by_v2_group_name, (lambda do |name|
includes(advertiser_account_groups: :v2_account_account_group)
.where(media_type: "line", advertiser_account_groups: { v2_account_account_groups: { name: "something" } })
end)
That will then allow you to keep your code clean and call Advertiser.by_v2_group_name("something").
Let me know how you get on with that and we'll work on it as needed :)

This answer is relevant only if you don't need a model for the join table
advertiser_account_gourps
(if you don't have any extra attributes other then advertiser_id and account_gourp_id on that join table - you usually don't need it)
While #SRack's answer is using the has_many :through association, I think you can use a simple many-to-many association (has_and_belongs_to_many) because in your images i can see that the join table only have the ids of the associated tables (Unless you do need the id column -> and in that case, use #SRack's solution!)
class Advertiser < ActiveRecord::Base
has_and_belongs_to_many :advertisers_account_groups
class AccountGroup < ActiveRecord::Base
has_and_belongs_to_many :advertisers
then you can get them using:
advertiser = Advertiser.first
advertiser.account_groups #to get account groups of that advertiser
and vice versa:
account_group = AccountGroup.first
account_group.advertisers # to get advertisers of that account group
#SRack's comment is correct! i changed it to HABTM association!
In that case you will need to create a migration to make the join table:
rails g migration CreateJoinTableAdvertiserAccountGroup advertiser account_group
This table will only include the associated advertiser_id and the account_group_id.
You don't need to create a model and handle stuff on that table, rails will fill that table for you.

Related

Rails query with has_many relation in a list of [types, ids]

I have models with variables (many model classes : polymorphic relation), and constraints between variables (variables are not necessarily in the same model).
I try to make a query to find all constraints associated to a list of models (with all vars associated to the models in list), and I really don't know how to do it.
My models looks like this.
class Model1 < ApplicationRecord
has_many :vars, as: :model
end
class Model2 < ApplicationRecord
has_many :vars, as: :model
end
class Var < ApplicationRecord
belongs_to :model, polymorphic: true
# model_type and model_id in vars table
has_many :cns_vars
has_many :constraints, through: :cns_vars
end
class_CnsVar < ApplicationRecord
belongs_to :var
belongs_to :constraint
end
class Constraint < ApplicationRecord
has_many :cns_vars
has_many :vars, through: :cns_vars
end
To find constraints related to one model I have this query :
Constraint.includes(:vars).where(active: true, vars: {model_id: model.id, model_type: model.class.to_s})
This query give me the constraints that have at least one var associated to my model.
I need constraints with all vars associated to a list of models.
Is there a way to make the same query, but with all vars associated to the model ?
Is there a way to make the same query, but with all vars associated to a list of models ?
Constraint.includes(:vars).where(active: true, vars: {*[var.model_type, var.model_id] in my models list*})
Is there a solution to do this with one query ?
Or do I have to do it another way ?
Thanks for your help.
(ruby : 2.6.0 / rails : 5.2.3)
EDIT :
To give better explanation, look at this function that returns what I need, but this make too much queries !
def constraints_for_models_list(models)
all_vars = models.flat_map(&:vars)
all_constraints = all_vars.flat_map(&:constraints)
all_constraints.uniq!
constraints = []
all_constraints.each do |constraint|
next unless constraint.vars.included_in?(all_vars)
constraints << constraint
end
return constraints
end
Constraint.includes(:vars).where(active: true).where.not(vars: { model: nil })
of course if I correctly get the point of what you're trying.
for what you asked in comment:
Constraint.includes(:vars).where(active: true).where('vars.model_type IN
?', ['Model1',Model2'])

How to make associations on this

Hello, i'm new to rails. I have been fed up with active record associations. I did study associations from the rails guides. Yet i cant find myself a clear way to add associations to the models suggested in the diagram.
I have one doubt whether a single foreign key(SECOND MODEL) can reference two primary keys (SECOND MODEL LEVEL 2 FIRST & SECOND MODEL LEVEL 2 SECOND). This has been done because the user has to choose whether to add from the SECOND MODEL LEVEL 2 FIRST TABLE or the SECOND MODEL LEVEL 2 SECOND TABLE while inserting values into the SECOND MODEL.
If u find this hard to understand please leave a comment, ill make appropriate changes. And i would appreciate on how to query from the FINAL LEVEL FIRST with BASE-MODEL thorough a join condition.
You can use polymorphic association to refer to one table OR another, check the model below as per the posted image:
class BaseModel
has_many :first_models
has_many :second_models
end
class FirstModel
belgons_to :base_model
has_one :level_two_first_model
end
class LevelTwoFirstModel
belgons_to :first_model
end
class SecondModel
belgons_to :base_model
has_many :final_first_levels, as: :referenceable, :dependent => :destroy
has_many :final_second_levels, as: :referenceable, :dependent => :destroy
end
class LevelOneSecondModel
belongs_to :referenceable, polymorphic: true
has_many :final_first_levels
end
class LevelTwoSecondModel
belongs_to :referenceable, polymorphic: true
has_many :final_first_levels
end
class FinalFirstLevel
belongs_to :LevelOneSecondModel
end
class FinalSecondLevel
belongs_to :LevelTwoSecondModel
end
referenceable is used as the glue between the parent table and other polymorphic associations (LevelOneSecondModel OR LevelTwoSecondModel)
NB:
Don't forget to add the below line in the migration files of the 2 children tables used in polymorphic association.
t.references :referenceable, polymorphic: true, index: true
Reference:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

search association having class_name

i have a Book model looking like this:
class Book < ActiveRecord::Base
belongs_to :author, class_name: 'User', touch: true
belongs_to :translator, class_name: 'User'
end
How can i search for books with specific author name? i join the tables first:
Book.joins(:author).joins(:translator)
But i cant chain
.where('authors.name = "Tolkien"')
because there is no "authors" table in the database. is
.where('users.name = "Tolkien"')
a safe approach here? is there no risk concerning both translators and authors being users in fact?
(select() method is not an option, i need activerecord::relation here)
You need to pass table name in query
Book.joins(:author).where(users: {name: 'Tolkien'})
is .where('users.name = "Tolkien"') a safe approach here?
If you are concerned about two associations using same table you might need to add a field to distinguish them
For ex. a boolean field author and use condition with association which will solve the problem
class Book < ActiveRecord::Base
belongs_to :author, -> { where(author: true) }, class_name: 'User', touch: true
belongs_to :translator, -> { where(author: false) }, class_name: 'User'
end

Conditions on has_many through

I have three classes
TeamMember, Service and ServiceTeamMember
I have
class Service
has_many :service_team_members
has_many :team_members, through: :service_team_members
end
class ServiceTeamMember
belongs_to :service
belongs_to :team_member
end
class TeamMember
has_many :service_team_members
has_many :services, through: :service_team_members
end
Now in a certain area of my site I want to eager load departments, where a department is a top level service, I'm using ancestry so all you need to know is a department is a service with ancestry of NULL
So I need an actual association so I can eager load it rather than a scope.
I've tried adding it as such
has_many :departments, -> { where(ancestry: nil) }, through: :service_team_members, source: :service
But I get the following error
Mysql2::Error: Unknown column 'services.ancestry' in 'where clause': SELECT `service_team_members`.* FROM `service_team_members` WHERE `services`.`ancestry` IS NULL AND `service_team_members`.`team_member_id` IN (28, 32)
So rails is applying my condition to the join table rather than the table I want. How can I get around this, I assume it's a simple solution and I'm missing something obvious.
Just change source: :service by source: :team_member

How to add a new attribute to Associate Table column in rails

In my rails application, I had to add a new column named is_leader for an association table.
The relationship for the association table is as follows:
has_and_belongs_to_many :analysis_responses, :join_table => "analysis_responses_participants"
Following is the code where the participant details have been saved to the db:
organization.people.create(participant)
the participant has got the following values
name: Test User
position:
birthdate:
id:
lead: "1"
If the lead value is 1, the is_leader column value should be 1 for the particular record.
I wanted to know how can I save the is_leader value in that association table in rails
Thanks
If you need to save attributes on the join table, then you will have to use a join model instead of a HABTM.
class Organization
has_many :analysis_responses
has_many :people, through: :analysis_responses
end
class AnalysisResponse
belongs_to :organization
belongs_to :person
end
class Person
has_many :analysis_responses
has_many :organizations, through: :analysis_reponses
end