坂ノ途中の技術日誌

環境負荷の小さい農業を広げるためのあれこれに取り組む株式会社坂ノ途中の技術ブログです。

脱 Paperclip2023. ActiveStorage への移行アイデアいくつか

はい。2023年の記事です。多くの方々はとっくの昔に対処済みで、2023年にもなっていまさらという記事かと思いますが、さまざまな事情でいまだ需要がある気配を感じています。そこで、恥ずかしながら取り組んだことを共有します*1

まとめ

  • Ruby on Rails のファイルアップロードとその表示を便利にする Gem である Paperclip は2018年にメンテナンス停止し非推奨になりました
  • 移行先は ActiveStorage が順当
  • 公式のドキュメントでは migration で移行する方法が案内されているけれど元のファイルパスを変えずに登録する手順のため少しわかりにくい
  • ファイル数が少ない見込みであれば全部移行してもいいかもしれない
  • PaperClip と ActiveStorage 同時に(別名で)ファイルをもつようにすると非同期でできて便利

前提:PaperClip について

2007年から提供されている、画像やファイルアップロードを扱う有名 Gem でお世話になった方も多いと思います。

github.com

Rails5.2で ActiveStorage が登場したこともあって2018年に deprecated になりました。移行しましょう。 とはいえ、小規模なあまり手がかけられていないアプリケーションではそうなっていない場合もあると思います。

事前準備:ActiveStorage の動作と移行の検証

公式の手順では S3 上のファイルを動かさずに ActiveStorage が把握するファイルの先を更新するためにわかりにくくなっています。ActiveStorage の動作について簡単なコードでファイルを移動させる例をあげてみます。

たとえば、Attachment クラスにて以下のように Paperclip を利用しているとします。

has_attached_file :document

このとき以下のように ActiveStorage をもつよう移行したい状況を考えます。

has_one_attached :active_document

以下のように attach メソッドで paperclip で管理していたリソースをActiveStorageに移せます(※ただし、このやり方だとクラウドストレージは倍かかるし実ファイルを移行することになり時間もかかるので ActiveStorage の理解のための動作確認とお考え下さい)。 こうして試してみてから active_storage_attachments と active_storage_blobs にできたレコードをみると、公式で案内する手順を理解しやすくなると思います。

document_url = attachment.document&.url(:original)

uri = open(document_url)

attachment.active_document.attach(
  io: open(document_url),
  filename: attachment.document_file_name,
  content_type: attachment.document_content_type
)

ファイル数が少ないなどでしたらこの方式で公式の migration スクリプトを書き替えて実行してもよいかもしれません。

作戦

以下のように、Paperclip と ActiveStorage を両方有効にし、追加・更新時に両方に保存するようにすると移行のダウンタイムを小さくできます。そして表示時には、 attached? メソッドで ActiveStorage にリソースがある場合はそちらを表示するようにする。

has_attached_file :document
has_one_attached :active_document

あとはテストをしながら愚直に書き換えていくだけです。画像利用クラスや箇所が多いとたいへんです。いくつかはまりどころがあったので触れておきます。

ActiveStorage でのはまりどころ

1.画像を変更できるかどうか

これまでは、事前にクラスにサイズを指定しておくと変換したものを用意してくれて image.url(:thumb) などで簡単に表示できましたが、そうすることはできませんし、ファイルによって変更できるかどうかで分岐させる必要があります。

たとえば画像を

has_one_attached :active_image

として保存している場合、以下のように variable かどうかをみる必要がある。

  def image_icon_size
    if active_image&.attached? && active_image&.variable?
      active_image.variant(resize_to_fill: [100, 100])
    else
      active_image
    end
  end

2.公開URLは Rails6.1 までない

ActiveStorageではデフォルトではファイルへのURLはrailsを経由し、有効期間が短い。 このままではCDNがキャッシュしにくいので公開して問題ない場合はURLを公開するとよい。

ただ、この設定はRails6.1からなので移行時にバージョンを確認しておいてください。

Rails 6.1 つまみ食い② : ActiveStorage の永続的なURL - インゲージ開発者ブログ

3.PDFで画像を使う場合、テストで気付きにくい

