Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Rails Designer
Rails Designer

Posted on • Originally published atrailsdesigner.com

     

Add Invite to Rails 8 Authentication

This article was originally published onRails Designer Build a SaaS


This is the third article on Building a SaaS with Ruby on Rails. This article continues where the previous one,adding sign up to Rails 8' authentication, stops.


Most SaaS products are used by multiple people from the same team or company. Although I would, when you start building your SaaS, urge you to keep it simple and just support one user on launch. I would álso urge you to build the blocks needed for teams in your app from the get-go. Having to deal with workspaces, teams and roles when you already have users is a bigger pain than you can imagine.

This article will focus on an important feature, and a metric you likely want to keep track off for new sign ups; invitations. Not just an invitation to use the app (though you can tweak it to do just that), but an invitation to the inviter's workspace.

As always the repo can be foundhere. Let's get right to it.

Adding workspaces

The previous article on adding sign ups, left the option to add a Workspace on sign up. Let's add that now. It's simple really. All it is, for now, is a record with ahas_many :users. All business records your users create will belong to the workspace, giving the users access to them (but that will be for another article).

Let's create the model first:rails g model Workspace name:string. Then update the existing User model by adding aworkpace_id:rails g migration add_workspace_id_to_users workspace_id:integer.

Update the new Workspace model like this:

# app/models/workspace.rbclassWorkspace<ApplicationRecordhas_many:users,dependent: :destroyend
Enter fullscreen modeExit fullscreen mode

Then the User model:

classUser<ApplicationRecord# …belongs_to:workspace# …end
Enter fullscreen modeExit fullscreen mode

Let's also updateapp/models/current.rb for some nicety: adddelegate :workspace, to: :user. This will allow you to doCurrent.workspace to get the user's workspace. Nice!

Head over the Signup class and the actual code to create a new workspace on sign up:

classSignupincludeActiveModel::ModelincludeActiveModel::Attributesdefsave# …endprivatedefcreate_workspace_for(user)Workspace.create(name:"New Workspace").tapdo|workspace|workspace.users<<userendendend
Enter fullscreen modeExit fullscreen mode

Alright. Now the pieces are already in place to invite others to the Workspace.

Adding invites

What I am having in mind is the following:

  1. Workspace "owner" (the User would created the Workspace) adds email from invitee;
  2. Invitation model is created, with: email and inviter_id;
  3. After Invitation create, an email is sent to the email with an invite link;
  4. On clicking the link, invitee sees a form with email and password;
  5. Upon submit, an User model is created and attached to the Workspace.

Doesn't look all too bad when written out like this, right? Let's create the invitation model first:rails g model Invitation workspace_id:integer inviter_id:integer email_address:string accepted_at:datetime.

Let's update the newly created model:

classInvitation<ApplicationRecordbelongs_to:workspacebelongs_to:inviter,class_name:"User"end
Enter fullscreen modeExit fullscreen mode

Let's also update the Workspace model by addinghas_many :invitations, dependent: :destroy. This will allow to list all (pending) invitations per Workspace.

In its most basic form there needs to be two controller with two actions each:

  • one for creating the invitation by the workspace owner (actions: new and create);
  • one for accepting the invitation by the invitee (actions: new and create).

Let's do the invitations creations first. It is simple really!

classInvitationsController<ApplicationControllerdefindex@invitations=Current.workspace.invitationsenddefnew@invitation=Invitation.newenddefcreateifinvitation=Invitation.create(invitation_params)InvitationsMailer.invite(invitation).deliver_laterendredirect_toinvitations_pathendprivatedefinvitation_paramsparams.expect(invitation:[:email_address]).with_defaults(inviter_id:Current.user.id,workspace_id:Current.user.workspace_id)endend
Enter fullscreen modeExit fullscreen mode

Then add to the routesresources :invitations, only: %w[index new create]. Now it's also clear what views are needed:

NB: as with all articles in this series, the UI is left up to you. Head over the articles fromRails Designer, thecomponents and check out the cool newForm Builder. It will give you beautiful form inputs by just typingform.input :email_address. 👀

Simple list of invitations:

# app/views/invitations/index.html.erb<ul><%@invitations.eachdo|invitation|%><li><%=invitation.email_address%> -<%=invitation.accepted_at%></li><%end%></ul>
Enter fullscreen modeExit fullscreen mode

Then the form to create a new invitation:

<%=form_for@invitationdo|form|%><%=form.text_field:email_address%><%=form.submit%><%end%>
Enter fullscreen modeExit fullscreen mode

Simple enough! Let's create the mailer to send the invitation over:

# app/mailers/invitations_mailer.rbclassInvitationsMailer<ApplicationMailerdefinvite(invitation)@invitation=invitationmailsubject:"You are invited!",to:invitation.email_addressendend# app/views/invitations_mailer/invite.html.erb<p>Hereisyourinvitation.Clickthislinkto<%= link_to "get started", accept_invitation_url(token: @invitation.generate_token_for(:invitation)) %>.</p>
Enter fullscreen modeExit fullscreen mode

This shows a couple things that needs to be added and create the functionality for@invitation.generate_token_for(:invitation) and the route, controller, class and views foraccept_invitation_url().

Let's do the easy one first, let's add the following to yourInvitation Active Model:

classInvitation<ApplicationRecord# …generates_token_for:invitation,expires_in:7.daysdoaccepted_atend# …end
Enter fullscreen modeExit fullscreen mode

This is a featureintroduced in Rails 7.1. This also gives the method find_by_token_for() to look up the invitation as you will see in a moment. The nice thing, it will returnnil if theaccepted_at column is set.

Let's speed through the boilerplate for accepting invitations and then look at the class to add the new user to the workspace.

# app/controllers/accept_invitations_controller.rbclassAcceptInvitationsController<ApplicationControllerdefnew@invitation=Invitation.find_by_token_for(:invitation,params[:token])enddefcreateAcceptInvitation.create(invitations_params)redirect_toroot_pathendprivatedefinvitation_paramsparams.expect(accept_invitation:[:email_address,:password])endend# config/routes.rbRails.application.routes.drawdo# …resources:accept_invitations,only:%w[new create]end# app/views/accept_invitations/new.html.erb<%= form_with model: @invitation, url: accept_invitations_path do |form| %>  <%=form.email_field:email_address%><%= form.password_field :password %>  <%=form.submit%><% end %>
Enter fullscreen modeExit fullscreen mode

Wow! That was fast. 🏎️ Let's create theAcceptInvitation class. It will be similarform object is done previously forSignup.

classAcceptInvitationincludeActiveModel::ModelincludeActiveModel::Attributesattribute:email_address,:stringattribute:password,:stringattribute:token,:stringvalidates:email_address,presence:truevalidates:password,length:8..128defsaveifvalid?User.create!(email_address:email_address,password:password).tapdo|user|update_invitationadd_newuser,to:invitation.workspaceendendenddefmodel_nameActiveModel::Name.new(self,nil,self.class.name)endprivatedefupdate_invitationinvitation.update(accepted_at:Time.current)enddefadd_new(user,to:)to.users<<userenddefinvitation=Invitation.find_by_token_for(:invitation,token)end
Enter fullscreen modeExit fullscreen mode

This class is the place to handle everything needed after the invite is accepted. I've left it to updating theaccepted_at column (remember how that expires the token) and adding the new user to the workspace. But you can add any action that makes sense for your business logic.

And there you have it. A simple way to add invites to your Rails 8 authentication generator.

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
railsdesigner profile image
Rails Designer
I help teams around the world make their Rails apps a bit nicer to maintain and look at. 🎨 Also kickstart SaaS' in a month. 👷 And built a Rails UI Components library, used by 1000+ developers. 🚀

Any next extension of Rails 8' authentication you want to see?

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I help teams around the world make their Rails apps a bit nicer to maintain and look at. 🎨 Also kickstart SaaS' in a month. 👷 And built a Rails UI Components library, used by 1000+ developers. 🚀
  • Location
    Amsterdam, The Netherlands
  • Joined

More fromRails Designer

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp