Users Avatars - Uploading Images Using Paperclip

By:, On:

This tutorial assumes you have already completed:

  1. Styling Devise forms with TwitterBootstrap
  2. Adding custom fields to Devise

We have already created our users. Time to allow them to upload their own avatars and eventually some other images that they would like to share.

Since we work with Heroku.com as a platform we deploy to, there will be some extra steps we have to perform (in the next tutorial).

We will follow this path:

  1. Add Paperclip gem
  2. Add avatar to user model
  3. Add avatar upload to devise forms

Let's get started.

Step 1: Add Paperclip Gem

There are two paths to do images for Rails app. Some people use Paperclip, some others Carrierwave. We will use Paperclip in this tutorial. Also, I assume you have users managed with Devise.

First, visit Paperclip documentation and have a look, we will use some of the instructions listed there. You can have a look at additional settings available for the gem.

We will start by installing image processor - ImageMagick that will be responsible for manipulating our images, resizing, cropping, etc. Open terminal and type if you are in Mac:

bash


$ brew install imagemagick

If you are in Linux, type:

bash


$ sudo-apt-get install imagemagick

Step 1.2

Now, we need to add Paperclip to the Gemfile

Gemfile


source 'https://rubygems.org'

# existing gems

gem 'paperclip', '~> 4.1'

Step 1.3

And run bundler

bash


$ bundle install

Step 2: Add Avatar to User Model

Let me start with a little explanation. When we upload images, we don't really save them to the database. Our database can not hold any files. So what we will be doing, or rather what Paperclip will be doing for us, is saving images into designated folder. So how can we later find where the user avatar is, and which avatar is it? For this we need place in the database to store things like file name or size. On a Github page you can find a command to automatically generate migration, but I've been having serious problems with it recently, therefore we will generate migration by hand. In your terminal, type:

bash


$ rails generate migration add_avatars_to_users

      invoke  active_record
      create    db/migrate/20140516064357_add_avatars_to_users.rb

Step 2.2

Now, open the migration file and paste the code that will add the avatar related fields to the user model:

db/migrate/20140516064357_add_avatars_to_users.rb


class AddAvatarsToUsers < ActiveRecord::Migration
  def self.up
    change_table :users do |t|
      t.attachment :avatar
    end
  end

  def self.down
    drop_attached_file :users, :avatar
  end
end

Step 2.3

And migrate database in your terminal:

bash


$ rake db:migrate

Step 2.4

Now, what's left is the declaration in the model file of a user to let it know that it can have file attached.


class User < ActiveRecord::Base
  # existing code

  has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100#" }, :default_url => "/images/:style/missing.png"
  validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
end

There is couple of things going on here. First, we declare that there is a file attached to the user model and it's called an "avatar." If we would call it differently, say a "picture," we would of course have to change the code above.

Second, we declare two styles for images and we call them medium and thumb. We could add more styles or have less of them. It really depends on our user's experience. We want to send as small files as possible to make everything work fast, but at the same time we want good quality images. If you're planning on having very large profile pictures you may considering changing dimensions too. "300x300>" means that if picture is bigger than 300 by 300 pixels, we will proportionally scale it down and save it so no dimension is exceeding 300 pixels. If we wanted to crop pictures so they are square - we would write "300x300#".

Last thing is the default url. This is the image that will be served if our avatar is not present. Maybe user didn't upload it yet, maybe there was an error while uploading. We need to make sure our placeholder image is in the location specified in above path. By default Rails would look in public folder. So, if the "thumb" version of the file is not present - we will display file form public/images/thumb/missing.png . We need to create a new folder inside of the public called "images." Inside the images folder, we need to more folders: one called thumb and the other called medium. Both should contain a file named "missing.png". In thumb folder file should be 100 by 100 pixels and in medium it should be 300 by 300.

success after uploading avatar

We also included validation. Without this line, our upload won't work. Validation is a security feature preventing submission of malicious files.

Awesome, our website can already handle the images, but we still have no way to submit images from the user interface.

Step 2.5

