has_many :through default values - ruby-on-rails

I have a need to design a system to track users memberships to groups with varying roles (currently three).
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class Role < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :role
belongs_to :group
end
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
end
Ideally what I want is to simply set
#group.users << #user
and have the membership have the correct role. I can use :conditions to select data that has been manually inserted as such :
:conditions => ["memberships.role_id= ? ", Grouprole.find_by_name('user')]
But when creating the membership to the group the role_id is not being set.
Is there a way to do this as at present I have a somewhat repetitive piece of code for each user role in my Group model.
UPDATED
It should be noted what id ideally like to achieved is something similar to
#group.admins << #user
#group.moderators << #user
This would create the membership to the group and set the membership role (role_id ) appropriately.

You can always add triggers in your Membership model to handle assignments like this as they are created. For instance:
class Membership < ActiveRecord::Base
before_save :assign_default_role
protected
def assign_default_role
self.role = Role.find_by_name('user')
end
end
This is just an adaptation of your example.

Related

How can I implement a has_many :through association two ways?

I have a property model and a user model.
A user with the role of 'admin', which is represented by a column on the users table, can have many properties.
A user with a role of 'guest' can also belong to a property, which gives them access to that property.
How should I do this in Rails?
authorizations table -> user_id, property_id
class Authorization < ActiveRecord::Base
belongs_to :user
belongs_to :property
end
class User < ActiveRecord::Base
has_many :authorizations
has_many :properties, through: :authorizations
end
class Property < ActiveRecord::Base
has_many :authorizations
has_many :users, through: :authorizations
end
then you can do User.find(id).properties
First, you need a has_many :through association between your models User and Property. So, create a new table properties_users with columns user_id and propety_id. And do following changes to the models:
class PropertiesUser < ActiveRecord::Base
belongs_to :user
belongs_to :property
end
class User < ActiveRecord::Base
has_many :properties_users
has_many :properties, through: :properties_users
end
class Property < ActiveRecord::Base
has_many :properties_users
has_many :users, through: :properties_users
end
Now, we need to make sure that a guest user does not have more than one property. For that we can add a validation to model PropertiesUser like below:
class PropertiesUser < ActiveRecord::Base
validate :validate_property_count_for_guest
private
def validate_property_count_for_guest
return unless user && user.guest?
if user.properties.not(id: self.id).count >= 1
self.errors.add(:base, 'guest user cannot have more than one properties')
end
end
end
class User < ActiveRecord::Base
def guest?
# return `true` if user is guest
end
end
Finally, to access a guest user's property, define a dedicated method in model User:
class User < ActiveRecord::Base
def property
# Raise error if `property` is called on non-guest users
raise 'user has multiple properties' unless guest?
properties.first
end
end
Now, you can fetch a guest user's property by running:
user = User.first
user.guest?
=> true
user.property
=> <#Property 1> # A record of Property

Convinient way to implement user roles in my app

Thanks for reading!
I'm currently working on my new app and searching for the best way to implement next feature
By scenario I need to implement "As a user a have role in the location"
WHAT I HAVE DONE:
Scenario:
When user adds new location to the profile
one of the requred fields is "role". That could be "guest", "manager" or "seller". What's the best way to accomplish his in the model side?
I accomplished this with has_many_through assosiation
CONTROLLER:
def create
#location = Location.new(location_params)
#location.profiles << current_user.profile
#set user role
current_user.profile.profile_location_throughs.where(location_id: location.id).set_role(params[:location][:role])
respond_to do |format|
if #location.save
....
end
end
end
MODELS:
class Profile < ActiveRecord::Base do
has_many :profile_location_throughs
has_many :locations, through: :profile_location_throughs
end
class Location < ActiveRecord::Base do
has_many :profile_location_throughs
has_many :locations, through: :profile_location_throughs
end
class ProfileLocationThrough < ActiveRecord::Base
# with boolean fields: manager, seller, guest
belongs_to :location
belongs_to :profile
def set_role(role)
case role
when "guest"
self.guest = true
when "seller"
self.seller = true
when "manager"
self.manager = true
end
end
end
=====
QUESTION:
Could you suggest more beatiful way to implement his feature?
There are several ways to do role based authorization.
The simplest way is by adding a enum to the users themselves:
class Profile < ApplicationRecord
enum role: [:guest, :seller, :manager]
end
This is pretty limited though as it only allows "global" roles.
If you want resource scoped roles you need a join table.
class Profile < ApplicationRecord
has_many :roles
has_many :locations, through: :roles
def has_role?(role, location = nil)
self.roles.exists?( { name: role, location: location}.compact )
end
def add_role(role, location)
self.roles.create!( { name: role, location: location } )
end
end
class Role < ApplicationRecord
belongs_to :profile
belongs_to :location
end
class Location < ApplicationRecord
has_many :roles
has_many :profiles, through: :roles
end
In this example we are simply using a string for the roles.name column. You could also use an enum if the kinds of roles are limited. If you want to use the same Role model (no pun intended) to scope roles on different kinds of resources you can use a polymorphic belongs_to relationship.
class Role < ApplicationRecord
belongs_to :profile
belongs_to :resource, polymorphic: true
end
class Location < ApplicationRecord
has_many :roles, as: :resource
has_many :profiles, through: :roles
end
class OtherThing < ApplicationRecord
has_many :roles, as: :resource
has_many :profiles, through: :roles
end
Note that roles are just one part of an authentication solution. You would combine this with a authorization lib such as Pundit or CanCanCan which defines the rules about what role gets to do what and enforces those rules.
Rolify - Role management library with resource scoping

