Work
with us

Tell us about your idea
and we will find a way
to make it happen.

Get estimate

Join our awesome team

Check offers

The problem with image upload raises every time when it comes to giving a possibility of adding visuals to a project. Almost every application needs this kind of functionality, doesn’t matter if it is a blog or online shop. Even if it’s not going to be dedicated to users it could be important from the admin point of view. When the project is getting bigger, the product owner needs to do more tasks in shorter time, and every functionality should work as fast as possible, so in case of images it would be good to allow multiple image upload with an easy implementation. It could be also good for a developer to have a quick and simple solution. After some research I’ve fortunately found one.

Let’s start with basic image upload…

To start with something simpler I’m going to handle a basic file upload with Carrierwave. You need to add it to your gemfile and create some files to work with. While doing it you need to generate an uploader file.

gem 'carrierwave'
gem 'rmagick'
rails generate scaffold Photo title:string image:string
rails generate uploader Image
rake db:migrate

Uploader is a place for manipulating the files. For example it allows to change the upload location, perform simple image processing after uploading and to restrict the type of files that can be uploaded. There is no need to play with it right now, you should only set the storage directory and include the RMagick. The uploader also needs to be mounted inside model.

class ImageUploader < CarrierWave::Uploader::Base
  include CarrierWave::RMagick
  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end
validates :title, presence: true
mount_uploader :image, ImageUploader

There is no need to do any magic inside the controller. Just put in the code for typical actions for creating objects and make a simple form in your view.

def new
  @photo = Photo.new
end

def create
  @photo = Photo.new(photo_params)
  redirect_to photos_path, notice: 'Photo uploaded' if @photo.save
end
=form_for @photo, html: {multipart: :true} do |f|
%div
  = f.label :title, 'Title'
  = f.text_field :title
%div
  = f.label :image, 'Choose Image'
  = f.file_field :image
%div
  = f.submit 'Add'

Almost done. To see the results just add an image_tag with image_url in the show action.

%h1 Photo:
= @photo.title
= image_tag @photo.image_url() if @photo.image?

Image upload with Carrierwave is as simple as a typical scaffold. With a little piece of code the project became more useful and versatile. Now you could ask “Can I easily adjust the image while uploading it?”. I will not let you down, everything is possible, uploader has just the tools for doing it.

…and add a watermark.

Now we can play with the image. RMagick gives a posibility to modify the uploaded file. You can easily do common manipulations like image scaling or marking it with your logo. Start with editing the uploader file. Depending on your requirements you can create more versions of your image. Put a file with your logo in the standard image directory and it will be merged with your upload. In my watermark function you can change the RMagick constants to obtain different results.

process :convert => 'png'
  process :watermark
  version :thumb do
    process :resize_to_limit => [200, 200]
  end
  version :index do
    process :resize_to_limit => [400, 400]
  end

  def watermark
  manipulate! do |img|
    logo = Magick::Image.read("#{Rails.root}/app/assets/images/logo.png").first
    img = img.composite(logo, Magick::NorthWestGravity, 0, 0, Magick::OverCompositeOp)
  end
end

Now you can add a version of the uploaded file in your show view, and check how the picture looks. You just need to upload a new file.

= image_tag @photo.image_url(:index) if @photo.image?

Switch to multiple upload

Ok, everything is working well, but now think about optimization. I don’t mean examining the code, but analyzing a simple situation – the website is getting more and more popular, and the page owner has to upload a hundred images per day.A nice solution is to add a multiple upload function. You can change the methods in the controller but I’d rather add another ones for this. You need to add another gem – plupload.

gem 'plupload-rails'

Update your controller and routes. To make a multiupload even faster you may find it convenient to add a method that parses the filename into the title of the image.

resources :photos
  get '/upload_photos' => 'photos#upload_photos', as: :upload_photos
  post '/upload' => 'photos#upload', as: :upload
def upload_photos
end

def upload
    @photo = Photo.new(image: params[:file])
    parsed = Photo.parse_filename(params[:name])
    @photo.title = parsed[:title]
    if @photo.save
      head 200
    end
  end
def self.parse_filename(filename)
    filename.gsub!(/(.jpg|.png)/, '')
    return nil unless filename =~ /^\w*-(([a-zA-Z])*(_|$))*/
    filename.split('_').join(' ')
    {title: filename}
  end

Multiupload view is based on the one presented on plupload webpage. Don’t forget to include aplupload javascript. Once again filename is checked here – this is actually important. The validations in your code will not allow you to save a file with the wrong name, but to show an appropriate message it has to be checked in javascript code too.

= javascript_include_tag "plupload.full.min.js"

%p Add images
.upload
  .filelist{:id=>"filelist"}
  %br
  %a{:id=>"pickfiles", :href=>"#"} [Select files]
  %a{:id=>"uploadfiles", :href=>"#"} [Upload files]
%br

:javascript
  $(function(){
    var uploader = new plupload.Uploader({
      runtimes : "html5",
      browse_button : 'pickfiles',
      max_file_size : '10mb',
      url : "/upload",
      multipart: true,
      urlstream_upload: true,
      multipart_params: {
       "authenticity_token" : '#{form_authenticity_token}'
      }
    });

    uploader.bind('FilesAdded', function(up, files) {
      $.each(files, function(i, file) {
        $('#filelist').append(
          '<div id="' + file.id + '">' +
          'File: ' + file.name + ' (' + plupload.formatSize(file.size) + ') <b></b>' +
          '</div>'
          );
        });
      });

    uploader.bind('UploadProgress', function(up, file) {
      $('#' + file.id + " b").html(file.percent + "%");
    });

    uploader.bind('FileUploaded', function(up, file) {
      if(file.name.match(/^\w*-(([a-zA-Z]|)*(_|$))*/)){
        $('#' + file.id + " b").html("OK");
      }
      else{
        $('#' + file.id + " b").html("<span style='color:red;'>Wrong filename</span>");
      }
    });

    $('#uploadfiles').click(function(e) {
      uploader.start();
      e.preventDefault();
    });

    uploader.init();
  });
Rails.application.config.assets.precompile += %w( plupload.full.min.js )

Restart your server and try to upload files.

Success!

There’s a lot more that could have been covered here. It can be build up in many ways, dependent on your own ideas. It could be styled with a more impressive design but my plan was to give you just a brief step-by-step solution. If you are satisfied with it, you can easily add it to your project, wearing a smile.

The project with solution is available here

Post tags: