You are on page 1of 10

1 Topic by daibatzu 2006-10-24 05:41:02


• daibatzu
• Coach Class
• Offline
• Registered: 2006-06-23
• Posts: 63

Topic: Image uploads and resizing for Rails models with mini-magick
You can download a sample application which uses the method described here at
http://www.campdojo.com/assets/zip/mage.zip
It uses a rails scaffold though.
We've all heard of RMagick but I hear it uses quite a bit of memory which can in turn be an issue
for users of shared hosting. Here's a small tutorial on how to enable image uploads and resizing
with mini-magick.
First of all you will need to install mini-magick. I would normally recommend you type in "gem
install mini_magick" into a command prompt but you are automatically assuming your rails hosting
provider has support for mini-magick. Worse, the "gem install mini_magick" command doesn't
seem to work on Windows for whatever reason. So here is another method of doing this.
Go to the rubyforge project site for mini-magick at http://rubyforge.org/frs/?group_id=1358
Download mini_magick-1.2.0.zip from the list of files. Or just download the latest version. There
will surely be something new at some point.
The next thing you have to download is ImageMagick. The website for ImageMagick is found at
http://www.imagemagick.org/script/binary-releases.php
The windows releases seem to be found near the bottom of the page. If you're on windows, get the
file named ImageMagick-6.3.0-0-Q16-windows-dll.exe or something similar.
Next, unzip the mini_magick-1.2.0.zip file you got from RubyForge. Go to the lib folder and copy
the file "mini_magick.rb" to the lib folder of your rails application.
Now, I'm assuming you are uploading an image for a rails model. We'll be working here with a
model named Product. The first thing you can do is to create a partial for image uploads. This
partial should be rendered in the form for adding or editing a new product. Lets call it _image.rhtml.
Here goes an example:
<div>
<p>Upload an image for your product</p>
<input type="hidden" id="picture_id" value="<%= @product.id %>"
name="picture[id]"/><br/>
<input type="file" id="picture_file" name="picture[file]"/><br />
</p>
</div>
Next, I'm assuming you have the main form for creating or editing a product. It should have it's
multipart value set to true. Here's how to do it.
For the forms where you will be editing a product, you can use this:
<%= start_form_tag({:action => 'edit', :id => @product}, :multipart => true) %>

For the forms where you will be adding a new product, you can use this:
<%= start_form_tag({:action => 'new'}, :multipart => true) %>

You can see, that they will both generate very normal form tags except that the value of multipart is
set to true. Now, inside this form, you should render the partial for _image. You can do it like so:
<%= render :partial => 'image', :object => @product %>

Very good. Next, it is good to create a model to store pictures. Here is how I wrote mine.
require 'mini_magick'

class Picture

attr_accessor :id, :file

def initialize(id,file)
@id = id
@file = file
@filename = base_part_of(file.original_filename)
@content_type = file.content_type.chomp
end

def base_part_of(file_name)
name = File.basename(file_name)
name.gsub(/[^\w._-]/, '')
end

def save
is_saved = false
begin
if @file
#using @id, find the id of the User, then use the id to create the
title
if @content_type =~ /^image/
#instead of product = Product.find, you can say dog = Dog.find or whatever
corresponds to your model.
current_product = Product.find(@id.to_i)
#Make the directory for the id
Dir.mkdir("#{RAILS_ROOT}/public/images/products/#{@id}") unless
File.exist?("#{RAILS_ROOT}/public/images/products/#{@id}")
#Then create the temp file

File.open("#{RAILS_ROOT}/public/images/products/#{@id}/#{@filename}", "wb") do
|f|
f.write(@file.read)
end
product_image_crop("#{@filename}")
#update the current product
image_names = product_image_names("#{@filename}")
File.open("#{RAILS_ROOT}/public/yo.txt", "wb") do |f|
f.write(image_names)
end
current_product.update_attributes("image_square" => image_names[0],
"image_small" => image_names[1], "image_medium" => image_names[2],
"image_original" => image_names[3])
is_saved = true
end
end
rescue
end
return is_saved
end

def product_image_crop(product_image_title)

#product_title = product_title.squeeze.gsub(" ","_").downcase


#find the extension for this file
image_file_extension =
product_image_title[product_image_title.rindex(".") ..
product_image_title.length].strip.chomp
image =
MiniMagick::Image.from_file("#{RAILS_ROOT}/public/images/products/#{@id}/#{produ
ct_image_title}")

image.resize "400X300"

image.write("#{RAILS_ROOT}/public/images/products/#{@id}/#{@id}_medium#{image_fi
le_extension}")
image.resize "240X180"

image.write("#{RAILS_ROOT}/public/images/products/#{@id}/#{@id}_small#{image_fil
e_extension}")

