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.
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 am trying to create an association between two tables. A student table and a computer table.
A computer can only ever be assigned to one student (at any one time) but a student can be assigned to multiple computers.
This is what I currently have in mind. Setting up a has-many through relationship and modifying it a bit.
class Student < ActiveRecord::Base
has_many :assignemnts
has_many :computers, :through => :assignments
end
class Computer < ActiveRecord::Base
has_one :assignment
has_one :student, :through => :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :student
belongs_to :computer
end
Does this seem like the best way to handle this problem? Or something better sound out quickly to the experts here. Thanks!
You need first to decide if a simple one-to many relationship is enough for you.
If yes, it gets a lot easier, because you can get rid of the Assignment-class and table.
Your database-table "computers" then needs a student_id column, with a non-unique index
Your models should look like this:
class Computer < ActiveRecord::Base
belongs_to :student
end
class Student < ActiveRecord::Base
has_many :computers, :dependent => :nullify
end
"dependent nullify" because you don't want to delete a computer when a student is deleted, but instead mark it as free.
Each of your computers can only be assigned to a single student, but you can reassign it to a different student, for example in the next year.
Actually your approach is fine, as one offered by #alexkv. It is more discussion, than question.
Another thing if you want to use mapping table for some other purposes, like storing additional fields - then your approach is the best thing. In has_many :through table for the join model has a primary key and can contain attributes just like any other model.
From api.rubyonrails.org:
Choosing which way to build a many-to-many relationship is not always
simple. 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.
I can advise you read this, to understand what approach better to choose in your situation:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
You can also use has_and_belongs_to_many method. In your case it will be:
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers, :join_table => 'assignments',
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student, :join_table => 'assignments',
end
or you can rename assignments table to computers_students and remove join_table
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student
end
Can someone help me to correct my associations ?
I have the following models:
User, Developer, Application, Comments, Rating, Permission
Requirements:
A user can be a Developer or not.
A user can have Default Permissions and Permissions for each application
A user can install multiple Applications
A user can comment and rate multiple Applications
A developer can develop multiple applications
An application can request a list of permissions.
I already created some associations but I believe its not 100% correct or an easier way to do it exist.
Can someone suggest me a correct way to do it?
You are confusing models with authorization.
You should check out CanCan for role based authorization. For example you don't need your developer model since its just a user with a different role/permissions.
Edit: Changed 'role based authentication' to 'role based authorization'. As the comment below points out the difference between authentication and authorization.
I think this is what you want as far as your model work. You can use a join model to manage your application permissions, and use Rails STI to manage what each type of user can do, whether it's developing or not.
user.rb
class User < ActiveRecord::Base
has_many :apps
has_many :comments, :through => :user_comments
has_many :ratings, :through => :user_ratings
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
end
rating.rb
class Rating < ActiveRecord::Base
belongs_to :user
end
user_comment.rb
class UserComment < ActiveRecord::Base
belongs_to :app
end
user_rating.rb
class UserRating < ActiveRecord::Base
belongs_to :app
end
normal_user.rb (STI)
class NormalUser < User
end
developer.rb (STI)
class Developer < User
end
app.rb
class App < ActiveRecord::Base
has_many :permissions, :through => :app_permissions
has_many :user_comments
has_many :user_ratings
end
permission.rb
class Permission < ActiveRecord::Base
belongs_to :app
end
app_permission.rb
class AppPermission < ActiveRecord::Base
end
I agree with #Mark, don't use STI. The better way will be implement as suggested by #Chris Barretto except for STI and use CanCan for role based authentication. The change for User model will be:
class User < ActiveRecord::Base
has_many :apps
has_many :comments, :through => :user_comments
has_many :ratings, :through => :user_ratings
has_many :roles
end
And there will be another model for Role:
class Role < ActiveRecord::Base
has_many :users
end
If you are using gems like Devise for authentication, it will be much easy.
Often three (or more) way associations are needed for habtm associations. For instance a permission model with roles.
A particular area of functionality has many users which can access many areas.
The permissions on the area are setup via roles (habtm)
The user/roles association is also habtm
The permissions (read, write, delete, etc) are habtm towards roles.
How would that be best done with rails/activerecord?
I'm not sure if you are just using Role based user permissions as an example, or if that is actually your goal.
Nested habtm relationships are easy in Rails, though I would highly recommend Nested has_many :through, just set them up as you would imagine:
class Permission < ActiveRecord::Base
end
#this table must have permission_id and role_id
class PermissionAssignment < ActiveRecord::Base
belongs_to :permission
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users, :through => :role_assignments
has_many :permissions, :through => :permission_assignments
end
#this table must have user_id and role_id
class RoleAssignment < ActiveRecord::Base
belongs_to :role
belongs_to :user
end
class User < ActiveRecord::Base
has_many :roles, :through => :role_assignments
end
This question about rails and RBAC (role-based authentication control) has a bunch of useful samples that provide the answer to this question and also sample implementations of your example.
I have two models, Users and Groups. Each group can have many users and each user can be in many groups.
I currently have something simple like:
User:
has_many :groups
Group:
has_many :users
So I have a groups_users table which is just creating rows with group_id and user_id.
I want to add another column to this, (which I have), the question is how do I access it from a model without using a custom SQL call? In the group model I can go self.users and in user I can go self.groups
Is there a way to change the third column in this table from a user model?
Sorry if this is confusing, please advise on this
Here are a couple of tutorials that should help. Basically there two approaches to make many-to-many work, either has_and_belongs_to_many or has_many :through (recommended).
links:
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
http://railscasts.com/episodes/47-two-many-to-many
http://railscasts.com/episodes/154-polymorphic-association
In Rails 3 you want to make a join table for many to many relationships, using the plural names of the tables you want to join in alphabetical order. So in this case it would be groups_users.
models
class GroupsUser < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :groups_users
has_many :groups, :through => :groups_users
end
class Group < ActiveRecord::Base
has_many :groups_users
has_many :users, :through => :groups_users
end
I [added] another column to [users_groups]...The question is how do
I access it from a model without using
a custom SQL call?
It sounds like you want to access a column of your user_groups table by calling a method on your User model or your Group model.
Some suggestions:
I'd name the table "user_groups" to work with ActiveRecord's pluralization expectations, but I'm not sure if that's essential.
Following Dave's advice, you'd want to set things up using the "has_many :through" technique...
# Declare a Model based on the many-to-many linking table.
class UserGroup < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :user_groups
has_many :groups, :through => :user_groups
end
class Group < ActiveRecord::Base
has_many :user_groups
has_many :users, :through => :user_groups
end
Is there a way to change the third column in this table from a user model?
This is a little unclear, but keep in mind that each User can have a lot of UserGroups. So if you wanted to change that third column you'd have to find the particular one you're looking for.