To allow creation of new users (registration)

1.) Set the routes in routes.rb

# routes.rb

get '/register', to: 'users#new' # manually mapping user registration to /register instead of /users/new -> therefore not listed under resources

resources :users, only: [:show, :create, :edit, :update] #note we don't want an index route to show all users

2) Create users_controller.rb

# users_controller.rb

...

def new
  @user = User.new
end

def create
  @user = User.new(user_params)

  if @user.save
    session[:user_id] = @user.id # to automatically login a user that signed up
    flash[:notice] = "You are registered."
    redirect_to root_path
  else
    render :new
  end
end

def user_params
  params.require(:user).permit(:username, :password)
end

3.) Create the views

  • Create users folder
  • Create users/new.html.erb + edit.html.erb + _form.html.erb

  • Create the model backed form in _form.html.erb
<div class="well">
  <%= form_for @user do |f| %>
    <div class="control-group">
      <%= f.label :username %>
      <%= f.text_field :username %>
    </div>
    <div class="control-group">
      <%= f.label :password %>
      <%= f.password_field :password %>
    </div>
    <%= f.submit("Register", class: 'btn btn-success' %>
  <% end %>
</div>

Update _navigation.html.erb to show register link, when logged out

<!-- _navigation.html.erb -->

...

<div class="nav_item">
  <%= link_to "Register", register_path, class: "btn btn-success btn-small" %>
</div>

4.) Add validations in user.rb

# user.rb
...

validates :username, presence: true, uniqueness: true
validates :password, presence: true, on: :create, length: {minimum: 5} # on: :create means that a password has to be present when creating a new user, but not when updating user details


To build user login/logout

1.) Install the bcrypt gem

gem 'bcrypt', '~> 3.1.7'

Example using Active Record (which automatically includes ActiveModel::SecurePassword):


# Schema: User(name:string, password_digest:string)
class User < ActiveRecord::Base
  has_secure_password
end


user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
user.save                                                       # => false, password required
user.password = 'mUc3m00RsqyRe'
user.save                                                       # => false, confirmation doesn't match
user.password_confirmation = 'mUc3m00RsqyRe'
user.save                                                       # => true
user.authenticate('notright')                                   # => false
user.authenticate('mUc3m00RsqyRe')                              # => user
User.find_by(name: 'david').try(:authenticate, 'notright')      # => false
User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user

-> http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html

Note that bcrypt together with has_secure_password give us a virtual attribute, called “password” and also the “authenticate” method, so in rails console:

$ bob.password = "password"
$ bob.save

$ bob.authenticate('iforgot')
=> "false"
$ bob.authenticate('password')
=> "true"

2.) In models/user.rb add has_secure_password validations: false

3.) Create the file controllers/sessions_controller.rb with “new, update and destroy methods”

# sessions_controller.rb

...

def create
  user = User.where(username: params[:username]).first
  if user && user.authenticate(params[:password])
    session[:user_id] = user.id # important to only store userid in cookie(session) and not other information of the user to not have too big a cookie and risk "cookie overflow".
    flash[:notice] = "Welcome, you've logged in."
    redirect_to root_path
  else
    flash[:error] = "There is something wrong with your username or password."
    redirect_to login_path
  end
end

def destroy
  session[:user_id] = nil # all that's necessary to end the session (set session user_id to nil)
  flash[:notice] = "You've logged out."
  redirect_to root_path
end

4.) In routes.rb add

get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
get '/logout', to: 'sessions#destroy'

5.) Add a non-model backed form in a new folder and file ‘views/sessions/new.html.erb’

<div class='well'>
<%= form_tag '/login' do %>
  <div class='control-group'>
  <%= label_tag :username %>
  <%= text_field_tag :username %>
  </div>
  <div class='control-group'%>
  <%= label_tag :password %>
  <%= password_field_tag :password %>
  </div>
  <%= submit_tag 'Login', class: 'btn btn-success' %>
<% end %>
</div>

6.) In application_controller.rb add

  
helper_method :current_user, :logged_in? # this makes the helper methods below also available to all our controller actions and all our view templates

def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id] # means that this code will return nil if the user has no session and the part after ||= is only executed if @current_user is nil
  # ||= is for "memoization" to not hit the database too many times.
end

def logged_in?
  !!current_user # !! turns current_user into boolean value
end

7.) Wrap items in views that should only be available to logged in users into if statements (if logged in)

Do this for all items you wan’t to display based on status

<!-- _navigation.html.erb -->

<% if logged_in? %>
  <div class='nav_item'>
    <%= link_to 'New Post', new_post_path, class: 'btn btn-success btn-small' %>
  </div>
    <div class='nav_item'>
    <%= link_to 'Log out', logout_path, class: 'btn btn-small' %>
  </div>
<% else # logged out %>
  <div class='nav_item'>
    <%= link_to 'Log in', login_path, class: 'btn btn-small' %>
  </div>
<% end %>

8.) Shut down routes based on logged in / logged out status

Do this for all actions you want to disable based on status

In posts_controller.rb add

# posts_controller.rb

before_action :require_user, except: [:show, :index] # means that if you are not logged in you can only use the show or index action 

In application_controller.rb add

# application_controller.rb

def require_user
  if !logged_in?
    flash[:error] = "Must be logged in to do that."
    redirect_to root_path
  end
end