🐵

本番環境にminimagickで合成した画像を投稿する

2023/09/10に公開

環境

  • rails7
  • minimagick
  • carrierwave
  • s3

状況

s3にバケット名/uploads/frame.pngがアップロードしてあります
ユーザーが投稿した画像にframe.pngをminimagickで合成して投稿する
これを実現するために書いていきます。

最初のエラー

本番環境でframeを合成して投稿しようとするとErrno::ENOENT (No such file or directory @ rb_sysopen - uploads/tmp~~~というエラーが出ました。こちらは、〜〜ていうファイルもディレクトリもないよというものです。
最後に全部のコードを載せるので抜粋で載せますが、当初はpost.rbに記述してある"/uploads/post/image/#{output_filename}"に保存する予定なのにtmpという一時保存に入ってしまいました。
このエラーを含め状況を再度確認するために以下のように区分けして1つずつ確認しました。

  • 開発環境でadd_frameなし
  • 開発環境でadd_frameあり
  • 本番環境でadd_frameなし
  • 本番環境でadd_frameあり

の4つです。その結果本番環境のadd_frameありのみエラーになりました。
そこで、次に本番環境のadd_frameありのどこまでできるのかを確認しました。

  • 投稿画像のみで表示する
  • frameを合成して表示する

この結果、投稿画像のみでも不具合があり、修正しました。
すると次のエラーが発生しました。

2つめのエラー

やってるときは整理しながら解消できなかったので、次の解消法に行くまでにAIに質問したり、質問サイトに投稿したり、、、色々ありました。なんやかんやあり、frame合成できないけど以下のエラーが出ていました。

OpenURI::HTTPError (403 Forbidden):

初見のエラーです。
ざっくり書くとアクセス許可エラーです[こちらを参照しました](https://saturncloud.io/blog/how-to-resolve-openhttp-403-forbidden-openurihttperror-with-rails-openuri-and-amazon-s3/)
このサイトに書いてあることを1つずつやっていき解消できました。

あとがき

あっさり書きましたが、解消するのに1週間弱かかりましたw
不貞腐れてやらない日もあったり、、、
ですが、エラーが出たら区分けしてどこまで成功していてどこからエラーなのかを分けていく作業が重要だなと思いました。
(追記)
s3の方で書名付きurlを発行してます。こちら、時間や期間を定めてアクセス許可を出すものです。常に必要な方はバケットポリシーにあーだこーだするみたいです。(これからやります。。)

コード

class Post < ApplicationRecord
  require 'aws-sdk-s3'
  mount_uploader :image, ImageUploader
  before_save :add_frame

  private

  def add_frame
    return unless image.present?
    # 投稿画像のパス
    input_path = image.url
    # 額縁画像のパス
    s3 = Aws::S3::Resource.new(region: 'ap-northeast-1')
    obj = s3.bucket('バケット名').object('使う画像のKey')
    url = obj.presigned_url(:get)
    Rails.logger.info "frame_path"
    # 投稿画像を読み込む
    image = MiniMagick::Image.open(input_path)
    image.resize "210x280!"
    Rails.logger.info "image open"
    # 額縁画像を読み込む
    frame = MiniMagick::Image.open(url)
    frame.resize "210x280!"
    Rails.logger.info "frame open"
    # 投稿画像と額縁画像を合成する
    result = image.composite(frame) do |c|
      c.compose "Over"
    end
    Rails.logger.info "compose_ok"
    # 合成した画像を保存する
    output_filename = "#{Time.now.to_i}.png"
    output_path = Rails.root.join("public", "uploads", "post", "image", output_filename)
    result.write(output_path)
    self.compose_image = "/uploads/post/image/#{output_filename}"
    Rails.logger.info "outpath_ok"
  end
end

要所にあるRails.logger.info "frame_path"というものは、fly logsした際にどこまで実行できたかを表示するために書いてあります。実際に動かすには全くいらないコードです。

Discussion