+
First step is to add Devise to your Gemfile
But we're not done just yet...
And the run bundle install, of course...
gem 'devise'
$ bundle install
In your Terminal/Command Prompt run:
$ rails g devise:install
We need to tell Devise, in our database, where to store all this fun authentication stuff: email, password, name.
Don't forget get to rake db:migrate!
$ rails g devise Blogger
+
=
You can now go in your app's code and find a model (blogger.rb), that will look like this:
class Blogger < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Set up your root page so that it allows you to sign in, or knows that you're already signed in.
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>
<% if blogger_signed_in? %>
<%= link_to "Sign out",destroy_blogger_session_path, method: :delete, class: "button" %>
<% else %>
<%= link_to "Sign in",new_blogger_session_path %>
<% end %>
You now have access to the Class 'current_blogger'.
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>
<% if blogger_signed_in? %>
Hello, <%= current_blogger.email %>!<br />
<%= link_to "Sign out",destroy_blogger_session_path, method: :delete, class: "button" %>
<% else %>
<%= link_to "Sign in",new_blogger_session_path %>
<% end %>
We can customize the sign-in/sign-up forms.
First we need to create the views for Devise.
$ rails g devise:views
A new folder has been created, under 'views', called 'devise', and there's a whole bunch of stuff in there!
Devise gives you Email and Password, but wouldn't it be great to have Name, too?
We can add attributes to the User just as we would to any Resource.
$ rails g migration AddNameToBloggers name:string
And there's one more thing we can't forget to run...
#add the following lines:
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit({ roles: [] }, :email, :password,
:password_confirmation, :name) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:email, :password,
:password_confirmation, :current_password, :name) }
end
Since we have no Blogger controller, we'll need the help of the Application controller...
We need to also add fields to our devise/registration views (edit & new), so a name can be entered.
<div><%= f.label :name %><br />
<%= f.text_field :name, autofocus: true %>
</div>
autofocus: true means that field will be automatically selected when the page is loaded. I moved this attribute from the e-mail field.
Let's use Bootstrap to fancy-up these Devise views...
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h2>Log in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="input-group input-group-lg">
<%= f.email_field :email, autofocus: true, placeholder: "Email", class: "form-control" %>
</div>
<br/>
<div class="input-group input-group-lg">
<%= f.password_field :password, autocomplete: "off", placeholder: "Password", class: "form-control" %>
</div>
<% if devise_mapping.rememberable? -%>
<div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
<% end -%>
<br/>
<div><%= f.submit "Log in", class: "btn btn-primary btn-lg"%></div>
<% end %>
<%= render "devise/shared/links" %>
</div>
</div>
devise/sessions/new.html.erb
Let's add some glyphicons to add that extra bit of flair!
<span class="input-group-addon glyphicon glyphicon-envelope"></span>
<span class="input-group-addon glyphicon glyphicon-lock" ></span>
Let's remove the partial link reference. How could we add back just the "forgot password?" link under the password field?
Final sign-in page code:
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h2>Log in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="input-group input-group-lg">
<span class="input-group-addon glyphicon glyphicon-envelope"></span>
<%= f.email_field :email, autofocus: true, placeholder: "Email", class: "form-control" %>
</div>
<br/>
<div class="input-group input-group-lg">
<span class="input-group-addon glyphicon glyphicon-lock" ></span>
<%= f.password_field :password, autocomplete: "off", placeholder: "Password", class: "form-control" %>
</div>
<% if devise_mapping.rememberable? -%>
<div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
<% end -%>
<p class="pull-right">
<%= link_to "Forgot Password?", new_blogger_password_path %>
</p>
<br/>
<div class="input-group input-group-lg">
<%= f.submit "Log in", class: "btn btn-primary btn-lg"%>
</div>
<% end %>
</div>
</div>
Let's require users to log in before entering any page of our app. We'll add the following filter to our application controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :authenticate_blogger!
#all your other devise stuff is below
...
Where would we move this filter if we wanted the home page to be available to all users, but the destinations page behind the login?
Let's add Devise to our Travel app.
Since you already have a Traveler resource, let's call the Devise resource User.
Let's add a new gem called 'cancan.' This will allow us to control user roles so that only admins can edit content.
Add the gem to your gemfile and bundle!
gem 'cancan'
$ bundle install
We now create a model specific to cancan.
$ rails g cancan:ability
Now we set roles. Admins can manage all content.
app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
Admins should be the only ones allowed to create, update, and delete (CUD?) on our destinations page. So let's add some logic around our button links in destination/index.html.erb:
<% if can? :update, destination %>
<div class="btn-group">
<%= link_to 'Show', destination, class: "btn btn-default" %>
<%= link_to 'Edit', edit_destination_path(destination), class: "btn btn-default" %>
<%= link_to 'Destroy', destination, method: :delete, data: { confirm: 'Are you sure?' },
class: "btn btn-default" %>
</div>
<% end %>
And don't forget the new destination button!
<% if can? :update, @destination %>
<%= link_to 'New Destination', new_destination_path, class: "btn btn-primary" %>
<% end %>
This all fine and good, but we don't actually have an admin column defined in our database. So let's add one.
$ rails g migration AddAdminToUsers admin:boolean
Now let's set admin to be true for the blogger of our choice using rails console. Blogger.all will display all bloggers in the db.
$ rails c
> User.all
Make sure you have at least 2 users in your database for testing purposes. We'll set one of them to have "true" in their admin field. Find the id of the user you'd like to promote to top dog!
Now try logging in with your admin user and navigating to the destinations page. Notice your edit buttons are in place. Log in with a regular non-admin user and notice the buttons are missing.
> admin_guy = User.find(1)
> admin_guy.admin = "true"
> admin_guy.save
In order to automatically authorize all actions on the destinations page we must add this line to the top of the destinations_controller.rb:
load_and_authorize_resource
We'll also need this method at the bottom of the
application_controller.rb to redirect in case of an exception:
rescue_from CanCan::AccessDenied do |exception|
redirect_to destinations_path, :alert => exception.message
end
And output the exception message on destinations/show.html.erb:
<% if alert %>
<div id="notice" class="alert alert-danger" role="alert"><%= alert %></div>
<% end %>
Finally, we'll output the exception message on destinations/index.html.erb:
<% if alert %>
<div id="notice" class="alert alert-danger" role="alert"><%= alert %></div>
<% end %>
Go ahead and test this out. Try to browse to "destinations/new" with a non-admin user logged in. The user should be re-routed to the destination index page with error message that says:
"You are not authorized to access this page."