Rails - how to find out user's role in the system?

I have following models:
class Role < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class User < ActiveRecord::Base
has_many :assignments
has_many :roles, :through => :assignments
...
end
I am trying to find out user's role, but when I try to
user.assignments.name
It doesn't print out the user's role from the table roles (column name).
How to print out that?
You need to map on your association in order to get a specific field:
user.roles.map(&:name)
Try this.
user.roles.each {|role| puts role }
You can't call name method on user.assignments because it's an array.
user.assignments.each do |a|
puts a.name
end

Rails 3, how to make sure Group has_one user.role = groupleader

My current Group model:
class Group < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :users, :through => :memberships
end
My Current User Model
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
has_many :memberships, :dependent => :destroy
has_many :groups, :through => :memberships
#some more stuff
end
Membership Model
class Membership < ActiveRecord::Base
attr_accessible :user_id, :group_id
belongs_to :user
belongs_to :group
end
Role Model
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
I have a Ability class and CanCan installed to handle roles. I have a role type groupleader and need to make sure a Group has only one groupleader...
I think its something like: Group has_one User.role :groupleader... but I know thats not it.
It doesn't make sense to me to have the role on the users table if you want it to determine what the user can do within the context of a group.
Where it would make sense is to have it on the memberships table for groups and users. Records in this table would then have three columns: user_id, group_id and role.
Then to retrieve the leader for the group you would execute a query like this:
group.users.where("memberships.role = 'leader'").first
Where group is a Group object, i.e. Group.first or Group.find(13).
This then leaves open the possibility that you can have more than one leader for a group further down the track if required.
If your roles are in a separate table, then you can do this:
group.users.where("memberships.role_id = ?", Role.find_by_name("leader").id).first

Can a 3-way relationship be modeled this way in Rails?

A User can have many roles, but only one role per Brand.
Class User < AR::Base
has_and_belongs_to_many :roles, :join_table => "user_brand_roles"
has_and_belongs_to_many :brands, :join_table => "user_brand_roles"
end
The problem with this setup is, how do I check the brand and the role at the same time?
Or would I better off with a BrandRole model where different roles can be set up for each Brand, and then be able to assign a user to a BrandRole?
Class User < AR::Base
has_many :user_brand_roles
has_many :brand_roles, :through => :user_brand_roles
end
Class BrandRole < AR::Base
belongs_to :brand
belongs_to :role
end
Class UserBrandRole < AR::Base
belongs_to :brand_role
belongs_to :user
end
This way I could do a find on the brand for the user:
br = current_user.brand_roles.where(:brand_id => #brand.id).includes(:brand_role)
if br.blank? or br.role != ADMIN
# reject access, redirect
end
This is a new application and I'm trying to learn from past mistakes and stick to the Rails Way. Am I making any bad assumptions or design decisions here?
Assuming Roles,Brands are reference tables. You can have a single association table Responsibilities with columns user_id, role_id, brand_id.
Then you can define
Class User < AR::Base
has_many : responsibilities
has_many :roles, :through => responsibilities
has_many :brands,:through => responsibilities
end
Class Responsibility < AR::Base
belongs_to :user
has_one :role
has_one :brand
end
The you can define
Class User < AR::Base
def has_access?(brand)
responsibility = responsibilities.where(:brand => brand)
responsibility and responsibility.role == ADMIN
end
end
[Not sure if Responsibility is the term used in your domain, but use a domain term instead of calling it as user_brand_role]
This is a conceptual thing. If BrandRole is an entity for your application, then your approach should work. If BrandRole is not an entity by itself in your app, then maybe you can create a UserBrandRole model:
class User < AR::Base
has_many :user_brand_roles
end
class Brand < AR::Base
has_many :user_brand_roles
end
class Role < AR::Base
has_many :user_brand_roles
end
class UserBrandRole < AR::Base
belongs_to :user
belongs_to :brand
belongs_to :role
validates_uniqueness_of :role_id, :scope => [:user_id, :brand_id]
end

Resources