refile seedデータでの画像登録方法と仕組み🤔💭

ある日、『refileで扱う画像をseedデータで設定するにはどうすればいいのですか？』という質問を受けたので調べながら答えたのをこの記事にまとめようと思いました。

設定方法だけでなく、軽く仕組みも調査してみました。

gem refile のgithubはこちら ↓

https://github.com/refile/refile


## 先ずは設定方法の結論から 🤑

例えば、公式githubのコードを参考にして説明すると...

usersテーブルのカラムに `profile_image_id` というものが存在するとします。

その場合は、Userモデルで例えると以下のようになります。

```app/models/user.rb
class User < ActiveRecord::Base
  # _id は書かないのがポイント
  attachment :profile_image
end

```

### `app/assets/images/` ディレクトリ以下に任意の画像を置く

>今回は smile.jpg というものが存在する前提で話を進めます。

### seeds.rbでuserを作成する際にある画像を設定する

```db/seeds.rb
# この時、_id を書かないのがポイント
User.create(
  任意のカラム名など: 任意の値,
  ...,
  profile_image: File.open("./app/assets/images/smile.jpg")
)
```

```
$ rails db:seed
```

これで、画像の設定が完成しています！

だが しか~~~しっ !、！! 

Ruby on Rails をよく分かっている方は違和感を持つことでしょう。
なぜ `profile_image` が create の引数にあるのか。。。
DBのカラム名に相当する`profile_image_id` ではないのか。。。

より深く仕組みを知りたい方は続きを読んでみてください。

## そもそもrefileの仕組み ~ 公式のREADMEを読んでみる🧐📕

>Files are uploaded to a backend. The backend assigns an ID to this file, which will be unique for this file within the backend.

ファイルがuploadされたら、fileにuniqueなidを割り当てる。

>By default files will be uploaded to ./tmp/uploads/store

デフォルトではuploadされたファイルはコンパイルか何かされてファイル名がそのuniqueなidになっており、 `./tmp/uplaods/store` に保存される。(ここではstoreと呼びます。)

`画像を復元する場合、uniqueなidを元にstoreから画像を復元する仕組みのようです。`

[Attachments の部分](https://github.com/refile/refile#2-attachments)

```ruby
class User < ActiveRecord::Base
  attachment :profile_image
end
```

>Calling attachment generates a getter and setter with the given name. When you assign a file to the setter, it is uploaded to the cache:

#### attachment の引数の名前が getter setterのメソッドになるようです。
今回の例でいうと、
`user.profile_image` で何かしら値を取得したり、
`user.profile_image=` で値を設定できるということです。

ちなみに、ActiveRecordの仕様として
setterメソッド`xxx=`は、`モデル名.new(xxx: 任意の値)`で設定できるようになっています。(気が向いたらブログかqiitaで詳しく書きます。)

つまり、 以下のコードは同じ結果になるということです。

```ruby
# インスタンス生成後にsetするパターン。
user = User.new
user.name = 'Refile太郎'
user.name # => Refile太郎
user.save

# ----------------------------------

# インスタンス生成時の引数に値を指定するパターン。
user = User.new(name: 'Refile太郎')
user.name # => Refile太郎
user.save
```
createメソッドは実はnewメソッドとsaveメソッドを実行してオブジェクトを返却するものです。
以下ソースコード (https://github.com/rails/rails/blob/main/activerecord/lib/active_record/persistence.rb#L33-L41)

なので冒頭の createの引数に なぜ `profile_image: 値` が create の引数にある場合、newの引数に `profile_image: 値` が渡っていき最終的にsaveされます。

しかし、 `profile_image_id` カラムに画像idが保存される仕組みは謎です🤔💭

続いてrefile公式のREADMEから一部コードと説明の抜粋して仕組み追っていきます。

```ruby
User.new
# 公式では↑のように書いているが
# user = User.new と解釈しておきましょう。

File.open("/some/path", "rb") do |file|
  user.profile_image = file # fileオブジェクトをsetしている。
end

# profile_image.id とすると画像idが取得できる。
user.profile_image.id # => "fec421..." 
```

>When you call save on the record, the uploaded file is transferred from the cache to the store. 

cacheは一時的なファイルデータ(DBなどの永続ストレージに保存されていない状態。)
~~storeはDBなどの永続ストレージと判断して良さそうです。~~
storeは永続的なストレージですが、デフォルトで画像fileが保存される `./tmp/uploads/store` のことでした。

説明文を解釈すると`user.profile_image` にfileオブジェクトをセットした後に `user.save` すると、画像ファイルが`./tmp/uploads/store`に保存される。

`もしかして、この時についでにDBのusersテーブルのprofile_image_idカラムに画像idが保存されるんじゃね？！(◎_◎;)`

実際に検証してみましょう。
![スクリーンショット 2021-05-20 1.31.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240416/3b2bcccd-e4f5-93fb-d61f-321754e73af6.png)
save前は`profile_image_id`が`nil`ですが、save後は、
`profile_image_id`に画像idが入っていました！
(実際にrefileのコードを見るとsave前にそうする仕組みになっていました。気が向いたらそのことの書きます。。。)

これで、 `しかし、 profile_image_id カラムに画像idが保存される仕組みは謎です🤔💭` の謎は解けました。

## 別の書き方でseedデータを作成するなら🧐💭(全くこの方法で書く必要性はないのですが)

先ほど最後の行で画像idを取得できていました。

```ruby
# profile_image.id とすると画像idが取得できる。
user.profile_image.id # => "fec421..." 
```

なので `user.profile_image_id = 画像id` をした後にsaveしても画像idの保存は可能です。
例えば以下のような書き方でもseedデータを作成できます。

```db/seeds.rb
user = User.create(
  任意のカラム名など: 任意の値,
  ...
)

File.open("./app/assets/images/smile.jpg") do |file|
  user.profile_image = file
end 
# ちなみに、上記の記述はこのように書くことも可能です。
# user.profile_image = File.open("./app/assets/images/smile.jpg")

# 画像idをuser.profile_image_idにsetします。
# しかし以下の記述はなくてよい。
user.profile_image_id = user.profile_image.id

user.save
```

```
rails db:seed
```

## まとめ😌

- モデルに定義した `attachment :xxx` によってfileオブジェクトを格納できる `xxx` という  getter, setterメソッドが利用可能になる。

- fileオブジェクトがsetされているならば、 `xxx.id` で画像idが取得できる。

- fileオブジェクトがsetされている状態でsaveすると `xxx_id` カラムに画像idが保存される。

この仕組みをrefileのソースコードを追いながら調査した記事も自分のブログかqiitaで近日公開しようかと思います。(気が向いたら...)