Build a Blog with Ruby on Rails – Part 2

Ruby on Rails can build a blog quickly and easily.

In the first part of this tutorial you were able to set up a blog that accepts postings using a nice editor called Ckeditor. The format of our blog posts can be well formatted to suit our taste. At this moment anyone who visits our blog can create a new post. You do not want to push this kind of a blog to the internet because it is unwise and unprofessional. In this part, we are going to learn the following:

  • How to enable authentication using Devise.
  • How to enable image uploading.

To properly understand this let's write some code to show it. Let's go the easy part first. The source for this tutorial is available on Github.

Installing Devise Gem.

Devise is is a flexible authentication solution for Rails.

Add Devise gem to your Gemfile,

#Gemfile

gem 'devise'

Now run the command to install the gem;

bundle install

Run the command to generate the necessary Devise files.

rails g devise:install

Running the command creates two new files in your application. You can find them at; config/initializers/devise.rb and config/locales/devise.en.yml. The first is an initializer that is required for Devise to work in your application. According to the output displayed on your terminal you need to perform some tasks. First, navigate to config/environments/development.rb and paste in the line below just above the end line like I have below:

#config/environments/development.rb
...
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end

This is to help handle your mailer in the development environment. The second thing to do is add a block of code in your application layout for flash messages. So go ahead and open app/views/layouts/application.html.erb Paste the line below above the yield block:

#app/views/layouts/application.html.erb
...
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>

Now to generate your Admin model, run the command below:

rails generate devise Admin

And that should do it! When that is done, run the command to migrate your database:

rake db:migrate

You will want to generate Devise views. Devise provides you with a command to do that:

rails g devise:views admin

And that will generate a lot of files for you. For the purpose of this tutorial, we want to edit the formats of a few pages Devise generated for us so that they look beautiful and presentable for our users. Paste the code below into their respective files: This is the file that will handle the editing of our Admin profile. We want to make it look beautiful using Devise. You will agree with me that user experience is very important.

#views/admins/registrations/edit.html.erb

<div class="col-sm-offset-4 col-sm-4 col-xs-12">
  <h2 class="text-center">Edit <%= resource_name.to_s.humanize %></h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: "form" }) do |f| %>
    <%= devise_error_messages! %>

    <div class="form-group">
      <%= f.label :email %><br />
      <%= f.email_field :email, autofocus: true, class: "form-control" %>
    </div>

    <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
      <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
    <% end %>

    <div class="form-group">
      <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
      <%= f.password_field :password, autocomplete: "off", class: "form-control" %>
    </div>

    <div class="form-group">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
    </div>

    <div class="form-group">
      <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
      <%= f.password_field :current_password, autocomplete: "off", class: "form-control" %>
    </div>

    <%= f.submit "Update", class: "btn btn-primary btn-lg" %>
  <% end %>

  <h3 class="text-center">Cancel my account</h3>

  <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: "btn btn-danger btn-lg" %></p>

  <%= link_to "Back", :back, class: "btn btn-primary btn-lg" %>

</div>

Next we want to work on the sign up page for our Admin. It is important you know that we are just editing the default pages by adding some snippets of code from bootstrap. Copy and paste the code below into views/admins/registrations/new.html.erb and see the beautiful look that turns up.

#views/admins/registrations/new.html.erb

<div class="col-sm-offset-4 col-sm-4 col-xs-12">
  <h2 class="text-center">Sign up</h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {class: "form"}) do |f| %>
    <%= devise_error_messages! %>

    <div class="form-group">
      <%= f.label :email %><br />
      <%= f.email_field :email, autofocus: true, class: "form-control" %>
    </div>

    <div class="form-group">
      <%= f.label :password %>
      <% if @minimum_password_length %>
      <em>(<%= @minimum_password_length %> characters minimum)</em>
      <% end %><br />
      <%= f.password_field :password, autocomplete: "off", class: "form-control" %>
    </div>

    <div class="form-group">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
    </div>

    <%= f.submit "Sign up", class: "btn btn-primary btn-lg" %>
  <% end %>

</div>
<%= render "admins/shared/links" %>

We are done with the registrations of our Admin. You should have noticed by now that Devise groups all of these pages into separate folders. This is because there are different controllers handling each of this pages. The next page we want to edit is the sign in page. Paste the following code into views/admins/sessions/new.html.erb

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

<div class="col-sm-offset-4 col-sm-4 col-xs-12">
  <h2 class="text-center">Log in</h2>

  <%= form_for(resource, as: resource_name, url: session_path(resource_name), html: {class: "form"}) do |f| %>
    <div class="form-group">
      <%= f.label :email %><br />
      <%= f.email_field :email, autofocus: true, class: "form-control" %>
    </div>

    <div class="form-group">
      <%= f.label :password %><br />
      <%= f.password_field :password, autocomplete: "off", class: "form-control" %>
    </div>

    <% if devise_mapping.rememberable? %>
      <div class="form-group">
        <%= f.check_box :remember_me %>
        <%= f.label :remember_me %>
      </div>
    <% end %>

    <%= f.submit "Log in", class: "btn btn-primary btn-lg" %>
  <% end %>

