When you are introduced to ruby on rails or any backend language your being exposed to a different view of applications. Most the time when individuals think about applications they think about the user interface and what it looks like. Well backend focused languages, or Ruby on Rails in this case, gives you the visibility into how the user interface receives and sends any data to offsite servers hosting large databases. For me, this part of software engineering is the most interesting. Lots of tables, routes, validations, security, and authorization. As applications grow larger and more complex you may have to build additional rules around security and authorization to protect the integrity of your application. An interesting problem that id like to focus on and talk about is role assignment for users.
Sure, we can assign a user an admin role, but what about other roles? What if your application had specific security settings for certain individuals, and you wanted to guard against visibility into certain data. Well Rails has a handy solution to this problem allowing developers more customization around how users can interact with applications.
Lets talk about my problem first and map some unique security roles around it. I have an application that will have many users, and yes a couple will have admin rights, but some of my users will be able to post certain data that others cannot post and vice versa. We will need to set up a few different roles to accomplish this and restrict access based on the roles. Lets start to build out a solution in our newly created rails application.
Lets make our users first.
rails g model User username password_digest
Then lets make a Role model.
rails g model Role name
Finally lets make an assignment model. This will be our join table between User and Role.
rails g model Assignment user:references role:references
Now that we have set up the model migrations we’ll need to complete the migration by running: rails db:migrate. You should see both our belongs_to :user and :role already built out in the Assignment model, but now we will need to add some validations to our Role model and make sure we set up our other relationships in our models.
First we can quickly verify the Assignment model has the following relationships associated with it:
Class Assignment < ActiveRecord::Base belongs_to :user belongs_to :role end
With that we are done with the Assignment model. Lets jump into the User model and confirm its relationships are complete. Our User will have a has many relationship with assignments and a has many roles through assignments.
Class User < ActiveRecord::Base has_secure_password has_many :assignments has_many :roles, through: :assignments end
With that we have completed our User model for our current purpose. Lets jump into the Role model, just like the User model, our roles will have a has many relationship with assignments and a has many users through assignments. Our Role model will need some validation steps though. If we think about these roles, we want to make sure that all roles have a name and that the name is unique. Our model should look lie so:
class Role < ActiveRecord::Base has_many :assignments has_many :users, through: :assignments validates :name, presence: true, uniqueness: true end
Our models are all set up! Now comes the cool part that ties everything together.
We’ll be working with a gem called Pundit. Pundit is a much larger topic so I wont go to far into it since it can be its own blog topic but I will show you how I will use it in this scenario. We’ll be using pundit because it allows us to define and enforce policies by using simple Ruby objects. Lets get pundit added to our gemfile:
Lets install it now:
Now the fun part that will be new to everyone! Lets run the below command to generate a new directory called polices directly under app where, you guessed it, we’ll store our policies.
rails g pundit:install
Lets start writing some policies for our application!
For this scenario I want my trainers that are using my application to adjust a workout for their clients. I have a Workout model that has already been created, and now it is time to add some policies around the workouts. We will need to create a WorkoutPolicy file in our policies directory. This policy will inherit from ApplicationPolicy allowing our resource object to be referenced with “record”, this will take some additional code off out plate. The file would look something like this:
class WorkoutPolicy < ApplicationPolicy def update? user.role? :trainer or not record.published? end end
You have the ability to define roles for each action you create for a resource. For this example, I am only showing an update action. This policy is stating that I am allowing users with the role as trainers to update workouts if they have already been published.
Roles and permissions are important in many applications, and businesses tend to use very large applications that have a lot of security controls so being able to understand and write polices like this will allow you to show off a bit. Your now able to lock down data manipulation to specific types of users, giving you more flexibility and control in your application. Now go ahead and experiment more with it, enjoy!