Passing Referral Codes or Invite Links w/ Devise in Rails
By: Sy Rashid
Tags: Ruby on Rails, Web Development, Software Design, Software Development, Ruby

Because who doesn’t want 20% off their Flat Tummy Tea subscription ☕️

Welcome to part two of a mini-series I started on helping a student setup invitation links for users to join groups in their Rails project. The basic premise involved two main parts to solve.

We need custom or vanity urls for our invite links to groups. We need a way to link these urls to create a group membership for users (signed in and signed out) as well as people who don’t have accounts yet.

Part one can be found here 👉 Custom & Vanity URLs with Rails

The second part will be how do we pass information from the invitation link to create a membership in a group, regardless of whether or not they have an account, or if they are signed in.

Now you might be saying, “Hey! You pulled a shenanigan on me, and got me here by putting referral code in the title!”

To which I would say fair enough, but I promise, the logic will essentially be the same. I’m not saying this is the only way, just one fun way I found to pass information through Devise. All you’ll need to do to translate invitation link into referral code is CTRL+F and a little bit of imagination!

So to start off, let’s get a grounding on what exactly we’re building. The db schema below 👇 hopefully clarifies this.

A three model application that has users (through devise) and groups they can join. These users join groups through a join table; memberships, to set up a nice n:n relationship.

A few points to take note of is we’ll be using Devise to set up our user model and provide authentication for our application. And the invite tokens in groups have already been set up in part one ☝️ of this series.

Our two main models, groups and users, will look something like this.

Our Group Model

Our User Model

So with our models set up, it’s on to the task of how do we actually generate these memberships for users in any type of scenario? We’ll cover these in the following sequence…

A user has an account and is signed in A non-user doesn’t have an account (so they must register first) A user has an account but isn’t signed in (so they must sign in first)

Scenario 1: A user has an account and is signed in

Well, the first scenario seems simple enough, we just need to pass the url to create a new membership. If they’re signed in, go ahead and create a membership for the group with the current user.

Let’s take a quick peek to see how our routes are setup.

Nothing too wild, just a nested route for memberships, so we have access to the correct group while creating memberships using our invite token.

To provide these invite links to our users we’ve set up a little copy input field on our index for groups. As this article isn’t on front end, if you’re curious to see how this is done, please check out the repo for the project here 👈

The Invite Link is actually the new membership route

Cool, so now we have a way for one user to pass their invite link (the new_group_membership route) to another user. So let’s imagine they do exactly that, they copy the invite link and pass it to another user. The route format is below

In more practical terms it would look something like this

/groups/AKSv69pM8ZsfpZ22GnXYob2A/memberships/new

In our membership#new action our logic for the first part 👇

If the user is signed in, redirect them to the create membership action. Here in the create action, we first find the group we’re creating a membership for thanks to our nested route (which provides us params[:group_invite_token]). We then use the first_or_create method to create a membership if non exists for that unique combo of user and group, otherwise do nothing except return the existing membership. After, we route our user back to the root_path, which for us is the user’s dashboard with all of the groups they belong to.

First part, done! 🎉

Time for the next part, what happens when the user isn’t signed in?

A non-user doesn’t have an account

This is where we’ll start to get a bit tricky and play around with the default forms and controllers devise provides us. To make the logic flow a little bit easier we’ll solve for the issue of what happens when a person doesn’t yet have an account and needs to register first.

If you recall from our MembershipsController above we had an else in our new action, this is exactly where that else block comes in. We will make the assumption that if they are not signed in, chances are they need to register for an account as opposed to signing in. (Don’t worry we’ll still deal with that as well, and we could flip this if we wanted, this is purely a UX design decision)

Ok, so what are we doing here? We are redirecting our user to the devise sign up action, otherwise known as the new_user_registration_path. If you ever need a quick refresher remember you can always run

rails routes | grep user OR rails routes -g user

The key here is to pass the param :invite_token to the path so we can use it in our sign up form.

