Skip to content

Commit c39b9e4

Browse files
author
Mattia Roccoberton
authored
Merge pull request #17 from blocknotes/update_service_api
Update Service API
2 parents ab501b4 + ea03fac commit c39b9e4

8 files changed

Lines changed: 239 additions & 70 deletions

File tree

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ RSpec/MultipleExpectations:
3636

3737
RSpec/MultipleMemoizedHelpers:
3838
# Default is 5
39-
Max: 10
39+
Max: 12
4040

4141
RSpec/NestedGroups:
4242
# Default is 3

lib/active_storage/service/db_service.rb

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
# frozen_string_literal: true
22

3+
require 'active_storage/service/db_service_rails60'
4+
require 'active_storage/service/db_service_rails61'
5+
require 'active_storage/service/db_service_rails70'
6+
37
module ActiveStorage
8+
# Wraps a DB table as an Active Storage service. See ActiveStorage::Service
9+
# for the generic API documentation that applies to all services.
410
class Service::DBService < Service
5-
def initialize(**)
11+
if Rails::VERSION::MAJOR >= 7
12+
include ActiveStorage::DBServiceRails70
13+
elsif Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR == 1
14+
include ActiveStorage::DBServiceRails61
15+
else
16+
include ActiveStorage::DBServiceRails60
17+
end
18+
19+
def initialize(public: false, **)
620
@chunk_size = ENV.fetch('ASDB_CHUNK_SIZE') { 1.megabytes }
21+
@public = public
722
end
823

924
def upload(key, io, checksum: nil, **)
@@ -22,22 +37,27 @@ def download(key, &block)
2237
else
2338
instrument :download, key: key do
2439
record = ::ActiveStorageDB::File.find_by(ref: key)
25-
record&.data || raise(ActiveStorage::FileNotFoundError)
40+
raise(ActiveStorage::FileNotFoundError) unless record
41+
42+
record.data
2643
end
2744
end
2845
end
2946

3047
def download_chunk(key, range)
3148
instrument :download_chunk, key: key, range: range do
3249
chunk_select = "SUBSTRING(data FROM #{range.begin + 1} FOR #{range.size}) AS chunk"
33-
::ActiveStorageDB::File.select(chunk_select).find_by(ref: key)&.chunk ||
34-
raise(ActiveStorage::FileNotFoundError)
50+
record = ::ActiveStorageDB::File.select(chunk_select).find_by(ref: key)
51+
raise(ActiveStorage::FileNotFoundError) unless record
52+
53+
record.chunk
3554
end
3655
end
3756

3857
def delete(key)
3958
instrument :delete, key: key do
4059
::ActiveStorageDB::File.find_by(ref: key)&.destroy
60+
# Ignore files already deleted
4161
end
4262
end
4363

@@ -55,50 +75,23 @@ def exist?(key)
5575
end
5676
end
5777

58-
def url(key, expires_in:, filename:, disposition:, content_type:)
59-
instrument :url, key: key do |payload|
60-
content_disposition = content_disposition_with(type: disposition, filename: filename)
61-
verified_key_with_expiration = ActiveStorage.verifier.generate(
62-
{
63-
key: key,
64-
disposition: content_disposition,
65-
content_type: content_type
66-
},
67-
expires_in: expires_in,
68-
purpose: :blob_key
69-
)
70-
current_uri = URI.parse(current_host)
71-
generated_url = url_helpers.service_url(
72-
verified_key_with_expiration,
73-
protocol: current_uri.scheme,
74-
host: current_uri.host,
75-
port: current_uri.port,
76-
disposition: content_disposition,
77-
content_type: content_type,
78-
filename: filename
79-
)
80-
payload[:url] = generated_url
81-
82-
generated_url
83-
end
84-
end
85-
8678
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, custom_metadata: {})
8779
instrument :url, key: key do |payload|
8880
verified_token_with_expiration = ActiveStorage.verifier.generate(
8981
{
9082
key: key,
9183
content_type: content_type,
9284
content_length: content_length,
93-
checksum: checksum
85+
checksum: checksum,
86+
service_name: respond_to?(:name) ? name : 'db'
9487
},
9588
expires_in: expires_in,
9689
purpose: :blob_token
9790
)
98-
generated_url = url_helpers.update_service_url(verified_token_with_expiration, host: current_host)
99-
payload[:url] = generated_url
10091

