has_many :through multiple joins table - mysql

I'm trying to create a 4 way joins table.
class User < ApplicationRecord
has_many :user_configurations
has_many :teams, through: :user_configurations
has_many :companies, through: :user_configurations
scope :supervisors, -> { joins(:user_configurations).where(user_configurations: { supervisor: true }) }
scope :agents, -> { joins(:user_configurations).where(user_configurations: { supervisor: false }) }
end
class Team < ApplicationRecord
has_many :user_configurations
has_many :users, through: :user_configurations
belongs_to :company_unit
end
class CompanyUnit < ApplicationRecord
has_many :user_configurations
has_many :users, through: :user_configurations
has_many :teams
belongs_to :company
end
class Company < ApplicationRecord
has_many :user_configurations
has_many :users, through: :user_configurations
has_many :company_units
has_many :teams, through: :company_units
end
class UserConfiguration < ApplicationRecord
belongs_to :user, optional: true
belongs_to :team, optional: true
belongs_to :company, optional: true
#supervisor:boolean in this table
end
When I create I get 2 separate entries into the UserConfiguration Table.
Company.first.users.create(team_ids: [1])
id: 1, user_id: 1, team_id: nil, company_id: 1
id: 2, user_id: 1, team_id: 1, company_id: nil
I don't know if it's good practice to attempt something like this any suggestions will be really helpful thanks. Every search results in trying to do a sql join to query data.
EDIT: Decided not to do this and will try and figure out a different approach.

I would set it up with indirect relationships instead:
class User
has_many :employments
has_many :companies, through: :employments
has_many :positions
has_many :teams, through: :positions
end
class Company
has_many :employments
has_many :users, through: :employments
has_many :teams, through: :users
end
class Team
has_many :positions
has_many :users, through: :positions
has_many :companies, through: :users
end
# join model between User and Company
class Employment
belongs_to :user
belongs_to :company
end
# join model between User and Team
class Position
belongs_to :user
belongs_to :team
end
While you could potentially use a single 3 way join model this violates the Single Responsibility Principle and does not map the domain very well.
3 way joins introduce quite a bit of complexity as you cannot simply unlink two records by deleting a row in the join table. And ActiveRecord does not automatically keep track of the foreign key columns.
If you want to add roles to this data model there are a few ways to do it:
1 add an enum to the join table:
# join model between User and Team
class Position
enum role: [:grunt, :supervisor]
belongs_to :user
belongs_to :team
end
2 create a reusable role system.
class User
has_many :user_roles
has_many :roles, through: :user_roles
def has_role?(name, resource = nil)
roles.exists?(name: name, resource: resource)
end
end
class Role
belongs_to :resource, polymorpic: true, optional: true
has_many :user_roles
end
class UserRole
belongs_to :user
belongs_to :role
end
This is a really flexible system lets you attach roles to anything - companies, teams etc. And lets you build systems where the roles can even by defined by end users. Check out the rolify gem for a full example.

Related

Struggling to add has_many through relationship

I am trying to make a has_many through relationship like this:
#user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :availabilities
has_many :timeslots, :through => availabilities
end
#availability.rb
class Availability < ApplicationRecord
belongs_to :timeslot
belongs_to :user
end
#timeslot.rb
class Timeslot < ApplicationRecord
has_many :availabilities
has_many :timeslots, :through => availabilities
end
I created the two models and than ran rake db:migrate without adding the code in the models (to create the tables).
I made a migration file:
class AddFieldsToTables < ActiveRecord::Migration[5.0]
def change
add_column :users, :availability_id, :integer
add_column :timeslots, :availability_id, :integer
add_column :availabilities, :user_id, :integer
add_column :availabilities, :timeslot_id, :integer
end
end
and ran rake db:migrate
Than I added the code above to all the files.
And then if I try to generate anything it gives me NameError: undefined local variable or method availabilities for User (call 'User.connection' to establish a connection):Class
I am new to Ruby on Rails.
One issue I see is that in your timeslot.rb you have has_many :timeslots, :through => availabilities. I'm guessing you want has_many :users, :through => :availabilites.
Another is in user.rb, you have has_many :timeslots, :through => availabilities but you need the symbol :availabilites. This is what is causing the error you posted, I believe. It should look like this (all I've changed is the second-to-last line):
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :availabilities
has_many :timeslots, :through => :availabilities
end
I see a tiny problem in your code:
#timeslot.rb
class Timeslot < ApplicationRecord
has_many :availabilities
has_many :timeslots, :through => availabilities
end
it should be:
#timeslot.rb
class Timeslot < ApplicationRecord
has_many :availabilities
has_many :users, :through => availabilities
end
I'm not sure if it can solve your problem but your code (exclude the above mistake) sounds fine for me.
In order to setup has_many through relationship between two tables users and timeslots, you need to setup join table availabilities with columns user_id and timeslot_id.
Setup your rails models like below:
# models/user.rb
class User < ApplicationRecord
has_many :availabilities
has_many :timeslots, :through => :availabilities
end
# models/availability.rb
# projects table should have these columns - user_id:integer, timeslot_id:integer
class Availability < ApplicationRecord
belongs_to :timeslot
belongs_to :user
end
# models/timeslot.rb
class Timeslot < ApplicationRecord
has_many :availabilities
has_many :users, :through => :availabilities
end
You need a migration to create availabilities table which acts as a join table for your has_many through relationship between Timeslot Object and User Object. Migration file looks something like this:
class CreateAvailabilities < ActiveRecord::Migration[5.0]
def change
create_table :availabilities do |t|
t.integer :user_id
t.integer :timeslot_id
end
end
end
Access
User.last.timeslots gives 0, 1 or many timeslots associated with User.last
Timeslot.last.users gives 0, 1 or many users associated with Timeslot.last

Active Record Third-Tier Relation Query

I have models User, Filter and Messages:
User has_many :filters and has_many :messages, as: :owner
Filter belongs_to :user, has_many :messages, as: :processor
Messages belongs_to :owner, polymorphic: true and :processor, polymorphic: true
What is the right way to query "Filter.find_by(user_id: [#whatever].id).messages"without returning undefined method "messages" for nil:NilClass?
Try this :
Filter.find_by(user_id: [#whatever].id).try(:messages)

Direct association for a Group with a User

I have Group model that has_many institutions, and schools.
class Group
has_many :institutions
has_many :schools
end
However I need my Group to have_and_belongs_to_many :users but I want all the users that are associated through the institutions and schools like this:
class Group
has_many :institutions
has_many :users, :through => :instiutions
has_many :schools
has_many :users, :through => :schools
end
class School
belongs_to :group
has_many :educations
has_many :users, :through => :educations
end
class Institution
belongs_to :group
has_many :institutional_educations
has_many :users, :through => :institutional_educations
end
Obviously this isn't possible so what should I do?
Just offhand, have you considered using single table inheritance, so that school is a type of institution?
Your School class would inherit from Institution ("class School < Institution"). Plus, I guess you'd call it something else but you could have "class GenericInstitution < Institution" as well. Then your Group class could look like this:
class Group
:has_many :school_users, :through => :schools,
:has_many :generic_institution_users, :through => :generic_institutions
# if you need all of the users at once:
:has_many :users, :through => :institutions
end
You'd probably have to specify a foreign key or two to get this to work for you.
Also, I can't quite figure see what an :institutional_education is, but if you really need it you could do the same thing there (maybe "class institutional_education < education", or maybe the other way around.

ActiveRecord Multi-association query

So I've got three models:
User
User Interest
Interest Tags
User Model
class User < ActiveRecord::Base
has_many :user_interests
end
InterestTag Model
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
UserInterest Model
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
I'd like to use ActiveRecord to include the name of the user's interests when loading their profile using the following query:
#user = User.find(current_user.id, :include => [{:user_interests => :interest_tags}])
Migrations for interest_tags + user_interests
create_table :interest_tags do |t|
t.string :name, :null => false, :size => 30
t.timestamp :created_at
end
create_table :user_interests do |t|
t.integer :user_id
t.integer :interest_tag_id
end
What am I doing wrong?
You have to add an has_many :through association on User model.
class User < ActiveRecord::Base
has_many :user_interests
has_many :interest_tags, :through => :user_interests
end
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
Now you can eager load the tags as follows:
User.find(current_user.id, :include => :interest_tags)
Note:
You might want to look at the acts_as_taggable_on gem for your requirement.
I assume you are building a tagging system, like in stackoverflow: every user kann have multiple tags that they are interested in. In that case the user_interests table is only a join table and does not need a model. Just use has_and_belong_to_many on the two real models.
See also this article on different ways to implement tagging with more or less normalized relational databases. You could also use a non-relational database like Mongodb, there you would need only one table to do tagging, see the cookbook.

Rails ActiveRecord Multiple Levels of Association

I'm having a little trouble with querying multiple tables with different types of association. Can someone please point me in the right direction?
class Sale < ActiveRecord::Base
has_many :items, :dependent => :destroy
end
class Item < ActiveRecord::Base
belongs_to :sale, :dependent => :destroy
has_many :images, :dependent => :destroy
end
class Image < ActiveRecord::Base
belongs_to :item, :dependent => :destroy
end
What would be the query to get all items relating to a sale with the ID 1, and then loop through all the images relating to each item returned?
Thanks for your help.
You can define :through => :something in a has_many association
class Sale < ActiveRecord::Base
has_many :items, :dependent => :destroy
has_many :images, :through => :items
end
and then simply query
Sale.find(1).images