wicked-pdf でPDFを生成し、そこでActiveStorageの画像を用いる場合、background-image で設定すると表示されないようです(謎)。 素直に image タグを使って解決させました。 PDFでの表示内容、自動テストで確認しにくく見逃してしまっていました・・・。

4.ファイルのバリデーション

公式ドキュメントで探しにくかったのですが、下記の記事が参考になりました。感謝

ActiveStorageのバリデーション - Qiita

まとめ

そんなこんなで 脱PaperclipしてRailsのバージョンも7まであげてすっきりしました。 振り返ってみると、Paperclipはファイルアップロードをシンプルに使えてたいへん便利だったことがわかります。ほんとありがとうございました。

生産者さまがデータを活かした営農をできるようにするファーモを開発している片山がお届けいたしました。ご質問やコメントあればお気軽にお知らせください。

ファーモ | 農家のための無料で使える販売管理

参考URL

公式の脱出ガイド https://github.com/thoughtbot/paperclip/blob/master/MIGRATING.md

日本語訳(感謝!)
Rails: PaperclipからActiveStorageへの移行ガイド by thoughtbot(翻訳)|TechRacho by BPS株式会社

新登録されたファイルをどちらにも登録することでダウンタイムゼロを目指す戦略
From Paperclip to Active Storage: An incremental, zero-downtime approach | TokyoDev

ActiveStorage の仕組みとコードリーディングについてはこちらの記事がたいへん参考になりました感謝。
[Rails5.2]ActiveStorageの仕組み(図あり)と使ってみてわかったこと - Qiita

Active Storage Overview — Ruby on Rails Guides

コーポレートサイトとECサイトの一部をNext.jsでJAMStack化し高速化した話

三行まとめ

  • 坂ノ途中の技術ブログはじまります
  • WordpressのヘッドレスCMS化とNext.jsによるJAMstack化を採用しました
  • ヘッドレスCMSは速度改善でき、これからのCMS運用の標準になるかもしれません

こんにちは。「100年先も続く、農業を。」というビジョンを掲げ、環境負荷の小さい農産物の流通やコーヒーの輸入・加工などに取り組んでいる坂ノ途中の片山です。機運が高まってきたので技術ブログをはじめます。

会社紹介

ぼくたちは、環境負荷の小さい農業を広げるために、少量だったり不安定だったり、けれど美味しいお野菜を流通させる仕組み、多様性を排除しない流通の仕組みをつくることをめざし、生産者さまからお野菜を仕入れて個人のお客さまや飲食店さまや小売店さま向けに野菜を販売しています。 そのためにさまざまなシステムを開発していますが、そのなかでもECサイトは個人のお客さまとの接点であり重要なプロダクトです。 世の中のECとの違いとして、定期宅配をサポートし、スキップや日程変更などを柔軟にできるようにしつつ、単品の商品も定期便に同梱できるようにしているのが特徴でパッケージなどを使わず自前で開発しています。

やまのあいだファーム
自社農場から見える風景

第一回として、このWebショップ(以下ECサイト)のアーキテクチャ変更についてお話しします。 今回のプロジェクトは、レンタル移籍という仕組みでさる大企業からやってきた植木快さんによる取り組みによるものです。かれは昨年4月から10月までこのプロジェクトを立ち上げて離陸までさせましたが、みなに惜しまれつつ涙の帰任を果たしたので代わりに紹介させていただきます。

旧環境とその課題

まず、ぼくたちのECサイトはこれまで、トップページや読み物などのあるいわゆるコーポレートサイト部分をWordPressで構築し、お届け先選択やマイページなどのカート部分はRuby on Railsで開発していました。デザインを共通にしているため、お客さまはどこからRailsかは気にしないことでシームレスに読み物を読んだりお買い物をすることができます*1

f:id:sakanotochu:20211224120121p:plain

WordPressというとレガシーでは?いまどきまだ使ってるの?というお声もあるかもしれませんが、2位のShopify以下に大差をつけて世界でもっとも使われているCMSです*2ホワイトハウスでも2017年末から利用されており*3、多数のpluginが提供され慣れている編集者やライターや開発会社も多く普及しているプロダクトです。ぼくたちも仲の良い開発会社さんにデザインから構築までお願いしていました。

