Skip to content

Commit 70c4220

Browse files
author
Mattia Roccoberton
authored
Merge pull request #19 from blocknotes/improve-rake-tasks
Improve rake tasks
2 parents 952c5a0 + 4548d65 commit 70c4220

3 files changed

Lines changed: 120 additions & 55 deletions

File tree

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
An Active Storage service upload/download plugin that stores files in a PostgreSQL or MySQL database.
99

1010
Main features:
11-
- supports Rails 6.0, 6.1 and 7.0;
11+
- supports Rails _6.0_, _6.1_ and _7.0_;
1212
- all service methods implemented;
1313
- attachment data stored in a binary field (or blob).
1414

@@ -30,10 +30,16 @@ db:
3030

3131
## Misc
3232

33-
Some rake tasks are available:
33+
Some utility tasks are available:
3434

35-
- `asdb:list`: list the stored attachments
36-
- `asdb:get`: download an attachment (ex. `bin/rails "asdb:get[ruby-logo.png,/tmp]"`)
35+
```sh
36+
# list attachments ordered by blob id desc (with limit 100):
37+
bin/rails 'asdb:list'
38+
# search attachments by filename (or part of it)
39+
bin/rails 'asdb:search[some_filename]'
40+
# download attachment by blob id (retrieved with list or search tasks) - the second argument is the destination:
41+
bin/rails 'asdb:download[123,/tmp]'
42+
```
3743

3844
## Do you like it? Star it!
3945

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,64 @@
11
# frozen_string_literal: true
22

3+
module ActiveStorage
4+
module Tasks
5+
module_function
6+
7+
def print_blob_header(digits: 0)
8+
puts ['Size'.rjust(8), 'Date'.rjust(18), 'Id'.rjust(digits + 2), ' Filename'].join
9+
end
10+
11+
def print_blob(blob, digits: 0)
12+
size = (blob.byte_size / 1024).to_s.rjust(7)
13+
date = blob.created_at.strftime('%Y-%m-%d %H:%M')
14+
puts "#{size}K #{date} #{blob.id.to_s.rjust(digits)} #{blob.filename}"
15+
end
16+
end
17+
end
18+
319
namespace :asdb do
4-
desc 'ActiveStorageDB: list attachments'
20+
desc 'ActiveStorageDB: list attachments ordered by blob id desc'
521
task list: [:environment] do |_t, _args|
6-
::ActiveStorage::Blob.order(:filename).pluck(:byte_size, :created_at, :filename).each do |size, dt, filename|
7-
size_k = (size / 1024).to_s.rjust(7)
8-
date = dt.strftime('%Y-%m-%d %H:%M')
9-
puts "#{size_k}K #{date} #{filename}"
22+
query = ::ActiveStorage::Blob.order(id: :desc).limit(100)
23+
digits = Math.log(query.maximum(:id), 10).to_i + 1
24+
25+
::ActiveStorage::Tasks.print_blob_header(digits: digits)
26+
query.each do |blob|
27+
::ActiveStorage::Tasks.print_blob(blob, digits: digits)
1028
end
1129
end
1230

13-
desc 'ActiveStorageDB: download attachment'
14-
task :get, [:src, :dst] => [:environment] do |_t, args|
15-
src = args[:src]&.strip
16-
dst = args[:dst]&.strip
17-
abort('Required arguments: source file, destination file') if src.blank? || dst.blank?
18-
19-
dst = "#{dst}/#{src}" if Dir.exist?(dst)
20-
dir = File.dirname(dst)
21-
abort("Can't write on: #{dir}") unless File.writable?(dir)
31+
desc 'ActiveStorageDB: download attachment by blob id'
32+
task :download, [:blob_id, :destination] => [:environment] do |_t, args|
33+
blob_id = args[:blob_id]&.strip
34+
destination = args[:destination]&.strip || Dir.pwd
35+
abort('Required arguments: source blob id, destination path') if blob_id.blank? || destination.blank?
2236