image.resize "50X50"

image.write("#{RAILS_ROOT}/public/images/products/#{@id}/#{@id}_square#{image_fi
le_extension}")

#Finally, rename the originally uploaded image

File.rename("#{RAILS_ROOT}/public/images/products/#{@id}/#{product_image_title}"
,
"#{RAILS_ROOT}/public/images/products/#{@id}/#{@id}_original#{image_file_extensi
on}")
end

def product_image_names(product_image_title)
image_file_extension =
product_image_title[product_image_title.rindex(".") ..
product_image_title.length].strip.chomp
#Generate an array containing the url of all the images
["/images/products/#{@id}/#{@id}_square#{image_file_extension}",
"/images/products/#{@id}/#{@id}_small#{image_file_extension}","/images/products/
#{@id}/#{@id}_medium#{image_file_extension}",
"/images/products/#{@id}/#{@id}_original#{image_file_extension}"]
end

end

Now, if I could explain what this class does. First of all, it should be named "picture.rb" and should
be saved in your models directory. But please note that it does not inherit from ActiveRecord::Base.
Now what is happening here. First of all, if you look at the partial, _image.rhtml which we created
earlier, You will notice that it has two fields, picture_id and picture_file. Now, we can create a new
Picture object using Picture.new(id,file), so id corresponds to picture_id and picture_file
corresponds to the file being uploaded. More on this later. Now the Picture model has a method
called save and this returns true or false. If it returns true, then the image has been saved.
At this point, you should probably change the migration for your Product model. You add the
following:
t.column :image_square, :string
t.column :image_small, :string
t.column :image_medium, :string
t.column :image_original, :string

So you have four columns for different images. I'm assuming you will want 4 different versions of
your image. From a really small thumbnail to the original image.
The method product_image_names finds the url for each picture. Note that each image is saved
under the id for the product. Images for a product with an id of 1, are saved under the folder,
/images/products/1 e.t.c
Anyway, still more work to do. Go to your Product model and add the following.
First, add this filter to your product model
before_destroy :delete_image_directory

Here is the actual method which should also be added to the Product model.
#Method to delete a directory
def rmtree(directory)
Dir.foreach(directory) do |entry|
next if entry =~ /^\.\.?$/ # Ignore . and .. as usual
path = directory + "/" + entry
if FileTest.directory?(path)
rmtree(path)
else
File.delete(path)
end
end
Dir.delete(directory)
end
def delete_image_directory
image_dir = "#{RAILS_ROOT}/public/images/products/#{self.id}"
if File.exist?(image_dir)
begin
rmtree(image_dir)
rescue
end
end
end

The above methods simply delete the directory of images for your product.
Next, the controllers.
Let's say you are adding products via the admin interface. So the controller would be
AdminController.
So let's say you are using the AdminController. Inside this controller, add this method:
def save_picture
@picture = Picture.new(params[:picture][:id], params[:picture][:file])
resp = ""
if @picture.save
resp = 'and it\'s picture was successfully uploaded'
else
if Product.find(params[:picture][:id]).image_square != nil
resp = ''
else
if params[:picture][:file].class == StringIO
resp = 'but you have not yet chosen a picture for it'
else
resp = 'but I could not upload the picture because it is not a valid
Jpeg, Gif or Png file'
end
end
end
return resp
end

Now, I'm assuming you have save and edit methods for your product model here also. Here's mine.
def new
reps = ""
if request.post?
@product = Product.new(params[:product])
if @product.save
if params[:picture]
params[:picture][:id] = @product.id
reps = save_picture
end
flash[:notice] = "A new product was successfully added #{reps}"
redirect_to :action => 'list'
end
else
@product = Product.new
end
end
def edit
reps = ''
@product = Product.find(params[:id])
if request.post?
if @product.update_attributes(params[:product])
if params[:picture]
params[:picture][:id] = @product.id
reps = save_picture
end
flash[:notice] = "The product was successfully edited #{reps}"
redirect_to :action => 'show', :id => @product
end
end
end

Note that reps simply tells you if the product image has been saved or not. Now you're basically
done. If you want to display an image for your product, you can simply add in your view.
<%= @product.image_medium %>

or
<%= @product.image_small %>

A small note on mini-magick commands though.


Let's say I want to make one, and only one change to a file. To load it, I use the new command.
image = MiniMagick::Image.new("william.jpg")
image.resize "240X180"

Using MiniMagick::Image.new means that any change to the file is permanent. If william.jpg had
dimensions 1028X876, it's dimensions will be permanently changed to 240X180.
Now if we want to save multiple versions of the file, this is what we need to do.
image = MiniMagick::Image.from_file("william.jpg")
#resize to 400X300
image.resize "400X300"
#now save it with a different name
image.write("william_medium.jpg")
image.resize "240X180"
image.write("william_240.jpg")

