Skip to content

Commit ac13300

Browse files
authored
Merge pull request #82 from blocknotes/internal-improvements-2
Internal improvements (2)
2 parents c996242 + 88b2755 commit ac13300

7 files changed

Lines changed: 88 additions & 17 deletions

File tree

db/migrate/20200702202022_create_active_storage_db_files.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ def change
2020

2121
def primary_key_type
2222
config = Rails.configuration.generators
23-
primary_key_type = config.options[config.orm][:primary_key_type]
24-
primary_key_type || :primary_key
23+
config.options.dig(config.orm, :primary_key_type) || :primary_key
2524
end
2625
end

lib/active_storage/service/db_service.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ class Service::DBService < Service
1818
end
1919
# :nocov:
2020

21+
MINIMUM_CHUNK_SIZE = 1
22+
2123
def initialize(public: false, **)
22-
@chunk_size = ENV.fetch("ASDB_CHUNK_SIZE") { 1.megabytes }.to_i
24+
@chunk_size = [ENV.fetch("ASDB_CHUNK_SIZE") { 1.megabytes }.to_i, MINIMUM_CHUNK_SIZE].max
2325
@public = public
2426
end
2527

@@ -49,7 +51,7 @@ def download_chunk(key, range)
4951
size = range.size
5052
args = adapter_sqlserver? || adapter_sqlite? ? "data, #{from}, #{size}" : "data FROM #{from} FOR #{size}"
5153
record = object_for(key, fields: "SUBSTRING(#{args}) AS chunk")
52-
raise(ActiveStorage::FileNotFoundError) unless record
54+
raise ActiveStorage::FileNotFoundError unless record
5355

5456
record.chunk
5557
end
@@ -158,15 +160,18 @@ def generate_url(key, expires_in:, filename:, content_type:, disposition:)
158160
end
159161

160162
def ensure_integrity_of(key, checksum)
161-
return if Digest::MD5.base64digest(object_for(key).data) == checksum
163+
record = object_for(key)
164+
raise ActiveStorage::FileNotFoundError unless record
165+
166+
return if Digest::MD5.base64digest(record.data) == checksum
162167

163168
delete(key)
164169
raise ActiveStorage::IntegrityError
165170
end
166171

167172
def retrieve_file(key)
168173
file = object_for(key)
169-
raise(ActiveStorage::FileNotFoundError) unless file
174+
raise ActiveStorage::FileNotFoundError unless file
170175

171176
file.data
172177
end

spec/dummy/app/controllers/posts_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,6 @@ def post_params
7272
end
7373

7474
def unprocessable
75-
Gem::Version.new(Rails.version) >= Gem::Version.new("7.1") ? :unprocessable_content : :unprocessable_entity
75+
Rails::VERSION::MAJOR > 7 || (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) ? :unprocessable_content : :unprocessable_entity
7676
end
7777
end

spec/requests/file_controller_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
RSpec.describe "File controller" do
44
def unprocessable
5-
Gem::Version.new(Rails.version) >= Gem::Version.new("7.1") ? :unprocessable_content : :unprocessable_entity
5+
Rails::VERSION::MAJOR > 7 || (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) ? :unprocessable_content : :unprocessable_entity
66
end
77

88
let(:blob) { create_blob(filename: "img.jpg", content_type: "image/jpeg") }

spec/service/active_storage/service/db_service_spec.rb

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,31 @@
7777
expect(compose).to be_a ActiveStorageDB::File
7878
expect(compose.data).to eq [first_db_file.data, second_db_file.data, third_db_file.data].join
7979
end
80+
81+
context "when a source key is missing" do
82+
subject(:compose) { service.compose(%w[key1 missing_key key3], "dest_key") }
83+
84+
it "raises FileNotFoundError" do
85+
expect { compose }.to raise_exception(ActiveStorage::FileNotFoundError)
86+
end
87+
88+
it "does not create the destination file" do
89+
compose rescue nil # rubocop:disable Style/RescueModifier
90+
expect(ActiveStorageDB::File.find_by(ref: "dest_key")).to be_nil
91+
end
92+
end
93+
94+
context "with empty source keys" do
95+
subject(:compose) { service.compose([], "dest_key") }
96+
97+
it "does not create a destination file" do
98+
expect { compose }.not_to change(ActiveStorageDB::File, :count)
99+
end
100+
101+
it "returns nil" do
102+
expect(compose).to be_nil
103+
end
104+
end
80105
end
81106
end
82107

@@ -238,7 +263,7 @@
238263
end
239264

240265
describe ".url_for_direct_upload" do
241-
subject do
266+
subject(:url_for_direct_upload) do
242267
service.url_for_direct_upload(
243268
key,
244269
expires_in: 5.minutes,
@@ -260,5 +285,49 @@
260285
after { service.delete(key) }
261286

262287
it { is_expected.to start_with "#{url_options[:protocol]}#{url_options[:host]}" }
288+
289+
it "generates a token that contains the expected fields" do
290+
url = url_for_direct_upload
291+
token = url.split("/").last
292+
verified = ActiveStorage.verifier.verified(token, purpose: :blob_token).symbolize_keys
293+
expect(verified).to include(
294+
key: key,
295+
content_type: content_type,
296+
content_length: fixture_data.size,
297+
checksum: checksum
298+
)
299+
expect(verified[:service_name]).to be_present
300+
end
301+
end
302+
303+
describe ".ensure_integrity_of" do
304+
before { upload }
305+
306+
after { service.delete(key) rescue nil } # rubocop:disable Style/RescueModifier
307+
308+
context "when the record is missing during integrity check" do
309+
it "raises FileNotFoundError" do
310+
# Simulate the record disappearing between upload and integrity check
311+
allow(service).to receive(:object_for).with(key).and_return(nil)
312+
313+
expect {
314+
service.send(:ensure_integrity_of, key, "bad_checksum")
315+
}.to raise_exception(ActiveStorage::FileNotFoundError)
316+
end
317+
end
318+
end
319+
320+
describe "chunk_size validation" do
321+
it "enforces a minimum chunk size of 1" do
322+
allow(ENV).to receive(:fetch).with("ASDB_CHUNK_SIZE").and_return("0")
323+
svc = described_class.configure(:tmp, tmp: { service: "DB" })
324+
expect(svc.instance_variable_get(:@chunk_size)).to eq(1)
325+
end
326+
327+
it "enforces a minimum chunk size for negative values" do
328+
allow(ENV).to receive(:fetch).with("ASDB_CHUNK_SIZE").and_return("-5")
329+
svc = described_class.configure(:tmp, tmp: { service: "DB" })
330+
expect(svc.instance_variable_get(:@chunk_size)).to eq(1)
331+
end
263332
end
264333
end

spec/support/output.rb

Lines changed: 0 additions & 6 deletions
This file was deleted.

spec/support/shared_contexts/rake_context.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# frozen_string_literal: true
22

3-
shared_context 'with rake tasks' do
4-
Rails.application.load_tasks
3+
require "rake"
4+
5+
shared_context "with rake tasks" do
6+
before(:context) do
7+
Rails.application.load_tasks unless Rake::Task.task_defined?("environment")
8+
end
59

610
def execute_task(task, args = nil)
711
with_captured_stdout do

0 commit comments

Comments
 (0)