RMagic::Image#thumbnail!

強いなぁ、これ。例外処理省略して書くとこんな感じで、ajax っぽいuploader作りました。

ポイントは、dummy フレームを使って、multipartのフォームをpostする点と、画像の表示更新にURIクエリでタイムスタンプを付与しているところ。タイムスタンプを付与しないと、ブラウザがローカルキャッシュにあるものを表示しちゃうのです。

    <table style="text-align:center">
    <tbody>
        <tr>
            <td>
                <form target="dummy" action="<%= direct_url_for :action => 'upload_photo' %>"
                                   enctype="multipart/form-data" method="post" >
                    <input type="file" name="photo" size="70"/>
                    <input type="submit" value="send" />
                </form>
            </td>
        </tr>
        </tr>
            <td id="<%= my_dom_id('photo') %>" style="padding:1em;">
                <img src="/component/system/account_photo/download_photo/<%= @account.id %>" />
            </td>
        </tr>
    </tbody>
    </table>
    <iframe id="dummy" name="dummy" style="display:none"></iframe> 
require 'RMagick'
include Magick

class System::AccountSettingsController < ApplicationController
  uses_component_template_root
  is_widget 

  (snip)

  def upload_photo
    photo = params[:photo].read
    photo = Image.from_blob(photo)[0]
    if photo.columns > 120 || photo.rows > 120
      # resize image
      scale_factor = photo.columns > photo.rows ? (120.0 / photo.columns) : (120.0 / photo.rows)
      photo.thumbnail!(scale_factor)
    end
      
    @account.photo = photo.to_blob
    @account.photo_type = params[:photo].content_type.gsub(/\r/, '')
    @account.save
    time = Time.now
    
    responds_to_parent do
      render :update do |page|
        page << "Element.update('#{widget_instance.dom_id('photo')}', '<img src=\"/component/system/account_photo/download_photo/#{@account.id}?#{time.strftime('%y%m%d%H%M%S')}\" />')"
      end
    end
  end  
end
class System::AccountPhotoController < ApplicationController
  uses_component_template_root

  def download_photo
    user = WjUser.find(params[:id])
    if user.photo
      opts = {
        :filename => "user_#{user.id}_photo",
        :type => user.photo_type,
        :disposition => 'inline'
      }
      send_data user.photo, opts
    else
      # TODO prepare the photo when the user data is not set.
      render :nothing => true
    end
  end
end