I've been going back and forward on this and I would like some advices.
I have "User" that can be part of many "Organizations", and for each one they can have many "Roles". (actually I have this scenario repeated with other kind of users and with something like roles, but for the sake of the example I summed it up).
My initial approach was doing a Table with user_id, organization_id and role_id, but that would mean many registers with the same user_id and organization_id just to change the role_id.
So I thought of doing an organization_users relation table and an organization_users_roles relation. The thing is, now I don't exactly know how to code the models.
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users, join_table: :organization_users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :organizations, join_table: :organization_users
end
class OrganizationUser < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :organizations
has_many :organization_user_roles
has_many :roles, through: :organization_user_roles
end
class OrganizationUserRole < ActiveRecord::Base
has_and_belongs_to_many :roles
has_and_belongs_to_many :organization_users
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :organization_user_roles
end
If for example I want to get: ´OrganizationUser.first.roles´ I get an error saying: PG::UndefinedTable: ERROR: relation "organization_user_roles" does not exist
How should I fix my models?
You should use a much simpler approach. According to your description, Roles is actually what connects Users to Organizations and vice-versa.
Using the has_many and has_many :through associations, this can be implemented like the following:
class User < ActiveRecord::Base
has_many :roles, inverse_of: :users, dependent: :destroy
has_many :organizations, inverse_of: :users, through: :roles
end
class Organization < ActiveRecord::Base
has_many :roles, inverse_of: :organizations, dependent: :destroy
has_many :users, inverse_of: :organizations, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user, inverse_of: :roles
belongs_to :organization, inverse_of: :roles
end
If you wish to preserve roles when you destroy users or organizations, change the dependent: keys to :nullify. This might be a good idea if you add other descriptive data in your Role and want the role to remain even though temporarily vacated by a user, for example.
The has_many :through association reference:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
To add to jaxx's answer (I upvoted), I originally thought you'd be best looking at has_many :through:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :positions
has_many :organizations, through: :positions
end
#app/models/position.rb
class Position < ActiveRecord::Base
#columns id | user_id | organization_id | role_id | etc | created_at | updated_at
belongs_to :user
belongs_to :organization
belongs_to :role
delegate :name, to: :role #-> #position.name
end
#app/models/organization.rb
class Organization < ActiveRecord::Base
has_many :positions
has_many :users, through: :positions
end
#app/models/role.rb
class Role < ActiveRecord::Base
has_many :positions
end
This will allow you to call the following:
#organization = Organization.find x
#organization.positions
#organization.users
#user = User.find x
#user.organizations
#user.positions
This is much simpler than your approach, and therefore has much more ability to keep your system flexible & extensible.
If you want to scope your #organizations, you should be able to do so, and still call the users / positions you need.
One of the added benefits of the code above is that the Position model will give you an actual set of data which can be shared between organizations and users.
It resolves one of the main issues with jaxx's answer, which is that you have to set a role for every association you make. With my interpretation, your roles can be set on their own, and each position assigned the privileges each role provides.
If the user can have many Roles for a single organisation,
and OrganizationUser represents this membership,
than, yes, you need another table for organization_user_roles.
You need to explicitly create it in the database (normally with a migration)
To not get confused, try to find a nice name for OrganisationUser, like employment, membership, etc.
Related
I have a question on a platform I'm developing in Ruby on Rails 5.2.
I have an Owner model which is the owner of properties/property. The owner will post a property so that users (in this case roomates) can share the same property/house/department, etc.
I have Owners and I have Users (both tables are created using devise):
Owner.rb:
class Owner < ApplicationRecord
has_many :properties
end
User.rb:
class User < ApplicationRecord
#Theres nothing here (yet)
end
This is where the magic happens. Property.rb:
class Property < ApplicationRecord
belongs_to :owner
has_many :amenities
has_many :services
accepts_nested_attributes_for :amenities
accepts_nested_attributes_for :services
mount_uploaders :pictures, PropertypictureUploader
validates :amenities, :services, presence: true
scope :latest, -> { order created_at: :desc }
end
How can multiple users share a property? I'm aware that it will have a many-to-many association but I'm a bit confused how to connect these relationships so when the owner posts a property it will display something like:
Property available for: 3 users
And then begin to limit users until it completes the amount of users available.
This sounds like your average many to many assocation:
class User < ApplicationRecord
has_many :tenancies, foreign_key: :tenant_id
has_many :properties, through: :tenancies
end
class Tenancy < ApplicationRecord
belongs_to :tenant, class_name: 'User'
belongs_to :property
end
class Property < ApplicationRecord
has_many :tenancies
has_many :tenants, through: :tenancies
def availablity
# or whatever attribute you have that defines the maximum number
max_tenants - tenancies.count
end
end
You can restrict the number of tenants with a custom validation.
You can use a join table, called users_properties. This table will have a property_id and user_id. You'll then have the following in your properties model:
has_many :users_properties
has_many :users, through: :users_properties
Read more about it here https://guides.rubyonrails.org/association_basics.html
Problem
I have two models a client and a user.
A client can have many administrators and a user can be the administrator of many clients.
I found a couple of people that suggest using has_many :through is the better way to model this relationship in my situation versus has_and_belongs_to_many.
User model
class V1::User < ActiveRecord::Base
has_many :administrators,
class_name: 'V1::ClientAdministrator'
has_many :clients,
through: :administrators
class_name: 'V1::Clients'
Client model
class V1::Client < ActiveRecord::Base
has_many :users,
class_name: "V1::User"
has_many :administrators,
through: :users,
class_name: "V1::ClientAdministrator"
validates :administrators,
length: { minimum: 1}
ClientAdministrator model
class V1::ClientAdministrator < ActiveRecord::Base
belongs_to :v1_user
belongs_to :v1_client
end
Demo using rails c
u = V1::User.create!(name: 'test_user')
c = V1::Client.new(name: 'test_client')
c.administrators << u
ActiveRecord::AssociationTypeMismatch: V1::ClientAdministrator(#70347494104080) expected, got V1::User(#70347494299440)
Before switching to has_many :through I was successfully using has_and_belongs_to_many:
class V1::Client < ActiveRecord::Base
has_and_belongs_to_many :administrators,
-> { uniq },
join_table: :v1_client_administrators,
foreign_key: "v1_client_id",
association_foreign_key: "v1_user_id",
class_name: "V1::User"
The problem with this approach was I was not able to do any validation on the association such as before_destroy make sure there is still one administrator. Additionally, it's likely that I'll add metadata to that relationship in the future.
The ASK
How can I get / set the administrators of the client?
Is there any way that client.administrators would be an array of users instead of forcing me to client.administrators.each { |admin| admin.user} to get access to the user? (If I eventually add metadata this probably can't doesn't make sense)
Is there a way to restrict the use of client.users in favor of client.administrators?
Do model concerns help here?
Is this the right approach for ensuring there is always at least one administrator?
I believe here is what you're looking for, please namespace appropriately:
class User
has_many :clients_users
has_many :clients, through: :clients_users
end
class Client
has_many :clients_users
has_many :administrators, through: :clients_users, source: :user
validates :adminstrators, presence: true # I think this should ensure at least one admin
end
class ClientsUser
belongs_to :client
belongs_to :user
end
Client.first.administrators # fetch all adminstrators
Client.first.adminstrators << User.first # add an administrator
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.
The Ruby on Rails app I am working on allows users to create and share agendas with other users.
In addition, we must be able to:
Display a list of agendas for each user, on his profile
Display a list of users associated with an agenda, on the agenda's page
When sharing an agenda with another user, define a role for this user, and display the role of this user on the list mentioned right above
I was going to go with a has_and_belongs_to_many association between the user and the agenda models, like that:
class User < ActiveRecord::Base
has_and_belongs_to_many :agendas
end
class Agenda < ActiveRecord::Base
has_and_belongs_to_many :users
end
But then I wondered whether this would let me get and display the #user.agenda.user.role list of roles on the given agenda page of a given user.
And I thought I should probably go with a has_many :through association instead, such as:
class User < ActiveRecord::Base
has_many :roles
has_many :agendas, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class Agenda < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
And although I was pretty comfortable about the idea of a user having several roles (one for each agenda), I am not sure about the idea of an agenda having several roles (one for each user?).
Finally, to add to the confusion, I read about the polymorphic association and thought it could also be a viable solution, if done this way for instance:
class Role < ActiveRecord::Base
belongs_to :definition, polymorphic: true
end
class User < ActiveRecord::Base
has_many :roles, as: :definition
end
class Agenda < ActiveRecord::Base
has_many :roles, as: :definition
end
Does any of the above solutions sound right for the situation?
UPDATE: Doing some research, I stumbled upon this article (from 2012) explaining that has_many :through was a "smarter" choice than has_and_belongs_to_many. In my case, I am still not sure about the fact that an agenda would have many roles.
UPDATE 2: As suggested in the comments by #engineersmnkyn, a way of solving this would be to go with two join tables. I tried to implement the following code:
class User < ActiveRecord::Base
has_many :agendas, through: :jointable
end
class Agenda < ActiveRecord::Base
end
class Role < ActiveRecord::Base
end
class Jointable < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
has_many :agendaroles through :jointable2
end
class Jointable2 < ActiveRecord::Base
belongs_to :roles
belongs_to :useragenda
end
I am not sure about the syntax though. Am I on the right track? And how should I define the Agenda and the Role models?
UPDATE 3: What if I went with something like:
class User < ActiveRecord::Base
has_many :roles
has_many :agendas, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class Agenda < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
and then, in the migration file, go with something like:
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.belongs_to :user, index: true
t.belongs_to :agenda, index: true
t.string :privilege
t.timestamps
end
end
end
Would I be able to call #user.agenda.privilege to get the privilege ("role" of creator, editor or viewer) of a given user for a given agenda?
Conversely, would I be able to call #agenda.user.privilege ?
Okay I will preface by saying I have not tested this but I think one of these 2 choices should work well for you.
Also if these join tables will never need functionality besides a relationship then has_and_belongs_to_many would be fine and more concise.
Basic Rails rule of thumb:
If you need to work with the relationship model as its own entity, use has_many :through. Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship itself.
First using your example (http://repl.it/tNS):
class User < ActiveRecord::Base
has_many :user_agendas
has_many :agendas, through: :user_agendas
has_many :user_agenda_roles, through: :user_agendas
has_many :roles, through: :user_agenda_roles
def agenda_roles(agenda)
roles.where(user_agenda_roles:{agenda:agenda})
end
end
class Agenda < ActiveRecord::Base
has_many :user_agendas
has_many :users, through: :user_agendas
has_many :user_agenda_roles, through: :user_agendas
has_many :roles, through: :user_agenda_roles
def user_roles(user)
roles.where(user_agenda_roles:{user: user})
end
end
class Role < ActiveRecord::Base
has_many :user_agenda_roles
end
class UserAgenda < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
end
class UserAgendaRoles < ActiveRecord::Base
belongs_to :role
belongs_to :user_agenda
end
This uses a join table to hold the relationship of User <=> Agenda and then a table to join UserAgenda => Role.
The Second Option is to use a join table to hold the relationship of User <=> Agenda and another join table to handle the relationship of User <=> Agenda <=> Role. This option will take a bit more set up from a CRUD standpoint for things like validating if the user is a user for that Agenda but allows a little flexibility.
class User < ActiveRecord::Base
has_many :user_agendas
has_many :agendas, through: :user_agendas
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
def agenda_roles(agenda)
roles.where(user_agenda_roles:{agenda: agenda})
end
end
class Agenda < ActiveRecord::Base
has_many :user_agendas
has_many :users, through: :user_agendas
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
def user_roles(user)
roles.where(user_agenda_roles:{user: user})
end
end
class Role < ActiveRecord::Base
has_many :user_agenda_roles
end
class UserAgenda < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class UserAgendaRoles < ActiveRecord::Base
belongs_to :role
belongs_to :user
belongs_to :agenda
end
I know this is a long answer but I wanted to show you more than 1 way to solve the problem in this case. Hope it helps
I am implementing an authentication+authorization system in my Rails 3 Application.
I have a HABTM relationship with between Users and Roles.
The roles I will have are : manager,dealer,operator,admin
Now a manager can have many dealers under him. How do I model this relationship?
It is a simple question but I could not find an answer. Also a similar question here: Role-dependent associations but it does not clear things properly.
EDIT:
I am thinking my requirement is further simple. For me a user can only be either an Admin, Operator, Dealer or Manager.
For this I can simply add a role column to User table. How will the relationship between Managers and Dealers be enforced now?
I think you're looking for something more like
class Organization < ActiveRecord::Base
has_many :users
has_many :managers
has_many :dealer
has_many :admins
has_many :operators
has_many :dealer_users, :through => :managers, :class_name=>"User"
end
class Admin < ActiveRecord::Base
has_many :organizations
belongs_to :user
end
class Dealer
has_many :organizations
belongs_to :user
end
class Operator < ActiveRecord::Base
has_many :organizations
belongs_to :user
end
class Manager < ActiveRecord::Base
has_many :dealers
belongs_to :organization
end
class User < ActiveRecord::Base
has_many :organizations
has_many :admins
has_many :operators
has_many :managers
has_many :dealers
end