association and migration between users and teams (rails) - ruby-on-rails

I have this User and team model which has the following association:
user.rb
class User < ActiveRecord::Base
belongs_to :team
team.rb
class Team < ActiveRecord::Base
has_many :users
has_one :leader, class_name: "User", foreign_key: "leader_id"
belongs_to :manager, class_name: "User", foreign_key: "manager_id"
but it seems that I can't imagine representing it properly into a migration. At first, this is what I did:
class AddTeamIdToUsers < ActiveRecord::Migration
def change
add_column :users, :team_id, :integer
add_index :users, :team_id
end
end
class AddUsersToTeams < ActiveRecord::Migration
def change
add_reference :teams, :leader, index: true
add_reference :teams, :manager, index: true
end
end
for sure, what I did on AddTeamToIdUsers was a many-to-one association since a Team can have many Users, but the leader position should only be exclusive for a specific team only (same goes to members as well, they should not belong to other teams). Managers, however, can have many teams to manage. Going back to my concern, how can I represent my scenario into a migration? Or are there any adjustments I should make in my associations? After the necessary adjustments and solutions considered, will the application automatically follow the association rules upon adding/updating teams?

Your migrations look correct, but your associations are not complete:
class User < ActiveRecord::Base
belongs_to :team
has_one :leading_team, class_name: 'Team', foreign_key: 'leader_id'
has_many :managed_teams, class_name: 'Team', foreign_key, 'manager_id'
class Team < ActiveRecord::Base
has_many :users
belongs_to :leader, class_name: "User"
belongs_to :manager, class_name: "User"
And you should be all set.

Because a manager can have multiple teams, but is still "part of" the team, I'd suggest creating a join table for users and teams. We'll call it members. It will reference both user and team.
class CreateMembers < ActiveRecord::Migration
def change
create_table :members do |t|
t.references :user
t.references :team
t.timestamps
end
end
end
Then, we'll need add the members association to the User model. Users will have many members, and, because of managers, have many teams as well. I've also included a function to get the team of a worker or leader, since there's only one.
class User < ActiveRecord::Base
has_many :members
has_many :teams, through: members, dependent: destroy
validates_associated :members # More on this later
# for workers and leaders
def team
self.teams.first
end
end
Similar to the User model, we'll need to add the members association to the Team model. We'll also include a few functions to get the leader and manager of a team, and validation to make sure a team has exactly one leader and one manager.
class Team < ActiveRecord::Base
has_many :members
has_many :users, through: :members, dependent: destroy
validate :has_one_leader_and_manager
validates_associated :members # More on this later
def manager
self.users.where(type: 'manager').first
end
def leader
self.users.where(type: 'leader').first
end
def has_one_leader_and_manager
['leader', 'manager'].each do |type|
unless self.users.where(type: type).count == 1
errors.add(:users, "need to have exactly one #{type}")
end
end
end
end
Lastly, we'll set up the Member model. We can also include some validation to ensure that a team can only have one leader and one manager, and that workers and leader cannot belong to more than one team.
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :team
validate :team_has_one_leader_and_manager
# Make sure player (worker or leader) is only on one team
validates :user_id, uniqueness: true, if: :is_player?
def is_player?
['worker', 'leader'].include? user.type
end
def team_has_one_leader_and_manager
if ['leader', 'manager'].include?(user.type)
if team.users.where('type = ? AND id != ?' user.type, user_id).count.any?
errors.add(:team, "can't add another #{user.type}")
end
end
end
end
Note that with the validation methods, you may want to move them around and/or refactor them, depending on how you add users and team, and how you'll add new members. However, this answer will hopefully give you enough information to get started.

Related

What's Best Practice For Multiple Types of Users in Ruby on Rails

