refileの使い方徹底解説

最近refileを使うことがあったのでまとめてみました。

refileはRubyで作られたアプリケーション用のファイルアップロードを行うためのライブラリです。Githubはこちら。

他にも似たようなライブラリにcarrierwaveがあります。

- 1枚画像を保存する
- 複数枚画像を保存する

これらの方法について書いていきたいと思います。

まずはrails new。不要なファイルを作らないようにオプションを追加してます。

~/w/rails ❯❯❯ rails new refile_app \
else> --skip-action-mailer \
else> --skip-action-mailbox \
else> --skip-action-text \
else> --skip-action-storage \
else> --skip-action-cable

gemの追加、

# Gemfile
gem "refile", require: "refile/rails", github: 'manfe/refile'
gem "refile-mini_magick"

追加後

~/w/r/refile_app ❯❯❯ bundle install --path=vendor/bundle

画像を保存できるかどうか確認するだけなので適当なモデルを作ってみます。

今回は Book モデルを作成。viewを作ったりするのが面倒なのでscaffoldで楽をしたいと思います。

~/w/r/refile_app ❯❯❯ rails g scaffold book                                                                                           master ◼
Running via Spring preloader in process 28279
     invoke  active_record
     create    db/migrate/20200731071447_create_books.rb
     create    app/models/book.rb
     invoke    test_unit
     create      test/models/book_test.rb
     create      test/fixtures/books.yml
     invoke  resource_route
      route    resources :books
     invoke  scaffold_controller
     create    app/controllers/books_controller.rb
     invoke    erb
     create      app/views/books
     create      app/views/books/index.html.erb
     create      app/views/books/edit.html.erb
     create      app/views/books/show.html.erb
     create      app/views/books/new.html.erb
     create      app/views/books/_form.html.erb
     invoke    test_unit
     create      test/controllers/books_controller_test.rb
     create      test/system/books_test.rb
     invoke    helper
     create      app/helpers/books_helper.rb
     invoke      test_unit
     invoke    jbuilder
     create      app/views/books/index.json.jbuilder
     create      app/views/books/show.json.jbuilder
     create      app/views/books/_book.json.jbuilder
     invoke  assets
     invoke    coffee
     create      app/assets/javascripts/books.coffee
     invoke    scss
     create      app/assets/stylesheets/books.scss
     invoke  scss
     create    app/assets/stylesheets/scaffolds.scss

タイトルと画像を保存するだけのテーブルを作成してみます。

# db/migrate/20200731071447_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.2]
 def change
   create_table :books do |t|
     t.string :title
     t.string :image_id

     t.timestamps
   end
 end
end

migrateを実行します。

~/w/r/refile_app ❯❯❯ rails db:migrate
== 20200731071447 CreateBooks: migrating ======================================
-- create_table(:books)
  -> 0.0018s
== 20200731071447 CreateBooks: migrated (0.0018s) =============================​

次にBookモデルに attachment メソッドを記述します。

# book.rb
class Book < ApplicationRecord
 attachment :image
end

この attachment メソッドの引数に与える名前は、なんでも良いというわけではなく、カラム名と合わせなければならないようです。

つまり image_id というカラム名だったらカラム名の _id の部分をとったもの、つまり image にするということです。

この状態でbooksテーブルにデータを保存できるか確認してみます。

