I am trying to build an invitation system for my app and I feel like there's a nice way to do what I need, yet I'm not sure about it.
I have a User
model and I created a UserInvite
model.
When an existing user sends out a new invite, a UserInvite
is created. I am storing when the invite was sent, generate an invite token to make signing up easier and send an email to the invited user.
When the invite is accepted, I would also like to store the user id of the new user on the UserInvite
object.
That is, a UserInvite
belongs to both inviter and the invitee, and the latter is only being added to the object later on, not at creation, since I don't know the id of the to-be-created User
.
What is the right way to model this relationship?
I'm using Rails 7.
If you look at Devise::Invitable as a reference then the answer is You Ain't Gonna Need It. It just adds columns to the user model to track invitations:
create_table :users do
...
## Invitable
t.string :invitation_token
t.datetime :invitation_created_at
t.datetime :invitation_sent_at
t.datetime :invitation_accepted_at
t.integer :invitation_limit
t.integer :invited_by_id
t.string :invited_by_type
...
end
add_index :users, :invitation_token, unique: true
These columns are used to bypass several validations so that you can create a user record without a password - which is then "claimed" by updating the record with a password.
If you want to do a separate table for invitations then there isn't really a clear need to store the invited users id either unless you actually plan on displaying or using the invitations in some way.
The invitation will simply be used as a token when signing the user up and can then be considered spent and deletable. If you want to record who invited the user I would just add a inviter_id
to the users table:
class AddInviterToUsers < ActiveRecord::Migration[7.0]
def change
add_reference :users, :inviter, null: true, foreign_key: { to_table: :users }
end
end
class User < ApplicationRecord
belongs_to :inviter, class_name: 'User',
optional: true
has_many :invited_users,
class_name: 'User',
foreign_key: :inviter_id
end
If you really want to go down the path of two columns pointing to the users table then you can do it with something like:
class CreateUserInvitations < ActiveRecord::Migration[7.0]
def change
create_table :user_invitations do |t|
t.string :token
t.belongs_to :inviter, null: false, foreign_key: { to_table: :users }
t.belongs_to :invitee, null: true, foreign_key: { to_table: :users }
t.timestamps
end
end end
class UserInvitation < ApplicationRecord
belongs_to :invitee, class_name: 'User',
optional: true
belongs_to :inviter, class_name: 'User'
end
class User < ApplicationRecord
has_many :user_invitations_as_inviter,
class_name: 'UserInvitation',
foreign_key: :inviter_id
has_many :invited_users,
through: :user_invitations_as_inviter,
source: :invitee
has_many :user_invitations_as_invitee,
class_name: 'UserInvitation',
foreign_key: :invitee_id
end
But again - YAGNI. This will add additional complexity and a UPDATE query.