So this will leave us with three image. The original william.jpg, william_medium.jpg and
william_240.jpg.
Last edited by daibatzu (2006-10-24 06:32:53)

2 Reply by misiek 2006-11-17 12:38:55


• misiek
• Ticketholder
• Offline
• From: Chicago
• Registered: 2006-11-17
• Posts: 5

Re: Image uploads and resizing for Rails models with mini-magick
That's cool .
I use the MiniMacgick a while but one thing does not work
.resize or .thumbnail
actualy the arguments like .resize "width X hight".
For example the oryginal image has 252x144 and I want to resize to 12x12 so I do:
.resize "12x12" but results are 12x7
the arguments like "12x" or "x12" works because I got "12x7" and "21x12"
The same for a .thumbnail
What I do wrong ?
misiek's Website
3 Reply by daibatzu 2006-11-20 12:15:55


• daibatzu
• Coach Class
• Offline
• Registered: 2006-06-23
• Posts: 63

Re: Image uploads and resizing for Rails models with mini-magick
misiek, that's because it's trying to scale the image. resizing to 12x12 may not make the image look
good

4 Reply by misiek 2006-11-20 15:40:45


• misiek
• Ticketholder
• Offline
• From: Chicago
• Registered: 2006-11-17
• Posts: 5

Re: Image uploads and resizing for Rails models with mini-magick
There is a way to scale image like I need with Minimagick ?
And one more thing .
How to check an error for upload image if the file actually is not an image.
I changed txt file on image.gif and that's not gonna work.
I have tried like begine rescue and, but does not work in MiniMagick, works for RMagick.
Thanks
Last edited by misiek (2006-11-20 15:47:04)
misiek's Website

5 Reply by jayjee 2006-12-29 17:06:18


• jayjee
• Ticketholder
• Offline
• Registered: 2006-12-29
• Posts: 1

Re: Image uploads and resizing for Rails models with mini-magick
I have this up and running on my server, but when the images are written to the server, the
permissions are 0600 and the images don't show up.
I tried adding @photo.chmod(0755) in your save_picture method, and I also tried
image.chmod(0755) in the Photo.rb model.
Do you know what I'm doing wrong?

6 Reply by letimati 2007-05-09 10:43:48



• letimati
• Passenger
• Offline
• From: Barcelona
• Registered: 2007-01-25
• Posts: 31

Re: Image uploads and resizing for Rails models with mini-magick
by putting the images in a folder how do you think best to extend this tutorial to have more than one
image for a product?
letimati's Website

7 Reply by rajesh0363 2007-06-07 09:05:13


• rajesh0363
• Ticketholder
• Offline
• Registered: 2007-06-07
• Posts: 2

Re: Image uploads and resizing for Rails models with mini-magick
Hi,
I'm getting this error message when loading the page.
NoMethodError in PeopleController#new
undefined method `original_filename' for #<String:0x47a9c08>...
My error is somewhere in this code block...
def initialize(id,file)
@id = id
@file = file
@filename = base_part_of(file.original_filename)
@content_type = file.content_type.chomp
end

def base_part_of(file_name)
name = File.basename(file_name)
name.gsub(/[^\w\.\-]/,'_')
end

Does anyone know why am i getting this error message.


Any help would be highly appreciated.
Thanks & best regards,
Rajesh

8 Reply by wowo 2007-06-11 17:23:03


• wowo
• Ticketholder
• Offline
• From: Munich
• Registered: 2007-06-11
• Posts: 1

Re: Image uploads and resizing for Rails models with mini-magick
rajesh0363 wrote:
Hi,

I'm getting this error message when loading the page.

NoMethodError in PeopleController#new
undefined method `original_filename' for #<String:0x47a9c08>...

My error is somewhere in this code block...

def initialize(id,file)
@id = id
@file = file
@filename = base_part_of(file.original_filename)
@content_type = file.content_type.chomp
end

def base_part_of(file_name)
name = File.basename(file_name)
name.gsub(/[^\w\.\-]/,'_')
end

Does anyone know why am i getting this error message.

Any help would be highly appreciated.


Thanks & best regards,

Rajesh

Hi Rajesh,
I suppose, that you have no file selected for upload. In this case there is no metadata available for
the content_type and the original_filename attributes.
To solve this problem, I just have checked for the presence of the file. If there is no file present you
don't have to initialize anything.
def initialize(id,file)
if file != ""
@id = id
@file = file
@filename = base_part_of(file.original_filename)
@content_type = file.content_type.chomp
end
end

THat's all. This should work.


Best wishes,
wowo
Last edited by wowo (2007-06-11 19:48:22)

You might also like