DEV Community

Alexa Gamil
Alexa Gamil

Posted on • Updated on

Adding a Log in, Log out feature using Ruby on Rails

Hi everyone! As for my second blog, I wanted to go through the step by step process of how we created a log in and a log out feature to our simple app project. I'm going to try my best to keep it concise!

Step 1 - Creating a user

In order to be able to log in you need to create a user model. In our User model we added username as one of the columns.

rails g resource User username
Enter fullscreen mode Exit fullscreen mode

Here I am generating a User resource with a column username and because I didn't provide a data type for username, the default data type is a string. This provides me the User MVC(Model, Views, Controller), the migration, and the routes.

#app/config/routes.rb

resources :users
Enter fullscreen mode Exit fullscreen mode

Since the only goal is to 'register' a user we just need to render a form and submit the inputted data to the database. The controller-action pair users#new will display the form and once the submit button is clicked a post request will be sent to users#create which will create the user object and save it to the database for us. Because we're only using those routes for this example let's specify our routes real quick to:

#config/routes.rb

resources :users, only:[:new, :create]
Enter fullscreen mode Exit fullscreen mode

Display a form and submit data

Our UsersController should look like this:

#app/controllers/users_controller.rb

class UsersController < ApplicationController
    def new
        @user = User.new
    end
Enter fullscreen mode Exit fullscreen mode

And we're going to use form_for:

#app/views/users/new.html.erb

<h1>Sign up</h1>
<%= form_for @user do |f|%>
<%= f.label :username%>
<%= f.text_field :username%>
<%= f.submit%>
<% end %>
Enter fullscreen mode Exit fullscreen mode

The inputted data in the text field will then be posted to the users#create action. Our create action will look like this:

#app/controllers/users_controller.rb

class UsersController < ApplicationController
    def new
      ...
    def create
      @user = User.new(username:params[:user][:username])
      @user.save
      redirect_to user_path(@user)
    end
Enter fullscreen mode Exit fullscreen mode

You might ask "Why do we have to call User.new and @user.save instead of just using User.create?" A quick explanation to this is because you don't want bad data to be stored in your database. An example of a bad data is a duplicate of email used or even a blank email. It would cause a lot of bugs down the line. A quick fix to this is adding validation to your User model.

#app/models/user.rb

class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
...
end
Enter fullscreen mode Exit fullscreen mode

@user.save will check those validations first to see if username is not blank(presence) and if username is not already in the database(uniqueness). Our create action will now look like this:

#app/controllers/users_controller.rb

def create
        @user = User.new(username:params[:user][:username])
        if @user.save
            redirect_to @user
        else
            render :new
        end
    end
Enter fullscreen mode Exit fullscreen mode

If it passes all validations, it will then be saved to the database and it will redirect us to the @user showpage otherwise we are rendering back the same form.

Step 2 - Create a Sessions_Controller

Through the sessions controller is where we're going to have our login page, login our current user, and log out our user.

rails g controller Sessions
Enter fullscreen mode Exit fullscreen mode

Then we're going to specify our routes

#config/routes.rb

  get '/login', to: 'sessions#new', as: 'login'
  post '/sessions', to: 'sessions#create', as: 'sessions'
  delete '/sessions', to: 'sessions#destroy'
Enter fullscreen mode Exit fullscreen mode

sessions#new will display our log in form:

#app/views/sessions/new.html.erb

<h1>Login</h1>
<%=form_tag (sessions_path) do %>
<%=text_field_tag :email %>
<%=submit_tag %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

We are using the form tag because this form is not based on an active record object. In order for this form to know where to submit the data, we supply the (sessions_path) and once the submit button is clicked, this form will send a post request to the action controller sessions#create.
Going to our sessions controller we will define our #create action:

#app/controllers/sessions_controller.rb

class SessionsController < ApplicationController    

    def new
    end

    def create
        user = User.find_by(username: params[:username])
            session[:user_id] = user.id
            redirect_to user_path(user)
        end

    def destroy
        session.clear
        redirect_to login_path
    end
end
Enter fullscreen mode Exit fullscreen mode

In our create action we are going to grab the user object from the db using the :username provided in the login. We will then make a key in the session called :user_id and assign the user.id as the value. Now that we have this, we can technically say that if there is a session[:user_id] then someone must be logged in and if it is not present then they must be logged out. That is what the destroy method is doing here. Creating a link_to that calls that action#destroy on the sessions controller will clear the session and therefore logging out the user and redirecting back to the login_path.

 <%=link_to 'Log Out', sessions_path, method: :delete%>
