Adding invite only users
Hi,
I'm building an app where users must be invited. I added the Devise Invitable gem, and added :invitable to the user model. I created an invitations controller and then generated custom views for the inviting and accepting. I can invite users just fine, but I am getting some errors when submitting the acceptance form:
```
NoMethodError (undefined method `update' for nil:NilClass):
app/models/concerns/user_accounts.rb:39:in `sync_personal_account_name'
app/controllers/users/invitations_controller.rb:14:in `accept_resource'
```
Here is my invitations_controller:
```
class Users::InvitationsController < Devise::InvitationsController
I'm building an app where users must be invited. I added the Devise Invitable gem, and added :invitable to the user model. I created an invitations controller and then generated custom views for the inviting and accepting. I can invite users just fine, but I am getting some errors when submitting the acceptance form:
```
NoMethodError (undefined method `update' for nil:NilClass):
app/models/concerns/user_accounts.rb:39:in `sync_personal_account_name'
app/controllers/users/invitations_controller.rb:14:in `accept_resource'
```
Here is my invitations_controller:
```
class Users::InvitationsController < Devise::InvitationsController
private
# This is called when creating invitation.
# It should return an instance of resource class.
def invite_resource
super
end
# This is called when accepting invitation.
# It should return an instance of resource class.
def accept_resource
resource_class.accept_invitation!(update_resource_params)
resource
end
end
```
I suspect that I am missing something during the creation that is not linking users to accounts appropriately, even though the logs look like they are trying to create the associated account. I also noticed the the invite logic is already being used for account invites, so I'm wondering if that is conflicting as well. Or it's possible I'm overcomplicating this. Any ideas?
```
I suspect that I am missing something during the creation that is not linking users to accounts appropriately, even though the logs look like they are trying to create the associated account. I also noticed the the invite logic is already being used for account invites, so I'm wondering if that is conflicting as well. Or it's possible I'm overcomplicating this. Any ideas?
I'm trying to figure out something similar, but without devise_invitable since there is already an invitation structure in place. But I'd like to require an invitation in order to create an account, without linking the invite to an existing account.... an "invitation only" user registration process during a beta testing phase. Becasue there is so much "baked-in magic" in JSP's invitations, I'll likely resort to adding some sort of CouponCode table, and disallow a new user registration without a valid coupon code. (I could but probably won't include an email address in the CouponCode to tie coupon X to invitee X.)
As for your approach...
First, if you are using the gem 'devise_invitable' I wonder if there is a conflict between the "invited_by_id" the gem uses vs the polymorphic "invited_by" that JSP already uses*, which has both an invited_by_id and invited_by_type.
Second, in your customer invitation process, is an invite from (e.g., linked to) an Account, or a User? I ask because it looked to me like JSP widely assumes invite is linked to an Account:
class AccountInvitation .... belongs_to :account ....
* Out of the box, the JSP migrations include:
As for your approach...
First, if you are using the gem 'devise_invitable' I wonder if there is a conflict between the "invited_by_id" the gem uses vs the polymorphic "invited_by" that JSP already uses*, which has both an invited_by_id and invited_by_type.
Second, in your customer invitation process, is an invite from (e.g., linked to) an Account, or a User? I ask because it looked to me like JSP widely assumes invite is linked to an Account:
class AccountInvitation .... belongs_to :account ....
* Out of the box, the JSP migrations include:
class DeviseInvitableAddToUsers < ActiveRecord::Migration[5.2] def up safety_assured { change_table :users do |t| t.string :invitation_token t.datetime :invitation_created_at t.datetime :invitation_sent_at t.datetime :invitation_accepted_at t.integer :invitation_limit t.references :invited_by, polymorphic: true t.integer :invitations_count, default: 0 t.index :invitations_count t.index :invitation_token, unique: true # for invitable t.index :invited_by_id end } end def down safety_assured { change_table :users do |t| t.remove_references :invited_by, polymorphic: true t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at end } end end
Right now, my invites are for a user, but JSP seems to have the logic to auto create a linked default account when you create a new user (same flow as a normal sign up really). In fact, I can see JSP doing this in the rails console output, but it eventually breaks:
User Update (10.7ms) UPDATE "users" SET "encrypted_password" = $1, "first_name" = $2, "last_name" = $3, "time_zone" = $4, "updated_at" = $5, "invitation_token" = $6, "invitation_accepted_at" = $7 WHERE "users"."id" = $8 [["encrypted_password", "$2a$11$aHPD8rLSpBKugVY1h7TFu.f/qJCHNYtj.WEEL7r2/.S5ITaHUtO2W"], ["first_name", "james"], ["last_name", "stiller"], ["time_zone", "Eastern Time (US & Canada)"], ["updated_at", "2021-06-07 00:06:51.858210"], ["invitation_token", nil], ["invitation_accepted_at", "2021-06-07 00:06:51.857320"], ["id", 28]] ↳ app/controllers/users/invitations_controller.rb:16:in `accept_resource' Account Load (0.3ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."owner_id" = $1 AND "accounts"."personal" = $2 LIMIT $3 [["owner_id", 28], ["personal", true], ["LIMIT", 1]] ↳ app/models/concerns/user_accounts.rb:36:in `sync_personal_account_name' Account Exists? (0.8ms) SELECT 1 AS one FROM "accounts" INNER JOIN "account_users" ON "accounts"."id" = "account_users"."account_id" WHERE "account_users"."user_id" = $1 LIMIT $2 [["user_id", 28], ["LIMIT", 1]] ↳ app/models/concerns/user_accounts.rb:25:in `create_default_account' AccountUser Exists? (0.6ms) SELECT 1 AS one FROM "account_users" WHERE "account_users"."user_id" = $1 AND "account_users"."account_id" IS NULL LIMIT $2 [["user_id", 28], ["LIMIT", 1]] ↳ app/models/concerns/user_accounts.rb:29:in `create_default_account' Account Create (9.3ms) INSERT INTO "accounts" ("name", "owner_id", "personal", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["name", "james stiller"], ["owner_id", 28], ["personal", true], ["created_at", "2021-06-07 00:06:51.913156"], ["updated_at", "2021-06-07 00:06:51.913156"]] ↳ app/models/concerns/user_accounts.rb:29:in `create_default_account' AccountUser Exists? (0.7ms) SELECT 1 AS one FROM "account_users" WHERE "account_users"."user_id" = $1 AND "account_users"."account_id" = $2 LIMIT $3 [["user_id", 28], ["account_id", 38], ["LIMIT", 1]] ↳ app/models/concerns/user_accounts.rb:29:in `create_default_account' AccountUser Create (8.9ms) INSERT INTO "account_users" ("account_id", "user_id", "roles", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["account_id", 38], ["user_id", 28], ["roles", "{\"admin\":true}"], ["created_at", "2021-06-07 00:06:51.925467"], ["updated_at", "2021-06-07 00:06:51.925467"]] ↳ app/models/concerns/user_accounts.rb:29:in `create_default_account' TRANSACTION (0.6ms) ROLLBACK ↳ app/controllers/users/invitations_controller.rb:16:in `accept_resource' Completed 500 Internal Server Error in 253ms (ActiveRecord: 47.6ms | Allocations: 34512) NoMethodError (undefined method `update' for nil:NilClass): app/models/concerns/user_accounts.rb:39:in `sync_personal_account_name' app/controllers/users/invitations_controller.rb:14:in `accept_resource'
If I remove the `sync_personal_account_name` function, everything appears to work correctly, but I have no idea what the downstream effects of that are.
I only allow invite only in my app, and the way that I did it was that I added a "allows_registration" field to the account. This is something each tenant (I have a multi-tenant app) can enable or disable themselves. The invite stuff still works with just a couple small changes.
In the
In the
registrations_controller.rb
I added this: before_action :new_registration_allowed?, only: [:new, :create]
Which checks if you can register for a new account, and in that method I merely do this:
def new_registration_allowed? # Send them back to the root path if no invite is present and sign_up is not allowed. redirect_to root_path unless policy(:account).sign_up? || account_invitation_present? end def account_invitation_present? AccountInvitation.find_by(token: params[:invite]).present? end
The policy, is a Pundit policy and that pundit policy looks like this:
class AccountPolicy attr_reader :account_user, :account def initialize(account_user, account) @account_user = account_user @account = account end def sign_up? # turns off registration for all new tenants and customers if Current.account Current.account.registration_allowed else # True in test because some jumpstart tests require the signup page to be visible # could be improved, but works for me Rails.env.test? end end end
I then also use this policy to show the Sign Up links on the site in the views:
<% if policy(:account).sign_up? %> sign_up button <% end %>
Notifications
You’re not receiving notifications from this thread.