Adding invite only users

Brandon Brown
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
  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'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:

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

Brandon Brown
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.
Notifications
You’re not receiving notifications from this thread.
Subscribe
© 2022 GoRails, LLC