RailsでPostgreSQLに格納した画像を表示する
PostgreSQLデータベースにbinary
型のデータとして画像を格納し、Railsアプリで表示する方法を調べました。
目次
いかにしてその心情に至ったか
HerokuにデプロイするRailsアプリを作成していて、アバター画像をプロフィールに登録する方法を調べたら、PaasであるHerokuには画像を保存する事ができないため、ファイルサーバとしてAnazon S3を使用し、Herokuと連携させるのが一般的な方法であると知りました。
しかしながら、いくらAmazon S3に無料枠があるとは言え、個人的に利用するだけのアプリのたかだか数十kb程度のアバター画像のためにファイルサーバを準備するのも大袈裟で面倒な気がしたので、データベースに直接格納する方法が無いか調べると、PostgreSQLデータベースに画像ファイルをバイナリデータとして格納する方法があると知り試してみました。
データベースに画像を格納する
マイグレーションファイルを生成
マイグレーションファイルを生成して、データベースに画像を格納するためのモデルの準備をします。
モデルを新しく作る場合
画像の格納先としてbinary
型のavatar
カラムを持つUser
モデルをgenerate
し、マイグレーションファイルを生成します。
$rails generate model User name:string email:string avatar:binary
今回はbinary
型のavatar
カラムの他にも、string
型のname
カラムとemail
カラムをUser
モデルに待たせましたので、マイグレーションファイルのchange
メソッドに含まれるcreate_table
ブロックにはt.string :name
とt.string :email
とt.bynary :avatar
が記述されています。
class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :name t.string :email t.bynary :avatar t.timestamps null: false end end end
画像用のカラムをモデルに追加する場合
generate
したモデルに後からカラムを追加する場合は、generate migration
で追加するカラムと追加先のテーブルを指定します。
$rails generate migration add_avatar_to_users avatar:bynary
今回はUser
モデルのusers
テーブルにbinary
型のavatar
カラムを追加するので、add_avatar_to_users
をgenerate migration
し、change
メソッドにadd_column :users, :avatar, :bynary
が含まれるマイグレーションファイルを生成します。
class AddAvatarToUsers < ActiveRecord::Migration def change add_column :users, :avatar, :bynary end end
DBにマイグレーションを適用
rake db:migrate
で、データベースにマイグレーションファイルの内容を適用します。
$rake db:migrate
無事にデータベースにマイグレート出来たかを確認するため、psql raislapp_development
で開発環境のデータベースに接続し、users
テーブルを見てみます。
id
Columnやその他のColumnに加えて、bytea
Typeのavatar
Columnが作成されていれば一安心して次の作業に進みます。
$psql railsapp_development psql (9.5.1) Type "help" for help. railsapp_development=# \d users Table "public.users" Column | Type | Modifiers -----------+-------------------------+--------------------------------------------------------- id | integer | not null default nextval('users_id_seq'::regclass) name | character varying | email | character varying | avatar | bytea | railsapp_development=#
SQLコマンドでDBに画像を格納
ブラウザから画像をRailsアプリにアップロードするためのフォームとかはまだ作成して無いので、とりあえず、SQLコマンドで画像をデータベースに格納してみます。 SQLコマンドでファイルをPostgreSQLデータベースに格納するためには、PosgreSQLデータベースのデータディレクトリからの絶対パスか相対パスをコマンド入力中に記述する必要があるらしいので、画像ファイルをデータディレクトリ*1の直下に格納しました。
データベースに新しい行を追加して画像を格納する場合
VALUES( ... )
で追加先の行(id)と格納する値を指定し、INSERT INTO
コマンドでusers
テーブルに追加します。
$psql railsapp_development psql (9.5.1) Type "help" for help. railsapp_development=# INSERT INTO users VALUES (1, 'b0npu nomi', 'b0npu@example.com', 'default_avatar.png'); INSERT 0 1 railsapp_development=#
users
テーブルには、画像を格納するavatar
カラムの他にname
とemail
のカラムを作成していますので、VALUES ( ... )
の括弧ので、id, name, mail, avatar
の値を記述しています。
画像を追加してデータベースの行を更新する場合
pg_read_binary_file
関数を使用し、UPDATE
コマンドでusers
テーブルに画像ファイルをSET
します。
$psql railsapp_development psql (9.5.1) Type "help" for help. railsapp_development=# UPDATE users SET avatar = pg_read_binary_file('default_avatar.png') WHERE id = 1; UPDATE 1 railsapp_development=#
データベースに画像ファイルが格納されると、avatar
カラムに\x
から始まるバイナリ文字列が格納されます。
$psql railsapp_development psql (9.5.1) Type "help" for help. railsapp_development=# SELECT * FROM users; -[ RECORD 1 ]---+----------------------------------------------------------------------------------------- id | 1 name | b0npu nomi email | b0npu@example.com avatar | \x89504e470d0a1a0a0000000d4948445200000080000000800806000000c33e61cb000000017352(長いので以下略)
参考記事
Railsアプリに画像を表示する
Controllerにアクションメソッドを追加
バイナリデータの文字列として格納されている画像ファイルをRailsアプリで表示するためには、send_data
を利用すればいいらしいので、Users
コントローラにUser
モデルのavatar
属性をsend_data
で送るためのアクションメソッドavatar_for
を記述します。
ちなみに、Users
コントローラには画像を表示するビューshow.html.erb
のためのアクションメソッドshow
も記述しています。
class UsersController < ApplicationController def show @user = User.find(params[:id]) end def avatar_for @user = User.find(params[:id]) send_data(@user.avatar) end end
Routesにメンバルーティングを追加
Users
コントローラに記述したアクションメソッドavatar_for
を使用するため、ルーティング*2にavatar_for
へのメンバールーティングを追加したusers
へのリソースフルルーティングを記述します。
Rails.application.routes.draw do resources :users do member do get 'avatar_for' end end
Viewで画像を表示
画像を表示するビューshow.html.erb
を作成し、image_tag
にavatar_for
へのルーティングヘルパーavatar_for_user_path
を記述して画像を表示します。
<%= image_tag(avatar_for_user_path, alt: @user.name, :size => '40x40') %>
参考記事
- rails で画像ファイルを DB に保存する - ckazu.info
- DBに格納したバイナリ画像を表示させる方法 - rochefort's blog
- Rails のルーティング | Rails ガイド
画像をフォームから登録する
注:調べた限りでは以下の方法でフォームから画像をアップロードできるらしいのですが、私の環境では頻繁にアップロードに失敗し*3正しく表示できない場合があるため、原因を調査中*4です。
Viewにフォームを表示する
画像をアップロードするためのビューnew.html.erb
にform_for
でフォーム作成し、画像ファイルのためのfile_field
を記述します。
<%= form_for(@users) do |f| %> <%= f.file_field :avatar %> <%= f.submit "Submit" %> <% end %>
Controllerにメソッドを追加する
アクションメソッドcreate
でフォームで入力された値をparams
から取得し、User
モデルに格納します。
また、アップロードされた画像やフォームから入力された値をセキュアに取得するために、private
メソッドのuser_params
でUser
モデルが受け取れるパラメータを制限します。
class UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end def avatar_for @user = User.find(params[:id]) send_data(@user.avatar) end def create @user = User.new(user_params) if @user.save redirect_to @user else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :avatar) end end
参考記事
開発環境
- OSX 10.11.4 El Capitan
- テキストエディタ: MacVim
- ターミナルエミュレータ: Macターミナル
- シェル: zsh
- パッケージマネージャ: Homebrew - Ruby 2.3.0
- バージョンマネージャ: rbenv
- Webフレームワーク: Ruby on Rails 4.2.6 - データベース
- ORDBMS: PostgreSQL