ProblemYou want to create resized thumbnails as you upload images to your application. SolutionUse the RMagick image library to process thumbnails as each image is uploaded and saved to your application. This solution extends Section 15.2," by adding a "thumb" field to the photo table for storing image thumbnails: db/migrate/001_build_db.rb: class BuildDb < ActiveRecord::Migration def self.up create_table :items do |t| t.column :name, :string t.column :description, :text end create_table :photos do |t| t.column :item_id, :integer t.column :name, :string t.column :content_type, :string t.column :data, :binary t.column :thumb, :binary end end def self.down drop_table :photos drop_table :items end end It also adds image-processing code to the photo method of the Photo model definition: app/models/photo.rb: require 'RMagick' # or, this line can go in environment.rb include Magick class Photo < ActiveRecord::Base belongs_to :item def photo=(image_field) self.name = base_part_of(image_field.original_filename) self.content_type = image_field.content_type.chomp img = Magick::Image::read_inline(Base64.b64encode(image_field.read)).first img_tn = img img.change_geometry!('600x600') do |cols, rows, image| if cols < img.columns or rows < img.rows then image.resize!(cols, rows) end end self.data = img.to_blob img_tn.change_geometry!('100x100') do |cols, rows, image| if cols < img.columns or rows < img.rows then image.resize!(cols, rows) end end self.thumb = img_tn.to_blob # Envoke RMagick Garbage Collection: GC.start end def base_part_of(file_name) name = File.basename(file_name) name.gsub(/[^\w._-]/, '') end end The Photos Controller gets an additional method, show_thumb, to fetch and display thumbnail images: app/controllers/photos_controller.rb: class PhotosController < ApplicationController def show @photo = Photo.find(params[:id]) send_data(@photo.data, :filename => @photo.name, :type => @photo.content_type, :disposition => "inline") end def show_thumb @photo = Photo.find(params[:id]) send_data(@photo.thumb, :filename => @photo.name, :type => @photo.content_type, :disposition => "inline") end end DiscussionTo get a better feel of what's going on behind the scenes when you upload a file you can set a breakpoint in the photo method and inspect the properties of the incoming image_field parameter using the breakpointer.
The class method tells us that we are dealing with a object of the StringIO class: irb(#<Photo:0x40a7dd10>):001:0> image_field.class => StringIO The first thing we extract from this object is the name of the uploaded file. The solution uses the base_part_of method to perform some cleanup on the filename by removing spaces and any unusual characters. The result is saved in the "name" attribute of the Photo object: irb(#<Photo:0x40a7dd10>):002:0> image_field.original_filename => "logo.gif" Next, we can examine the content_type of the image. The content type method of the StringIO class returns the file type with a carriage return appended to the end. The solution removes this character with chomp and saves the result. irb(#<Photo:0x40a7dd10>):003:0> image_field.content_type => "image/gif\r" The solution attempts two resize operations for each uploaded image. This is usually what you want to avoid storing arbitrarily large image files in your database. Each call to RMagick's change_geometry! method attempts to resize its own copy of the Magick::Image object if the size of that object is larger than the dimensions passed to change_geometry!. If the uploaded image is smaller than the minimum requirements for your primary or thumbnail images fields, then skip resizing it. RMagick's change_geometry! is passed a geometry string (e.g., '600x600'), which specifies the height and width constraints of the resize operation. Note that the aspect ratio of the image remains the same. The method then yields to a block that we define based on our specific requirements. In the body of our blocks, we check that the image's height and width are both smaller than the corresponding values we're constraining to. If so, the call does nothing, and the image data is save to the database, otherwise the resizing is performed. After a resize attempt, each image object is converted to a blob type and saved in either the data or thumb fields of the photos table. As in Section 15.3," we display these images with methods that use send_data in our Photos controller. See Also
|