Update many-to-many association in rails (User-Team relationship aka Membership) - ruby-on-rails

I just created a create feature for adding a team together with adding the members to a said team.
The form contains the following:
Name of the team.
Team's department.
Leader (value is the user id, but displayed as full name)
Members (values are user ids, but displayed also as full names) //I used a special select menu called select2.
Here are the models. I'll only show the associations and some methods related to my problem.
user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :teams, through: :memberships
accepts_nested_attributes_for :memberships, :teams
end
team.rb
class Team < ActiveRecord::Base
has_many :memberships
has_many :teams, through: :memberships
accepts_nested_attributes_for :memberships, :users
def build_membership(user_ids)
unless user_ids.blank?
user_ids.each do |id|
self.users << User.find_by_id(id)
end
end
end
end
membership.rb
class Membership < ActiveRecord::Base
belongs_to :team
belongs_to :user
#Note that leader and members are both users
end
Here is the controller, with the create and update methods.
class TeamsController < ApplicationController
def create
#team = Team.new(team_params)
#team.build_membership(build_members_array(members_params))
if #team.save
flash.now[:success] = 'Team was successfully created.'
redirect_to #team
else
flash.now[:notice] = #team.errors.full_messages
render "new"
end
end
def update
# TO-DO: update leader and members (i.e. add or remove member)
if #team.update(team_params)
flash.now[:success] = "Team was successfully updated."
redirect_to #team
else
flash.now[:notice] = #team.errors.full_messages
render "edit"
end
end
private
def team_params
params.require(:team).permit(:name,:department)
end
def members_params
params.require(:team).permit(:leader, members:[])
end
def build_user_ids_array(members)
#put ids of leader and members in an array
end
end
It seems that only the name and department attributes are only updated while the leader and the members are not. Should I create my own method again for updating the roster of the team or do something else in mind?

#team.build_membership would be if you are passing in the params to create a new user, but you are using existing User's so this wouldn't apply. You will want to instead fetch all of the User's by their id and then add that the the Team. You also need to permit the id in the members_attributes in your member_params method.
Something like:
def create
#team = Team.new(team_params)
#team.memberships << users_from_params
if #team.save
flash.now[:success] = 'Team was successfully created.'
redirect_to #team
else
flash.now[:notice] = #team.errors.full_messages
render "new"
end
end
def members_params
params.require(:team).permit(:leader, members_attributes: [:id])
end
def users_from_params
#This members_params[:members_attributes].values should be an array of `id`s
User.find(members_params[:members_attributes].values)
end

Related

Create record for another model when account is created?