しかし、WordPressはページの表示速度やバージョン管理やデプロイに課題があったり、攻撃対象にも選ばれやすいほか、テーマをさくっとつくるとすぐにコードのViewとロジックが深く結合してしまうなどメンテナンス性や改善速度に課題があります。

特にページ表示速度はUIの重要な要素です。 ページ生成までの処理が全体的に重いことが課題となっており、このTTFB短縮がパフォーマンス改善における最重要課題でした。 しかし、オンラインショップは商品の在庫数、販売可否、ログイン状態やカート状態に応じて表示を出しわける必要があり、すぐにCDNを導入することが難しい状況でした。

JAMstackアーキテクチャの採用

これらの状況ではコーポレートサイトをWordPressからリプレースするなどの方法もありますが、移行コストなどを考慮し、今回は、Wordpressを残したままフロントエンドをNext.jsでSSGしていくことで解決をはかりました。 WordpressからGraphQLで記事情報を取り出す、いわゆるヘッドレスCMSとこれをもとにするJAMstackアーキテクチャにしていく、名付けて「坂ノ途中JAMプロジェクト」です*4

JAMstackについてはさまざまな記事が執筆されていますが、この記事がたいへん参考になりました。

lealog.hateblo.jp

各状態に応じた表示出し分けについては、クライアントサイドにてswrを用いる方針とし、cssについては特段手を入れず*5、グローバルcssを全ページで読み込む方式として、テンプレートや表示用データ取得のロジックをNext.jsに移植していきました。

下記がシステム全体のざっくり構成図です(※ いろいろ省略されています)。

f:id:sakanotochu:20211224120140p:plain

ページ更新処理は、mainブランチにマージされるとPipelineが実行され*6 → Next.js によるSSG →Amazon S3 へのアップロードという流れです。 この結果、最もTTFBが長かったオンラインショップトップページでは、1000ms短縮に成功しました*7

振り返りと残課題

今回のリプレースで、各種ページパフォーマンスが大きく改善されただけでなく、データ取得ロジックのリファクタリングとReactコンポーネントの導入によって、表示ロジックの変更も容易になりました。 TypeScriptの導入やCypressを用いたE2Eテストも導入し、安全にフロントエンドの信頼性を高める取り組みもできています。 そのほか、記事更新時のキャッシュパージはSlackのbotからAWS lambdaで実行しています。

残課題としては、キャッシュパージの自動化、サイトをまたぐCSSの管理、プレビュー環境の用意などあり引き続き対応中です。 このあたりはまだまだ改善の余地はありますが、Next.jsやフロントエンドエコシステムの進化によって表示速度と開発速度の改善を感じています。

いまの時代、新しくCMSを利用したサイトをつくる際はSaaSで利用できるヘッドレスCMSが第一線の候補になるとは思いますが、既存のWordPressサイト(なにより世界のCMSの40%で使われています!)を比較的低コストで改善していく際にはWordpressのヘッドレスCMS化は十分ありだと感じています。

今後も、坂ノ途中ITチームでは、お客さまの購買体験を改善したり、生産現場のことをお伝えできるよう機能改善を行う予定です。React, Typescript, Next.jsでECサイト開発をしたいエンジニアの方はぜひお気軽にご連絡くださいませ!

*1:この構成についてはさまざまな議論の余地があると思っています。いろんな仕掛けと試行錯誤があるのでご興味ある方お知らせください!

*2:Usage Statistics and Market Share of Content Management Systems, February 2022

*3: 米ホワイトハウスがCMSとしてWordPressを採用。Drupalから変更 - Publickey

*4:プロジェクト名の由来はあくまでJAMstack。ちなみに弊社オンラインショップで販売されているジャムには、やまのあいだファームのみかんジャム 120gべじたぶるぱーくさんの栗ジャムなどがございます

*5:html構造とid, class名を一致させることで、今のところ、運用しています

*6:CMSで記事更新時にはSlackショートカットから実行する運用としています

*7:そのほかのページはTTFBが平均約500ms短縮されました。「100ms遅延すると、売上が1%失われる」という言説もあります。