I use Devise and the devise_invitable
gem to manage and invite users / members to my Rails app. I have some issues with form validations. I have two fields in my invitation form, one for name
and the other for recipient_email
. Name is optional. When I click submit button - either when the form is valid or not, e.g. when both fields are empty - I get the following error:
undefined method `errors' for false:FalseClass
The error point to my invitations_controller
create
action. This method looks like this:
def create
@user = User.find_by(email: invite_params[:email])
@team = Team.find_by(id: params[:team_id])
already_invited = false
# Check if the user already s invited to the team.
@team.invitations.each do |invitation|
if @user && invitation.recipient_email.eql?(@user.email)
already_invited = true
end
end
if already_invited
redirect_to edit_team_path(@team), notice: "User already invited"
else
# The app crashes here
super
end
end
I have also customised these two devise_invitable
methods: after_invite_path_for
and invite_resource(&block)
. Much based on this tutorial. They look like this:
def after_invite_path_for(resource)
team = Team.find_by_id(params[:team_id])
edit_team_path(team)
end
def invite_resource(&block)
@user = User.find_by(email: invite_params[:email])
@team = Team.find_by(id: params[:team_id])
if @user && @user.email != current_user.email
token = Digest::SHA1.hexdigest([Time.now, rand].join)
@user.invite_existing_user!(@user, current_user, token)
member = @team.members.find_by(user_id: current_or_guest_user.id)
invitation = @team.invitations.build(invitation_token: token, member_id: member.id, recipient_email: invite_params[:email])
invitation.save
@user
else
@user = resource_class.invite!(invite_params, current_inviter, &block)
member = @team.members.find_by(user_id: current_or_guest_user.id)
invitation = @team.invitations.build(invitation_token: @user.raw_invitation_token, member_id: member.id, recipient_email: invite_params[:email])
invitation.save
end
end
I have four relevant models, User
(the entity that is getting invited), Team
each user can be a Member
(join table) of multiple Team
. I also have a custom Invitation
model, each Team
can have many invitations, I use this to list invitations sent for a specific team. The invitation functionality worked before, but I had some issues when the form was invalid, for example when both fields where empty
. To fix this I tried to enable this in Devise.rb
:
config.validate_on_invite = true
And in my User
model have the following:
devise [...], :invitable, validate_on_invite: true
In my user model I also have this:
validates_presence_of :name
validates_presence_of :email
And in my custom Invitation
model I have this:
validates_presence_of :recipient_email
The issue I had before was that invite_resource
was getting called despite invalid input, and I would create a new custom Invitation
resource for my team.
What I want in error cases is simply:
How can I get this to work when using devise_invitable
?
You're returning false
from your custom invite_resource
implementation. The base controller calls this method and expects an ActiveRecord model instance back, then examines errors
on that. But you're returning false
, so it tries to execute false.errors
- hence the problem you see when you invoke super
.
See line 27 via:
...and the default implementation of invite_resource
at:
...versus your complex implementation:
def invite_resource(&block)
@user = User.find_by(email: invite_params[:email])
@team = Team.find_by(id: params[:team_id])
if @user && @user.email != current_user.email
token = Digest::SHA1.hexdigest([Time.now, rand].join)
@user.invite_existing_user!(@user, current_user, token)
member = @team.members.find_by(user_id: current_or_guest_user.id)
invitation = @team.invitations.build(invitation_token: token, member_id: member.id, recipient_email: invite_params[:email])
invitation.save
@user
else
@user = resource_class.invite!(invite_params, current_inviter, &block)
member = @team.members.find_by(user_id: current_or_guest_user.id)
invitation = @team.invitations.build(invitation_token: @user.raw_invitation_token, member_id: member.id, recipient_email: invite_params[:email])
invitation.save
end
end
Note that the else
case in the above evaluates to the result of invitation.save
, which for validation errors will be false
. You forgot the @user
declaration as present in the above-else
case. Just DRY up that code a bit, e.g. as follows (stylistically this might not be to everyone's taste):
def invite_resource(&block)
@user = User.find_by(email: invite_params[:email])
@team = Team.find_by(id: params[:team_id])
invitation = if @user && @user.email != current_user.email
token = Digest::SHA1.hexdigest([Time.now, rand].join)
@user.invite_existing_user!(@user, current_user, token)
member = @team.members.find_by(user_id: current_or_guest_user.id)
@team.invitations.build(invitation_token: token, member_id: member.id, recipient_email: invite_params[:email])
else
@user = resource_class.invite!(invite_params, current_inviter, &block)
member = @team.members.find_by(user_id: current_or_guest_user.id)
@team.invitations.build(invitation_token: @user.raw_invitation_token, member_id: member.id, recipient_email: invite_params[:email])
end
invitation.save
@user
end
That should at least fix your undefined method
exception, though it leaves any validation errors on invitation
ignored (is a save!
in order here, with the whole thing wrapped in User.transaction do...
to keep all those invitations atomic?).