Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,34 @@ db:

### Customizations

#### Use DB storage for selected attachments

When your application uses another service by default, configure the engine's
service separately in `config/application.rb` or an environment configuration:

```rb
config.active_storage.service = :local # Keep your existing default service
config.active_storage_db.service = :db
```

The `active_storage_db.service` value is the name of a DB service in
`config/storage.yml` (a string or symbol), not the adapter name `DB`.
Select that same service on attachments that should use database storage:

```rb
class Report < ApplicationRecord
has_one_attached :template, service: :db
end
```

The engine uses the configured service for downloads and direct uploads without
changing the application's default service. The setting defaults to `nil`, which
preserves the existing behavior of using `ActiveStorage::Blob.service`.
An unknown service name raises the Active Storage registry's configuration error.
This setting selects one service for all engine requests.

#### Separate database connection

To setup a separate database connection for the `ActiveStorageDB` migrations and files data:

1. Add a different database configuration per environment to `config/database.yml`, e.g:
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/active_storage_db/files_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def acceptable_content?(token)
end

def db_service
ActiveStorage::Blob.service
service_name = Rails.application.config.active_storage_db.service
service_name ? ActiveStorage::Blob.services.fetch(service_name) : ActiveStorage::Blob.service
end

def decode_verified_key
Expand Down
3 changes: 3 additions & 0 deletions lib/active_storage_db/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ module ActiveStorageDB

class Engine < ::Rails::Engine
isolate_namespace ActiveStorageDB

config.active_storage_db = ActiveSupport::OrderedOptions.new
config.active_storage_db.service = nil
end
end
80 changes: 80 additions & 0 deletions spec/requests/configured_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# frozen_string_literal: true

RSpec.describe "Configured engine service" do
let(:data) { "Database attachment" }
let(:disk_service) { ActiveStorage::Blob.services.fetch(:test) }
let(:blob) do
ActiveStorage::Blob.create_and_upload!(
io: StringIO.new(data), filename: "template.txt", content_type: "text/plain",
service_name: "db"
)
end

before do
allow(ActiveStorage::Blob).to receive(:service).and_return(disk_service)
if ActiveStorage::Current.respond_to?(:url_options)
allow(ActiveStorage::Current).to receive(:url_options).and_return(protocol: "http://", host: "test.example.com")
else
allow(ActiveStorage::Current).to receive(:host).and_return("http://test.example.com")
end
end

shared_examples "a configured DB service" do
it "downloads from DB storage without changing the default service", :aggregate_failures do
get blob.url

expect(response).to have_http_status(:ok)
expect(response.body).to eq(data)
expect(ActiveStorage::Blob.service).to eq(disk_service)
expect(disk_service).not_to exist(blob.key)
end

it "uploads directly to DB storage without changing the default service", :aggregate_failures do
pending_blob = ActiveStorage::Blob.create_before_direct_upload!(
filename: "template.txt", byte_size: data.bytesize, content_type: "text/plain",
checksum: Digest::MD5.base64digest(data), service_name: "db"
)

put pending_blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" }

expect(response).to have_http_status(:no_content)
expect(pending_blob.download).to eq(data)
expect(ActiveStorage::Blob.service).to eq(disk_service)
expect(disk_service).not_to exist(pending_blob.key)
end
end

context "with a symbol service name" do
before { allow(Rails.application.config.active_storage_db).to receive(:service).and_return(:db) }

it_behaves_like "a configured DB service"
end

context "with a string service name" do
before { allow(Rails.application.config.active_storage_db).to receive(:service).and_return("db") }

it_behaves_like "a configured DB service"
end

context "without a configured service" do
before do
allow(Rails.application.config.active_storage_db).to receive(:service).and_return(nil)
allow(ActiveStorage::Blob).to receive(:service).and_return(ActiveStorage::Blob.services.fetch(:db))
end

it "downloads using the application's default service" do
get blob.url

expect(response).to have_http_status(:ok)
expect(response.body).to eq(data)
end
end

context "with an unknown service name" do
before { allow(Rails.application.config.active_storage_db).to receive(:service).and_return(:missing_db) }

it "raises the registry error instead of falling back" do
expect { get blob.url }.to raise_error(KeyError, /missing_db/)
end
end
end