Enter fullscreen mode Exit fullscreen mode

Step 3 - Use the Application_Controller

Since the rest of the controllers inherit from the application controller, we will use this to our advantage to define methods so that all the other controllers can share the same functions.

Here we will define the methods:

#app/controllers/application_controller.rb    

class ApplicationController < ActionController::Base    

    def logged_in?
        session[:user_id] 
    end    

    def require_login
        if !logged_in?
        redirect_to login_path
    end

    def current_user
        @current_user ||= User.find(session[:user_id]) if 
        session[:user_id]
    end
Enter fullscreen mode Exit fullscreen mode

The first method, logged_in? is the method we call to check if the user is logged in.

The second method, require_login is the method we use to basically make certain controller actions inaccessible if a user is not logged in.

The last method, current_user enables us to have access to the specific user object.

The code is written in way so that the first time it is called @current_user is nil, it will then go to the or (||) condition and look into the database, grab that user, and assign that user to itself. The next time it is called @current_user is present and will return the found user the first time it was called.

We are then going to declare some of these methods as helper methods so that these functions are accessible to all views as well.

#app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
    helper_method :current_user
    helper_method :logged_in? 
    before_action :require_login
Enter fullscreen mode Exit fullscreen mode

Remembering that all other controllers inherit from the application controller, declaring a before_action :require_login, also means this before_action applies to all other controllers as well. It means in order for a user to be able to do anything that involves with any of the controllers, a user MUST be logged in.

If you want certain actions to still be accessible you can explicitly do so by going to that specific controller and use skip_before_action:

#app/controllers/users_controller.rb

class UsersController < ApplicationController

skip_before_action :require_login, only:[:new, :create]
Enter fullscreen mode Exit fullscreen mode

In this case because our users#new and users#create are responsible for signing up an account, we want them to be accessible without being logged in.

Step 4 - Adding Passwords

When storing passwords in the database, you want them to be encrypted so for any chance your database gets hacked, the hacker will not have access to the real passwords. This is when the has_secure_password come in!

In order to use this you need to add gem 'bcrypt' (usually in your gemfile already, just need to uncomment it) and this will also require a column in your user model called password_digest which will store the encrypted password. has_secure_password will allow us to write the password and encrypt it before storing it to password_digest.

With that said we create a new migration to add password_digest column to the Users table. Once that's all migrated, we need to declare it in the User model

#app/models.user.rb

class User < ApplicationRecord
    has_secure_password
...
end
Enter fullscreen mode Exit fullscreen mode

We then need to update our sign up form:

#app/views/users/new.html.erb

<h1>Sign up</h1>
<%= form_for @user do |f|%>
<%= f.label :user%>
<%= f.text_field :user%>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit%>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Then were going to update the users#create by:

  1. Adding the password(even though we called it password_digest in our users we could still refer to it as password)
  2. and also logging them in after they created their accountsession[:user_id] = @user.id
#app/controllers/users_controller.rb

def create
        @user = User.new(username:params[:user][:username])
        @user.password = params[:user][:password]
        if @user.save
            session[:user_id] = @user.id
            redirect_to @user
        else
            render :new
        end
    end
Enter fullscreen mode Exit fullscreen mode

Now we're going to update the our log in form:

#app/views/sessions/new.html.erb

<h1>Login</h1>
<%=form_tag (sessions_path) do %>
<%=text_field_tag :user %>
<%=text_field_tag :password %>
<%=submit_tag %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

has_secure_password also give us the method .authenticate which takes in a string and checks to see if the encrypted password matches the string passed in. If the string matches it will return the user object otherwise it will return false. The string comes from the inputted data when a user fills in the login form displayed bysessions#new which will then send a post request to sessions#create.

Lastly we will update the sessions#create:

#app/controllers/sessions_controller.rb

class SessionsController < ApplicationController    

    def new
    end

    def create
        user = User.find_by(username: params[:username])
        if user && user.authenticate(params[:password])
            session[:user_id] = user.id
            redirect_to user_path(user)
        else 
            render 'login_path'
        end
    end

    def destroy
        session.clear
        redirect_to login_path
    end
end
Enter fullscreen mode Exit fullscreen mode

Now our create action is not only checking if the user exists in the db. If it exists then it authenticates the password and if it returns a user object we log them in, otherwise we send them back to the login page.

Conclusion

Hopefully this helps you set up your log in and log out features to your app. I'm sure there are tons of other ways to do it, but for now this is what my newbie brain can offer! If you have other suggestions please reach out to me, I'd love to learn!

Oldest comments (0)