Adding a Log in, Log out feature using Ruby on Rails
Tags: codenewbie, ruby, rails
Published: 2020-11-09 00:29:52

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! 

<img width="100%" style="width:100%" src=https://media.giphy.com/media/26BROFLJSFhP0cMGk/giphy.gif>

###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.

```Ruby
rails g resource User username
```
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. 
```Ruby
#app/config/routes.rb

resources :users
```
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 <code>users#new</code> will display the form and once the submit button is clicked a post request will be sent to <code>users#create</code> 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:
```Ruby
#config/routes.rb

resources :users, only:[:new, :create]
```
#### Display a form and submit data
Our UsersController should look like this: 
```Ruby
#app/controllers/users_controller.rb

class UsersController < ApplicationController
    def new
        @user = User.new
    end
```
And we're going to use form_for:
```Ruby
#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 %>
```
The inputted data in the text field will then be posted to the <code>users#create</code> action. Our create action will look like this:
```Ruby
#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
```
You might ask "Why do we have to call <code>User.new</code> and <code>@user.save</code> instead of just using <code>User.create?</code>" 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 <code>User</code> model.
```Ruby
#app/models/user.rb

class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
...
end
```
<code>@user.save</code> will check those validations first to see if <code>username</code> is not blank(presence) and if <code>username</code> is not already in the database(uniqueness). Our create action will now look like this:
```Ruby
#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
```
If it passes all validations, it will then be saved to the database and it will redirect us to the <code>@user</code> 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.
```Ruby
rails g controller Sessions
```
Then we're going to specify our routes
```Ruby
#config/routes.rb

  get '/login', to: 'sessions#new', as: 'login'
  post '/sessions', to: 'sessions#create', as: 'sessions'
  delete '/sessions', to: 'sessions#destroy'
```
<code>sessions#new</code> will display our log in form: 
```Ruby
#app/views/sessions/new.html.erb

<h1>Login</h1>
<%=form_tag (sessions_path) do %>
<%=text_field_tag :email %>
<%=submit_tag %>
<% end %>
```
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 <code>(sessions_path)</code> and once the submit button is clicked, this form will send a <code>post</code> request to the action controller <code>sessions#create</code>.
Going to our sessions controller we will define our </code>#create</code> action:

```Ruby
#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
```
In our <code>create</code> action we are going to grab the user object from the db using the <code>:username</code> provided in the login. We will then make a key in the <code>session</code> called <code>:user_id</code> and assign the <code>user.id</code> as the value. Now that we have this, we can technically say that if there is a <code>session[:user_id]</code> 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<code>#destroy</code> on the sessions controller will clear the <code>session</code> and therefore logging out the user and redirecting back to the login_path.

```Ruby
 <%=link_to 'Log Out', sessions_path, method: :delete%>
```

###Step 3 - Use the Application_Controller

Since the rest of the controllers inherit from the <code>application controller</code>, 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: 
```Ruby 
#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
```

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

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

The last method, <code>current_user</code> enables us to have access to the specific user object.

The code is written in way so that the first time it is called <code>@current_user</code> 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 <code>@current_user</code> 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.
```Ruby
#app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
    helper_method :current_user
    helper_method :logged_in? 
    before_action :require_login
```
Remembering that all other controllers inherit from the application controller, declaring a before_action <code>:require_login</code>, 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:
```Ruby
#app/controllers/users_controller.rb

class UsersController < ApplicationController

skip_before_action :require_login, only:[:new, :create]
```
In this case because our <code>users#new</code> and <code>users#create</code> 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 <code>has_secure_password</code> come in! 

In order to use this you need to add <code>gem 'bcrypt'</code> (usually in your gemfile already, just need to uncomment it) and this will also require a column in your user model called <code>password_digest</code> which will store the encrypted password. <code>has_secure_password</code> 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 <code>password_digest</code> column to the <code>Users</code> table. Once that's all migrated, we need to declare it in the <code>User model</code>
```Ruby
#app/models.user.rb

class User < ApplicationRecord
    has_secure_password
...
end
```
We then need to update our sign up form:
```Ruby
#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 %>
```

Then were going to update the <code>users#create</code> by:
<ol>
<li>Adding the password(even though we called it <code>password_digest</code> in our <code>users</code> we could still refer to it as <code>password</code>) </li>

<li>and also logging them in after they created their account<code>session[:user_id] = @user.id</code></li>
</ol>

```Ruby
#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
```

Now we're going to update the our log in form:
```Ruby
#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 %>
``` 

<code>has_secure_password</code> also give us the method <code>.authenticate</code> 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 <code>user</code> object otherwise it will return false. The string comes from the inputted data when a user fills in the login form displayed by<code>sessions#new</code> which will then send a <code>post</code> request to <code>sessions#create</code>. 

Lastly we will update the <code>sessions#create</code>:

```Ruby
#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
```
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!

<img width="100%" style="width:100%" src=https://media.giphy.com/media/42D3CxaINsAFemFuId/giphy.gif>