You can download a sample application which uses the method described here at download
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>
For the forms where you will be editing a product, you can use this:
- <%= start_form_tag({:action => 'edit', :id => @product}, :multipart => true) %>
- <%= start_form_tag({:action => 'new'}, :multipart => true) %>
- <%= render :partial => 'image', :object => @product %>
- 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}/#{product_image_title}")
- image.resize "400X300"
- image.write("#{RAILS_ROOT}/public/images/products/#{@id}/#{@id}_medium#{image_file_extension}")
- image.resize "240X180"
- image.write("#{RAILS_ROOT}/public/images/products/#{@id}/#{@id}_small#{image_file_extension}")
- image.resize "50X50"
- image.write("#{RAILS_ROOT}/public/images/products/#{@id}/#{@id}_square#{image_file_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_extension}")
- 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 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
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
- #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
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
- 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
- <%= @product.image_medium %>
- <%= @product.image_small %>
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"
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")
discuss this topic to forum
