From d204a40d74d96f18426a0e6135e1b10974dacdb9 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 23 Oct 2025 11:20:29 +0900 Subject: [PATCH 1/8] rails g controller attachments show --- app/controllers/attachments_controller.rb | 4 ++++ app/helpers/attachments_helper.rb | 2 ++ app/views/attachments/show.html.erb | 4 ++++ config/routes.rb | 1 + test/controllers/attachments_controller_test.rb | 8 ++++++++ 5 files changed, 19 insertions(+) create mode 100644 app/controllers/attachments_controller.rb create mode 100644 app/helpers/attachments_helper.rb create mode 100644 app/views/attachments/show.html.erb create mode 100644 test/controllers/attachments_controller_test.rb diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb new file mode 100644 index 0000000..1f22103 --- /dev/null +++ b/app/controllers/attachments_controller.rb @@ -0,0 +1,4 @@ +class AttachmentsController < ApplicationController + def show + end +end diff --git a/app/helpers/attachments_helper.rb b/app/helpers/attachments_helper.rb new file mode 100644 index 0000000..3c961c9 --- /dev/null +++ b/app/helpers/attachments_helper.rb @@ -0,0 +1,2 @@ +module AttachmentsHelper +end diff --git a/app/views/attachments/show.html.erb b/app/views/attachments/show.html.erb new file mode 100644 index 0000000..2efc155 --- /dev/null +++ b/app/views/attachments/show.html.erb @@ -0,0 +1,4 @@ +
+

Attachments#show

+

Find me in app/views/attachments/show.html.erb

+
diff --git a/config/routes.rb b/config/routes.rb index 60f87d3..7ca8ecb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + get "attachments/show" resources :messages get '/:list_name/:list_seq', to: 'messages#show' get '/:list_name/', to: 'messages#index' diff --git a/test/controllers/attachments_controller_test.rb b/test/controllers/attachments_controller_test.rb new file mode 100644 index 0000000..853fd3b --- /dev/null +++ b/test/controllers/attachments_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class AttachmentsControllerTest < ActionDispatch::IntegrationTest + test "should get show" do + get attachments_show_url + assert_response :success + end +end From d32f366f20bfb4f1ba892b5554b1fc33174715f9 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 24 Oct 2025 12:01:47 +0900 Subject: [PATCH 2/8] gomi --- app/helpers/attachments_helper.rb | 2 -- app/views/attachments/show.html.erb | 4 ---- test/controllers/attachments_controller_test.rb | 8 -------- 3 files changed, 14 deletions(-) delete mode 100644 app/helpers/attachments_helper.rb delete mode 100644 app/views/attachments/show.html.erb delete mode 100644 test/controllers/attachments_controller_test.rb diff --git a/app/helpers/attachments_helper.rb b/app/helpers/attachments_helper.rb deleted file mode 100644 index 3c961c9..0000000 --- a/app/helpers/attachments_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module AttachmentsHelper -end diff --git a/app/views/attachments/show.html.erb b/app/views/attachments/show.html.erb deleted file mode 100644 index 2efc155..0000000 --- a/app/views/attachments/show.html.erb +++ /dev/null @@ -1,4 +0,0 @@ -
-

Attachments#show

-

Find me in app/views/attachments/show.html.erb