23-
blob = ::ActiveStorage::Blob.order(created_at: :desc).find_by(filename: src)
37+
blob = ::ActiveStorage::Blob.find_by(id: blob_id)
2438
abort('Source file not found') unless blob
2539

26-
ret = File.binwrite(dst, blob.download)
27-
puts "#{ret} bytes written"
28-
rescue StandardError => e
29-
puts e
40+
destination = "#{destination}/#{blob.filename}" if Dir.exist?(destination)
41+
dir = File.dirname(destination)
42+
abort("Can't write on path: #{dir}") unless File.writable?(dir)
43+
44+
ret = File.binwrite(destination, blob.download)
45+
puts "#{ret} bytes written - #{destination}"
46+
end
47+
48+
desc 'ActiveStorageDB: search attachment by filename (or part of it)'
49+
task :search, [:filename] => [:environment] do |_t, args|
50+
filename = args[:filename]&.strip
51+
abort('Required arguments: filename') if filename.blank?
52+
53+
blobs = ::ActiveStorage::Blob.where('filename LIKE ?', "%#{filename}%").order(id: :desc)
54+
if blobs.any?
55+
digits = Math.log(blobs.first.id, 10).to_i + 1
56+
::ActiveStorage::Tasks.print_blob_header(digits: digits)
57+
blobs.each do |blob|
58+
::ActiveStorage::Tasks.print_blob(blob, digits: digits)
59+
end
60+
else
61+
puts 'No results'
62+
end
3063
end
3164
end

spec/tasks/active_storage_db_tasks_spec.rb

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,52 @@
66
describe 'asdb:list' do
77
subject(:task) { execute_task('asdb:list') }
88

9-
let(:file1) { create(:active_storage_blob, filename: 'file1', created_at: Time.now - 1.hour) }
10-
let(:file2) { create(:active_storage_blob, filename: 'file2', created_at: Time.now - 5.hour) }
11-
let(:file3) { create(:active_storage_blob, filename: 'file3', created_at: Time.now - 3.hour) }
9+
let(:file1) { create(:active_storage_blob, filename: 'some file 1', created_at: Time.now - 1.hour) }
10+
let(:file2) { create(:active_storage_blob, filename: 'some file 2', created_at: Time.now - 5.hour) }
11+
let(:file3) { create(:active_storage_blob, filename: 'some file 3', created_at: Time.now - 3.hour) }
1212

1313
before do
1414
[file1, file2, file3]
1515
end
1616

17-
it { is_expected.to match /file1.+file2.+file3/m }
18-
19-
it 'prints the list of 3 files' do
20-
expect(task.split("\n").size).to be 3
17+
it 'prints the columns header + the list of 3 files', :aggregate_failures do
18+
pattern = /#{file3.id} #{file3.filename}.+#{file2.id} #{file2.filename}.+#{file1.id} #{file1.filename}/m
19+
expect(task).to match pattern
20+
expect(task.split("\n").size).to be 4
2121
end
2222
end
2323

24-
describe 'asdb:get' do
25-
subject(:task) { execute_task('asdb:get') }
24+
describe 'asdb:download' do
25+
subject(:task) { execute_task('asdb:download') }
2626

2727
it 'exits showing the required arguments' do
2828
with_captured_stderr do
29-
expect { task }.to raise_exception(SystemExit, /Required arguments/)
29+
expect { task }.to raise_exception(SystemExit, /Required arguments: source blob id, destination path/)
3030
end
3131
end
3232

33-
context 'with only the source specified' do
34-
subject(:task) { execute_task('asdb:get', src: 'some_file') }
33+
context 'with a missing source' do
34+
subject(:task) { execute_task('asdb:download', blob_id: 'some_file') }
35+
36+
before do
37+
allow(File).to receive(:writable?).and_return(true)
38+
end
3539