~/w/r/refile_app ❯❯❯ rails c                                                                    master ◼
Running via Spring preloader in process 30975
Loading development environment (Rails 5.2.4.3)
irb(main):001:0> b = Book.new
=> #<Book id: nil, title: nil, image_id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> b.image = StringIO.new("hello world")
=> #<StringIO:0x00007fa498cc2498>
irb(main):003:0> b.title = 'test'
=> "test"
irb(main):004:0> b.save
  (0.1ms)  begin transaction
 Book Create (0.4ms)  INSERT INTO "books" ("title", "image_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "test"], ["image_id", "77cf945b6743d7853c0b5a25bbdb1fc309322090e198c7dd9d8eae854020"], ["created_at", "2020-07-31 07:49:58.951364"], ["updated_at", "2020-07-31 07:49:58.951364"]]
  (2.1ms)  commit transaction
=> true

保存できました。そこで

attachment メソッドの引数に与える名前は、なんでも良いというわけでははなく、カラム名と合わせなければならない
image_id というカラム名だったらカラム名の _id の部分をとったもの

これが本当かどうか検証してみたいと思います。

まずカラム名を変更してみます。カラム名を変更するには新たにmigrationファイルを作成するかdbをrollbackして変更します。今回は後者を選択します(カラム名を変更するmigrationファイルを作成するのが面倒なため)。

~/w/r/refile_app ❯❯❯ rails db:rollback                                                          master ◼
== 20200731071447 CreateBooks: reverting ======================================
-- drop_table(:books)
  -> 0.0007s
== 20200731071447 CreateBooks: reverted (0.0033s) =============================
# # db/migrate/20200731071447_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.2]
 def change
   create_table :books do |t|
     t.string :title
     t.string :main_image_id

     t.timestamps
   end
 end
end
~/w/r/refile_app ❯❯❯ rails db:migrate                                                           master ◼
== 20200731071447 CreateBooks: migrating ======================================
-- create_table(:books)
  -> 0.0024s
== 20200731071447 CreateBooks: migrated (0.0025s) =============================

変更後は main_image_id というカラム名にしてみました。この状態でbooksテーブルに保存できるか確認してみます。

~/w/r/refile_app ❯❯❯ rails c                                                                    master ◼
Running via Spring preloader in process 31359
Loading development environment (Rails 5.2.4.3)
birb(main):001:0> b = Book.new
=> #<Book id: nil, title: nil, main_image_id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> b.image = StringIO.new("hello world")
Traceback (most recent call last):
       1: from (irb):2
NoMethodError (undefined method `image_id_will_change!' for #<Book:0x00007fa49be8c280>)

NoMethodError とでました。どうやら image というメソッド名ではダメなようです。

Gemのソースコードを見るとここattachment の引数に指定したメソッドを定義して、メソッドを呼び出せるかの処理が行われていました。やはりカラム名変更の影響が出ていそうです。

そこで attachment メソッドの引数の名前を変更してみます。

# book.rb
class Book < ApplicationRecord
 attachment :main_image
end

保存できるか確認してみます。

~/w/r/refile_app ❯❯❯ rails c                                                                    master ◼
Running via Spring preloader in process 31580
Loading development environment (Rails 5.2.4.3)
irb(main):001:0> b = Book.new
=> #<Book id: nil, title: nil, main_image_id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> b.main_image = StringIO.new("hello world")
=> #<StringIO:0x00007fa49bb3bae0>
irb(main):003:0> b.save
  (0.1ms)  begin transaction
 Book Create (2.7ms)  INSERT INTO "books" ("main_image_id", "created_at", "updated_at") VALUES (?, ?, ?)  [["main_image_id", "1bae063ba8442393dd62a7fdac3d4a6a8d82ab6d76cd83d8bc669707365c"], ["created_at", "2020-07-31 08:26:35.624701"], ["updated_at", "2020-07-31 08:26:35.624701"]]
  (1.3ms)  commit transaction
=> true

保存できました。一応、rails c で保存ができることは確認できました。

今度はTDDっぽくテストを書いてみます。今回はRails標準のminitestでやってみます。

books_controller_test.rbを実行してみます。このファイルはscaffoledで作成した状態のままで何も変更を加えていません。
bookを保存する処理のみ実行したいので行番号を指定してテストを実行します。

# books_controller.rb
def create
  @book = Book.new(book_params)

  respond_to do |format|
   if @book.save
     format.html { redirect_to @book, notice: 'Book was successfully created.' }
     format.json { render :show, status: :created, location: @book }
   else
     format.html { render :new }
     format.json { render json: @book.errors, status: :unprocessable_entity }
   end
  end
end
~/w/r/refile_app ❯❯❯ rails test test/controllers/books_controller_test.rb:18                    master ◼
2020-07-31 17:34:30 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
Running via Spring preloader in process 31694
2020-07-31 17:34:33 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
Run options: --seed 15210

# Running:

.

Finished in 0.113441s, 8.8152 runs/s, 17.6303 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

特に何もしていないので成功します。そこでタイトル画像のパラメータを保存してみたいと思います。

# books_controller.rb
def book_params
  params.fetch(:book, {}).permit(
   :title,
   :main_image
  )
end
# books_controller_test.rb
test "should create book" do
  params = { book: { title: 'test', main_image: nil } }
  params[:main_image] = StringIO.new("hello world")
  assert_difference('Book.count') do
    post books_url, params: params
  end

  assert_redirected_to book_url(Book.last)
end
~/w/r/refile_app ❯❯❯ rails test test/controllers/books_controller_test.rb:18                    master ◼
Running via Spring preloader in process 32141
Run options: --seed 2400

# Running:

.

Finished in 0.070730s, 14.1383 runs/s, 28.2765 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

テストが成功することを確認できました。

次回は、画像がどこに保存されるか、viewから画像を登録する方法について書いていきます。

この記事が気に入ったらサポートをしてみませんか?