Learn to create internal gems in your Rails applications by rebuilding Pundit in 30 lines of code

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we are going to rebuild the pundit gem in about 35 lines of code so the goal of this video is to learn how to create our own little libraries our own ruby gems and to understand that there is no magic behind how ruby gems are implemented it's actually quite simple to understand if we are guided and if we go step by step which is exactly what we're going to do in this video pundit is a popular gem to manage authorization in Ruby and rails application so here I created a very simple rails application for you you can be logged in as an admin or as a reader and if you are logged in as an admin you'll be able to see a list of articles that have the stat use draft or published and you'll be able to delete some articles well if you're only a reader you'll only be able to see the published articles if you click on one article you'll be directed to the Articles show page but if you try to visit a draft article as a reader it will raise a pundit not authorized error before we start working on the application let's just have a look at the seeds so as we can see we destroy all the data when we see it then we recreate some new data so we create two users one with the role of reader and one with the rule of admin the user class is handled by device and the role is an enum that can take the two value reader or admin we also create three articles that are in status draft and three articles that are in status published and same as for the user role the article status is an innum that can take two values draft or published so let's have a look at how this is implemented in the code so we have an articles controller here that implements three different actions we have the index action that is responsible for showing the list of all the Articles we have the show action that is responsible for showing us a specific article and we have the destroy action which is responsible for deleting an article the authorization logic here is handled by the pundit gem so the policy scope method and the authorize method here are both pundit methods and if we have a look at how it works with pundit you create some classes that are called policies for each model and in the case of our article model you will be able to show a specific article only if the user is an admin or if the record is published so in our case this means that if you are logged in as an admin you will be able to access the show page for all the articles but as we saw earlier if you're a simple reader you will only be able to access the show page for the published articles there's the same logic for the destroy action you will only be able to delete an article if you are logged in as an admin and as for the index method this policy scope here under the hoods called the resolve method and it means that if you are an admin you will be able to see all of the articles but if you're only a reader you will be able to see the published articles only which is exactly what we see here by default all policies inherit from the application policy so the application policy is just a class that gets copied when we run the pundit installation generator and it only specifies that by default all policies are initialized with a user and a record and all policy Scopes are initialized with a user and a scope and also policy Scopes must implement the resolve method so if we go back to our article policy this is why in the show and Destroy action we are able to use the user and the record methods and in the article policy scope we implement the resolve method and we are able to access the user and scope methods now that we have a good understanding of how things work we are going to remove the pundit gem from our gem file run bundle install to uninstall the gem and restart our rail server by doing this we just broke everything and the goal of the rest of this video will be to rebuild the pundit gem with the same features that we had before but without relying on the official gem we're going to rebuild everything ourselves in order to rebuild our bunded gym we are going to rely on the error messages and the first error message that we can see is that we used to have a module called pundit authorization that was included in application controller and of course if we look a bit closer the methods such as police scope or authorize they must come from somewhere and in our case this somewhere is the pundit authorization module that is included in application controller so we will have to recreate our pundit authorization module and we have to find it a home and I think a very natural home for it is the app lib folder I think of the apply folder as a place to host the code for my in-house gems that are not extracted yet and so this is exactly what we're doing here with our pundit authorization module and there's one more Advantage is that in every Ruby enrance application the folders that are in the app folder are autoloaded which means that we don't have to manually require them or load them it will be done for us so let's go ahead and create a pundit folder with an authorization module so module pundit module authorization and now if we go back to our browser here we can see that we still get the same error but this is because we didn't restart the rail server every time we add a new folder under app we have to restart the server for the changes to be picked by Ruby and rails so if I did this we can see that the error changed and now we cannot find the policy scope method and of course we didn't Define it yet and so we're going to add it to our pundit authorization module so let's get started and Implement our policy scope method so the policy scope method actually takes an argument and the first argument here is a class so let's write it down and now let's take some time to think about our objective so our objective is based on this article argument here what we want to be able to do is generate a new article policy scope instance and call resolve on it the resolve method will be the one responsible for retrieving the right scope right so if the user is an admin we will retrieve all the Articles and if the user is not in admin we will only retrieve the published articles so how are we going to do that well the first step is to be able to generate the name of this class here based on the arguments that we pass here so the argument is article so we can go in console and say class equal article and our goal is to get back this class here equal policy scope so what we're going to do to do this is we are going to interpolate class policy scope so that's the first step here and this will generate the correct string if we want the class we can call constantize on it and now we have the class and what we want is an instance of this class and how do we generate instances of policy Scopes well we have to have a look at the application policy and as we can see policy Scopes are initialized with a user and a scope so what we'll be able to do is say that we want an instance with a user so which user is that the user will be the current user that we will have access in every controller thanks to device and the scope by default will be the class so we will be able to write this and then the last thing that we have to do is call resolve on this new instance of a policy class so let me just copy this and paste it and call resolve so this should work in order to make to make it a bit more readable we are going to extract a method that is responsible for generating the policy scope class like this so I can extract this put it there and rewrite this here so this is how we would Implement our policy scope method if we go back to the browser we can see that the error message has changed this time it complains that the policy method here isn't defined in the view so if we go have a look at the article partial and if you remember well what we wanted to do is only show the button to delete an article to users who are allowed to perform this destroy action so how do we know if a user is allowed to perform this this reaction well we can use the policy and if we have a look at the article policy we can see that it implements a destroy question mark method that only authorizes admin users to destroy articles so what we want to do is that this policy method returns an instance of the article policy and so that way we can check if we have the right to destroy the article thanks to the destroy method that is defined here so let's go ahead and try to do this what we're going to do is create a policy method and as we can see this policy method takes a record as a first argument so let's go ahead and write it down and what we want to do is to generate a new instance of the article policy class so we're going to use the same technique as we did before but this time we don't have a class here we have a record but we can call that class on this record and if we check in the console here if we have record is equal to article that first then record class is equal to the article class and so if I copy this and paste it this will be able to article policy once again I can call constantize on it and this way it will be equal to article policy but not as a string anymore but as a class so now our objective here is to create a new instance of that class so if we go and visit the application policy we can see that policies are initialized by default with a user and a record and of course they are initialized with those two arguments because this is what we want to be able to access in all the sub methods later so let's go ahead and initialize it with the current user and the article record that we just passed here before we move on I'm just going to perform the same refactoring that we did previously so policy class in our case takes a record and is responsible for returning the correct class so it just makes things a bit more readable I think so now we have our policy method that Returns the new instance of the policy that we want but if we go back to the browser and refresh the page nothing happens and this is because for now the policy method is only defined in a module that gets included an application controller but it's not yet available to The View and to make it available to the view what we're going to do is we're going to convert this module here into a concern by doing extend active support concern and then we're going to say that when the module is included in application controller what we want to do is call the helper method with policy as an argument and what this will do here this helper method with the policy argument it will make this method accessible to the view so now if I refresh the page it works as expected it means that when I'm logged in as an admin this uh article policy destroy method here will return true and so we will be able to see our button otherwise if we're logged in as a reader then this policy instance when we call the destroy action on it will return false and so we don't show the button in that case the last thing that we want to do is to be able to show articles or delete articles and for now it doesn't work in both cases because the authorize method is undefined so if we go and have a look at the Articles controller we can see that before the show and the destroy action we are going to retrieve the article from the database that's what we do here and then we're going to call the authorize method with this article and so the behavior that we want is that if the article policy allows us to show or destroy we want to let the action work as expected but if we're not allowed to do it then we want to raise an error so that's what we're going to implement here we're going to create the authorize method the authorize method takes a record as a first argument and then it instantiates an article policy instance with the right arguments and so that's exactly what is already done for us here so we're going to reuse it this here will generate an article policy instance that's initialized with the current user and the record so in our case the current user and the instance of the article and now what we want to do is that if we're in the show action we want to send a show question mark to our article policy instance and if we're in the destroy action we want to send destroy question mark to our article policy instance so to do this we're going to use send then the action name followed by a question mark and the action name here is a method that is defined for every controller so if we want to do a little test for uh the index method for example we can write this here action name and if we go back to the index we can see that index is written here so it would be the same for show and Destroy and so this here will tell us if the policy allows the action for this user and this record so it will return true if it's authorized or false if it's not authorized and what we want to do is let the action perform as expected if it's authorized and we want to raise an error if it's not authorized so the error that we want to raise is not authorized error so we're going to create it it inherits from standard error in order to be an error and we can raise it here like this so now if we go back to our implementation as a reader I can read the articles that are published as an admin I can read all the articles but if I try from the reader account to read a draft article it will raise a not not authorized error so that's it we rebuilt the pundit gem in a bit more than 30 lines of code I really hope you enjoyed this video and that you learned something feel free to subscribe to the channel because in the next video we will learn how to extract this piece of code in a real ruby gem that we will be able to use across multiple projects so see you in the next one
Info
Channel: Alexandre Ruban
Views: 500
Rating: undefined out of 5
Keywords: Ruby, Ruby on Rails, Pundit gem, Ruby gem
Id: nUnJzaKW-ts
Channel Id: undefined
Length: 19min 43sec (1183 seconds)
Published: Mon May 29 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.