I'm building an application that has multiple types of users. Think: patient and doctor.
A patient has many doctors, and a doctor has many patients.
Initially I was going to put them all under one user, and maybe add a role column, or was considering polymorphic referencing(which I'm not so familiar with yet).
Is it better to do the above, or is it better to simply have them as different Classes. The problem I'm trying to resolve starts with logging in, and then class relations with the join table. For example, How can a User have many Users through Appointments? Is that possible?
What's the best practice for what I'm going for here?
You can Single table inheritance over here.
class User
end
class Patient < User
end
class Doctor < User
end
In users table you will have type column which will decide what type of the user it is.
reference - https://medium.com/#dcordz/single-table-inheritance-using-rails-5-02-6738bdd5101a
If patients have many doctors and doctors have many patients, then you have a many-to-many relationship. A many-to-many relationship requires a linking table.
Let's call this a "treatments" relationship, (because I don't have a good name for it, it should be a noun) as a doctor treats a patient. Your migration would be
create_table :treatments do |table|
table.timestamps
table.integer :doctor_id
table.integer :patient_id
end
Then your associations would have to specify that these ids go to the users table.
class Treatment < ApplicationRecord
belongs_to :doctor, class_name: 'User'
belongs_to :patient, class_name: 'User'
end
class User < ApplicationRecord
has_many :as_patient_treatments, class_name: 'Treatment', foreign_key: patient_id
has_many :doctors, through: :as_patient_treatments
has_many :as_doctor_treatments, class_name: 'Treatment', foreign_key: doctor_id
has_many :patients, through: :as_doctor_treatments
end
You may want to break this out using STI, if you have more behavior which differs between doctor and patient.
class User < ApplicationRecord
end
class Patient < User
has_many :as_patient_treatments, class_name: 'Treatment', foreign_key: patient_id
has_many :doctors, through: :as_patient_treatments
end
class Doctor < User
has_many :as_doctor_treatments, class_name: 'Treatment', foreign_key: doctor_id
has_many :patients, through: :as_doctor_treatments
end
Another, entirely legitimate approach, especially if you have additional data for patients and doctors, is to have a doctors, a patients, and a users table, instead of STI. You still need the treatments table to link doctors and patients.
The meaning of User is an account which can log in. A doctor and a patient can have a user_id to their login account.
create_table :users do |table|
table.timestamps
table.string :username
table.string :email
...
end
create_table :doctors do |table|
table.timestamps
table.bigint :user_id
table.specialty
...
end
create_table :doctors do |table|
table.timestamps
table.bigint :user_id
table.payer
...
end
create_table :treatments do |table|
table.timestamps
table.bigint :doctor_id
table.bigint :patient_id
end
class User
has_one :doctor
has_one :patient
...
end
class Treatment
belongs_to :doctor
belongs_to :patient
end
class Doctor
belongs_to :user
has_many :treatments
has_many :patients, through: :treatments
delegate :username, :email, :to => :user
...
end
class Patient
belongs_to :user
has_many :treatments
has_many :doctors, through: :treatments
delegate :username, :email, :to => :user
...
end

Rails: Bad Associations? [has_many , through] How to test if working?