Since we're using Devise to manage our users, we will have to make sure, that Devise permits our new avatar. If you haven't yet, have a look at the tutorial about custom fields when using devise.

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

    protected

    def configure_permitted_parameters
        devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :email, :password) }
        devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:name, :email, :password, :current_password, :is_female, :date_of_birth, :avatar) }
    end
end

Step 3: Add Avatar Upload to Devise Forms

Final step to see the upload working is to add file upload field to our registration edit form:

app/views/users/registrations/edit.html.erb


    <h2>Edit <%= resource_name.to_s.humanize %></h2>

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

    <div class="form-group">
        <%= f.label :avatar, class: 'col-sm-2 control-label'  %>
        <div class="col-sm-6">
            <%= f.file_field :avatar %>
        </div>
    </div>


    <div class="form-group">
        <%= f.label :name, class: 'col-sm-2 control-label'  %>
        <div class="col-sm-6">
            <%= f.text_field :name, autofocus: true, class: 'form-control'  %>
        </div>
    </div>

    <div class="form-group">
        <%= f.label :is_female, "Gender", class: 'col-sm-2 control-label'  %>
        <div class="col-sm-6">
            <%= f.radio_button :is_female, true %> <%= f.label :is_female, "Female" %>
            <%= f.radio_button :is_female, false %> <%= f.label :is_female, "Male" %>
        </div>
    </div>


    <div class="form-group">
        <%= f.label :date_of_birth, class: 'col-sm-2 control-label'  %>
        <div class="col-sm-6">
            <%= f.date_select :date_of_birth, start_year: 1920, end_year: 2000, class: 'form-control'  %>
        </div>
    </div>

    <div class="form-group">
        <%= f.label :email, class: 'col-sm-2 control-label'  %>
        <div class="col-sm-6">
            <%= f.email_field :email, class: 'form-control'  %>
        </div>
    </div>

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


    <div class="form-group">
        <%= f.label :password_confirmation, class: 'col-sm-2 control-label' %>
        <div class="col-sm-6">
            <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control'  %>
        </div>
    </div>

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

    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-6">
            <%= f.submit "Update", class: "btn btn-primary" %>
        </div>
    </div>

    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-6">
            <%= render "users/shared/links" %>
        </div>
    </div>
    <% end %>

    <h5>Cancel my account</h5>

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

    <%= link_to "Back", :back %>

Our profile edit (http://localhost:3000/users/edit) should look like this:

form for uploading user's avatars

After you successfully upload the avatar, you will see success notice:

success after uploading avatar

Step 3.2

Unfortunately, we don't display the user's avatars anywhere yet. Let's fix that:

app/views/layouts/_menu.html.erb


    <div class="collapse navbar-collapse" id="navbar-collapse-1">
        <ul class="nav navbar-nav navbar-right">
            <li><%= link_to 'Posts', '#' %></li>
            <% if current_user %>
            <li><%= link_to 'Edit Profile',edit_user_registration_path %></li>
            <li><%= link_to 'Logout', destroy_user_session_path, method: :delete %></li>

            <li class="round-image-50"><%= image_tag(current_user.avatar.url(:thumb)) %></li>

            <% else %>
            <li><%= link_to 'Login', new_user_session_path %></li>
            <% end %>
        </ul>
    </div>

Step 3.3

And style it using CSS:

app/assets/stylesheets/custom.css.scss


.round-image-50 {
    background-color: white;
    border: 1px solid #d9d9d9;
    border-radius: 25px;
    -moz-border-radius: 25px;
    -webkit-border-radius: 25px;
    height: 50px;
    width: 50px;
    overflow: hidden;
    text-align: center;
    img { width: 100% }
}

Now, you can see your avatar in the menu:

success after uploading avatar

If you login you will be able to mark this tutorial as finished to track your progress



Comments

  • On: mohammad javad wrote:

    my image_tag(current_user...) does not work properly and it just shows missing picture?

  • On: mohammad javad wrote:

    i'm getting this error when uploading picture : Avatar has an extension that does not match its contents

  • On: Jose Pejuan wrote:

    This line right here: <%= imagetag(currentuser.avatar.url(:thumb)) %> its throwing me an error. Says the method doesn't exist

Comment

You can login to comment