Hatena::Grouprubyist

うんたらかんたらRuby RSSフィード

2010-02-07DBに格納したバイナリ画像データを表示させる方法

DBに格納したバイナリ画像を表示させる方法

| DBに格納したバイナリ画像を表示させる方法 - うんたらかんたらRuby を含むブックマーク はてなブックマーク - DBに格納したバイナリ画像を表示させる方法 - うんたらかんたらRuby

最近のDBはミラーリングやレブリケーションが当たり前にされてるので

DBにバイナリで画像格納ってのはよくある方法だと思う。


概要

1.controllerでバイナリデータ以外をselect

2.view側でurl_forによりcontrollerで定義した画像表示actionを指定

3.view側のimage_tagで表示

4.通常のhtmlレンダリング時に、画像表示actionがリクエストされると

controllerのsend_dataでは、id毎にバイナリデータを検索してviewに返す



この流れでは、画像毎にselectが走るが

まぁ、致し方ないか。


controller

selectオプションでバイナリデータ以外の項目を取得。

  def index
    #@images = Image.all
    @images = Image.all(:select => 'id, name, updated_at')
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @images }
    end
  end
  def get_image
    @image = Image.find(params[:id])
    send_data(@image.image, :disposition => "inline", :type => "image/png")
  end

view

<% @images.each do |image| %>
  <tr>
    <td><%=h image.name %></td>
    <td><%= image_tag(url_for(:action => 'get_image', :id => image.id), :size => '100x100')%></td>
    <td><%= link_to 'Show', image %></td>
    <td><%= link_to 'Edit', edit_image_path(image) %></td>
    <td><%= link_to 'Destroy', image, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>

こんな感じ

f:id:rochefort:20100207095527p:image

パチパチ


あれ?

生成されたhtmlを見ると

image_tagで通常表示した際に付く、画像のタイムスタンプが無い。

そうか、バイナリだもんな。

これだと、タイムスタンプによるキャッシュが使えない。

なんとかDB更新日時渡せないかな、と↓を思い返しながら思案。

passengerのcss - うんたらかんたらRuby - Rubyist


image_tagをオーバーライドしてやってみた。

view側でaddsrcオプションを指定。

<%= image_tag(url_for(:action => 'get_image', :id => image.id), :size => '100x100', :addsrc => image.updated_at.to_i.to_s) %>

helper

added start から addes endの箇所。

addsrcを追記して、最後に消してみた。

  #override method
  #actionpack-2.3.5/lib/action_view/helpers/asset_tag_helper.rb
  def image_tag(source, options = {})
    options.symbolize_keys!

    options[:src] = path_to_image(source)
    options[:alt] ||= File.basename(options[:src], '.*').split('.').first.to_s.capitalize

    if size = options.delete(:size)
      options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
    end 

    if mouseover = options.delete(:mouseover)
      options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
      options[:onmouseout]  = "this.src='#{image_path(options[:src])}'"
    end 

    # added start
    if options[:addsrc]
      if /\?/ =~ options[:src]
        options[:src] << "#{options[:addsrc]}"
      else
        options[:src] << "?#{options[:addsrc]}"
      end
      options.delete(:addsrc)
    end
    # added end

    tag("img", options)
  end

んで

どうなんでしょう。こういうやり方って。

使用箇所を最小限にするならありなんでしょうか?

file_columnとか使うと幸せになれるんだっけ?


参考

【Rails:6】コントローラとビュー(応用編) - L’Isle joyeuse


2010/03/12追記

DBに入れた画像を表示する - 篳篥日記


viewでurl_forを使って画像呼出しメソッド呼んでたけど

image_tag("/get_image/#{id}", options) みたいに書けるようだ。

これでもいいね。

babiebabie2010/04/20 20:19画像モデルにcreated_at, updated_atを付けておいた上で
1. public/images/hoge/以下に画像ファイルがなかったらDBからバイナリを取って画像を出力する(File.utime()必要)。
2. 画像ファイルがあったらDBからupdated_atだけ引いて比較する。DBの方が新しい場合はファイルを上書きする(File.utime()必要)。
3. 画像ファイルのURLパスを返す。
ってのを実行するimage_path()ってhelperを作るのはどうでしょうか?
これだと、httpdに画像を任せられて速いし、DBに負荷が余りかからないし、分散環境でも問題ないです。

rochefortrochefort2010/04/20 22:32コメントありがとうございます!
なるほど。DBにはマスタデータを格納し、WEBサーバ毎にディスクに画像持たせるという方式ですね。

ディスクIOが気にならない程度なら、それもよさそうですね。
でもディスクの画像を使用するのが前提なら、共有ディスクに置いたりするってのもよさそうですね。
disk_cache、memcachedなどキャッシュ使用を視野に入れるとさらに面白そうです。
いずれにせよ、実際に使う局面でベンチマークとってみたいところです(まだサービスインできそうなアイデアがない。。。早く纏めたいな。)。

babiebabie2010/04/21 07:40上のはアイデアだけで実行してないのですが、フロントにSquidをかませて画像ファイルをリバースプロキシでキャッシュさせたことがあります。メモリの潤沢なサーバを1台用意できたのでなかなか速かったですよ。

rochefortrochefort2010/04/21 22:00ああ、Squidですか。いいらしいですね。興味あります。
是非使ってみたいとは思ってますが、リバースプロキシが必要な状況(スケールさせる)っていうのがなかなか。

トラックバック - http://rubyist.g.hatena.ne.jp/rochefort/20100207