I am struggling with an issue in my data model. I do have the following models:
class User < ActiveRecord::Base
...
has_many :claims #user-claims
has_many :claims, through: :rulings, as: :commissars
...
end
class Claim < ActiveRecord::Base
...
belongs_to :user
has_many :users, through: :rulings, as: :commissars
...
end
class Ruling < ActiveRecord::Base
belongs_to :user
belongs_to :claim
end
Error:
undefined method `commissars' for #<Claim:0xc5ac090>
Model Explanation:
User can write claims (A claim belongs to one user), and users could do the role of commissars to do the ruling of the claim (max numbers of commissars = 3 per claim).
Is there any way to fix this or improve the relationship?
This domain model requires som pretty complex relations so there is no shame in not getting it on the first try.
Lets start with user and claims:
class User < ActiveRecord::Base
has_many :claims, foreign_key: 'claimant_id',
inverse_of: :claimant
end
class Claim < ActiveRecord::Base
belongs_to :claimant, class_name: 'User',
inverse_of: :claims
end
This is a pretty basic one to many relation with a twist. Since User will have a bunch of relations to Claim we call the relation something other than the default user so that the nature of the relation is defined.
The class_name: 'User' option tells ActiveRecord to load the class User and use it to figure out what table to query and also what class to return the results as. Its needed whenever the class name cannot be directly derived from the name of the association. The option should be a string and not a constant due to the way Rails lazily resolves class dependencies.
Now lets add the commissar role. We will use ruling as the join table:
class Ruling < ActiveRecord::Base
belongs_to :claim
belongs_to :commissioner, class_name: 'User'
end
Notice that here we have a relation to User that we call commissioner for clarity. Now we add the relations to Claim:
class Claim < ActiveRecord::Base
belongs_to :claimant, class_name: 'User',
inverse_of: :claims
has_many :rulings
has_many :commissioners, through: :rulings
end
Then we need to setup the relations on the User side:
class User < ActiveRecord::Base
has_many :claims, foreign_key: 'claimant_id',
inverse_of: :claimant
# rulings as claimant
has_many :rulings, through: :claims
has_many :rulings_as_commissioner, class_name: 'Ruling',
foreign_key: 'commissioner_id'
has_many :claims_as_commissioner, through: :rulings_as_commissioner,
source: :claim
end
Note the source: :claim option where we tell ActiveRecord which party we want from the join table.
Of course for this to work we need to setup the columns and the foreign keys properly. These migrations are to create the tables from scratch but you can easily rewrite them to alter your existing tables:
class CreateClaims < ActiveRecord::Migration
def change
create_table :claims do |t|
t.belongs_to :claimant, index: true, foreign_key: false
t.timestamps null: false
end
# we need to setup the fkey ourself since it is not conventional
add_foreign_key :claims, :users, column: :claimant_id
end
end
class CreateRulings < ActiveRecord::Migration
def change
create_table :rulings do |t|
t.belongs_to :claim, index: true, foreign_key: true
t.belongs_to :commissioner, index: true, foreign_key: false
t.timestamps null: false
end
add_foreign_key :rulings, :users, column: :commissioner_id
add_index :rulings, [:claim_id, :commissioner_id], unique: true
end
end
max numbers of commissars = 3 per claim
This is not really part of the associations rather you would enforce this rule by adding a validation or an association callback.
class Ruling < ActiveRecord::Base
# ...
validate :only_three_rulings_per_claim
private
def only_three_rulings_per_claim
if claim.rulings.size >= 3
errors.add(:claim, "already has the max number of commissars")
end
end
end
See:
Rails Guides: Active Record Migrations
Rails Guides: the has_many though: relations
First, I would suggest you go back and read the Guide carefully as I believe you have fundamentally misunderstood a number of things. The as: option, for instance, does not indicate role but, rather, the presence of a polymorphic join. Also, you can't declare has_many :claims twice on the same model. Anyway, go give it another read.
But, to your question - a functional although somewhat inelegant approach might look like:
class User < ActiveRecord::Base
...
has_many :claims
has_many :claim_commissars, foreign_key: "commissar_id"
has_many :commissar_claims, through: :claim_commissars, class_name: "Claim"
# ^^^^^^^^^^^^^^^^^^^^^
# this bit may be wrong
...
end
class Claim < ActiveRecord::Base
...
belongs_to :user
has_one :ruling
has_many :claim_commissars
has_many :commissars, through: :claim_commissars
...
end
class ClaimCommissar < ActiveRecord::Base
...
belongs_to :claim
belongs_to :commissar, class_name: "User"
...
end
class Ruling < ActiveRecord::Base
...
belongs_to :claim
belongs_to :commissar, class_name: "User"
...
end
You would need to enforce your 'max 3 commissars` in the code.
This is not tested and you will likely need to fiddle with it to get it to go. But, hopefully, it sets you in a better direction.
Good luck!