36-
it 'exits showing the required arguments' do
40+
it 'exits showing a not found error' do
3741
with_captured_stderr do
38-
expect { task }.to raise_exception(SystemExit, /Required arguments/)
42+
expect { task }.to raise_exception(SystemExit, /Source file not found/)
3943
end
4044
end
4145
end
4246

4347
context 'with an invalid destination' do
44-
subject(:task) { execute_task('asdb:get', src: 'some_file', dst: 'some_path') }
48+
subject(:task) { execute_task('asdb:download', blob_id: 'some_file', destination: 'some_path') }
49+
50+
let(:blob) { build_stubbed(:active_storage_blob) }
4551

4652
before do
4753
allow(File).to receive(:writable?).and_return(false)
54+
allow(ActiveStorage::Blob).to receive(:find_by).and_return(blob)
4855
end
4956

5057
it 'exits showing a write error' do
@@ -54,35 +61,54 @@
5461
end
5562
end
5663

57-
context 'with a missing source' do
58-
subject(:task) { execute_task('asdb:get', src: 'some_file', dst: 'some_path') }
64+
context 'with valid arguments' do
65+
subject(:task) { execute_task('asdb:download', blob_id: 'some_file', destination: 'some_path') }
66+
67+
let(:blob) { build_stubbed(:active_storage_blob) }
5968

6069
before do
61-
allow(File).to receive(:writable?).and_return(true)
70+
allow(File).to receive_messages(binwrite: 1000, writable?: true)
71+
allow(ActiveStorage::Blob).to receive(:find_by).and_return(blob)
72+
allow(blob).to receive(:download).and_return('some data')
6273
end
6374

64-
it 'exits showing a not found error' do
65-
with_captured_stderr do
66-
expect { task }.to raise_exception(SystemExit, /Source file not found/)
67-
end
75+
it 'prints the number of bytes written' do
76+
expect(task).to eq "1000 bytes written - some_path\n"
6877
end
6978
end
79+
end
7080

71-
context 'with valid arguments' do
72-
subject(:task) { execute_task('asdb:get', src: blob.filename.to_s, dst: 'some_path') }
81+
describe 'asdb:search' do
82+
subject(:task) { execute_task('asdb:search') }
7383

74-
let(:blob) { build_stubbed(:active_storage_blob) }
75-
let(:blobs) { instance_double(ActiveRecord::Relation) }
84+
let(:blob) { build_stubbed(:active_storage_blob) }
85+
86+
it 'exits showing the required arguments' do
87+
with_captured_stderr do
88+
expect { task }.to raise_exception(SystemExit, /Required arguments/)
89+
end
90+
end
91+
92+
context 'when no files are found' do
93+
subject(:task) { execute_task('asdb:search', filename: 'just ') }
94+
95+
it 'prints "No results" message' do
96+
expect(task).to eq "No results\n"
97+
end
98+
end
99+
100+
context 'with there are some results' do
101+
subject(:task) { execute_task('asdb:search', filename: 'just ') }
76102

77103
before do
78-
allow(File).to receive_messages(binwrite: 1000, writable?: true)
79-
allow(ActiveStorage::Blob).to receive(:order).and_return(blobs)
80-
allow(blobs).to receive(:find_by).and_return(blob)
81-
allow(blob).to receive(:download).and_return('some data')
104+
create(:active_storage_blob, filename: 'just a file', created_at: Time.now - 1.hour)
105+
create(:active_storage_blob, filename: 'just another file', created_at: Time.now - 5.hour)
106+
create(:active_storage_blob, filename: 'the last file', created_at: Time.now - 3.hour)
82107
end
83108

84-
it 'prints the number of bytes written' do
85-
expect(task).to eq "1000 bytes written\n"
109+
it 'prints the files that matches', :aggregate_failures do
110+
expect(task).to match /just another file.+just a file/m
111+
expect(task.split("\n").size).to be 3
86112
end
87113
end
88114
end

0 commit comments

Comments
 (0)