Skip to content

Conversation

@matuaya
Copy link
Contributor

@matuaya matuaya commented Nov 6, 2025

Issue

概要

Markdownエディタにアップロードした画像にはEifFデータが残っており、情報漏洩のリスクがありました。
今回の変更でEXif情報が削除される処理を追加し、セキュリティを向上させました。

変更確認方法

  1. feature/strip-exif-from-image-in-markdown-editorをローカルに取り込む
  2. 適当な画像を用意する
  3. 画像にEXifデータがあることを以下の手順で確認する(Macの場合)
  • 画像を開き「プレビュー」画面を表示する。
  • 上部メニューの「ツール」→「インスペクタを表示」をクリックする。
  • 以下のようにEXifデータが表示されることを確認する。
スクリーンショット 2025-11-06 21 37 58
  1. サーバーを起動する(foreman start -f Procfile.dev
  2. 任意のユーザーでログインする
  3. 日報を作成し、用意した画像をアップロードする
  4. 作成した日報を開き、画像をローカルに保存する
  5. 先ほどを同じように画像を「プレビュー」画面で表示する
  6. EXifデータが表示されないことを確認する

補足

プロフィール画像をアップロードするときにExif情報を削除してリネームするようにした #4963を参考にしました。

Screenshot

アプリ自体にはUIの変化はないため、スクリーンショットは省略します。

Summary by CodeRabbit

  • 新機能

    • 画像アップロード時に自動でEXIF(位置情報やカメラ情報など)を削除するようになりました。アップロード処理中にメタデータを除去し、処理に失敗した場合はアップロードを取り消してエラーを返します。プライバシー保護が向上します。
  • テスト

    • EXIF削除の動作を検証する統合テストを追加しました。アップロード後にメタデータが確実に除去されていることを確認します。

@matuaya matuaya self-assigned this Nov 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Walkthrough

画像アップロード後に保存済み画像をMiniMagickで再読み込みしてEXIFメタデータを除去し、除去処理中に失敗した場合は画像を削除して422を返すstrip_exifメソッドと、その検証を行う統合テストが追加されました。

Changes

コホート / ファイル(s) 変更内容
EXIFストリップ機能
app/controllers/api/image_controller.rb
createフローでポスト処理をbegin-rescueでラップ。新しいプライベートメソッドstrip_exifを追加:保存済みの元ファイルを読み込み、MiniMagickでmetadataをstripし、current_user.id+timestamp+拡張子のファイル名で再アタッチ。処理失敗時はImageをdestroyし、日本語メッセージで422を返す。
統合テスト
test/integration/api/image_test.rb
EXIF除去を検証する統合テストtest 'remove exif data from image when uploaded'を追加。認証、フィクスチャ画像アップロード、保存後の画像バイナリからEXIFが除去されていることをアサート。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Controller as API::ImageController
    participant ImageModel as Image
    participant Storage
    participant MiniMagick

    User->>Controller: POST /api/images (file)
    Controller->>ImageModel: create(params)
    ImageModel->>Storage: store original blob
    Controller->>Controller: call strip_exif()
    Controller->>Storage: read original blob
    Storage-->>MiniMagick: image data
    MiniMagick->>MiniMagick: strip metadata
    MiniMagick-->>Controller: sanitized temp file
    Controller->>ImageModel: reattach sanitized file (new filename)
    alt strip_exif success
      Controller-->>User: 201 Created + JSON
    else strip_exif failure
      Controller->>ImageModel: destroy
      Controller-->>User: 422 Unprocessable Entity + error JSON
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 分

注意点:

  • strip_exif 内の一時ファイル作成・確実な削除(ensureブロック)の確認
  • 再アタッチ時の拡張子検出/無拡張子ケースの扱い
  • 失敗時に呼ばれる Image.destroy の副作用(コールバックや関連削除)
  • テストが実際にEXIFを含むフィクスチャを使っているか、バイナリ検査の頑健性

Poem

🐇 ぴょんと来たよ、画像運び
メタは消して、軽やかに跳び去る
新しい名をつけてそっと眠るよ
テストがぴょんと跳ねて合図するよ ✨
ひとつまい、安心を持ち帰るんだ 🥕

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed プルリクエストのタイトルは、Markdownエディタにアップロードされた画像からEXifデータを削除する変更内容を正確に要約しており、メインの変更を明確に表現しています。
Description check ✅ Passed プルリクエストの説明は、リポジトリの説明テンプレートのすべての必須セクション(Issue、概要、変更確認方法、Screenshot)を含んでおり、セキュリティ改善の意図とテスト手順が詳細に記載されています。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/strip-exif-from-image-in-markdown-editor

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 RuboCop (1.81.7)
app/controllers/api/image_controller.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/config_loader_resolver.rb:310:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/g

... [truncated 255 characters] ...

fig_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/config_loader.rb:58:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/config_store.rb:34:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/config_store.rb:29:in apply_options!'
/var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/cli.rb:167:in act_on_options' /var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/cli.rb:49:in block in run'
/var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/cli.rb:87:in profile_if_needed' /var/lib/gems/3.1.0/gems/rubocop-1.81.7/lib/rubocop/cli.rb:45:in run'
/var/lib/gems/3.1.0/gems/rubocop-1.81.7/exe/rubocop:15:in <top (required)>' /usr/local/bin/rubocop:25:in load'
/usr/local/bin/rubocop:25:in `

'


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 and usage tips.

@matuaya matuaya force-pushed the feature/strip-exif-from-image-in-markdown-editor branch from 1d3c65a to 31d0268 Compare November 6, 2025 16:58
@matuaya
Copy link
Contributor Author

matuaya commented Nov 6, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

✅ 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
test/integration/api/image_test.rb (1)

22-22: 保存された画像の取得方法が脆弱です。

Image.order(:created_at).lastを使用していますが、並行テストで他のテストが画像を作成すると、意図しない画像を取得する可能性があります。

レスポンスボディから作成された画像のIDを取得する方が確実です。

以下の修正を検討してください:

     post api_image_path(format: :json), params: { file: image_uploaded }
+    assert_response :created
+    response_json = JSON.parse(response.body)
+    saved_image = Image.find(response_json['id'])
-    saved_image = Image.order(:created_at).last
     processed_image = MiniMagick::Image.read(saved_image.image.download)
app/controllers/api/image_controller.rb (1)

16-23: 画像処理ロジックのInteractorパターン検討を推奨します。

現在の実装は動作しますが、コーディングガイドラインに従うと、外部リソース(アップロードファイル)を処理する場合はInteractorパターンの使用を検討することが推奨されています。

これにより、以下のメリットが得られます:

  • コントローラーをスリムに保つ
  • テストが容易になる
  • エラーハンドリングをInteractor内でカプセル化できる
  • 将来的に他の処理(画像リサイズなど)を追加する際に拡張しやすい

Based on coding guidelines

Interactorの実装例:

# app/interactors/strip_image_exif.rb
class StripImageExif
  include Interactor

  def call
    original_image = context.image.image
    copied_image = MiniMagick::Image.read(original_image.download)
    copied_image.strip

    ext = File.extname(original_image.filename.to_s)
    timestamp = Time.current.to_i
    File.open(copied_image.path) do |file|
      original_image.attach(io: file, filename: "#{context.user_id}_#{timestamp}#{ext}")
    end
  rescue StandardError => e
    context.fail!(error: e.message)
  ensure
    copied_image&.destroy!
  end
end

コントローラーでの使用:

def create
  @image = Image.new(user: current_user, image: params[:file])
  if @image.save
    result = StripImageExif.call(image: @image, user_id: current_user.id)
    if result.success?
      render :create, status: :created
    else
      @image.destroy
      render json: { error: result.error }, status: :unprocessable_entity
    end
  else
    render json: @image.errors, status: :unprocessable_entity
  end
end
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c42e0d and 31d0268.

📒 Files selected for processing (2)
  • app/controllers/api/image_controller.rb (1 hunks)
  • test/integration/api/image_test.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • test/integration/api/image_test.rb
  • app/controllers/api/image_controller.rb
test/**/*

⚙️ CodeRabbit configuration file

test/**/*: # Test

  • TestCase名は英語で書く。
  • どうしても避けられない時以外にsystem testでsleepは使わない。

Unit Test

model, helper, decorator, view_componentについてはメソッドを追加した場合は必ず対応したUnit TestのTestCaseを1つは書く。

Files:

  • test/integration/api/image_test.rb
🔇 Additional comments (1)
test/integration/api/image_test.rb (1)

5-17: テストの構成が適切です。

テスト名が英語で記述されており、テストケースの前提条件(Exifデータが存在すること)を事前に検証している点が良いです。コーディングガイドラインに従っています。

Comment on lines 19 to 20
image_uploaded = fixture_file_upload(image_path, 'test.jpg')
post api_image_path(format: :json), params: { file: image_uploaded }
Copy link

@coderabbitai coderabbitai bot Nov 6, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

fixture_file_uploadの第2引数が不正です。

fixture_file_uploadの第2引数はコンテントタイプを指定すべきですが、ファイル名'test.jpg'が渡されています。正しくは'image/jpeg'を指定する必要があります。

また、アップロード後のレスポンスステータスを検証していないため、アップロードが成功したかどうかを確認できません。

以下の修正を適用してください:

-    image_uploaded = fixture_file_upload(image_path, 'test.jpg')
+    image_uploaded = fixture_file_upload(image_path, 'image/jpeg')
     post api_image_path(format: :json), params: { file: image_uploaded }
+    assert_response :created
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
image_uploaded = fixture_file_upload(image_path, 'test.jpg')
post api_image_path(format: :json), params: { file: image_uploaded }
image_uploaded = fixture_file_upload(image_path, 'image/jpeg')
post api_image_path(format: :json), params: { file: image_uploaded }
assert_response :created
🤖 Prompt for AI Agents
In test/integration/api/image_test.rb around lines 19 to 20, fixture_file_upload
is passed an incorrect second argument (a filename) and the test doesn't assert
the upload response; change the second argument to the correct MIME type
'image/jpeg' when calling fixture_file_upload, and after the post request add an
assertion that the response status indicates success (e.g., assert_response
:success or assert_equal 201, response.status as appropriate for the endpoint)
to verify the upload succeeded.

Copy link

Choose a reason for hiding this comment

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

@matuaya さん、修正ありがとうございます!✨

修正内容を確認しました。fixture_file_uploadの第2引数が正しいコンテントタイプ('image/jpeg')に修正され、レスポンスステータスのアサーションも追加されていることが確認できました。これでテストがより堅牢になりましたね!

٩(◕‿◕。)۶ 🐰✨


🧠 Learnings used
Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9081
File: test/models/transcoder/movie_test.rb:0-0
Timestamp: 2025-08-26T07:31:10.235Z
Learning: Transcoder::Movie クラス(app/models/transcoder/movie.rb)では、Google::Cloud::Storage::FileVerificationError(Storage固有の例外)とGoogle::Cloud::Storage.new(メインクラス)の両方を使用しているため、テストファイルでは完全な google/cloud/storage gem の require が必要。google/cloud/errors gem だけでは不十分。

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@matuaya matuaya force-pushed the feature/strip-exif-from-image-in-markdown-editor branch from 31d0268 to 870aad0 Compare November 6, 2025 23:31
@matuaya matuaya marked this pull request as ready for review November 6, 2025 23:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/controllers/api/image_controller.rb (1)

27-27: ファイルハンドルのリソースリークがあります。

File.openで開いたファイルハンドルが閉じられていません。大量のアップロードがあると「開きすぎたファイル」エラーが発生する可能性があります。

以下のようにブロック形式を使用してファイルを自動的に閉じるようにしてください:

     ext = File.extname(original_image.filename.to_s)
-    original_image.attach(io: File.open(copied_image.path), filename: "#{current_user.id}#{ext}")
+    File.open(copied_image.path) do |file|
+      original_image.attach(io: file, filename: "#{current_user.id}#{ext}")
+    end
🧹 Nitpick comments (2)
app/controllers/api/image_controller.rb (2)

7-13: エラーログの追加を検討してください。

例外処理は適切に実装されていますが、デバッグのためにエラー情報をログに記録することを推奨します。

以下のようにログ記録を追加してください:

       begin
         strip_exif
         render :create, status: :created
-      rescue StandardError
+      rescue StandardError => e
+        Rails.logger.error("Failed to strip EXIF data: #{e.message}\n#{e.backtrace.join("\n")}")
         @image.destroy
         render json: { error: '画像処理に失敗しました' }, status: :unprocessable_entity
       end

21-30: Interactorパターンの使用を検討してください。

外部ライブラリ(MiniMagick)とのやりとりを含む処理のため、コーディングガイドラインに従ってInteractorパターンの使用を検討してください。

以下のようにInteractorを作成できます:

# app/interactors/strip_exif_from_image.rb
class StripExifFromImage
  include Interactor

  def call
    original_image = context.image.image
    copied_image = MiniMagick::Image.read(original_image.download)
    copied_image.strip

    ext = File.extname(original_image.filename.to_s)
    File.open(copied_image.path) do |file|
      original_image.attach(io: file, filename: "#{context.user_id}#{ext}")
    end
  ensure
    copied_image&.destroy!
  end
end

コントローラーでの使用:

def create
  @image = Image.new(user: current_user, image: params[:file])
  if @image.save
    result = StripExifFromImage.call(image: @image, user_id: current_user.id)
    if result.success?
      render :create, status: :created
    else
      @image.destroy
      render json: { error: '画像処理に失敗しました' }, status: :unprocessable_entity
    end
  else
    render json: @image.errors, status: :unprocessable_entity
  end
end

コーディングガイドラインに基づく

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31d0268 and 870aad0.

📒 Files selected for processing (2)
  • app/controllers/api/image_controller.rb (1 hunks)
  • test/integration/api/image_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/integration/api/image_test.rb
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/controllers/api/image_controller.rb
🧠 Learnings (7)
📚 Learning: 2025-10-22T12:22:33.168Z
Learnt from: sjabcdefin
Repo: fjordllc/bootcamp PR: 9247
File: app/controllers/checks_controller.rb:0-0
Timestamp: 2025-10-22T12:22:33.168Z
Learning: CheckCallbacks内のメール通知・サイト内通知で例外が発生した場合、例外を握りつぶさず上位に伝搬させる。これにより通知失敗を検知し、運用上の混乱を防ぐ。CheckCallbacksはafter_create_commitで実行されるため、例外発生時にはCheckとステータス更新はすでにコミット済みであり、ユーザーにはエラーメッセージが表示されるが、DBには変更が残る状態になる。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-12T21:18:00.834Z
Learnt from: komagata
Repo: fjordllc/bootcamp PR: 9101
File: app/notifiers/discord_notifier.rb:131-135
Timestamp: 2025-09-12T21:18:00.834Z
Learning: Rails アップグレードPRにおいて、product_review_not_completed メソッドの webhook URL 設定の shared フォールバック追加も、設定システム全体の変更として別PRで対応すべきである。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-12T21:16:47.639Z
Learnt from: komagata
Repo: fjordllc/bootcamp PR: 9101
File: app/models/graduation_notifier.rb:23-28
Timestamp: 2025-09-12T21:16:47.639Z
Learning: Rails upgrade PRにおいて、configuration systemの変更やwebhook設定の改善提案も、アップグレードに直接関連しない場合は別PRで対応すべきである。PRのスコープ維持が重要。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-04T01:39:22.261Z
Learnt from: Miya096jp
Repo: fjordllc/bootcamp PR: 9102
File: app/controllers/mentor/buzzes_controller.rb:0-0
Timestamp: 2025-09-04T01:39:22.261Z
Learning: fjordllc/bootcamp プロジェクトでは、更新処理の成功時の通知メッセージについて、I18n を使用せずに日本語文字列を直接記述する方針で一貫性を保っている。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-11-01T06:32:17.530Z
Learnt from: Miya096jp
Repo: fjordllc/bootcamp PR: 9287
File: db/migrate/20250831044340_add_unique_index_to_buzzes_url_and_title.rb:1-6
Timestamp: 2025-11-01T06:32:17.530Z
Learning: Buzzモデルでは、仕様変更によりURLの重複は不可となった。URLには一意制約(unique index)が必要だが、タイトルの重複は許容される。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-08-24T16:19:17.661Z
Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9081
File: app/models/transcoder/client.rb:98-102
Timestamp: 2025-08-24T16:19:17.661Z
Learning: In Ruby on Rails ActiveStorage code, when accessing the same attachment property multiple times within a method, it's better to use consistent syntax (e.g., always use `.blob.key` rather than mixing `.key` and `.blob.key`) for better readability and maintainability.

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-07-23T20:31:13.856Z
Learnt from: jun-kondo
Repo: fjordllc/bootcamp PR: 8977
File: app/controllers/reports_controller.rb:63-63
Timestamp: 2025-07-23T20:31:13.856Z
Learning: fjordllc/bootcampプロジェクトの`app/controllers/reports_controller.rb`において、`create`と`update`アクションは両方とも`report.save_uniquely`を使用し、同じ`:report_save`イベントと`'report.save'`イベントを発行する。これは両方とも本質的に「レポートの保存」操作であり、作成と更新を区別する必要がないためである。

Applied to files:

  • app/controllers/api/image_controller.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 870aad0 and dfbf465.

📒 Files selected for processing (1)
  • app/controllers/api/image_controller.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/controllers/api/image_controller.rb
🧠 Learnings (7)
📚 Learning: 2025-10-22T12:22:33.168Z
Learnt from: sjabcdefin
Repo: fjordllc/bootcamp PR: 9247
File: app/controllers/checks_controller.rb:0-0
Timestamp: 2025-10-22T12:22:33.168Z
Learning: CheckCallbacks内のメール通知・サイト内通知で例外が発生した場合、例外を握りつぶさず上位に伝搬させる。これにより通知失敗を検知し、運用上の混乱を防ぐ。CheckCallbacksはafter_create_commitで実行されるため、例外発生時にはCheckとステータス更新はすでにコミット済みであり、ユーザーにはエラーメッセージが表示されるが、DBには変更が残る状態になる。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-12T21:18:00.834Z
Learnt from: komagata
Repo: fjordllc/bootcamp PR: 9101
File: app/notifiers/discord_notifier.rb:131-135
Timestamp: 2025-09-12T21:18:00.834Z
Learning: Rails アップグレードPRにおいて、product_review_not_completed メソッドの webhook URL 設定の shared フォールバック追加も、設定システム全体の変更として別PRで対応すべきである。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-12T21:16:47.639Z
Learnt from: komagata
Repo: fjordllc/bootcamp PR: 9101
File: app/models/graduation_notifier.rb:23-28
Timestamp: 2025-09-12T21:16:47.639Z
Learning: Rails upgrade PRにおいて、configuration systemの変更やwebhook設定の改善提案も、アップグレードに直接関連しない場合は別PRで対応すべきである。PRのスコープ維持が重要。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-04T01:39:22.261Z
Learnt from: Miya096jp
Repo: fjordllc/bootcamp PR: 9102
File: app/controllers/mentor/buzzes_controller.rb:0-0
Timestamp: 2025-09-04T01:39:22.261Z
Learning: fjordllc/bootcamp プロジェクトでは、更新処理の成功時の通知メッセージについて、I18n を使用せずに日本語文字列を直接記述する方針で一貫性を保っている。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-11-01T06:32:17.530Z
Learnt from: Miya096jp
Repo: fjordllc/bootcamp PR: 9287
File: db/migrate/20250831044340_add_unique_index_to_buzzes_url_and_title.rb:1-6
Timestamp: 2025-11-01T06:32:17.530Z
Learning: Buzzモデルでは、仕様変更によりURLの重複は不可となった。URLには一意制約(unique index)が必要だが、タイトルの重複は許容される。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-08-24T16:19:17.661Z
Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9081
File: app/models/transcoder/client.rb:98-102
Timestamp: 2025-08-24T16:19:17.661Z
Learning: In Ruby on Rails ActiveStorage code, when accessing the same attachment property multiple times within a method, it's better to use consistent syntax (e.g., always use `.blob.key` rather than mixing `.key` and `.blob.key`) for better readability and maintainability.

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-07-23T20:31:13.856Z
Learnt from: jun-kondo
Repo: fjordllc/bootcamp PR: 8977
File: app/controllers/reports_controller.rb:63-63
Timestamp: 2025-07-23T20:31:13.856Z
Learning: fjordllc/bootcampプロジェクトの`app/controllers/reports_controller.rb`において、`create`と`update`アクションは両方とも`report.save_uniquely`を使用し、同じ`:report_save`イベントと`'report.save'`イベントを発行する。これは両方とも本質的に「レポートの保存」操作であり、作成と更新を区別する必要がないためである。

Applied to files:

  • app/controllers/api/image_controller.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test

Comment on lines 7 to 14
begin
strip_exif
render :create, status: :created
rescue StandardError
@image.destroy
render json: { error: '画像処理に失敗しました' }, status: :unprocessable_entity
end
Copy link

@coderabbitai coderabbitai bot Nov 7, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

例外を記録せずに握りつぶしている問題があります。

現在の実装では、StandardErrorを捕捉して例外情報を記録せずに破棄しています。これには以下の問題があります:

  1. デバッグが困難: ログがないため、画像処理が失敗した原因を特定できません。
  2. 運用上の問題: 過去のレビューでの学習内容によると、通知処理などでは例外を握りつぶさず伝搬させることで、運用上の混乱を防ぐべきとされています。
  3. 過度に広い捕捉: StandardErrorはプログラミングエラーまで含むため、予期しないバグを隠す可能性があります。

以下の修正を適用してください:

   if @image.save
     begin
       strip_exif
       render :create, status: :created
-    rescue StandardError
+    rescue StandardError => e
+      Rails.logger.error("画像のEXIF削除処理に失敗しました: #{e.class} - #{e.message}")
+      Rails.logger.error(e.backtrace.join("\n"))
       @image.destroy
       render json: { error: '画像処理に失敗しました' }, status: :unprocessable_entity
     end

補足: より具体的なエラー(MiniMagick::Errorなど)のみを捕捉することも検討してください。

🤖 Prompt for AI Agents
In app/controllers/api/image_controller.rb around lines 7–13, the rescue
currently swallows StandardError without logging and may hide bugs; change it to
rescue only expected image-processing exceptions (e.g. MiniMagick::Error or
another specific class), log the exception and backtrace (Rails.logger.error or
logger.error) and send it to your error monitoring (e.g.
Sentry.capture_exception) before rendering the error response, and move
@image.destroy into an ensure block so cleanup always runs; if you intentionally
want fatal errors to bubble, re-raise after logging.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

修正しました

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current state of the file to confirm the fix
cat app/controllers/api/image_controller.rb

Length of output: 991


@matuaya さん、修正ありがとうございます!

例外をキャプチャしてログに記録するようになったので、主要な問題は解決されました。これで画像処理が失敗した際に原因を特定できるようになります。

さらなる改善として以下も検討できますが、現時点の実装で十分機能します:

  1. バックトレースの記録(デバッグをより容易にするため):

    Rails.logger.error("Failed to strip EXIF: #{e.message}\n#{e.backtrace.join("\n")}")
  2. より具体的な例外クラスの捕捉(予期しないバグを隠さないため):

    rescue MiniMagick::Error => e

これらは必須ではありませんので、必要に応じて検討してください。


🧠 Learnings used
Learnt from: jun-kondo
Repo: fjordllc/bootcamp PR: 8977
File: app/controllers/reports_controller.rb:63-63
Timestamp: 2025-07-23T20:31:13.856Z
Learning: fjordllc/bootcampプロジェクトの`app/controllers/reports_controller.rb`において、`create`と`update`アクションは両方とも`report.save_uniquely`を使用し、同じ`:report_save`イベントと`'report.save'`イベントを発行する。これは両方とも本質的に「レポートの保存」操作であり、作成と更新を区別する必要がないためである。

Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9023
File: app/interactors/process_transcoding_notification.rb:58-63
Timestamp: 2025-08-12T09:39:53.259Z
Learning: app/interactors/process_transcoding_notification.rbの`audio_missing_error?`メソッドは、AudioMissingエラーのみを検出することが意図されており、他の音声関連エラー(コーデック非対応、エンコード失敗など)は対象外。これは、AudioMissing以外のエラーに対してinclude_audio: falseでのリトライを行っても問題解決にならず、不適切なリトライが発生してしまうため。

Learnt from: sjabcdefin
Repo: fjordllc/bootcamp PR: 9247
File: app/controllers/checks_controller.rb:0-0
Timestamp: 2025-10-22T12:22:33.168Z
Learning: CheckCallbacks内のメール通知・サイト内通知で例外が発生した場合、例外を握りつぶさず上位に伝搬させる。これにより通知失敗を検知し、運用上の混乱を防ぐ。CheckCallbacksはafter_create_commitで実行されるため、例外発生時にはCheckとステータス更新はすでにコミット済みであり、ユーザーにはエラーメッセージが表示されるが、DBには変更が残る状態になる。

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines 21 to 34
def strip_exif
original_image = @image.image
copied_image = MiniMagick::Image.read(original_image.download)
copied_image.strip

ext = File.extname(original_image.filename.to_s)
timestamp = Time.current.strftime('%Y%m%d%H%M%S')
original_image.attach(io: File.open(copied_image.path), filename: "#{current_user.id}_#{timestamp}#{ext}")
ensure
copied_image&.destroy!
end
Copy link

@coderabbitai coderabbitai bot Nov 7, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ファイルハンドルのリークが発生します。

Line 28のFile.open(copied_image.path)がブロック形式ではないため、ファイルハンドルが明示的にクローズされません。ensure節はcopied_imageのみを破棄しており、開いたファイルハンドルは残ったままになります。

以下の修正を適用してください:

 def strip_exif
   original_image = @image.image
   copied_image = MiniMagick::Image.read(original_image.download)
   copied_image.strip

   ext = File.extname(original_image.filename.to_s)
   timestamp = Time.current.strftime('%Y%m%d%H%M%S')
-  original_image.attach(io: File.open(copied_image.path), filename: "#{current_user.id}_#{timestamp}#{ext}")
+  File.open(copied_image.path) do |file|
+    original_image.attach(io: file, filename: "#{current_user.id}_#{timestamp}#{ext}")
+  end
 ensure
   copied_image&.destroy!
 end

その他の改善提案:

  1. タイムスタンプの粒度: 秒単位のタイムスタンプは、同時アップロードで衝突する可能性があります。ミリ秒を含めるか、SecureRandom.hexを追加することを検討してください。
timestamp = Time.current.strftime('%Y%m%d%H%M%S%L') # ミリ秒を含める
# または
timestamp = "#{Time.current.to_i}_#{SecureRandom.hex(4)}"
  1. Interactorパターンの検討: コーディングガイドラインによると、複数のActiveRecordモデル操作や外部API(MiniMagick)とのやりとりがある場合は、Interactorパターンの使用を検討すべきとされています。この処理は画像の保存、EXIF削除、再アタッチという複数の責務を持つため、StripImageExifのようなInteractorに切り出すことで、コントローラーをよりシンプルに保てます。

Based on coding guidelines

🤖 Prompt for AI Agents
app/controllers/api/image_controller.rb lines 21-31:
File.open(copied_image.path) is called without a block causing a file-handle
leak; change to using File.open with a block (or ensure the returned IO is
closed in an ensure) when calling original_image.attach so the IO is closed
after attach, keep the existing copied_image&.destroy! in ensure, and optionally
strengthen the timestamp to include milliseconds or a SecureRandom suffix to
avoid filename collisions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

修正しました

Copy link

Choose a reason for hiding this comment

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

@matuaya さん、修正ありがとうございます!

確認しました。File.openをブロック形式に変更していただき、ファイルハンドルのリークが解消されています。また、タイムスタンプにもミリ秒(%L)を含めていただいたので、ファイル名の衝突リスクも軽減されています。

完璧な対応です! 👍


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@matuaya matuaya force-pushed the feature/strip-exif-from-image-in-markdown-editor branch from dfbf465 to d236131 Compare November 7, 2025 03:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/controllers/api/image_controller.rb (1)

10-11: バックトレースをログに記録してください。

現在の実装では例外メッセージのみをログに記録していますが、バックトレースがないため本番環境でのデバッグが困難です。また、StandardErrorの捕捉範囲が広すぎるため、予期しないプログラミングエラーまで握りつぶしてしまう可能性があります。

以下の修正を適用してください:

-      rescue StandardError => e
-        Rails.logger.error("Failed to strip EXIF: #{e.message}")
+      rescue MiniMagick::Error, ActiveStorage::FileNotFoundError => e
+        Rails.logger.error("Failed to strip EXIF: #{e.class} - #{e.message}")
+        Rails.logger.error(e.backtrace.join("\n"))
         @image.destroy
         render json: { error: '画像処理に失敗しました' }, status: :unprocessable_entity

補足: より具体的な例外型(MiniMagick::Errorなど)のみを捕捉することで、予期しないエラーを隠さずに済みます。

🧹 Nitpick comments (1)
app/controllers/api/image_controller.rb (1)

22-34: 実装が大幅に改善されました!

過去のレビューフィードバックに対応し、以下の点が適切に実装されています:

  • ブロック形式でのFile.openによるファイルハンドルの適切なクローズ
  • ミリ秒を含むタイムスタンプによるファイル名衝突の回避
  • ensureブロックによる一時ファイルのクリーンアップ

任意の改善提案: コーディングガイドラインによると、外部API(MiniMagick)とのやりとりがある場合はInteractorパターンの使用が推奨されています。現在の実装は動作しますが、将来的なメンテナンス性向上のため、StripImageExifのようなInteractorクラスへの切り出しを検討してもよいかもしれません。

# app/interactors/strip_image_exif.rb (例)
class StripImageExif
  include Interactor

  def call
    original_image = context.image.image
    copied_image = MiniMagick::Image.read(original_image.download)
    copied_image.strip
    
    ext = File.extname(original_image.filename.to_s)
    timestamp = Time.current.strftime('%Y%m%d%H%M%S%L')
    File.open(copied_image.path) do |file|
      original_image.attach(io: file, filename: "#{context.user_id}_#{timestamp}#{ext}")
    end
  ensure
    copied_image&.destroy!
  end
end

ただし、これは必須ではなく、現在のシンプルな実装で十分機能している場合はこのままでも問題ありません。

Based on coding guidelines

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfbf465 and d236131.

📒 Files selected for processing (1)
  • app/controllers/api/image_controller.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/controllers/api/image_controller.rb
🧠 Learnings (11)
📓 Common learnings
Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9081
File: test/models/transcoder/movie_test.rb:0-0
Timestamp: 2025-08-26T07:31:10.235Z
Learning: Transcoder::Movie クラス(app/models/transcoder/movie.rb)では、Google::Cloud::Storage::FileVerificationError(Storage固有の例外)とGoogle::Cloud::Storage.new(メインクラス)の両方を使用しているため、テストファイルでは完全な google/cloud/storage gem の require が必要。google/cloud/errors gem だけでは不十分。
📚 Learning: 2025-10-22T12:22:33.168Z
Learnt from: sjabcdefin
Repo: fjordllc/bootcamp PR: 9247
File: app/controllers/checks_controller.rb:0-0
Timestamp: 2025-10-22T12:22:33.168Z
Learning: CheckCallbacks内のメール通知・サイト内通知で例外が発生した場合、例外を握りつぶさず上位に伝搬させる。これにより通知失敗を検知し、運用上の混乱を防ぐ。CheckCallbacksはafter_create_commitで実行されるため、例外発生時にはCheckとステータス更新はすでにコミット済みであり、ユーザーにはエラーメッセージが表示されるが、DBには変更が残る状態になる。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-12T21:18:00.834Z
Learnt from: komagata
Repo: fjordllc/bootcamp PR: 9101
File: app/notifiers/discord_notifier.rb:131-135
Timestamp: 2025-09-12T21:18:00.834Z
Learning: Rails アップグレードPRにおいて、product_review_not_completed メソッドの webhook URL 設定の shared フォールバック追加も、設定システム全体の変更として別PRで対応すべきである。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-12T21:16:47.639Z
Learnt from: komagata
Repo: fjordllc/bootcamp PR: 9101
File: app/models/graduation_notifier.rb:23-28
Timestamp: 2025-09-12T21:16:47.639Z
Learning: Rails upgrade PRにおいて、configuration systemの変更やwebhook設定の改善提案も、アップグレードに直接関連しない場合は別PRで対応すべきである。PRのスコープ維持が重要。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-04T01:39:22.261Z
Learnt from: Miya096jp
Repo: fjordllc/bootcamp PR: 9102
File: app/controllers/mentor/buzzes_controller.rb:0-0
Timestamp: 2025-09-04T01:39:22.261Z
Learning: fjordllc/bootcamp プロジェクトでは、更新処理の成功時の通知メッセージについて、I18n を使用せずに日本語文字列を直接記述する方針で一貫性を保っている。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-11-01T06:32:17.530Z
Learnt from: Miya096jp
Repo: fjordllc/bootcamp PR: 9287
File: db/migrate/20250831044340_add_unique_index_to_buzzes_url_and_title.rb:1-6
Timestamp: 2025-11-01T06:32:17.530Z
Learning: Buzzモデルでは、仕様変更によりURLの重複は不可となった。URLには一意制約(unique index)が必要だが、タイトルの重複は許容される。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-08-24T16:19:17.661Z
Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9081
File: app/models/transcoder/client.rb:98-102
Timestamp: 2025-08-24T16:19:17.661Z
Learning: In Ruby on Rails ActiveStorage code, when accessing the same attachment property multiple times within a method, it's better to use consistent syntax (e.g., always use `.blob.key` rather than mixing `.key` and `.blob.key`) for better readability and maintainability.

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-07-23T20:31:13.856Z
Learnt from: jun-kondo
Repo: fjordllc/bootcamp PR: 8977
File: app/controllers/reports_controller.rb:63-63
Timestamp: 2025-07-23T20:31:13.856Z
Learning: fjordllc/bootcampプロジェクトの`app/controllers/reports_controller.rb`において、`create`と`update`アクションは両方とも`report.save_uniquely`を使用し、同じ`:report_save`イベントと`'report.save'`イベントを発行する。これは両方とも本質的に「レポートの保存」操作であり、作成と更新を区別する必要がないためである。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-09-04T01:50:47.773Z
Learnt from: Miya096jp
Repo: fjordllc/bootcamp PR: 9102
File: db/migrate/20250831234516_remove_unique_index_from_buzzes_title.rb:1-5
Timestamp: 2025-09-04T01:50:47.773Z
Learning: fjordllc/bootcampプロジェクトでは、マイグレーションにおいてdisable_ddl_transaction!やalgorithm: :concurrentlyは使用せず、標準的なRailsのchangeメソッド内でremove_indexを実行する慣例がある。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-08-12T09:39:53.259Z
Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9023
File: app/interactors/process_transcoding_notification.rb:58-63
Timestamp: 2025-08-12T09:39:53.259Z
Learning: app/interactors/process_transcoding_notification.rbの`audio_missing_error?`メソッドは、AudioMissingエラーのみを検出することが意図されており、他の音声関連エラー(コーデック非対応、エンコード失敗など)は対象外。これは、AudioMissing以外のエラーに対してinclude_audio: falseでのリトライを行っても問題解決にならず、不適切なリトライが発生してしまうため。

Applied to files:

  • app/controllers/api/image_controller.rb
📚 Learning: 2025-08-24T07:39:34.701Z
Learnt from: sekito1107
Repo: fjordllc/bootcamp PR: 9081
File: app/jobs/transcode_job.rb:11-24
Timestamp: 2025-08-24T07:39:34.701Z
Learning: In the bootcamp application's TranscodeJob, exceptions are not re-raised after logging/notification because the job runs via after_create_commit. The Movie creation has already succeeded, so re-raising would cause users to see a creation failure screen, degrading user experience. Instead, failures are logged and sent to Rollbar for monitoring only.

Applied to files:

  • app/controllers/api/image_controller.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test

@matuaya
Copy link
Contributor Author

matuaya commented Nov 14, 2025

@jun-kondo
お疲れ様です😌!
もしご負担でなければレビューをお願いできますでしょうか?🙇‍♀️

@jun-kondo
Copy link
Contributor

@matuaya
お疲れ様です🍵
承知しました。数日内にレビュー致しますー🙋‍♂️

@matuaya
Copy link
Contributor Author

matuaya commented Nov 17, 2025

@jun-kondo
ありがとうございます!お願いいたします🙇‍♀️
全く急ぎではないので一週間以上かかっても大丈夫です☺️🙏

@jun-kondo jun-kondo self-requested a review November 18, 2025 08:29
Copy link
Contributor

@jun-kondo jun-kondo left a comment

Choose a reason for hiding this comment

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

動作確認してexifが削除されていることを確認しました。
問題ないと思いますのでapprove致します。
fixture_file_uploadを使って画像処理の単体テストの書き方などとても参考になりました🙋‍♂️

@matuaya
Copy link
Contributor Author

matuaya commented Dec 3, 2025

@jun-kondo
返信が遅れてしまい大変申し訳ないです🙇‍♀️
レビューしていただきありがとうございました☺️🙏とても助かりました!

@matuaya
Copy link
Contributor Author

matuaya commented Dec 3, 2025

@okuramasafumi
お疲れ様です!
チームメンバーからの確認が完了しましたので、お手すきの際にレビューをお願いいたします🙇‍♀️

Copy link
Contributor

@okuramasafumi okuramasafumi left a comment

Choose a reason for hiding this comment

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

提案

この処理はコントローラーではなくてモデルのコールバックに書いたほうがよいかと思いました。

理由

将来的に、画像をImageController以外で保存することがあるかもしれません。別のモデルを経由して作成されたりすることが特に考えられます。例えば、

some_model.images.build(image_params)

このとき、モデルに処理を書いておけば処理の漏れが起こる可能性が低下します。

実装方針

基本的にはstrip_exifをモデルのプライベートメソッドに移動し、before_saveで呼ぶだけで大丈夫だと思います。

考慮事項

モデルのコールバック、特にbefore_saveを使うということはそれがそのモデルの「要件」であることを意味します。ここでは画像からEXIFデータを削除することが要件なのかを考えることが必要です。要件であるならば、EXIFデータの削除に失敗した場合はモデルの保存自体が失敗することが望ましいことになり、before_saveの使いどころとなります。

@matuaya matuaya force-pushed the feature/strip-exif-from-image-in-markdown-editor branch from d236131 to 4fc90cd Compare December 21, 2025 21:10
@matuaya
Copy link
Contributor Author

matuaya commented Dec 21, 2025

@okuramasafumi
アドバイスいただきありがとうございます!
画像のEXIFデータの削除を必須要件にするべきか検討しました。
今までもEXIFデータを含めたまま画像が保存されていたことを考慮すると、要件である必要はないのではないかと考え、before_saveではなくafter_commitを使用しました。
ご確認いただけると嬉しいです🙇‍♀️

@okuramasafumi
Copy link
Contributor

@matuaya 返信が遅くなってしまってすみません!
なるほど、after_commit on: :createなので、既存のImageモデルには処理を適用しない、ということですね、良い方針だと思います!

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.

4 participants