Polymorphic has-many-through relationships in Rails

I am trying to set up a polymorphic has-many-through relationship with ActiveRecord. Here's the end goal:
Users can belong to many organizations and many teams
Organizations have many users and many teams
Teams have many users and belong to an organization
I am trying to use has-many-through instead of has-and-belongs-to-many, since I need to associate some information along with the relationships (like user role in the organization or team), so I made a join table Membership.
How would I implement this?
I would design the schema like this:
Organization has many Team
Team has many TeamMember
User has many TeamMember
TeamMember belongs to User and Team
The models will be:
organization.rb
class Organization < ActiveRecord::Base
has_many :teams
has_many :team_members, through: :teams
has_many :users, through: :team_members
end
team.rb
class Team < ActiveRecord::Base
belongs_to :organization # fk: organization_id
has_many :team_members
has_many :users, through: :team_members
end
user.rb
class User < ActiveRecord::Base
has_many :team_members
has_many :teams, through: :team_members
has_many :organizations, though: :teams
end
team_member.rb
class TeamMember < ActiveRecord::Base
belongs_to :team # fk: team_id
belongs_to :user # fk: user_id
attr_accessible :role # role in team
end
So, compare with your requirements:
Users can belong to many organizations and many teams
=> Okay
Organizations have many users and many teams
=> Okay
Teams have many users and belong to an organization
=> Okay
Btw, we don't use any polymorphic here, and TeamMember stands for Membership in your early idea!
For polymorphic association,
class User
has_many :memberships
end
class Team
belongs_to :organization
has_many :memberships, :as => :membershipable #you decide the name
end
class Organization
has_many :memberships, :as => :membershipable
has_many :teams
end
class Membership
belongs_to :user
belongs_to :membershipable, polymorphic: true
end
Note that User is indirectly associated to Team and Organization, and that every call has to go through Membership.
In my projects, I use a Relationship class (in a gem I've named ActsAsRelatingTo) as the join model. It looks something like this:
# == Schema Information
#
# Table name: acts_as_relating_to_relationships
#
# id :integer not null, primary key
# owner_id :integer
# owner_type :string
# in_relation_to_id :integer
# in_relation_to_type :string
# created_at :datetime not null
# updated_at :datetime not null
#
module ActsAsRelatingTo
class Relationship < ActiveRecord::Base
validates :owner_id, presence: true
validates :owner_type, presence: true
validates :in_relation_to_id, presence: true
validates :in_relation_to_type, presence: true
belongs_to :owner, polymorphic: true
belongs_to :in_relation_to, polymorphic: true
end
end
So, in your User model, you would say something like:
class User < ActiveRecord::Base
has_many :owned_relationships,
as: :owner,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :organizations_i_relate_to,
through: :owned_relationships,
source: :in_relation_to,
source_type: "Organization"
...
end
I believe you may be able to leave the source_type argument off since the joined class (Organization) can be inferred from :organizations. Often, I'm joining models where the class name cannot be inferred from the relationship name, in which case I include the source_type argument.
With this, you can say user.organizations_i_relate_to. You can do the same set up for a relationship between any set of classes.
You could also say in your Organization class:
class Organization < ActiveRecord::Base
has_many :referencing_relationships,
as: :in_relation_to,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :users_that_relate_to_me,
through: :referencing_relationships,
source: :owner,
source_type: "User"
So that you could say organization.users_that_relate_to_me.
I got tired of having to do all the set up, so in my gem I created an acts_as_relating_to method so I can do something like:
class User < ActiveRecord::Base
acts_as_relating_to :organizations, :teams
...
end
and
class Organization < ActiveRecord::Base
acts_as_relating_to :users, :organizations
...
end
and
class Team < ActiveRecord::Base
acts_as_relating_to :organizations, :users
...
end
and all the polymorphic associations and methods get set up for me "automatically".
Sorry for the long answer. Hope you find something useful in it.

How to do has_many and has_one association in same model?

I need to do two associations in the same model. Where:
Team has_many User
Now, I want that Team has_one Leader
This "Leader" will be a User
Im trying to use has_one throught but I think that association isn't work.
Leader.rb
class Leader < ActiveRecord::Base
belongs_to :user
belongs_to :team
Team.rb
class Team < ActiveRecord::Base
has_one :user, through: :leader
end
User.rb
class User < ActiveRecord::Base
belongs_to :team
has_one :captain
end
and the get following error around line 27:
NoMethodError in TeamsController#create
26 def create
**27 #team = current_user.teams.create(team_params)**
28 #team.save
29 respond_with(#team)
30 current_user.update(team_id: #team.id)
In this case I think you need 2 model are enough
1). User model
class User < ActiveRecord::Base
belongs_to :team
end
2). Team model
class Team < ActiveRecord::Base
has_many :users
belongs_to :leader, class_name: 'User', foreign_key: :leader_id
end
How about setting a boolean flag in users table called leader. And then your association can become:
class Team < ActiveRecord::Base
has_many :users
has_one :leader, class_name: 'User', -> { where leader: true }
end
Team has_many User Now, I want that Team has_one Leader
This "Leader" will be a User
Use inheritance (also called sub-classing), Leader is a User.
class User < ActiveRecord::Base
belongs_to :team
end
class Leader < User
end
class Team < ActiveRecord::Base
has_many :users
has_one :leader
end
Your users table is also important. Ensure that users has t.belongs_to :team and t.string :type in its create_table method. Note that a Leader is a User and does not need a separate table, however you do need to allow ActiveRecord to record its type so it can return the correct Model later.
References:
inheritance specifically you need 'single table inheritance'
belongs_to scroll down for has_one and has_many, the three relationships used here.
current_user.teams.create(team_params)
Teams is for a has_many association, you want current_user.create_team(team_params)
You have has_one association between user and team. Try this:
current_user.create_team(team_params)
Also, you should add proper back association from team to leader.
class Team < ActiveRecord::Base
belongs_to :leader
has_one :user, through: :leader
end

Using has_many through in Rails 4

I'm actually trying to create a has_many through association. Let me first explain a bit about how things are supposed to work.
I have a users, groups and members tables. The rules are as follow :
A user can create a group (depending on it's role) (groups table has a user_id)
A user can be member of one or many groups (members table contain user_id and group_id)
Here is my current relationship classes :
class User < ActiveRecord::Base
# Associations
has_many :groups # As user, I create many groups
has_many :members
has_many :groups, through: :members # But I can also belongs to many groups
end
class Group < ActiveRecord::Base
# Associations
belongs_to :user
has_many :members
has_many :users, through: :members
end
class Member < ActiveRecord::Base
# Associations
belongs_to :user
belongs_to :group
end
My problem is about the group relationship. You see a user can create groups, which means :
has_many :groups
but a user can also be member of groups :
has_many :groups, through: :members
Because of this new relationship, 75% of my specs are now broken. Also, I notice that if I logged in with a user associated to a group, I can see actually groups list. But when I'm trying to logged in as group owner (the one who created the group), I can not see the groups created by that user).
Idea?
You are not looking for an has_many through relationship here
Try that :
class User < ActiveRecord::Base
# Associations
has_and_belongs_to_many :groups
has_many :created_groups, class_name: 'Group', foreign_key: 'creator_id'
end
class Group < ActiveRecord::Base
# Associations
belongs_to :creator, class_name: 'User'
has_and_belongs_to_many :members, class_name: 'User'
end
This is a solution if you don't need the member class to do any special treatment.
You should have a migration that looks like that:
class CreateGroupsUsers < ActiveRecord::Migration
def change
create_table :groups_users, id: false do |t|
t.references :group
t.references :user
end
add_index :groups_users, [:group_id, :user_id]
add_index :groups_users, :user_id
end
end
And you have to make sure that your groups table have a creator_id !

Resources