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から画像を登録する方法について書いていきます。