Reputation: 53
I try to delete several records in my DB according to a few conditions. Before I talk about my problem I will explain how my app is working.
A User can create groups and links. This User own the groups he created same for the links. Other Users can then join this group by providing a token (which is created automatically when the group is created) via a Member model. Where member has the user_id and the group_id in the DB. Then once the User is part of a group he can share the links he created in the groups via Grouplink model. Where grouplink has groupd_id and link_id.
I managed to code the fact that if there is no member in a group the group is destroyed. But how do I manage to remove the links a user shared in the group if he leaves the group ? (when the user is destroyed automatically all the links are deleted so I think that the sharing should be gone as well, I have to try that tho'). When a User leaves the group I destroy his member record and he is gone but the links remain. I display the shared links, the fact that you can kick users (member destroy) and the list of the members of a group in the group show btw.
I thought about a few things. I write down what I did in the console
g = Group.find(66)
u = g.users.find(1)
u.links
and that give me all the links from the user in the group. Next to that
g.grouplinks
would give me all the shared links in the group.
g.grouplinks.map(&:link_id) returns [16, 17, 14, 13, 15]
u.links.map(&:id) returns [13, 15]
Now what can I do here ? My user is leaving the group. The member record is destroyed and how can I destroy those grouplinks according to the links the users has ?
Is there a magic trick I don't know yet ?
Thanks for your help.
EDIT
class User < ApplicationRecord
has_secure_password
has_many :links, dependent: :destroy
has_many :grouplinks, dependent: :destroy
has_many :members, :dependent => :destroy
has_many :groups, :through => :members
has_one :owned_group, foreign_key: "owner_id", class_name: "Group"
end
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
class Link < ApplicationRecord
has_many :grouplinks, :dependent => :destroy
belongs_to :user
end
class Grouplink < ApplicationRecord
belongs_to :group
belongs_to :link
end
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
has_many :grouplinks, :dependent => :destroy
def to_param
auth_token
end
end
I thought that actually I could add the user_id in the grouplinks so I could delete_all according to the user_id in the links and in the groupslinks. Not sure how to that tho' and don't know if there is a better solution.
EDIT 2
I tried your solution within the models. Actually it is smart and I didn't think about that... Problem is now with the creation of my grouplink (share the link). I had this :
def create
user = current_user if current_user
group = user.groups.find_by(auth_token: params[:auth_token])
share = group.id
group_link = group.grouplinks.build(link_id: params[:link_id])
gl = group.grouplinks
if gl.where(group_id: share).where(link_id: params[:link_id]).exists?
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path
else
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render 'new'
end
end
end
And this is obviously not working anymore and I have this error when I try to share a link : First argument in form cannot contain nil or be empty
<%= form_for @grouplink do |f| %>
.
I tried to change it like this :
def create
group = Group.find_by(auth_token: params[:auth_token])
share = group.id
group_link = group.grouplinks.build(link_id: params[:link_id])
gl = group.grouplinks
if gl.where(group_id: share).where(link_id: params[:link_id]).exists?
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path
else
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render 'new'
end
end
end
But it is not working either
Upvotes: 0
Views: 976
Reputation: 599
In your Member
model, you could use an after_destroy
callback to destroy all user links in the group after membership record is destroyed:
class Member < ApplicationRecord
after_destroy do |record|
record.user.links.each { |link| link.grouplinks.where(group_id: record.group.id).destroy_all }
end
belongs_to :user
belongs_to :group
...
end
Also, I suggest you change Member
to Membership
to be more clear.
Upvotes: 1
Reputation: 20263
How about:
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
has_many :group_links, dependent: :destroy
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
and
class Grouplink < ApplicationRecord
belongs_to :link
belongs_to :member
end
Now, when a Member
record is destroyed (i.e., the user
is kicked out of or leaves the group
), any links
shared with the group
(i.e., group_links
) are also destroyed. But, if the user
has shared the link
in another group
, the link
will continue to be shared with the other groups
.
As mentioned by @Pablo in the comments, you probably also want to do:
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :grouplinks, through: :members
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
def to_param
auth_token
end
end
Which will allow you to do:
group.grouplinks
I also agree with @amr-el-bakry that Member
is a bit confusing. I suggest GroupUser
as it makes it quite clear that it is an association between Group
and User
.
Also, I think it might be a bit more conventional to say GroupLink
instead of Grouplink
. Or, if you want to stick with naming based on associated classes, perhaps MemberLink
. If you change Member
to GroupUser
, then perhaps GroupUserLink
.
I'm thinking your create
code should probably look something like:
def create
if group
if member
if link
unless group_link
@group_link = member.group_links.build(link: link)
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render :new
end
else
flash[:error] = "You shared this link in '#{group.name}' already."
else
flash[:error] = "That link does not exist."
redirect_to somewhere #fix this
end
else
flash[:error] = "You must be a member of this group to add a link."
redirect_to somewhere #fix this
end
else
flash[:error] = "There is no group with that token."
redirect_to somewhere #fix this
end
end
private
def group
@group ||= Group.find_by(auth_token: params[:auth_token])
end
def member
@member ||= current_user.members.where(group: group)
end
def link
@link ||= Link.find_by(id: params[:link_id])
end
def group_link
@group_link ||= member.group_links.where(link: link)
end
You may be able to write this as:
def create
flash[:error] = "There is no group with that token."
redirect_to somewhere unless group
flash[:error] = "You must be a member of this group to add a link."
redirect_to somewhere unless member
flash[:error] = "That link does not exist."
redirect_to somewhere unless link
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path if group_link
flash[:error] = nil
@group_link = member.group_links.build(link: link)
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render :new
end
end
But I can't remember if those redirects will give you heartache.
Upvotes: 2
Reputation: 3005
I think the best idea is that the group_links belong to a member and a link (and not a group and a link). And a member (not a user) has many group_links. When your destroy the member, it will destroy the group_links.
EDIT
This is what jvillian suggested in his answer, just before I did. So I believe his answer is the right one (with some minor enhancements I suggested in comments that jvillian will surely accept and add :-).
EDIT2
Regarding the problem you faced after applying jvillian suggestion, when creating a new grouplink, it must be done from a member (not a group). So in the create action you must search the member (by user_id and group_id) and create the grouplink as member.grouplinks.build
Upvotes: 0
Reputation: 1505
What you're looking for is dependent :delete_all
In your Group
Model, you want to you should have a line like:
has_many :links, dependent :delete_all
This says, the group has many links and if you destroy the group, destroy all the related links.
Upvotes: 1