101-
generated_url
92+
url_helpers.update_service_url(verified_token_with_expiration, url_options).tap do |generated_url|
93+
payload[:url] = generated_url
94+
end
10295
end
10396
end
10497

@@ -108,15 +101,29 @@ def headers_for_direct_upload(_key, content_type:, **)
108101

109102
private
110103

111-
def current_host
112-
if ActiveStorage::Current.respond_to? :url_options
113-
opts = ActiveStorage::Current.url_options || {}
114-
url = "#{opts[:protocol]}#{opts[:host]}"
115-
url += ":#{opts[:port]}" if opts[:port]
116-
url || ''
117-
else
118-
ActiveStorage::Current.host
119-
end
104+
def generate_url(key, expires_in:, filename:, content_type:, disposition:)
105+
content_disposition = content_disposition_with(type: disposition, filename: filename)
106+
verified_key_with_expiration = ActiveStorage.verifier.generate(
107+
{
108+
key: key,
109+
disposition: content_disposition,
110+
content_type: content_type,
111+
service_name: respond_to?(:name) ? name : 'db'
112+
},
113+
expires_in: expires_in,
114+
purpose: :blob_key
115+
)
116+
117+
current_uri = URI.parse(current_host)
118+
url_helpers.service_url(
119+
verified_key_with_expiration,
120+
protocol: current_uri.scheme,
121+
host: current_uri.host,
122+
port: current_uri.port,
123+
disposition: content_disposition,
124+
content_type: content_type,
125+
filename: filename
126+
)
120127
end
121128

122129
def ensure_integrity_of(key, checksum)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveStorage
4+
module DBServiceRails60
5+
def url(key, expires_in:, filename:, disposition:, content_type:)
6+
instrument :url, key: key do |payload|
7+
generate_url(key, expires_in: expires_in, filename: filename, content_type: content_type, disposition: disposition).tap do |generated_url|
8+
payload[:url] = generated_url
9+
end
10+
end
11+
end
12+
13+
private
14+
15+
def current_host
16+
url_options[:host]
17+
end
18+
19+
def url_options
20+
{
21+
host: ActiveStorage::Current.host
22+
}
23+
end
24+
end
25+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveStorage
4+
module DBServiceRails61
5+
private
6+
7+
def current_host
8+
url_options[:host]
9+
end
10+
11+
def private_url(key, expires_in:, filename:, content_type:, disposition:, **)
12+
generate_url(
13+
key,
14+
expires_in: expires_in,
15+
filename: filename,
16+
content_type: content_type,
17+
disposition: disposition
18+
)
19+
end
20+
21+
def public_url(key, filename:, content_type: nil, disposition: :attachment, **)
22+
generate_url(
23+
key,
24+
expires_in: nil,
25+
filename: filename,
26+
content_type: content_type,
27+
disposition: disposition
28+
)
29+
end
30+
31+
def url_options
32+
{
33+
host: ActiveStorage::Current.host
34+
}
35+
end
36+
end
37+
end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveStorage
4+
module DBServiceRails70
5+
def compose(source_keys, destination_key, **)
6+
buffer = nil
7+
source_keys.each do |source_key|
8+
data = ::ActiveStorageDB::File.find_by!(ref: source_key).data
9+
if buffer
10+
buffer << data
11+
else
12+
buffer = data
13+
end
14+
end
15+
::ActiveStorageDB::File.create!(ref: destination_key, data: buffer) if buffer
16+
end
17+
18+
private
19+
20+
def current_host
21+
opts = url_options || {}
22+
url = "#{opts[:protocol]}#{opts[:host]}"
23+
url + ":#{opts[:port]}" if opts[:port]
24+
end
25+
26+
def private_url(key, expires_in:, filename:, content_type:, disposition:, **)
27+
generate_url(
28+
key,
29+
expires_in: expires_in,
30+
filename: filename,
31+
content_type: content_type,
32+
disposition: disposition
33+
)
34+
end
35+
36+
def public_url(key, filename:, content_type: nil, disposition: :attachment, **)
37+
generate_url(
38+
key,
39+
expires_in: nil,
40+
filename: filename,
41+
content_type: content_type,
42+
disposition: disposition
43+
)
44+
end
45+
46+
def url_options
47+
ActiveStorage::Current.url_options
48+
end
49+
end
50+
end

spec/rails_helper.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
add_filter %r{^/lib/active_storage_db/version.rb}
77
add_filter %r{^/spec/}
88
add_filter %r{^/vendor/}
9+
10+
case ENV['RAILS']
11+
when '6.0' then add_filter /_rails61|_rails70/
12+
when '6.1' then add_filter /_rails60|_rails70/
13+
when '7.0' then add_filter /_rails60|_rails61/
14+
end
915
end
1016

1117
require 'spec_helper'
@@ -35,13 +41,22 @@
3541
config.use_transactional_fixtures = true
3642

3743
config.before(:suite) do
44+
db_config =
45+
if ActiveRecord::Base.respond_to? :connection_db_config
46+
ActiveRecord::Base.connection_db_config.configuration_hash
47+
else
48+
ActiveRecord::Base.connection_config
49+
end
50+
3851
intro = ('-' * 80)
3952
intro << "\n"
4053
intro << "- Ruby: #{RUBY_VERSION}\n"
4154
intro << "- Rails: #{Rails.version}\n"
4255
intro << "- ActiveStorage: #{ActiveStorage.version}\n"
43-
intro << "- DB_TEST: #{ENV['DB_TEST']}\n"
56+
intro << "- DB adapter: #{db_config[:adapter]}\n"
57+
intro << "- DB name: #{db_config[:database]}\n"
4458
intro << ('-' * 80)
59+
4560
RSpec.configuration.reporter.message(intro)
4661
end
4762
end

spec/requests/file_controller_spec.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ def create_blob_before_direct_upload(byte_size:, checksum:, filename: 'hello.txt
2121
end
2222

2323
let(:blob) { create_blob(filename: 'img.jpg', content_type: 'image/jpg') }
24-
let(:host) { 'http://test.example.com:3001' }
2524
let(:url_options) do
2625
{
2726
protocol: 'http://',
2827
host: 'test.example.com',
2928
port: '3001',
3029
}
3130
end
31+
let(:host) { "#{url_options[:protocol]}#{url_options[:host]}:#{url_options[:port]}" }
3232
let(:engine_url_helpers) { ::ActiveStorageDB::Engine.routes.url_helpers }
3333

3434
before do
@@ -137,5 +137,19 @@ def create_blob_before_direct_upload(byte_size:, checksum:, filename: 'hello.txt
137137
expect(response).to have_http_status(:not_found)
138138
end
139139
end
140+
141+
context 'when the integrity check fails' do
142+
let(:invalid_file) { create(:active_storage_db_file, data: 'Some other data') }
143+
144+
before do
145+
allow(::ActiveStorageDB::File).to receive(:find_by).and_return(invalid_file)
146+
end
147+
148+
it 'fails to upload' do
149+
put blob.service_url_for_direct_upload, params: data, headers: { 'Content-Type' => 'text/plain' }
150+
151+
expect(response).to have_http_status(:unprocessable_entity)
152+
end
153+
end
140154
end
141155
end

0 commit comments

Comments
 (0)