I have what I'd think would not be a unique situation with ActiveRecord, but I can't seem to find anyone with a similar issue, so here goes:
I have a User class for users and a Roles class that defines the capabilities of the user. For ex, you could have a user with a role of 'tutor', a user with a role of 'student', and a user with a role of ['tutor', 'student']:
class User < ActiveRecord::Base
has_many :roles
end
class Role < ActiveRecord::Base
end
What I'd like to do is add activerecord associations based on roles. Clearly, a student may have many :courses, a tutor may have a :subject they teach, and both a student and tutor could have many :appointments, but it doesn't seem like adding all these associations to every User instance is the right way to go.
Subclassing User also seems wrong - I thought about doing Tutor < User and Student < User, and adding the proper associations in each subclass, but what if we have a student that is a tutor? Then we need a StudentTutor class? If more roles are added, this route seems dangerous.
I've considered doing something like:
class User < ActiveRecord::Base
protected
after_initialize do
if self.has_role?(Role::STUDENT)
has_many :courses # This does not work
else
# etc etc etc
end
end
end
But I have no idea if this is considered wrong or how I'd even make it work. What's the best method for dealing with this kind of user/role setup with associations?
I would recommend a gem for your issue. See a related SO question: Recommendation for Role Gem
I would do the following:
class User < ActiveRecord::Base
has_many :roles
end
class Role < ActiveRecord::Base
end
class Tutor < Role
has_one :subject
end
class Student < Role
has_many :courses
end
Related
I have a pretty standard User/Role setup going on (a user HABTM roles, a role HABTM users). I'm using CanCanCan for authorisation, and the role you have defines what you can do around the application. This part is all working fine, but now I want to be able to have users inherit roles as part of having a subscription to different memberships.
Here are the models concerned:
class User < ApplicationRecord
has_and_belongs_to_many :roles
has_one :membership_subscription
has_one :membership, through: :membership_subscription
end
class Role < ApplicationRecord
has_and_belongs_to_many :users
end
class MembershipSubscription < ApplicationRecord
belongs_to :user
belongs_to :membership
end
class Membership < ApplicationRecord
has_many :membership_subscriptions
has_many :users, through: :membership_subscriptions
end
I was thinking that I might be able to just add a has_many: roles association to the Membership, and then say that the user has_many roles through their subscription to the Membership, as well as the current HABTM association that allows roles to be assigned directly.
This way you can directly attach roles to users like you can now (as some roles are additive, and not related to the membership subscription/type at all) but also users will automatically inherit roles (and lose them again) as their membership subs come and go.
Does that make sense? I guess the other option would be to use callbacks in the model to deal with creating/deleting role associations but it doesn't seem as elegant.
Any advice greatly appreciated!
Okay so I think this is a valid answer:
First, update the models so that there is an associations between the Memberships and the Roles:
class Role < ApplicationRecord
has_and_belongs_to_many :users
has_and_belongs_to_many :memberships
end
class Membership < ApplicationRecord
has_many :membership_subscriptions
has_many :users, through: :membership_subscriptions
has_and_belongs_to_many :roles
end
Next, create a method in the user model that can be used to retrieve both directly assigned roles and inherited roles:
def combined_roles
if self.membership == nil
self.roles
else
self.roles + self.membership.roles
end
end
Then wherever you need to check a role, call that method instead of the usual user.roles
Not sure if that's a naive way of doing things, but seems to work okay. Comments still welcome if there's a better way
EDIT:
This allows a user to have the same role multiple times - it can be assigned directly or inherited. Modify the combined_roles method like so so that it strips out duplicates:
def combined_roles
if self.membership == nil
self.roles
else
(self.roles + self.membership.roles).uniq
end
end
I read "Multitenancy with Rails" by Ryan Bigg and I'm creating a multi-tenant application using Ruby on Rails.
I make two models, Tenant and User.
Tenant has many User, User belongs to Tenant.
To associate these models, I made this file,
active_record_extensions.rb
ActiveRecord::Base.class_eval do
def self.scoped_to_tenant
belongs_to :tenant
association_name = self.to_s.downcase.pluralize
Tenant.has_many association_name.to_sym, class_name: self.to_s
end
end
and add "scoped_to_tenant" to User.rb
class User < ActiveRecord::Base
scoped_to_tenant
end
When I want to get all users of one Tenant(id=1), I can get it by these code.
Tenant.find(1).users
The question is, what is the difference between I write
belongs_to :tenant
to User.rb and use scoped_to_tenant method ?
In both case, Tenant.rb is this.
Tenant.rb < ActiveRecord::Base
has_many :users
end
Thank you for answer.
I may get English wrong, so please tell me if you can't understand something.
A call to scoped_to_tenant method call the method belongs_to for you and add the many association to Tenant.
This is same as doing this :
# app/model/user.rb
class User < ActiveRecord::Base
belongs_to :tenant
end
# app/model/tenant.rb
class Tenant < ActiveRecord::Base
has_many :users
end
The benefit of the scoped_to_tenant is that you don't care about adding has_many relationship to Tenant model.
If you only have one model to associate with Tenant, you don't need this extension.
I'm building a Rails application with multiple roles. I can't use inheritance against the user model because each role has very different properties and behavior (ie - different page access and abilities once logged in). So I've decided to build separate models for each role (ie - customer, service provider, customer representative, etc).
How would the associations for this work? I came up with the following but the Role class just looks bad to me. It wouldn't be so bad if the User could have multiple roles but since they only have one I'd have to write a custom validation in Role ensuring that only one role was selected. What do you guys think?
class User < ActiveRecord::Base
has_one :role
end
class Role < ActiveRecord::Base
has_one :customer
has_one :service_provider
has_one :customer_representative
end
class Customer < ActiveRecord::Base
end
class ServiceProvider < ActiveRecord::Base
end
class CustomerRepresentative < ActiveRecord::Base
end
I think the problem here is with your "Role" model. The role isn't actually a physical thing, but an interface that other objects should adhere to. Therefore, its a polymorphic relationship.
class User < ActiveRecord::Base
belongs_to :role, polymorphic: true
end
class Customer < ActiveRecord::Base
has_one :user, as: :role
end
class ServiceProvider < ActiveRecord::Base
has_one :user, as: :role
end
class CustomerRepresentative < ActiveRecord::Base
has_one :user, as: role
end
Then you need to add role_id and role_type to your user table.
This should get your what you want I believe.
Joe
I have a user model, farmer model, doctor model, and education model.
A farmer has a user and many educations.
A doctor has a user and many educations.
How do I setup the database for the education model?
Should it have a farmer_id AND a doctor_id?
But a education cannot belong to a farmer AND and doctor at the same time. It's one or the other.
So my education database entry would either have a farmer_id OR a doctor_id filled in, but not both.
Is there a way to guarantee that only one of the ids could be filled in at a time?
Or is there a better way to associate these models?
Your help would be appreciated!
Oh, and don't worry about the names of the models (farmer, doctor, etc.). It's just an example.
I see two possible solutions for this scenario.
The first one is to make use of polymorphic associations for education. That could look like this:
class Farmer < ActiveRecord::Base
belongs_to :user
has_many :educations, :as => :profession
end
class Doctor < ActiveRecord::Base
belongs_to :user
has_many :educations, :as => :profession
end
class Education < ActiveRecord::Base
belongs_to :profession, :polymorphic => true
end
So instead of education having a doctor_id or a farmer_id it has one profession_id and one profession_type.
The second solution would be to make use of Single Table Inheritance. And in your scenrio, that could be accomplished by letting a Doctor be a User instead of belonging to a User. And of course the same thing for a Farmer. That could look like this:
class User < ActiveRecord::Base
has_many :educations
end
class Farmer < User
end
class Doctor < User
end
class Education < ActiveRecord::Base
belongs_to :user
end
And in this scenario you would add a type column to the User model to store what type of class it is and then only having a user_id in the Education model
I think its appropriate to have the relations this way based on roles.
Class User
has_one :role
has_many :educations
end
Class Role
#What ever roles you have.
#Farmer or Doctor
belongs_to :user
end
class Education
belongs_to :user
end
This way you will store the user_id in the education object, which solves your problem.
I've been reading up on a bit of STI Inheritence examples for Rails,
While it looks like it would suit my needs (User role/types inheriting from a base class of user). It seems like it doesn't handle a user having multiple roles too well. Particularly because it relies on a type field in the database.
Is there a quick and easy work around to this?
I'm not sure if STI is the right solution in a user role/user scenario - I don't think of User roles/types as a specific form of user. I would have the following schema:
class Membership < ActiveRecord::Base
belongs_to :user # foreign key - user_id
belongs_to :role # foreign key - role_id
end
class User < ActiveRecord::Base
has_many :memberships
has_many :roles, :through => :membership
end
class Role < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :membership
end
Another way of doing this would be to use has_and_belongs_to_many. You might also want to check out the restful_authentication plugin.
A good workaround for your problem could be the easy_roles gem. You can find it on github.
As the others already said STI is not the way you can implement your stuff.
Just like Andy said, has_and_belongs_to_many is another way to do this.
# user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
# role.rb
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
Note: You still need the association table in your database but it's just hidden from your models.