Rails 1 to 2 Polymorphic - mysql

I'm currently having trouble implementing a 1-to-2 association, which is polymorphic. I have three models involved in this, Rule, Ip and IpGroup. Each Rule has exactly two of Ipish, which can be either an Ip or IpGroup. However, when loading entries from DB, the ipish_from and ipish_to always remain nil. Also, don't get confused by class_name: "Ipish", there is no explicit class in the project named "Ipish"; in an older version of the project it was not polymorphic and it said class_name: "Ip".
class Rule
belongs_to :ipish_from, class_name: "Ipish", foreign_key: :ipish_id_from, polymorphic: true
belongs_to :ipish_to, class_name: "Ipish", foreign_key: :ipish_id_to, polymorphic: true
end
class Ip
has_and_belongs_to_many :ip_groups
has_many :rules_from, as: :ipish
has_many :rules_to, as: :ipish
end
class IpGroup
has_and_belongs_to_many :ips
has_many :rules_from, as: :ipish
has_many :rules_to, as: :ipish
end
The table in my MySQL-DB currently looks like this
++++++++++++++++++++++++++++
+ rules +
++++++++++++++++++++++++++++
+ ipish_id_from: int +
+ ipish_type_from: string +
+ ipish_id_to: int +
+ ipish_type_to: string +
++++++++++++++++++++++++++++
Any tips on how I should be doing this? I must use polymorphic associations for this. Also, this problem does only exist because of how the objects are related; I know how the "standard" polymorphism works. Maybe Rails expects a different column name to determine the Type of an Ipish object? I haven't found anything that says how to change what Rails expects, unfortunately.

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

Rails Active Record: How to join three tables?

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.

Activerecord "where" condition for Array attribute

I have model course.rb with pre_courses is an array.
class Course < ActiveRecord::Base
serialize :pre_courses, Array
end
Now I want to check if an exist course is pre_course of any course by Activerecord or raw SQL (I am using MySQL), such as Course.where("pre_courses INCLUDEs self.id").
Is there any way to do this?
A serialized array is just a string in the db, so try using LIKE, for example:
Course.where("pre_courses LIKE ?", "% #{self.id}\n%")
Notice that a serialzed array adds a space before each item and a new line after, thus the added space before the interpolated string and the \n at the end.
It sounds like a pre_course is actually a regular course, a course can have many pre_courses and a pre_course can belong to many courses. A self referential has_many through relationship is possible and may give you more flexibility to work with your data than serializing it as an array.
You'll need a join model I'll call CoursePreCourse. It will have the columns course_id and pre_course_id. pre_course_id will be a foreign key for records on the courses table.
class CreateCoursePreCourses < ActiveRecord::Migration[5.1]
def change
create_table :course_pre_courses do |t|
t.references :course, foreign_key: true
t.references :pre_course, foreign_key: { to_table: :courses }
t.timestamps
end
end
end
class CoursePreCourse < ApplicationRecord
belongs_to :course
belongs_to :pre_course, class_name: 'Course'
end
class Course < ApplicationRecord
# A straight-forward has_many :through association for a course that has_many :pre_courses
has_many :course_pre_courses
has_many :pre_courses, through: :course_pre_courses
# A little coercion is necessary to set up the association as a pre_course that has_many :courses
has_many :pre_course_courses, class_name: 'CoursePreCourse', foreign_key: :pre_course_id
has_many :courses, through: :pre_course_courses
end
Now you can retrieve the courses that are pre_courses of any course with course.pre_courses. If you want to see if a course is a pre_course of other courses it's pre_course.courses. Similarly you can add a course to another course's pre_courses with either course.pre_courses << pre_course or pre_course.courses << course.
Do you ever say a word so many times it loses its meaning?

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