LoginSignup
1
1

More than 3 years have passed since last update.

Viewを介さずActiveStorageを使いたい

Last updated at Posted at 2019-12-07

解決したいこと

ActiveStorage 使い方みたいな感じでググると、viewに画像アップロードのインターフェースを設置した上で、form_withを使ってアップロードできます、という記事が多く見られます。
そこで本記事では、viewのアップロードを介さずにモデルと画像とを紐付ける方法をご紹介します。

どんなユースケース

画像のアップロード機能無しにモデルと画像を紐付けたいことなんてある?という方もいるかと思うので、先に自分が実際に導入したいモチベーションとなったユースケースについて説明します。

僭越ではありますが私は現在、TwitterAPIを通じて、ネット古着屋の情報を収集するサービス「Clotion」を鋭意開発中です。(よかったら見ていってください)

このサイトを実現するに当たり、各Twitterアカウントが掲載した画像を取得する必要がありました。各TwitterアカウントはShopモデルとして定義しているので、Shopモデルがimageモデルを複数持つ という状況を作ろうとしたときに、viewを使わずにモデルと画像を紐つけたいというユースケースに当たるわけです。

実現方法

では早速どう実現したかについてご説明します。
ActiveStorageって何?とか導入方法は割愛をさせていただきます。Railsガイドをざっと眺めれば概要はつかめると思います。

ActiveStorageのインストールとmigrationが完了したところがスタート地点です。

まず、モデルへリレーションを定義します。1対1のリレーションならhas_one_attached1対Nのリレーションならhas_many_attachedを利用します。今回は、1つの古着屋が複数の投稿画像を持つという関係になるのでhas_many_attachedを使いました。

shop.rb
class Shop < ApplicationRecord
  # validationとかいろいろ

  # 今回の主役
  has_many_attached :images
end

imagesという名前は任意に付け替えられます。imagesモデルを用意する必要もないです。
イメージとしては、各Shopモデルに対してshop.imagesとすればそのshopが持つ画像を取ってこれる感じです。

ではshopモデルへ画像を割り当てていきましょう。割り当てにはattachメソッドを使います。

has_many_attached(or has_one_attached) でリレーションを定義すると、いくつかActiveStorageを使って画像情報を操作するためのヘルパーが定義されます。モデルと画像情報の紐付けにはattach、モデルが画像情報を保持しているかどうかを聞くattached?、モデルから画像情報を削除するpurgeなどがよく使うメソッドかなと思います。

fetch_images_with_active_storage.rb
Shop.all.each do |s|
  # TwitterAPIを介して、各古着屋の投稿から画像を取得する
  # imagesは各画像のURLの配列
  images = twitter_client.fetch_images(s.twitter_user_id)
  images.each do |image|
    #'s'に対して'image'を割り当てたい
    s.image.attach(........)
    # 引数に何渡せばいいんだ????
  end
end

整理するとimagesをeachで回しているimageは画像のURLを持っています。画像情報ではなくURLを保持している、というところがとても厄介で、URLから画像情報としてRails(Ruby)で扱えるように一手間を加える必要がありそう、というところまではわかるのですが、これをどう実現するかというところで詰まりました。

OpenURI

これをRubyの標準ライブラリであるOpenURIを使って解決しました。これは何かというと、http/ftp上のリソースをRubyオブジェクトへマッピングしてよしなにできるライブラリです。詳しくはruby-docを参照してください。
困っていた部分は、URL情報からどうRails(Ruby)で扱えるようにするかというポイントだったので、ちょうど使ってみることにしました。openメソッドを使っています。

fetch_images_with_active_storage.rb
# requireする必要がある
require 'open-uri'

Shop.all.each do |s|
  # TwitterAPIを介して、各古着屋の投稿から画像を取得する
  # imagesは各画像のURLの配列
  images = twitter_client.fetch_images(s.twitter_user_id)
  images.each_with_index do |image, i|
    # fileストリームのイメージでioと命名する
    io = open(image)
    s.image.attach(io: io, filename: "#{s.id}_#{i}")
  end
end

openメソッドで取得したストリームをioとしてattachへ渡しています。また、filenameが無いとActiveRecordから怒られるので、適当に名前をつけてます。今回のユースケースでは、shopモデルを介してイテレートした結果を表示するだけで良かったのでfilenameは適当につけましたが、filenameを参照して何かしたい場合はもう少しまともな名前をつけたほうが良さそうです。

これを実行すると

=> [#<ActiveStorage::Attachment:0x00007fcc470a7b78
  id: 1,
  name: "images",
  record_type: "Shop",
  record_id: 1,
  blob_id: 1,
  created_at: Sat, 07 Dec 2019 15:13:18 UTC +00:00>]

と、良さげなログが出てきます

[1] pry(main)> Shop.first.images
=> #<ActiveStorage::Attached::Many:0x00007fcc4798d2d8
 @dependent=:purge_later,
 @name="images",
 @record=
  #<Shop:0x00007fcc49429808
   id: 4,
   name: "𝚌𝚘𝚕𝚞𝚖𝚗",
   url: "https://t.co/zYSbCPqfAz",
   created_at: Mon, 25 Nov 2019 13:29:37 UTC +00:00,
   updated_at: Sat, 07 Dec 2019 15:13:18 UTC +00:00,
   twitter_user_id: 1040246647136051202,
   twitter_url: "https://twitter.com/column_tyo",
   twitter_thumbnail_image_url: "https://pbs.twimg.com/profile_images/1148918451223711744/GcOAVrns_normal.jpg">>

これもなんとなくできてそうな感があります。

実際にviewの中でimage_tagへこのimage情報を渡すことで表示することができました。
開発しているClotionではこのようにして古着の画像情報を表示しています。

ActiveStorageのパワー

ここまではローカル上の開発のお話でした。つまり、取得した画像はローカルのストレージに保存されるような仕組みです。
取り扱い方は若干ピーキーですが、画像に関するモデルの定義なしにリレーションの記述だけでこれだけのことができるようになるのは確かに便利だなーと感じていました。

ローカルで画像保存ができるようになったので、外部のクラウドサービス(今回はamazon s3を使いました)と連携しようとしたときにこの機能の本当のパワーを感じました。
というのもちょちょっとconfigをいじっただけで、コードはそのままでs3を使う形に変更することができました。これには本当に感動しました。
s3へのリプレースにはこちらの記事を参考にしました。

まとめ

使い方に癖がありますが(Railsへ追加されるActiveXXXはどれも癖があるイメージ)、ActiveStorageとOpenURIによってウェブ上にある画像のURLからRubyオブジェクトへマッピングし、それをrailsのdbへ登録することができました。
特殊なユースケースだったかもしれませんが、この世界の誰かの一助になれば幸いです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1