I have a multi-tenant application. When an account is created, the account belongs to an owner and also creates a user record for the owner. This owner can invite other users through a memberships join table. All users except for account owners have memberships to the account.
For users with memberships (not owners of accounts) the account/users/:id show page shows up. I would like the same for account owners, but am receiving the following error message:
ActiveRecord::RecordNotFound in Accounts::UsersController#show
Couldn't find User with 'id'=2 [WHERE "memberships"."account_id" = $1]
def show
#user = current_account.users.find(params[:id])
end
I can add a membership to the owner user in the admin panel and this error goes away, however I would like to add the membership to the owner/user when they create their account.
Any ideas?
Adding #account.memberships.build(user_id: current_user, account_id: current_account) before if #account.save in the accounts controller below does not seem to work.
controllers
user.rb
module Accounts
class UsersController < Accounts::BaseController
before_action :authorize_owner!, only: [:edit, :show, :update, :destroy]
def show
#user = current_account.users.find(params[:id])
end
def destroy
user = User.find(params[:id])
current_account.users.delete(user)
flash[:notice] = "#{user.email} has been removed from this account."
redirect_to users_path
end
end
end
accounts_controller.rb
class AccountsController < ApplicationController
def new
#account = Account.new
#account.build_owner
end
def create
#account = Account.new(account_params)
if #account.save
sign_in(#account.owner)
flash[:notice] = "Your account has been created."
redirect_to root_url(subdomain: #account.subdomain)
else
flash.now[:alert] = "Sorry, your account could not be created."
render :new
end
end
private
def account_params
params.require(:account).permit(:name, :subdomain,
{ owner_attributes: [:email, :password, :password_confirmation
]}
)
end
end
Models
user.rb
class User < ApplicationRecord
has_many :memberships
has_many :accounts, through: :memberships
def owned_accounts
Account.where(owner: self)
end
def all_accounts
owned_accounts + accounts
end
end
account.rb
class Account < ApplicationRecord
belongs_to :owner, class_name: "User"
accepts_nested_attributes_for :owner
validates :subdomain, presence: true, uniqueness: true
has_many :memberships
has_many :users, through: :memberships
end
membership.rb
class Membership < ApplicationRecord
belongs_to :account
belongs_to :user
end
Have you tried callback after_create?
If it works, you will need to figure it on client and admin create an account, self assigning (admin) against on create assigning (client).
# models/account.rb
after_create do
self.memberships.create(user_id: self.owner, account_id: self.id)
end
Ended up answering this question by putting this line under if #account.save:
if #account.save
#account.memberships.create(user_id: #account.owner.id, account_id: #account.id)
Probably not ideal, but it works for now. I might end up making a service or something like that for it, though.

has_many :through add extra param in join table in one call (object creation)

I have the following code letting a user to create a new album through a join table with an extra params (creator).
In order to do it, my controller does 2 requests (one for creating the album object and the collaboration object / the other to update the collaboration object with the extra params).
I would like to know if there is a way to do this call with only one request. (add the extra "creator" params in the same time than the album creation)
Thank you.
albums_controller.rb
class AlbumsController < ApplicationController
def new
#album = current_user.albums.build
end
def create
#album = current_user.albums.build(album_params)
if current_user.save
#album.collaborations.first.update_attribute :creator, true
redirect_to user_albums_path(current_user), notice: "Saved."
else
render :new
end
end
private
def album_params
params.require(:album).permit(:name)
end
end
Album.rb
class Album < ApplicationRecord
# Relations
has_many :collaborations
has_many :users, through: :collaborations
end
Collaboration.rb
class Collaboration < ApplicationRecord
belongs_to :album
belongs_to :user
end
User.rb
class User < ApplicationRecord
has_many :collaborations
has_many :albums, through: :collaborations
end
views/albums/new
= simple_form_for [:user, #album] do |f|
= f.input :name
= f.button :submit
You can just add associated objects on the new album instance:
#album = current_user.albums.new(album_params)
#album.collaborations.new(user: current_user, creator: true)
When you call #album.save ActiveRecord will automatically save the associated records in the same transaction.
class AlbumsController < ApplicationController
def new
#album = current_user.albums.new
end
def create
#album = current_user.albums.new(album_params)
#album.collaborations.new(user: current_user, creator: true)
if #album.save
redirect_to user_albums_path(current_user), notice: "Saved."
else
render :new
end
end
private
def album_params
params.require(:album).permit(:name)
end
end
You are also calling current_user.save and not #album.save. The former does work due to fact that it causes AR to save the associations but is not optimal since it triggers an unessicary update of the user model.

Rails - destroying membership/ownership in user group

I need to be able to "destroy" group ownership when I "destroy" membership of a group if a user is the owner of the group. I have "User", "Group/Cliq", and "Group/CliqMembership" models. When a user creates the group they are the "owner" of the group. Other users can then join the group. When a user leaves the group the membership association for that user and group is destroyed. However, when an owner leaves the group it only removes the "membership" and not the "ownership". I feel like there should be an easy solution, but I'm kind of stuck.
For clarity: Cliqs = Groups; the question is: how do I delete the ownership association and the membership association at the same time? When an "owner" leaves a group I want it to destroy their "group ownership" and their "group membership". As an aside: how would I make the owned group "destroy dependent" when the "owner" leaves?
Here are my models:
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships
has_many :members, through: :cliq_memberships, source: :user
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
class User < ActiveRecord::Base
has_one :owned_cliq, foreign_key: 'owner_id', class_name: 'Cliq', dependent: :destroy
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
.
.
.
end
And my controllers:
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = current_user.build_owned_cliq(cliq_params)
#cliq.members << current_user
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
def destroy
#cliq = current_user.owned_cliq.find(params[:id])
flash[:alert] = "Are you sure you want to delete your Cliq? Your Cliq and all of its associations will be permanently deleted."
#cliq.destroy
if #cliq.destroy
redirect_to current_user
flash[:notice] = "You deleted the Cliq."
else
redirect_to current_user
#set up error handler
flash[:notice] = "Failed to delete Cliq."
end
end
def cliq_params
params.require(:cliq).permit(:name, :cliq_id)
end
class CliqMembershipsController < ApplicationController
def show
end
def create
#Cliq or Cliq_ID?
#cliq = Cliq.find(params[:cliq])
#cliq_membership = current_user.cliq_memberships.build(cliq: #cliq)
#cliq.members << current_user
if #cliq_membership.save
flash[:notice] = "Joined #{#cliq.name}"
else
#Set up multiple error message handler for rejections/already a member
flash[:notice] = "Not able to join Cliq."
end
redirect_to cliq_url
end
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
#cliq_membership.destroy
if #cliq_membership.destroy
flash[:notice] = "You left the Cliq."
redirect_to user_path(current_user)
else
end
end
end
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#uploads = Upload.all
#cliq_memberships = CliqMembership.all
#cliqs = Cliq.all
end
end
In your CliqMemberships controller's destroy action, right before destroying the CliqMembership, you can check for the current_user's ownership of the group just like you're checking for his/her membership. I'm assuming you want an implementation wherein if the owner leaves the group, you want the group to automatically be destroyed too, along with all its memberships. (Correct me if I'm wrong on that.) In that case, you can add dependent: :destroy to has_many :cliq_memberships
If the current_user happens to be the owner, you can destroy the cliq altogether which would in turn destroy its cliq_memberships too.
You can do it like this in CliqMemershipsController's destroy action.
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
#cliq = #cliq_membership.cliq
if #cliq.owner == current_user
#cliq.destroy
flash[:notice] = "The Cliq is destroyed"
redirect_to user_path(current_user)
else
# destroy only the membership
flash[:notice] = "You left the Cliq."
redirect_to user_path(current_user)
end
end

Creating an association from an object

class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
The following successfully creates an new association in the memberships table:
def create
#group = Group.find(params[:group_id])
current_user.groups << #group
flash[:success] = "You have successfuly joined this group."
respond_to do |format|
format.html {redirect_to #group}
format.js
end
end
However I would like to test the association has been created first and display an appropriate flash message such as:
def create
#group = Group.find(params[:group_id])
membership = current_user.memberships.create(#group)
if membership.save
flash[:success] = "You have successfuly joined this group."
else
flash[:error] = "Unable to join this group."
end
respond_to do |format|
format.html {redirect_to #group}
format.js
end
end
This code however errors with:
"ArgumentError (When assigning attributes, you must pass a hash as an argument.):"
Is it possible to create an association using an existing object?
membership = current_user.memberships.create({group_id: #group.id})

after_create destroy a record

I have two tables bookings and rentals. A user books a car to rent and an admin approves the rental.
As the admin approves the rental. The booking is no longer needed. How can i delete the booking record at the same time as creating the rental record.
this was my attempt (i'm new to ruby so apolagies if i am being stupid)
#rental_controller.rb
after_create :delete_booking
def delete_booking
#booking = Booking.find(params[:id])
#booking.destroy
respond_to do |format|
format.html { redirect_to rental_url }
format.json { head :no_content }
end
end
After create belong in the model, not the controller. I'm assuming you have a rental model since the snippet is from the rentals controller.
In the rental model:
after_create :delete_booking
def delete_booking
#booking = Booking.where(:booking_no => self.booking_no).first
#booking.destroy
end
Ideally something like ..
# Booking.rb Model
has_one :booking
And
# Rental.rb Model
belongs_to :booking, :class_name => "Booking", :foreign_key => "booking_no"
after_create :delete_booking
private
def delete_booking
self.booking.destroy
end

Resources