I want to build "groups" that users can join,
The flow of steps and things needed is in my head but the code to build this lacks some bit since I'm still learning rails. I would like to ask some advice on your ideas best practice of what would be needed to accomplish below:
You can create a group with a name 'test'
You automatically join the group test, other users can join to
If want to see a list of the users joined my group 'test'
Im thinking of creating a model sessions like
groups|name|
But then how could I store the multiple users that are in this session?
Should I make an array of the user_id's for example and make an extra column like
groups|name|user_ids ?
What would be best practice and what rails (3) methods could I use to get a very rough version of the above functionality up and running ?
From what I understand this is a many to many relationship. So you would have 3 models :
class User < AR
has_many :memberships
has_many :groups, :through => :memberships
end
class Membership < AR
belongs_to :user
belongs_to :group
end
class Group < AR
has_many :memberships
has_many :users, :through => :memberships
end
and to know what users belong to a group :
#group = Group.find_by_name("test")
#users_group = #group.users
Update
to ensure the user creating the group also belongs to it :
# in group_controller.rb
def create
#group = Group.new(params[:group])
#group.users << current_user
if #group.save
# ... etc
end
end
of course the current_user should exist/be logged in with the usual before_filter (if I remember correctly its authenticate! with devise)
Sorry i'm creating a new thread, but I am unable to comment as of my new status right now.
#charlysisto your answer for this question involves a User, Membership, and Group class. I understand the need of User and Group, but I am confused to why we would need a Membership class. If anything doesn't it make more sense to have a Groups class with Group?
class User < AR
belongs_to :group
belongs_to :groups, :through => :group
end
class Groups < AR
has_many :groups
has_many :users, :through => :group
end
class Group < AR
has_many :users
belongs_to :groups
end
What do you think?
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'm using rails devise gem for users authentification, but now for my shop i must introduce user groups for discont's, special prices, etc. How to do this with devise? Note that this is one/many-to-many, becouse every user can have many groups, and every group some users.
Also when user is registering i't group must be for example 1.
Devise has "closed" controller (not as in authlogic). That is trouble also.
def create
super
group = Group.find_by_name("newuser")
user_group = UserGroup.create
user_group.user_id = current_user.id
user_group.group_id = group.group_id
user_group.save
end
This doesn't necessarily have to be integrated with Devise unless I'm reading your question wrong. Just create a Group model describing the attributes of a group, and a UserGroup join model:
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
# attr_accessible :user_id, :group_id
end
class Group < ActiveRecord::Base
has_many :user_groups
has_many :users, :through => :user_groups
end
As for the closed controller problem, you can lift the Devise controller into your application, or create a new controller which inherits from it and thus override the methods. Read more from their link GitHub page here.
Edit: I think you are approaching this from the wrong angle. You needn't do anything from within Devise's controllers, but rather add a before_save callback to your User model.
class User < ActiveRecord::Base
before_save(:on => :create) :assign_default_group
# Other model stuff here
private
def assign_default_group
# This automatically creates the UserGroup record
self.groups << Group.find_by_name("User")
end
end
I have three Models setup with the following associations
class User < ActiveRecord::Base
has_many :faculties
has_many :schools, :through => :faculties
end
class School < ActiveRecord::Base
has_many :faculties
has_many :users, :through => :faculties
end
class Faculty < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
and in my controller i go to create a school and assign the user
class SchoolsController < ApplicationController
def create
#school = current_user.schools.build(params[:school])
...
end
end
When I login and submit the form the flash displays success, but the association doesn't build on the join table.
I tried it inside the apps console and it builds the association just fine.
I've been stuck on this for a couple days now and I just cannot figure out what I am missing. Thank in advance for any and all advice
The build method does not save the object. You need to explicitly call #school.save.
Two things: If the schools association is :through a has_many association, you will have to select which parent the School exists through.
So, for instance, if you were to nest School resources under users as in /users/:id/faculties/:id you could create a school via current_user.faculties.find(params[:faculty_id]).schools.build(params[:school]).save
Based on the example code, it looks like the fundamental problem is that the has_many xxx, :through syntax is being used without specifying the id of the faculties record. Remember two things: 1) ActiveRecord doesn't natively support composite primary keys, and 2) you must call #save on associated records created using #build. If you remember these, you should be fine.
Just ran into an issue with a has_many :through association and after/before-destroy callbacks not being triggered.
Say, I have users, groups, and an intermediate relation called membership.
I have a form that allows users to be enrolled into groups by creating a new membership record when they check off associated checkboxes. Basically an array of group_ids.
Looks something like this:
Which group would you like to join? (check all that apply)
[] Group A
[] Group B
[] Group C
And I wish to record actions such as joining a group or leaving a group to activity log table and do some other less important thigns.
I have the following defined:
class Group < AR::Base
has_many :memberships
has_many :users, :through => :memberships
end
class Membership < AR::Base
belongs_to :user
belongs_to :group
after_create :log_event_to_audit_table
after_destroy :log_event_to_audit_table
end
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
attr_accessible :group_ids # enables mass-assignment
end
When a new membership record is created the after_create is run as expected. However, the after_destroy does not get triggered!
After google-ing and read up the docs I discovered the reason why:
"Automatic deletion of join models is
direct, no destroy callbacks are
triggered" - from Ruby Guides.
Hmmmmmm...
So the join model's (in this case Membership's) destroy callbacks are not being triggered. Well that's a downer. Any reason as to why?
So my question what is the best way to work around this issue?
Should I define my own membership_ids= method in User model that calls membership.destroy directly?
Open to any suggestions about the best practices in such a scenario.
Thanks!
After carefully examining the API docs, it turns out has_many and has_and_belongs_to_many ("HABTM") have a few options just for this case:
before_add
after_add
before_remove
after_remove
class User < ActiveRecord::Base
has_many :groups, :through => :memberships, :after_remove => :your_custom_method
end
Judging by how many responses I got, this must not be a very well documented/used feature.
Just noting it here for myself and others who may stumble like I did.
I've struggled with the same problem recently and solved it by extending association and overriding its delete method:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships do
def delete(*args)
groups = args.flatten
# destroy memberships in order to trigger their callbacks:
proxy_association.owner.memberships.where(group_id: groups).destroy_all
super
end
end
...
end
adding dependent: :destroy to the has many relationship actually calls the before_destroy and after_destroy methods in the Membership class.
class User < ActiveRecord::Base
has_many :groups, through: :memberships, dependent: :destroy
end
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.