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