Advanced Roles talk from RailsSaaS 2022

At RailsSaaS 2022 I did a talk on strategies for implementing role based systems in Ruby on Rails applications. I discussed various ways to model permission systems and did a demo using the new Bullet Train Roles gem.

For the demo, I built an application using Bullet Train and showed three different ways to assign permissions to users.

  1. Assign different levels of permission to different members of the same team
  2. Elevate a user’s permission in the context of a single model
  3. Add an “Agency” team and allow the agency users to manage the Campaigns of their clients

After my talk, a number of people asked me if I could share the exact shell commands I ran on stage to build the demo application. In this post, I’ll go through each of the commands with a brief summary of what it does. When the RailsSaaS videos are released, I’ll update this page with a link to my talk so you can see the full context for each of these commands. In the mean time, this article serves as a reference for anyone who was at the conference and wanted to experiment with the different approaches I used in the demo.

Initial Setup

These first commands I ran ahead of time and didn’t actually do in the demo:

  1. Clone the started repo
  2. Run the setup script
git clone ad-vantage
cd ad-vantage

Follow the prompts in the setup script and you should have a working starter application. You can start the server and test it out by running:


Creating the Application Models

Once you have the Bullet Train starter app running on your machine, you should be ready to dive into the commands from the talk.

First up, I added two basic models to the application. These models served as placeholders for “real” parts of the application. I added a Campaign model and a Payment model. I used the rails generator to create the model then ran the Bullet Train super-scaffolding command to build all the views.

bin/rails g model Campaign team:references name:string
bin/super-scaffold crud Campaign Team name:text_field
bin/rails g model Payment team:references name:string amount:float
bin/super-scaffold crud Payment Team name:text_field amount:text_field

You can learn more about the super-scaffolding command in the Bullet Train docs

Migrate the database and you should now be able to interact with the models we just created.

bin/rails db:migrate

Bullet Train ships with a default implementation of the Roles gem that meets the requirements of the first version of our permission system so there’s no additional commands to run here. In the talk, I removed access to the Payment model completely for the default user - I’m leaving this as an exercise for the reader.

Creating a Campaign Collaborator

Next up, we want to make a default user an admin of a single campaign - aka “Escalating the permissions of a user in the context of a single model”.

bin/rails g model Campaigns::Collaborator campaign:references user:references role_ids:jsonb
bin/super-scaffold crud Campaigns::Collaborator Campaign,Team campaign_id:super_select{class_name=Campaign} user_id:super_select{class_name=User} role_ids:super_select{vanilla}
bin/rails db:migrate

Creating an Agency Team

Finally, we create an association from one team to another as a way to implement our Agency team.

bin/rails g model Client team:references client_team:references role_ids:jsonb
bin/super-scaffold crud Client Team team_id:super_select{class_name=Team} client_team_id:super_select{class_name=Team} role_ids:super_select{vanilla}
bin/rails db:migrate

Wrap Up

You can see the finished state of the code from my talk here:

Please note that as this was a demo, I didn’t attempt to make the app complete. You will hit errors with missing translations and other small UI problems. For example, in the final version where I added the Agency team, I added an index view to show that the agency did in fact have access to their clients Campaigns. However, if the agency attempts to get to the show page of any of those campaigns, they will hit an error because the show page was only scaffolded and designed for the client side of things. In a real application, you would still need to solve these issues.

If you experiment with any of these, please let me know how you go. What are your thoughts on each of the setups and what would you do differently?

My twitter DMs are always open.