</div>

<%= render "admins/shared/links" %>

I told you about partial in the first part of this tutorial. Devise has a partial that it uses to render common links across all the pages we have seen above. This partial is in a different folder called shared. Let us edit that as well. Paste the code below in views/admins/shared/_links.html.erb

#views/admins/shared/_links.html.erb

<div class="col-sm-offset-4 col-sm-4 col-xs-12">
  <% if controller_name != 'sessions' %>
    <%= link_to "Log in", new_session_path(resource_name) %><br />
  <% end %>

  <% if devise_mapping.registerable? && controller_name != 'registrations' %>
    <%= link_to "Sign up", new_registration_path(resource_name) %><br />
  <% end %>

  <% if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
    <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
  <% end %>

  <% if devise_mapping.confirmable? && controller_name != 'confirmations' %>
    <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
  <% end %>

  <% if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
    <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
  <% end %>

  <% if devise_mapping.omniauthable? %>
    <% resource_class.omniauth_providers.each do |provider| %>
      <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
    <% end %>
  <% end %>
</div>

Note that we did all of the edits above to provide a great experience for our admins. When you reload the page you will notice that nothing changes. This is because of the way Devise was built to serve its views. To make our changes work, we have to edit a line in Devise initializer. Go to line 223, of your Devise initializer; config/initializers/devise.rb, uncomment the line and change false to true, so it looks like this:

  config.scoped_views = true

Now point your browser to http://localhost:3000/admins/sign_in and see the new format of your sign in page.

You need a navigation bar so that users can easy move from one page to another. We do not need something too serious. Create a navigation partial.

touch app/views/layouts/_navigation.html.erb

Paste the following code into the file you just created.

#app/views/layouts/_navigation.html.erb

<nav class="navbar navbar-default">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">Scotch Blog</a>
    </div>
    <div class="collapse navbar-collapse" id="navbar-collapse">
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to 'Home', root_path %></li>
        <% if admin_signed_in? %>
        <!--This block is only visible to signed in admins -->
          <li><%= link_to 'New Post', new_post_path %></li>
          <li><%= link_to 'My Account', edit_admin_registration_path  %></li>
          <li><%= link_to 'Logout', destroy_admin_session_path, :method => :delete %></li>
        <!-- The block ends here -->
        <% else %>
          <li><%= link_to 'Login', new_admin_session_path  %></li>
        <% end %>
      </ul>
    </div>
  </div>
</nav>

Before the navigation bar can be visible on your website you need to render it. In this case, the rendering will be done from the application layout. See what I have in mine;

#app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
<head>
  <title>ScotchBlog</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>
  <!-- Render navigation bar -->
  <%= render "layouts/navigation" %>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>
  <div class="container-fluid">
    <%= yield %>
  </div>

</body>
</html>

Now reload your browser and you should see the navigation bar displayed.

Authenticate Action

At this point, you have your admin all set up. Now you can set up the authentication as stated earlier; we need just one line to do so. Navigate to app/controllers/posts_controller.rb and add this line of code.

#app/controllers/posts_controller.rb
...
  #This authenticates admin whenever a post is to be created, updated or destroyed.
  before_action :authenticate_admin!, except: [:index, :show]

We are using the before_action callback provide by Rails to make sure that whenever ANY action EXCEPT index and show is called, authentication will be requested.

Validation

One more thing, you need to validate the presence of title and body each time a post is going to be created. You do not want your admins to create posts that have no title or body; that will piss your users off. Validation is always done in the Model. Open up your Post model and make it look like this:

#app/models/post.rb

class Post < ActiveRecord::Base
  #This validates presence of title, and makes sure that the length is not more than 140 words
  validates :title, presence: true, length: {maximum: 140}
  #This validates presence of body
  validates :body, presence: true
end

Admin Actions on Index Page

To make work easy for the admin, and for the sake of user experience, it is wise that we include links for easy navigation in the index. One important issue: though, these links have to be visible to just the admin. Now open your index file and paste in the code I have below:

#app/views/posts/index.html.erb

