Skip to content

画像からExifが削除されないバグの修正#9907

Merged
komagata merged 10 commits into
mainfrom
bug/fix-exif-not-removed
Jul 13, 2026
Merged

画像からExifが削除されないバグの修正#9907
komagata merged 10 commits into
mainfrom
bug/fix-exif-not-removed

Conversation

@matuaya

@matuaya matuaya commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Issue

概要

こちらのPR(アップロードされた画像からExifデータを削除#9298)にて画像からExifデータを削除する機能を追加していましたが、本番環境では削除処理が正常に動作していなかったため本PRで修正対応しました。

変更確認方法

  1. bug/fix-exif-not-removedをローカルに取り込む
  2. 画像をアップロードする
  3. アップロードされた画像をローカルに保存し、Exifデータが削除されていることを確認する

アップロードされた画像からExifデータを削除#9298の確認方法と同じです。

Summary by CodeRabbit

  • リファクタリング
    • 画像アップロード時の処理を改善しました。画像のメタ情報(EXIF)を保存前に確実に除去するようにし、アップロードの安定性とプライバシー保護を強化しました。
  • テスト
    • アップロード後の検証を調整し、返却される画像URLと保存された画像の整合性を確認するテストを更新しました。

Review Change Stack

Flakewatch Report

Flakewatch Report

Review App

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

EXIF除去処理をモデルのafter_commitからAPI::ImageController#create内で明示的にstrip_exif!を呼ぶ方式へ移行。Imageモデルにstrip_exif!と画像添付必須検証が追加され、テストは保存後URLとblob.signed_idの照合を追加。

Changes

EXIF strip refactor

Layer / File(s) Summary
Image model: validation と strip_exif! 実装
app/models/image.rb
validates :image, attached: true を追加し、ActiveStorage の attachment_changes['image']attachable.tempfile を MiniMagick で読み込み strip して再添付する公開メソッド strip_exif! を追加。旧 after_commit コールバックは削除。
API コントローラ: 保存前に strip_exif! を呼ぶ
app/controllers/api/image_controller.rb
API::ImageController#create で Image 作成直後に @image.strip_exif! を呼び出し、@image.save の前に EXIF 除去を実行するよう変更。
統合テスト更新
test/integration/api/image_test.rb
アップロード後の取得を Image.last に変更し、レスポンス JSON の urlsaved_image.image.blob.signed_id が含まれることを検証するアサーションを追加。EXIF が除去されていることの検証は継続。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • fjordllc/bootcamp#9298: 同様に API 側での EXIF 除去呼び出しや関連テストを扱っており、実装・テストの観点で関連性が高いです。

Suggested reviewers

  • komagata
  • okuramasafumi
  • jun-kondo

Poem

🐰📸 アップロードの旅路でぴょん、EXIFはそっと消える
テンポラリ抱えて、MiniMagickで磨いて再びくっつけるよ
コントローラが合図して、モデルは静かに整える
テストは道しるべ、署名された道を確かめるよ
軽くなった画像は、また跳ねていくのだ

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed タイトルはExif削除不具合の修正という主変更を端的に表しており、内容と一致しています。
Description check ✅ Passed Issue、概要、変更確認方法が揃っており、テンプレートに概ね沿っています。Screenshot欄は未記載です。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bug/fix-exif-not-removed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matuaya

matuaya commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/models/exif_stripper.rb`:
- Around line 3-13: Add a unit test for ExifStripper.call: create
test/models/exif_stripper_test.rb with at least one happy-path test that builds
a fake uploaded_file (e.g., fixture or Tempfile with known image bytes and
stubbed original_filename/content_type), calls ExifStripper.call(uploaded_file),
and asserts the returned hash has an :io responding to read (or to_blob
equivalence), filename equals uploaded_file.original_filename.to_s, and
content_type equals uploaded_file.content_type; ensure the test cleans up temp
files and uses MiniTest assertions consistent with the existing test suite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a39b98d4-83ea-4db6-b78a-543432b534b5

📥 Commits

Reviewing files that changed from the base of the PR and between de846d6 and eb2a7cc.

📒 Files selected for processing (4)
  • app/controllers/api/image_controller.rb
  • app/models/exif_stripper.rb
  • app/models/image.rb
  • test/integration/api/image_test.rb
💤 Files with no reviewable changes (1)
  • app/models/image.rb

Comment thread app/models/exif_stripper.rb Outdated
@github-actions

github-actions Bot commented Apr 13, 2026

Copy link
Copy Markdown

🚀 Review App

URL: https://bootcamp-pr-9907-fvlfu45apq-an.a.run.app

Basic認証: fjord / (ステージングと同じ)
PR更新時に自動で再デプロイされます。

@matuaya matuaya force-pushed the bug/fix-exif-not-removed branch 3 times, most recently from 983d229 to a0a5b37 Compare April 14, 2026 08:22
@matuaya matuaya marked this pull request as ready for review April 14, 2026 08:47
@github-actions github-actions Bot requested a review from okuramasafumi April 14, 2026 08:47
@matuaya matuaya requested review from komagata and removed request for okuramasafumi April 14, 2026 08:48
@matuaya

matuaya commented Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

@komagata
バグの修正を行いました🙇‍♀️
ご確認お願いいたします!

Comment thread app/controllers/api/image_controller.rb Outdated
Comment on lines +5 to +7
@image = Image.new(user: current_user)
processed_image = ExifStripper.call(params[:file])
@image.image.attach(processed_image)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@image = Image.new(user: current_user)
processed_image = ExifStripper.call(params[:file])
@image.image.attach(processed_image)
@image = Image.new(user: current_user, image: ExifStripper.call(params[:file]))

こういう感じでいいかも?

Comment thread app/models/exif_stripper.rb Outdated
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class ExifStripper

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExifStripper という名前だと「Exifを削除する処理」(動詞)に注目した名前になっていて、手続き的な印象があります。

クラス名は原則名詞なので名詞に注目した名前がいいと思います。
そういう意味ではImageクラスは既にそうなっているのでそちらに処理があるほうが自然かなと思いました。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

アドバイスいただきありがとうございます!
サービスクラスを削除し、モデルに処理を移動しました。

@matuaya matuaya force-pushed the bug/fix-exif-not-removed branch from a0a5b37 to b6103c8 Compare April 20, 2026 09:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/models/image.rb (1)

7-18: エラーハンドリングの追加を検討してください(任意)。

MiniMagick::Image.read は非画像ファイルや破損ファイルを渡された場合に MiniMagick::Invalid を送出する可能性があります。現状はコントローラで rescue されていないため、不正なファイルがアップロードされると 500 応答になります。ユーザー入力を直接処理するパスなので、例外を捕捉して errors.add でバリデーションエラーに変換するか、コントローラ側で rescue するとより堅牢になります。

また、uploaded_filenil や想定外のオブジェクト(tempfile / original_filename / content_type を持たない)だった場合も NoMethodError になるため、事前ガードがあるとより安全です。

♻️ 提案例
   def strip_exif(uploaded_file)
+    return if uploaded_file.blank?
+
     original_image = MiniMagick::Image.read(uploaded_file.tempfile)
     original_image.strip

     blob = {
       io: StringIO.new(original_image.to_blob),
       filename: uploaded_file.original_filename.to_s,
       content_type: uploaded_file.content_type
     }

     image.attach(blob)
+  rescue MiniMagick::Invalid => e
+    errors.add(:image, :invalid, message: "画像の処理に失敗しました: #{e.message}")
   end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/image.rb` around lines 7 - 18,
strip_exifでMiniMagick::Image.readに渡された不正な入力がそのまま例外になるため、uploaded_fileがnilまたは予期しないオブジェクトかを先にガードし、MiniMagick::Image.readをbegin/rescueで囲んでMiniMagick::Invalid(およびNoMethodError/TypeError)を捕捉してerrors.add(:image,
"invalid
image")のようにモデルのバリデーションエラーに変換するかfalseを返してimage.attachを呼ばないようにしてください(メソッド名:
strip_exif、例外: MiniMagick::Invalid、参照先: uploaded_file.original_filename /
tempfile / content_type、エラー報告: errors.add / image.attachをスキップ)。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/models/image.rb`:
- Around line 7-18:
strip_exifでMiniMagick::Image.readに渡された不正な入力がそのまま例外になるため、uploaded_fileがnilまたは予期しないオブジェクトかを先にガードし、MiniMagick::Image.readをbegin/rescueで囲んでMiniMagick::Invalid(およびNoMethodError/TypeError)を捕捉してerrors.add(:image,
"invalid
image")のようにモデルのバリデーションエラーに変換するかfalseを返してimage.attachを呼ばないようにしてください(メソッド名:
strip_exif、例外: MiniMagick::Invalid、参照先: uploaded_file.original_filename /
tempfile / content_type、エラー報告: errors.add / image.attachをスキップ)。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cafe8d7d-bc64-48fc-be89-7b46a0ab0099

📥 Commits

Reviewing files that changed from the base of the PR and between a0a5b37 and b6103c8.

📒 Files selected for processing (3)
  • app/controllers/api/image_controller.rb
  • app/models/image.rb
  • test/integration/api/image_test.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/integration/api/image_test.rb
  • app/controllers/api/image_controller.rb

@matuaya

matuaya commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

@komagata
修正しましたのでご確認お願いいたします!🙇‍♀️

Comment thread app/controllers/api/image_controller.rb Outdated
def create
@image = Image.new(user: current_user, image: params[:file])
@image = Image.new(user: current_user)
@image.strip_exif(params[:file])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

主語(名詞).述語(動詞)

のように読める必要があるともうのですが、imageの情報が引数にもあってわかりづらくなっていると思います。

@image = Image.new(user: current_user, file: params[:file])
@image.strip_exif! # 事故破壊的メソッドなので!をつける

みたいな感じの方が自然かなと思います。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

アドバイスをいただきありがとうございます!

アップロードされたファイルを保存前にモデル内でアクセスする方法が見つけられずにいるのですが、
@image = Image.new(user: current_user, file: params[:file])というのは、fileという仮の属性を通してparams[:file]をモデルでアクセスできるようにするということでしょうか?
教えていただけると嬉しいです🙇‍♀️

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matuaya 上記はイメージをお伝えするための仮のコードです。

モデル内でアクセスする方法

僕がちょっと意味がわかってないかもですが、モデル内でアクセスできる必要はないと思います。controllerでアクセスできればそれをmodelに渡せばいいはずですので。

controllerにはhttp的に必ずデータが渡っているはずなのでどうやってruby/railsで取得するかについては調べてみてください。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

アドバイスいただきありがとうございます!

attr_accessorを使用して、アップロードデータを取得するようにしました。

調べたところ、ActiveStorageのattachment_changesを使用するとsave前の画像を取得できることもわかったのですが、"undocumented internal API" と説明されているのを見て、もしかしたら使用を避けた方がよいかもしれないと思い、attr_accessorを使ってデータを取得するようにしました。
もし認識が違っていましたらご指摘いただけると嬉しいです🙇‍♀️

@komagata komagata May 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matuaya attr_accessorは外部から使えるアクセサを定義するものなのでクラス内の処理には関係がないことだと思います。

https://www.google.com/search?q=attr_accessor%E3%81%A8%E3%81%AF&oq=attr_accessor%E3%81%A8%E3%81%AF&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIKCAEQABiiBBiJBTIHCAIQABjvBTIKCAMQABiABBiiBNIBCDM5MDJqMGo3qAIAsAIA&sourceid=chrome&ie=UTF-8

@matuaya matuaya Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata
色々調べて試行錯誤しましたが、@image = Image.new(user: current_user, image: params[:file]) とした後で、params[:file]をモデルから扱う方法が分かりませんでした。
blobへはアクセスできますが、tempfiledownloadといったメソッドではデータへのアクセスできず、attachment_changes以外でアップロードされたファイルの取得方法がわかりません。
すでにこれ以上にないくらいアドバイスを頂いていて申し訳ないのですが、教えていただけますと嬉しいです🙇‍♀️

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matuaya すみません、ちょっとどの点がわからないのかわかっていないかもです。

@image = Image.new(user: current_user, image: params[:file])

コンストラクタに渡しているのであれば、それをクラスの中で使えばいいと思うのですが、どの部分がわからないでしょうか?
お手数ですが、もう少し詳細に言葉を尽くして説明していただけると非常にありがたいです。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata
説明が足りず申し訳ありません🙇‍♀️

@image = Image.new(user: current_user, image: params[:file])ですが、モデル内でself.imageActiveStorage::Attached::Oneオブジェクトだと分かりました。

Minimagickでファイルの処理をするにはファイルパスが必要だと理解しているのですが、 ActiveStorage::Attached::Oneからファイルパスを得られる手段が分かりませんでした。
それならActiveStorage::Blobオブジェクトだったら可能なのではないかと思いimage.attachment.blob.downloadを試してみましたが、ActiveStorage::FileNotFoundErrorとなってしましました。

image: params[:file]と渡した後に、モデルのself.imageを使ってその後どのActiveStorageオブジェクトやメソッドから画像のデータやファイルパスを取得できるのか分からずにいます。
全く見当違いのところを見ているのかもしれないですが、同じ場所でずっとつっかえています🙇‍♀️

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matuaya 一点ずつ確認していきたいのですが、

モデル内でself.imageはActiveStorage::Attached::Oneオブジェクトだと分かりました。

@matuaya さんが繰り返しおっしゃっている「モデル内では」というのがちょっと引っ掛かっているというか意味がわかっていないです。

params[:file]は引数として渡しているだけなので、モデル内でもモデル外でも同じものだと思うのですが、どういう意味でおっしゃっていますか?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata
会議でもアドバイスをくださりありがとうございます!
それにもかかわらずあまり理解できていません🙇‍♀️
tempfileを作る方法と、ファイルパスではなくデータ自体をMinimagickに渡すやり方を説明していただいたと思うのですが、具体的な方法が思いつかず悩んでいます。お教えいただけると嬉しいです🙇‍♀️

あと、

params[:file]は引数として渡しているだけなので、モデル内でもモデル外でも同じものだと思うのですが、どういう意味でおっしゃっていますか?

完全な理解不足ではあると思うのですが、self.image = params[:file]だと考えていました。だけれどself.image.tempfileのようなことはできなかったので直接的には必要な情報は得られないのだと考えていました。

@matuaya matuaya force-pushed the bug/fix-exif-not-removed branch from b6103c8 to c8c142d Compare May 18, 2026 09:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/controllers/api/image_controller.rb`:
- Around line 5-6: リクエストに file がないときに Line 6 の `@image.strip_exif`! が例外を投げて 422
分岐に到達できないため、コントローラの先頭で params[:file] の存在をガードし、なければ早期に 422 を返すようにしてください;具体的には
action 内で params[:file].present? をチェックして不在なら render json: { error: ... },
status: :unprocessable_entity を返し、存在する場合のみ Image.new(user: current_user, file:
params[:file]) と `@image.strip_exif`! を実行するように流れを分岐してください(関連シンボル: params[:file],
Image.new, `@image.strip_exif`!, current_user)。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79ce7d96-da56-440e-b84e-be4d9211d538

📥 Commits

Reviewing files that changed from the base of the PR and between b6103c8 and c8c142d.

📒 Files selected for processing (3)
  • app/controllers/api/image_controller.rb
  • app/models/image.rb
  • test/integration/api/image_test.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/integration/api/image_test.rb

Comment thread app/controllers/api/image_controller.rb Outdated
@matuaya

matuaya commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

@komagata
ご確認お願いいたします🙇‍♀️

@komagata komagata merged commit 5d0a1a8 into main Jul 13, 2026
14 checks passed
@komagata komagata deleted the bug/fix-exif-not-removed branch July 13, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants