Introduction & Project Goals
This video is going to cover creating Devise user accounts and giving them roles. You're going to have your default role, your moderator role, and your admin role. We're using Rails 7 for this on the Ruby 3.0.3 build because there's a bug currently with 3.1. So let's go ahead and let's get started.
Installing and Configuring Devise
The first thing we'll do is we'll add Devise to our application. So I'll just say bundle add devise, which will add it to our Gemfile.
After we have Devise added to our Gemfile, we can then run a rails g devise:install command.
Make this a little bit bigger. That will install Devise. We can then do a rails g devise user command, and we'll give the users a role of type integer.
So if I full screen this real quick, you can see that the command is rails g devise user role:integer. Go ahead and we'll run that, and we can exit full screen.
Database Migration and Routing
Next, let's come into our db, inside of our folder, or migrate, and we'll come into the Devise migration. A lot of this stuff should look familiar, but down here we'll see our t.integer :role, and we'll just give this a default value of zero. And this is just so that it's always set to what our default role will be, which will be zero.
Setting Up the Homepage
The next thing we can do is we can come over here and just do a rails db:migrate command so that we don't forget. And then we'll do a rails g controller pages and we'll give it a homepage. And that's just so we have something to test with.
Next, let's come over to our config and our routes.rb. And here we want to change this get to a root and this slash to a hash. And we'll save that. Next, we can go ahead and start our Rails server with the rails s command.
Building the Dynamic Homepage View
And hopefully see something on our homepage, which we do. Now let's come over to our app, views, pages, and our home page and let's create some conditional logic here. Start with a if current_user. So if our user is currently logged in, we want to show them something. For this, I guess I'm having some GitHub Copilot autocomplete so I'll be full screen. So it's suggesting autocompleting with this, which sure, I'll hit tab for this. So we're just going to say, hello current_user.name, which we don't have. We'll change this to user.email because it comes with an email by default. "You've successfully authenticated with your Devise account. You are now ready to interact with this application." That's neat. Sure, whatever.
Next, we'll do the else statement. And then we'll see what else is here. And it looks like it's trying to do a basic href for this. So we'll just, let's leave the "you are not signed in" and we'll clean that up in a second here. We don't want this. And we don't need it in a p tag. Instead, what I'll do is I'll say link_to 'Sign up', this will be the new_user_registration_path. Then I'll say, if you have already signed up before, we'll do a link_to 'Login', just so it reads a bit differently, and we'll say new_user_session_path for that one.
Now we can go ahead and save that. We'll move it over. And then we'll refresh this page. So you can see here, you are not signed in, so we can either sign up or we can log in. And we can, of course, add in like a br tag here for a bit of readability. So let's go ahead and let's click sign up. Now here it's going to ask for an email and a password so I'll do [email protected] with a password of password.
And this won't work because we haven't set the Turbo links to ignore this yet, so we'll get an error, which is fine. So here you can see it says NoMethodError in Devise::RegistrationsController create user_url. The easiest fix for this, there's a couple, but I think the cleanest as far as I can tell is to stop the server, do a rails g devise:views command, which will generate the views for Devise. We can then come over to our app/views and we should see a devise folder appear. We can open that, and we want to come into our registrations, our new. And we're going to do this if we have any pages that require it. So the edit registrations might require it, or sessions, we'll see as the video continues. But for the submit right here, I just want to say after we submit or when we click submit, I want the data to be turbo: false. So this is just adding some additional data where we tell Turbo links, please behave yourself, let me do what I want to because Turbo links is a bully.
We can save this. And this will work for our sessions, but not for, or no, this will work for our registrations, but not for our sessions. I don't know if it's necessary for sessions but we might as a well just add it in here as well. I'll say data: { turbo: false }. Better safe than sorry a couple weeks from now when this doesn't work and you have to Google the entire problem all over again.
Creating User Accounts
Now I'll go ahead and I'll type rails s to start the server again. I will go back to the home page, and I'll hit sign up. I'll do [email protected] with a password of password. Hopefully I typed that right. The email was already taken. So that means that when we signed up the first time, it looks like it went through, but it didn't redirect us. That's fine. Let's just test it with another one. So I'll do [email protected] with a password of password. And that looks good. "Hello [email protected], you've successfully authenticated. You are now ready to interact with the application, blah, blah, blah."
Let's go back to our home page. This is fine, but I would like to be able to sign out now.
Implementing Sign-Out and Displaying Roles
So after this p tag here, let's just do another link_to 'Sign out', and this will be the destroy_user_session_path. Make sure this is session and not registration. If it's registration, that'll be to delete your whole account. Session is just for your current session that's keeping you logged in. After this, we need to do a method: :delete, just like we would before. And I believe this no longer works, unless I'm mistaken. So if I click sign out, it should tell me that, yeah there's a routing error for the get. So let's go back, because we can't sign out with this anymore. Instead, I think what we need to do is in single quotes, and I'll full screen this and pump it up a bit just to make sure you can read this, we need to say, if I check my notes, data: { turbo_method: :delete }. I think that's all it takes. So now if I refresh and I click this, we log out and it tells us we're not signed in. So now we can log in. Let's try to log in as [email protected] with our password of password. Looks like that works.
So now what I would like to do is below the email, maybe we do a p tag, and it just says, you are a, and then in square brackets we'll do current_user.role, something like that. Let's give this a try and see what comes up. So here it says you are a 0. That's not zero out of ten, that's just saying that you're the first, I guess. So this is our default role being printed here.
Defining Roles with an Enum
Now what I would like to do is convert this from a zero into something a bit more meaningful. So what we can do is we can come over to our models and our user.rb. And I'll go ahead and I'll full screen this. And let's just do this below our Devise. You can see GitHub Copilot is trying to be useful there, but again, it's not going to be stealing any jobs anytime soon with advice like that.
Let's start with an enum and role. You can see here it's already suggesting this right here, which is from the Devise documentation. We're going to change this a little bit. I'll go ahead and I'll just type this manually. So our first one is going to be user, but our second is going to be moderator. And our third is going to be admin. And I think once I save this, if I come down here, it'll tell me to type this next, which it does. So here we want after_initialize :set_default_role, if: :new_record? and this is just after we create a new account, it wants to set the default role. And here it's already suggesting the next method, but we'll go ahead and we'll type this out. We'll say private def set_default_role... or sorry, just def set_default_role, self.role ||= :user, end. And that should be it.
Now if we come over here and we refresh, you can see that our role changed to a user. Now what we could do, if I open up another tab and I say rails c in our Rails console, I can say @user = User.first, which is our [email protected]. And here I can just say @user.role = 1, and now let's do a @user.save because we want to update this. And now if I refresh this page, my role is now a moderator. Or I can say @user.role = 2, @user.save, and now we should be an admin.
Conditional Content for Admins
So maybe I want to come to the home page, because it's nice that we have this, but now I want to use this. And then I want to say, you know, maybe, if current_user.admin?.
Now remember, this is already inside of a if current_user check, so I don't have to make sure that current_user exists. If I put this up here, of course, I'm going to run into some issues, because if I'm not logged in this will be null, and then I'm trying to call a method on a null which isn't going to work. But I can just do this in here for now. And I'll just say if I'm logged in, I don't know, show me the User.count, maybe. "There are blank users in the system." Cool, thank you GitHub Copilot. It's a little bit weird to have you suggest text like that. On here you can see, you are a admin. There are two users in the system. So maybe I want to add like a br in here to break this. There we go.
So you can see here how this is something where if I sign out now and I log into [email protected], which I think was our second user, here it just says you are a user and we don't see this "There are now X users in the system" text. So this is sort of how you can use roles here where maybe your moderators can see something and your admins can see something or only your admins can see something but the users can't see any of it. Of course, you can extend this just like you would with a regular admin boolean. Hopefully this was helpful and I will see you in the next tutorial.