Now that we have signed up to the application, we want to be able to actually log in.
The log in process is about:
Accessing an interface that allows the user to provide his credentials (the log in page)
Checking and validating the credentials (in our case querying the database and comparing the provided email and password to whatever we have stored)
If successful, telling the application that a user is logged in
Making the user data available to the application (in our case storing the user id in the session and/or in a global variable)
We start with high level acceptance tests simulating the actions of a user that wants to log in. Add the following scenario to the user_maintenance.feature:
# features/user_maintenance.feature
Scenario: Log in to the application
Given I am a registered user
Given I am on the home page
And I click "Log in" link
Then I should be on Log in page
And I fill in "Email" with "thomas@random.com"
And I fill in "Password" with "my_password"
And I click "Submit" link
Then I should be on the home page
And I should see "Successfully logged in Thomas"
And I should not see "Register"
Add the following step definition, reusing some previous steps:
# features/step_definitions/application_steps.rb
...
Given(/^I am a registered user$/) do
steps %(
Given I am on the home page
And I click "Register" link
Then I should be on Registration page
And I fill in "Name" with "Thomas"
And I fill in "Email" with "thomas@random.com"
And I fill in "Password" with "my_password"
And I fill in "Password confirmation" with "my_password"
And I click "Create" link
)
end
Run cucumber (cucumber features/user_maintenance.feature)
Update your application.erb by adding the Log in link:
And add that path to paths.rb so Cucumber knows where to go:
# features/support/paths.rb
...
when /Log in page/
'/users/login'
...
Add the following routes to application.rb
# lib/application.rb
get '/users/login' do
erb :'users/login'
end
post '/users/session' do
session[:flash] = "Successfully logged in ..."
redirect '/'
end
Create a login.erb file in the views/users folder:
Shifting our attention to the User model. We need to add a authenticate method to User.
Let's start by writing some specs.
# spec/user_spec.rb
...
describe 'user authentication' do
before { @user = User.create(name: 'Thomas', email: 'thomas@makersacademy.se', password: 'password', password_confirmation: 'password') }
it 'succeeds with valid credentials' do
expect(User.authenticate('thomas@makersacademy.se', 'password')).to eq @user
end
it 'fails with invalid credentials' do
expect(User.authenticate('thomas@makersacademy.se', 'wrong-password')).to eq nil
end
end
...
Again, run rspec and see the tests fail.
Add the authenticate method to the User class:
# lib/user.rb
...
def self.authenticate(email, password)
user = first(email: email)
if user && BCrypt::Password.new(user.password_digest) == password
user
else
nil
end
end
...
These test will still fail...
We need to add DatabaseCleaner to the spec_helper.rb
# spec/spec_helper.rb
require 'database_cleaner'
...
RSpec.configure do |config|
...
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
...
end
And now all the tests go green.
Back to the main controller. Change the post route to:
# lib/application.rb
...
post '/users/session' do
@user = User.authenticate(params[:email], params[:password])
session[:user_id] = @user.id
session[:flash] = "Successfully logged in #{@user.name}"
redirect '/'
end
...
Also, in the same file, we need to add some methods to be able to access the currently logged in user:
# lib/application.rb
...
before do
@user = User.get(session[:user_id]) unless is_user?
end
register do
def auth (type)
condition do
redirect '/login' unless send("is_#{type}?")
end
end
end
helpers do
def is_user?
@user != nil
end
def current_user
@user
end
end
...
We only want to show the Log in link if there is no user logged in, right? Update your application.erb by adding a condition for the display of the Log in link: