Rails
2019.10.19
active storage + active model serializerのN + 1問題を解決する

個人的備忘録 & 同じく悩んでる方の参考になればと思い書き留めておきたいと思います。

### バージョン

`ruby 2.6.3`

`rails 6.0.0`

## before do

この記事を通して扱うモデル関係を事前に。

userとpostが1対多の関係。

```user.rb

class User < ApplicationRecord

has_many :posts

has_one_attached :avatar

end

```

```post.rb

class Post < ApplicationRecord

belongs_to :user

has_many_attached :images

end

```

## active storage + API

少し記事のタイトルとはずれますが。。

active storageに付いてググってみると、Railsのviewを使ったやり方はたくさんでてくるのですが、APIで画像のURLだけ返したいなと思った時にハマったのでメモ。

```image_url.rb

# serializer/concerns/image_url.rb

module Concerns

module ImageUrl

\ extend ActiveSupport::Concern

\ include Rails.application.routes.url_helpers

\ included do

\ Rails.application.routes.default_url_options[:host] = 'your_host'

\ end

end

end

```

```user_serializer.rb

class UserSerializer < ActiveModel::Serializer

include Concerns::ImageUrl

attributes :avatar_url, ...

def avatar_url

\ url_for(object.avatar) if object.avatar.attached?

end

end

```

### ☝️のコードの意図

ぐぐってみると、画像のURLを取得したいなら

- `url_for`

- `rails_blob_path`

- `rails_blob_url`

とか使えばいいぞ!ってでてくるのですが、どれもserializer内では使えないです。

なので、concernsで `Rails.application.routes.url_helpers` モジュールをインクルード。([参照](https://edgeguides.rubyonrails.org/active_storage_overview.html#linking-to-files ))

`included` ブロックでhostを設定。これをしないと

`Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true`

とお願いされます。

## 本題: active storage + active model serializer の N + 1問題を解決

```user_serializer.rb

class UserSerializer < ActiveModel::Serializer

include Concerns::ImageUrl

attributes :atavar_url, ...

has_many :posts do

\ object.posts.with_attached_images

end

def avatar_url

\ url_for(object.avatar) if object.avatar.attached?

end

end

```

```post_serializer.rb

class PostSerializer < ActiveModel::Serializer

include Concerns::ImageUrl

attributes :image_urls, ...

def image_urls

\ object.images.each_with_object([]) do |image, array|

\ array << url_for(image)

\ end

end

end

```

てな感じにすれば、いい感じにN+1問題が解決されました!

`with_attached_images` は、modelで `has_many_attached :images` としたときに作成されるscopeです。 `has_one_attached` の時も同じ。

このスコープの中身 -> `scope :"with_attached_images", -> { includes(images_attachments: :blob) }` ([参照](https://github.com/rails/rails/blob/577922f11e43463c364e9a8aa4ba331e1ce1bede/activestorage/lib/active_storage/attached/model.rb#L127))

## 最後に

わかりずらかったかもしれませんが、最後まで読んでいただき、ありがとうございました。

「もっといいやり方あるよ!」、「やってることわかんねぇよ♡」、「こんな感じの設計、コードの書き方にすれば、そもそもN+1自体起きないよ!」などなどありましたら、コメントお願いします:bow_tone1:

## 参考

[Github rails/rails (activestorage)](https://github.com/rails/rails/blob/577922f11e43463c364e9a8aa4ba331e1ce1bede/activestorage/lib/active_storage/attached/model.rb)

[RAILS GUIDE (Active Storage Overview)](https://edgeguides.rubyonrails.org/active_storage_overview.html)

アプリケーション開発、ITマーケティングなど
お気軽にご相談ください

お問い合わせ