<div class="container">
  <div class="col-sm-10 col-sm-offset-1 col-xs-12">
    <% @posts.each do |post| %>
    <div class="col-xs-12 text-center">
      <div class="text-center">
        <h2><%= post.title %></h2>
        <h6><%= post.created_at.strftime('%b %d, %Y') %></h6>
      </div>
      <div>
        <%= raw(post.body).truncate(358) %>
      </div>
      <div class="text-center">
        <%= link_to "READ MORE", post_path(post) %>
      </div>
      <% if admin_signed_in? %>
        <%= link_to "Show", post_path(post), class: "btn btn-primary" %>
        <%= link_to "Edit", edit_post_path(post), class: "btn btn-default" %>
        <%= link_to "Delete", post_path(post), class: "btn btn-danger", data: {:confirm => "Are you sure?"}, method: :delete %>
      <% end %>
      <hr />
    </div>
    <% end %>
  </div>
</div>

Now log out if you are logged in and you should not be able to create a new post when you point your browser to http://localhost:3000/posts/new. At this point, your blog is secured from intruders who may want to gain unprivileged access.

Image Uploading for Posts

First, we want to enable image upload for our posts which allow images to be uploaded when new posts are created. We will make use of a gem called carrierwave. Open your Gemfile and add it in.

#Gemfile
...
gem 'carrierwave'
gem 'mini_magick'

Carrierwave helps you enable seamless upload of images in your Rails application. Mini_Magick helps in the processing of your images. Run the command to install.

bundle install

Next, we run the generator command to generate some important files to ensure CarrierWave works with CKEditor.

rails generate ckeditor:install --orm=active_record --backend=carrierwave

That will generate some outputs for us. Next, migrate your database by running the command: rake db:migrate. Run rails server to start up your server. Point your browser to http://localhost:3000/posts/new. Follow the steps shown in the screenshots below.

When done, paste in some text and write a title. Click on the Create Post button to submit your post. Your new posts should have an image uploaded. That was easy!

Image Uploading for Admins

Next, let us add an avatar feature for our admins. We will also make use of carrierwave. Create an initializer for carrierwave at config/initializers/carrier_wave.rb and the in the code below:

#config/initializers/carrier_wave.rb

require 'carrierwave/orm/activerecord'

We will start with generating an uploader;

rails generate uploader Avatar

This will create a new file in app/models/uploaders/avatar_uploader.rb Open the file in your text editor and edit yours to look like mine.

#app/uploaders/avatar_uploader.rb

# encoding: utf-8

class AvatarUploader < CarrierWave::Uploader::Base

  #include mini_magick for image processing
  include CarrierWave::MiniMagick

  #storage option
  storage :file

  #directory for storing image
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Create different versions of your uploaded files:
  version :thumb do
    process :resize_to_fit => [50, 50]
  end

  version :medium do
    process :resize_to_fit => [300, 300]
  end

  version :small do
    process :resize_to_fit => [140, 140]
  end

  # Add a white list of extensions which are allowed to be uploaded.
  def extension_white_list
    %w(jpg jpeg gif png)
  end
end

Let us add a string column to our admin table for the avatars.

rails g migration add_avatar_to_admins avatar:string
rake db:migrate

Open your admin model and mount your AvatarUploader. Here is how to do it;

#app/models/admin.rb

class Admin < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  #mount avatar uploader
  mount_uploader :avatar, AvatarUploader
end

For admins to be able to upload images, we need to whitelist avatar, which makes it a permitted parameter. We will do it inside our application_controller.rb. Like this:

#app/controllers/application_controller.rb

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 :configure_permitted_parameters, if: :devise_controller?

  protected

  #configure permitted parameters for devise
  def configure_permitted_parameters
    added_attrs = [:email, :password, :password_confirmation, :remember_me, :avatar, :avatar, :avatar_cache]
    devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
  end
end

Finally, let us add the field in our views through which the images will be uploaded in our admin sign up page.

#app/views/admins/registrations/new.html.erb

<div class="col-sm-offset-4 col-sm-4 col-xs-12">
  <h2 class="text-center">Sign up</h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {multipart: :true, class: "form"}) do |f| %>
    <%= devise_error_messages! %>

    <div class="form-group">
      <%= f.label :email %><br />
      <%= f.email_field :email, autofocus: true, class: "form-control" %>
    </div>

    <div class="form-group">
      <%= f.label :password %>
      <% if @minimum_password_length %>
      <em>(<%= @minimum_password_length %> characters minimum)</em>
      <% end %><br />
      <%= f.password_field :password, autocomplete: "off", class: "form-control" %>
    </div>

    <div class="form-group">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
    </div>

    <!-- Field for image upload -->
    <div class="form-group">
      <%= f.label :avatar do %><br />
        <%= f.file_field :avatar, class: "form-control" %>
        <%= f.hidden_field :avatar_cache %>
      <% end %>
    </div>

    <%= f.submit "Sign up", class: "btn btn-primary btn-lg" %>
  <% end %>

</div>
<%= render "admins/shared/links" %>

The page will look like this:

Conclusion

In this part you have learned how to authenticate in your Rails application using Devise. You also learned about a gem called carrierwave. With this gem, you were able to enable image uploading in your blog. I hope you enjoyed it.