-
diff --git a/test/controllers/attachments_controller_test.rb b/test/controllers/attachments_controller_test.rb deleted file mode 100644 index 853fd3b..0000000 --- a/test/controllers/attachments_controller_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "test_helper" - -class AttachmentsControllerTest < ActionDispatch::IntegrationTest - test "should get show" do - get attachments_show_url - assert_response :success - end -end From e629130ecc311f60b195d919710a471c21f88f80 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 24 Oct 2025 04:36:44 +0900 Subject: [PATCH 3/8] route to: attachments/show as :attachment --- config/routes.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 7ca8ecb..b9488a4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,9 +1,10 @@ Rails.application.routes.draw do - get "attachments/show" resources :messages get '/:list_name/:list_seq', to: 'messages#show' get '/:list_name/', to: 'messages#index' + get '/attachments/:encoded_key/*filename' => 'attachments#show', as: :attachment + # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. From edb8ccbf017c735b9ef31e587eb1973650a549c4 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 24 Oct 2025 04:37:57 +0900 Subject: [PATCH 4/8] Download blob from DB and send_data from attachments#show --- app/controllers/attachments_controller.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index 1f22103..e394240 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -1,4 +1,22 @@ +# frozen_string_literal: true + class AttachmentsController < ApplicationController + skip_forgery_protection + def show + if (key = decode_verified_key) + send_data ActiveStorage::Blob.service.download(key[:key]), content_type: key[:content_type], disposition: key[:disposition] + else + head :not_found + end + rescue Errno::ENOENT + head :not_found + end + + private + + def decode_verified_key + key = ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key) + key&.deep_symbolize_keys end end From b70a81094c84c9f2b4b695a6a7fc3a8c9736255e Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 24 Oct 2025 04:38:50 +0900 Subject: [PATCH 5/8] Redirect blob request to attachment_url --- .../service/database_service.rb | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/active_storage/service/database_service.rb b/lib/active_storage/service/database_service.rb index 6dfd866..18ffb70 100644 --- a/lib/active_storage/service/database_service.rb +++ b/lib/active_storage/service/database_service.rb @@ -33,6 +33,34 @@ def exist?(key) private + def private_url(key, expires_in:, filename:, content_type:, disposition:, **) + content_disposition = content_disposition_with(type: disposition, filename: filename) + verified_key_with_expiration = ActiveStorage.verifier.generate( + { + key: key, + disposition: content_disposition, + content_type: content_type, + service_name: name + }, + expires_in: expires_in, + purpose: :blob_key + ) + + if url_options.blank? + raise ArgumentError, "Cannot generate URL for #{filename} using Database service, please set ActiveStorage::Current.url_options." + end + + url_helpers.attachment_url(verified_key_with_expiration, filename: filename, **url_options) + end + + def url_helpers + @url_helpers ||= Rails.application.routes.url_helpers + end + + def url_options + ActiveStorage::Current.url_options + end + def service_name 'Database' end From 83eac1067fb3ef2c183bb0f8a4c30a6407742c23 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 24 Oct 2025 21:31:02 +0900 Subject: [PATCH 6/8] Fixture of a message with an attachment --- test/controllers/messages_controller_test.rb | 2 +- test/fixtures/active_storage/attachments.yml | 5 +++++ test/fixtures/active_storage/blobs.yml | 7 +++++++ test/fixtures/active_storage/file_blobs.yml | 3 +++ test/fixtures/messages.yml | 16 ++++++++-------- test/system/messages_test.rb | 2 +- 6 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/active_storage/attachments.yml create mode 100644 test/fixtures/active_storage/blobs.yml create mode 100644 test/fixtures/active_storage/file_blobs.yml diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index b4bf6f1..3301e33 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -2,7 +2,7 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest setup do - @message = messages(:one) + @message = messages(:message1) end test "should get index" do diff --git a/test/fixtures/active_storage/attachments.yml b/test/fixtures/active_storage/attachments.yml new file mode 100644 index 0000000..c565eaa --- /dev/null +++ b/test/fixtures/active_storage/attachments.yml @@ -0,0 +1,5 @@ +attachment2: + name: attachments + record_type: Message + record: message2 + blob: blob2 diff --git a/test/fixtures/active_storage/blobs.yml b/test/fixtures/active_storage/blobs.yml new file mode 100644 index 0000000..61f177f --- /dev/null +++ b/test/fixtures/active_storage/blobs.yml @@ -0,0 +1,7 @@ +blob2: + key: 3mj2l9tdw1nb531oajdax3r7vylh + filename: 1.patch + content_type: text/x-diff + metadata: '{"identified":true,"analyzed":true}' + byte_size: 5 + service_name: local diff --git a/test/fixtures/active_storage/file_blobs.yml b/test/fixtures/active_storage/file_blobs.yml new file mode 100644 index 0000000..cebe17b --- /dev/null +++ b/test/fixtures/active_storage/file_blobs.yml @@ -0,0 +1,3 @@ +file_blob2: + key: 3mj2l9tdw1nb531oajdax3r7vylh + data: Hello diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml index 24fc45e..426b835 100644 --- a/test/fixtures/messages.yml +++ b/test/fixtures/messages.yml @@ -1,15 +1,15 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - subject: MyString - from: MyString - body: MyText +message1: + subject: Mail1 + from: From1 + body: Body1 list_id: 1 list_seq: 123 -two: - subject: MyString - from: MyString - body: MyText +message2: + subject: Mail2 + from: From2 + body: Body2 list_id: 2 list_seq: 234 diff --git a/test/system/messages_test.rb b/test/system/messages_test.rb index 6eb747e..ba3be26 100644 --- a/test/system/messages_test.rb +++ b/test/system/messages_test.rb @@ -2,7 +2,7 @@ class MessagesTest < ApplicationSystemTestCase setup do - @message = messages(:one) + @message = messages(:message1) end test "visiting the index" do From 7be47b896d8e6c4802ee896e77333e67dbc76904 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 24 Oct 2025 04:40:44 +0900 Subject: [PATCH 7/8] Show attachments in HTML --- app/views/messages/_message.html.erb | 59 ++++++++++++++++++++++++++++ test/system/messages_test.rb | 13 +++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb index 09d923e..0f6f09f 100644 --- a/app/views/messages/_message.html.erb +++ b/app/views/messages/_message.html.erb @@ -23,4 +23,63 @@
<%= message.body %>
+ + <% if message.attachments.attached? %> +
+