This is where the fun really begins, because now we need to not only modify the controllers given to us by devise, but the views as well. To start off let’s review how to modify controllers with devise. This stack overflow answer does a great job but we’ll do a quick recap.

First, we need to generate a registrations controller in our application, namespaced the same as devise, see below 👇

In this controller we can now go ahead and implement the logic we need. In our case, we want to create an instance variable with the group to embed in our form to use later in the create action. From there, proceed as usual with the parent super call.

To make sure devise actually picks up this controller we need to explicitly mention it in our routes.

As, you can see, we’re going to do the same for sessions later on. But for now just focus on the registration portion.

From here, we need to embed the group invite_token in the view so it can be passed to our create action above. To do this we’ll run the command

rails generate devise:views

Once done, we can modify the new registrations form and embed the following line. (Mind I’m using simple form, thus the syntax)

The goal here is to pass the invite_token to our create action when a user hits submit upon sign up.

In this create action, we’ll go through the process of creating our user as usual with the super call, but once that is complete, we will check whether or not there is that :invite_token in our params. If so, and the resource (the user instance) is valid, we can go ahead and generate a membership for them. Fortunately, devise will then take care of the rest for us to redirect the user to the appropriate path. If you want to override that redirect feel free to do so here.

All right, part two is now complete! 🎊

Last but not least, the final part, when a user has an account but isn’t signed in.

A user has an account but isn’t signed in

Fortunately, we’re going to use a lot of the same tricks as the last part.

So to kick off, we’ll be setting up our sessions controller this time instead of our registrations controller. But the question is, how do we get our user to that sign in page, and how do we persist that information on the :invite_token?

For example, let’s go through the case of a user passing a group invite link to another user. As they are not signed in, they are redirected to the sign up page. The user at this time would naturally try and sign in instead of sign up as they already have an account. This behavior is accounted for by devise with these buttons at the bottom of the sign up page.

To persist the :invite_token information we’ll go ahead and embed that into the login button as well which we can find in the shared partial created by devise called _links.html.erb

This pattern of passing the invite_token as a params in a path is the same as we saw before, but instead of in the controller, we’re passing the param in the view. Now, if the user decides to click login while there is the instance variable @group present, they will also be taking along the param for :invite_token as well.

From here the logic is almost identical to before. First, we modify the sessions controller as mentioned before.

Second, we make sure to explicitly mention it in the routes for devise.

Third, in the new action we find the instance of group associated to the invite token to embed in the new form for sessions, NOT registration (remember we’re signing in aka creating a new session; not signing up aka creating a new registration).

Fourth, in our shared links partial we can modify the sign up link as well to make it come full circle and connect.

And finally in our sessions controller, in the create action we can go ahead and generate a new membership for our current user if one doesn’t already exist, and proceed thru our normal create action afterwards.

That’s all she wrote, we’re done folks. 🥳

As a quick recap, we just covered how to use a custom invitation link to create memberships through Devise through multiple scenarios. The same logic could also be applied to a referral code as well, you’ll just need to tinker with a few variables. Again, not the only way to do it, but a fun way to do it with Devise.

If you have any questions, critiques, or feedback please don’t hesitate to reach out through my LinkedIn below or simply leave a comment.

If you want to see the code base please find it here on GitHub 👈

As a fun note, also added a little functionality for users to be able to regenerate invite links for groups whenever they want with a bit of stimulus and some bootstrap toasts.

— — — — —

These are hilarious to me, watching someone write about themselves in the third person.

Sy Rashid is a former child who used to hop on the occasional flight and works as a designer and developer at his web development agency MANGOTREE. When he’s not busy coding he teaches as a Lead Instructor for Le Wagon — #1 coding school globally — where he has trained 100+ students across 3 campuses to bring over 25+ product visions to life thru teaching, guidance, and product management.

Curious to see the shenanigans he’s been getting himself into? Connect with him on Linkedin or MANGOTREE to grab a casual coffee or an extremely formal taco.