Attachments (<%= message.attachments.count %>)

+
+ <% message.attachments.each do |attachment| %> + <% if attachment.content_type&.start_with?('image/') %> +
+
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>) +
+ <%= image_tag rails_blob_path(attachment), class: "max-w-full h-auto rounded border border-gray-300 dark:border-gray-600", alt: attachment.filename.to_s %> +
+ <% elsif attachment.content_type&.start_with?('text/') %> +
+
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, <%= attachment.content_type %>) +
+ <% if attachment.byte_size < 100_000 %> +
<%= attachment.download %>
+ <% end %> +
+ <% elsif attachment.content_type == 'application/pdf' %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, PDF) +
+ <% elsif attachment.content_type&.match?(/zip|tar|gzip|compress|bzip|lha/) %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, Archive) +
+ <% else %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %><% if attachment.content_type %>, <%= attachment.content_type %><% end %>) +
+ <% end %> + <% end %> +
+
+ <% end %> diff --git a/test/system/messages_test.rb b/test/system/messages_test.rb index ba3be26..92045ed 100644 --- a/test/system/messages_test.rb +++ b/test/system/messages_test.rb @@ -2,7 +2,18 @@ class MessagesTest < ApplicationSystemTestCase setup do - @message = messages(:message1) + @message1 = messages(:message1) + @message2 = messages(:message2) + end + + test 'visiting ruby-dev index, and showing a message with an attachment' do + visit '/ruby-dev' + assert_content @message2.subject + + click_link @message2.subject + assert_content @message2.body + + click_link @message2.attachments_attachments.first.blob.filename.to_s end test "visiting the index" do From 8d1330951d26b613e7ad4ec1b87531498c2657bd Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 24 Oct 2025 11:48:47 +0900 Subject: [PATCH 8/8] Extract _attachment partial --- app/views/messages/_attachment.html.erb | 49 +++++++++++++++++++++++ app/views/messages/_message.html.erb | 52 +------------------------ 2 files changed, 50 insertions(+), 51 deletions(-) create mode 100644 app/views/messages/_attachment.html.erb diff --git a/app/views/messages/_attachment.html.erb b/app/views/messages/_attachment.html.erb new file mode 100644 index 0000000..bb47e56 --- /dev/null +++ b/app/views/messages/_attachment.html.erb @@ -0,0 +1,49 @@ +<% if attachment.content_type&.start_with?('image/') %> +
+
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>) +
+ <%= image_tag rails_blob_path(attachment), class: "max-w-full h-auto rounded border border-gray-300 dark:border-gray-600", alt: attachment.filename.to_s %> +
+<% elsif attachment.content_type&.start_with?('text/') %> +
+
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, <%= attachment.content_type %>) +
+ <% if attachment.byte_size < 100_000 %> +
<%= attachment.download %>
+ <% end %> +
+<% elsif attachment.content_type == 'application/pdf' %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, PDF) +
+<% elsif attachment.content_type&.match?(/zip|tar|gzip|compress|bzip|lha/) %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, Archive) +
+<% else %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %><% if attachment.content_type %>, <%= attachment.content_type %><% end %>) +
+<% end %> diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb index 0f6f09f..be51739 100644 --- a/app/views/messages/_message.html.erb +++ b/app/views/messages/_message.html.erb @@ -28,57 +28,7 @@

Attachments (<%= message.attachments.count %>)

- <% message.attachments.each do |attachment| %> - <% if attachment.content_type&.start_with?('image/') %> -
-
- - - - <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> - (<%= number_to_human_size(attachment.byte_size) %>) -
- <%= image_tag rails_blob_path(attachment), class: "max-w-full h-auto rounded border border-gray-300 dark:border-gray-600", alt: attachment.filename.to_s %> -
- <% elsif attachment.content_type&.start_with?('text/') %> -
-
- - - - <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> - (<%= number_to_human_size(attachment.byte_size) %>, <%= attachment.content_type %>) -
- <% if attachment.byte_size < 100_000 %> -
<%= attachment.download %>
- <% end %> -
- <% elsif attachment.content_type == 'application/pdf' %> -
- - - - <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> - (<%= number_to_human_size(attachment.byte_size) %>, PDF) -
- <% elsif attachment.content_type&.match?(/zip|tar|gzip|compress|bzip|lha/) %> -
- - - - <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> - (<%= number_to_human_size(attachment.byte_size) %>, Archive) -
- <% else %> -
- - - - <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> - (<%= number_to_human_size(attachment.byte_size) %><% if attachment.content_type %>, <%= attachment.content_type %><% end %>) -
- <% end %> - <% end %> + <%= render partial: 'messages/attachment', collection: message.attachments %>
<% end %>