active_storage_db 0.2.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +28 -6
- data/Rakefile +2 -1
- data/app/controllers/active_storage_db/files_controller.rb +22 -16
- data/db/migrate/20200702202022_create_active_storage_db_files.rb +15 -2
- data/lib/active_storage/service/db_service.rb +68 -52
- data/lib/active_storage/service/db_service_rails60.rb +25 -0
- data/lib/active_storage/service/db_service_rails61.rb +37 -0
- data/lib/active_storage/service/db_service_rails70.rb +50 -0
- data/lib/active_storage_db/version.rb +1 -1
- data/lib/tasks/active_storage_db_tasks.rake +52 -19
- metadata +29 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33872893aefc8f1086a8c422819990e10d1d46d2b97bd87d67662fd153536243
|
4
|
+
data.tar.gz: 0e662cd62a06ec353c9e02706c1c553500edafa146e4149d21fa57626d129d5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a418655f7275cc54264b25733712cbe813eeede79eb7d08833c2e333427d7a9c1cb44f1c46b3a338cf20407abf0d7d9b59e2dfcdec0f1467b77f47ca85b7357e
|
7
|
+
data.tar.gz: ac8d6c3c8fc9c44177d474c709f67ff5a46a8c1835c15e9f753773ac846851e23bd13ef2e8e0c3fd13185ea0d8f528f31aac8ae48275a46288bde22a6ad04dee
|
data/README.md
CHANGED
@@ -1,38 +1,60 @@
|
|
1
|
-
# Active Storage DB
|
1
|
+
# Active Storage DB
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/active_storage_db)
|
4
|
+
[](https://rubygems.org/gems/active_storage_db)
|
5
|
+
[](https://codeclimate.com/github/blocknotes/active_storage_db/maintainability)
|
6
|
+
|
7
|
+
[](https://github.com/blocknotes/active_storage_db/actions/workflows/linters.yml)
|
8
|
+
[](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_postgres_70.yml)
|
9
|
+
[](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_mysql_70.yml)
|
10
|
+
|
2
11
|
An Active Storage service upload/download plugin that stores files in a PostgreSQL or MySQL database.
|
3
12
|
|
4
13
|
Main features:
|
14
|
+
- supports Rails _6.0_, _6.1_ and _7.0_;
|
5
15
|
- all service methods implemented;
|
6
|
-
- data
|
7
|
-
- RSpec tests.
|
16
|
+
- attachment data stored in a binary field (or blob).
|
8
17
|
|
9
18
|
Useful also with platforms like Heroku (due to their ephemeral file system).
|
10
19
|
|
11
20
|
## Installation
|
21
|
+
|
12
22
|
- Setup Active Storage in your Rails application
|
13
23
|
- Add to your Gemfile `gem 'active_storage_db'` (and execute: `bundle`)
|
14
24
|
- Install the gem migrations: `bin/rails active_storage_db:install:migrations` (and execute: `bin/rails db:migrate`)
|
15
25
|
- Add to your `config/routes.rb`: `mount ActiveStorageDB::Engine => '/active_storage_db'`
|
16
26
|
- Change Active Storage service in *config/environments/development.rb* to: `config.active_storage.service = :db`
|
17
27
|
- Add to *config/storage.yml*:
|
28
|
+
|
18
29
|
```
|
19
30
|
db:
|
20
31
|
service: DB
|
21
32
|
```
|
22
33
|
|
23
34
|
## Misc
|
24
|
-
|
25
|
-
|
26
|
-
|
35
|
+
|
36
|
+
Some utility tasks are available:
|
37
|
+
|
38
|
+
```sh
|
39
|
+
# list attachments ordered by blob id desc (with limit 100):
|
40
|
+
bin/rails 'asdb:list'
|
41
|
+
# search attachments by filename (or part of it)
|
42
|
+
bin/rails 'asdb:search[some_filename]'
|
43
|
+
# download attachment by blob id (retrieved with list or search tasks) - the second argument is the destination:
|
44
|
+
bin/rails 'asdb:download[123,/tmp]'
|
45
|
+
```
|
27
46
|
|
28
47
|
## Do you like it? Star it!
|
48
|
+
|
29
49
|
If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
|
30
50
|
|
31
51
|
Or consider offering me a coffee, it's a small thing but it is greatly appreciated: [about me](https://www.blocknot.es/about-me).
|
32
52
|
|
33
53
|
## Contributors
|
54
|
+
|
34
55
|
- [Mattia Roccoberton](https://blocknot.es/): author
|
35
56
|
- Inspired by [activestorage-database-service](https://github.com/TitovDigital/activestorage-database-service) project
|
36
57
|
|
37
58
|
## License
|
59
|
+
|
38
60
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -16,7 +16,8 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
16
16
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
app_ver = ENV.fetch('RAILS', '').tr('.', '')
|
20
|
+
APP_RAKEFILE = File.expand_path("spec/dummy#{app_ver}/Rakefile", __dir__)
|
20
21
|
load 'rails/tasks/engine.rake'
|
21
22
|
|
22
23
|
load 'rails/tasks/statistics.rake'
|
@@ -8,34 +8,41 @@ module ActiveStorageDB
|
|
8
8
|
if (key = decode_verified_key)
|
9
9
|
serve_file(key[:key], content_type: key[:content_type], disposition: key[:disposition])
|
10
10
|
else
|
11
|
-
head
|
11
|
+
head(:not_found)
|
12
12
|
end
|
13
13
|
rescue ActiveStorage::FileNotFoundError
|
14
|
-
head
|
14
|
+
head(:not_found)
|
15
15
|
end
|
16
16
|
|
17
17
|
def update
|
18
18
|
if (token = decode_verified_token)
|
19
|
-
|
20
|
-
|
21
|
-
else
|
22
|
-
head :unprocessable_entity
|
23
|
-
end
|
19
|
+
file_uploaded = upload_file(token, body: request.body)
|
20
|
+
head(file_uploaded ? :no_content : :unprocessable_entity)
|
24
21
|
else
|
25
|
-
head
|
22
|
+
head(:not_found)
|
26
23
|
end
|
27
24
|
rescue ActiveStorage::IntegrityError
|
28
|
-
head
|
25
|
+
head(:unprocessable_entity)
|
29
26
|
end
|
30
27
|
|
31
28
|
private
|
32
29
|
|
30
|
+
def acceptable_content?(token)
|
31
|
+
token[:content_type] == request.content_mime_type && token[:content_length] == request.content_length
|
32
|
+
end
|
33
|
+
|
33
34
|
def db_service
|
34
35
|
ActiveStorage::Blob.service
|
35
36
|
end
|
36
37
|
|
37
38
|
def decode_verified_key
|
38
|
-
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
39
|
+
key = ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
40
|
+
key&.deep_symbolize_keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def decode_verified_token
|
44
|
+
token = ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
|
45
|
+
token&.deep_symbolize_keys
|
39
46
|
end
|
40
47
|
|
41
48
|
def serve_file(key, content_type:, disposition:)
|
@@ -43,15 +50,14 @@ module ActiveStorageDB
|
|
43
50
|
type: content_type || DEFAULT_SEND_FILE_TYPE,
|
44
51
|
disposition: disposition || DEFAULT_SEND_FILE_DISPOSITION
|
45
52
|
}
|
46
|
-
send_data
|
53
|
+
send_data(db_service.download(key), options)
|
47
54
|
end
|
48
55
|
|
49
|
-
def
|
50
|
-
|
51
|
-
end
|
56
|
+
def upload_file(token, body:)
|
57
|
+
return false unless acceptable_content?(token)
|
52
58
|
|
53
|
-
|
54
|
-
|
59
|
+
db_service.upload(token[:key], request.body, checksum: token[:checksum])
|
60
|
+
true
|
55
61
|
end
|
56
62
|
end
|
57
63
|
end
|
@@ -2,12 +2,25 @@
|
|
2
2
|
|
3
3
|
class CreateActiveStorageDBFiles < ActiveRecord::Migration[6.0]
|
4
4
|
def change
|
5
|
-
create_table :active_storage_db_files do |t|
|
5
|
+
create_table :active_storage_db_files, id: primary_key_type do |t|
|
6
6
|
t.string :ref, null: false
|
7
7
|
t.binary :data, null: false
|
8
|
-
|
8
|
+
|
9
|
+
if connection.supports_datetime_with_precision?
|
10
|
+
t.datetime :created_at, precision: 6, null: false
|
11
|
+
else
|
12
|
+
t.datetime :created_at, null: false
|
13
|
+
end
|
9
14
|
|
10
15
|
t.index [:ref], unique: true
|
11
16
|
end
|
12
17
|
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def primary_key_type
|
22
|
+
config = Rails.configuration.generators
|
23
|
+
primary_key_type = config.options[config.orm][:primary_key_type]
|
24
|
+
primary_key_type || :primary_key
|
25
|
+
end
|
13
26
|
end
|
@@ -1,9 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
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
|
+
|
3
7
|
module ActiveStorage
|
4
|
-
|
5
|
-
|
8
|
+
# Wraps a DB table as an Active Storage service. See ActiveStorage::Service
|
9
|
+
# for the generic API documentation that applies to all services.
|
10
|
+
class Service::DBService < Service
|
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, **)
|
6
20
|
@chunk_size = ENV.fetch('ASDB_CHUNK_SIZE') { 1.megabytes }
|
21
|
+
@public = public
|
7
22
|
end
|
8
23
|
|
9
24
|
def upload(key, io, checksum: nil, **)
|
@@ -21,29 +36,30 @@ module ActiveStorage
|
|
21
36
|
end
|
22
37
|
else
|
23
38
|
instrument :download, key: key do
|
24
|
-
|
25
|
-
record&.data || raise(ActiveStorage::FileNotFoundError)
|
39
|
+
retrieve_file(key)
|
26
40
|
end
|
27
41
|
end
|
28
42
|
end
|
29
43
|
|
30
44
|
def download_chunk(key, range)
|
31
45
|
instrument :download_chunk, key: key, range: range do
|
32
|
-
|
33
|
-
::
|
34
|
-
|
46
|
+
record = object_for(key, fields: "SUBSTRING(data FROM #{range.begin + 1} FOR #{range.size}) AS chunk")
|
47
|
+
raise(ActiveStorage::FileNotFoundError) unless record
|
48
|
+
|
49
|
+
record.chunk
|
35
50
|
end
|
36
51
|
end
|
37
52
|
|
38
53
|
def delete(key)
|
39
54
|
instrument :delete, key: key do
|
40
55
|
::ActiveStorageDB::File.find_by(ref: key)&.destroy
|
56
|
+
# Ignore files already deleted
|
41
57
|
end
|
42
58
|
end
|
43
59
|
|
44
60
|
def delete_prefixed(prefix)
|
45
61
|
instrument :delete_prefixed, prefix: prefix do
|
46
|
-
::ActiveStorageDB::File.where('ref LIKE ?', "#{prefix}%").destroy_all
|
62
|
+
::ActiveStorageDB::File.where('ref LIKE ?', "#{ApplicationRecord.sanitize_sql_like(prefix)}%").destroy_all
|
47
63
|
end
|
48
64
|
end
|
49
65
|
|
@@ -55,53 +71,23 @@ module ActiveStorage
|
|
55
71
|
end
|
56
72
|
end
|
57
73
|
|
58
|
-
def
|
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
|
-
|
86
|
-
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
74
|
+
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, custom_metadata: {})
|
87
75
|
instrument :url, key: key do |payload|
|
88
76
|
verified_token_with_expiration = ActiveStorage.verifier.generate(
|
89
77
|
{
|
90
78
|
key: key,
|
91
79
|
content_type: content_type,
|
92
80
|
content_length: content_length,
|
93
|
-
checksum: checksum
|
81
|
+
checksum: checksum,
|
82
|
+
service_name: respond_to?(:name) ? name : 'db'
|
94
83
|
},
|
95
84
|
expires_in: expires_in,
|
96
85
|
purpose: :blob_token
|
97
86
|
)
|
98
|
-
generated_url = url_helpers.update_service_url(
|
99
|
-
verified_token_with_expiration,
|
100
|
-
host: current_host
|
101
|
-
)
|
102
|
-
payload[:url] = generated_url
|
103
87
|
|
104
|
-
generated_url
|
88
|
+
url_helpers.update_service_url(verified_token_with_expiration, url_options).tap do |generated_url|
|
89
|
+
payload[:url] = generated_url
|
90
|
+
end
|
105
91
|
end
|
106
92
|
end
|
107
93
|
|
@@ -111,24 +97,54 @@ module ActiveStorage
|
|
111
97
|
|
112
98
|
private
|
113
99
|
|
114
|
-
def
|
115
|
-
|
100
|
+
def generate_url(key, expires_in:, filename:, content_type:, disposition:)
|
101
|
+
content_disposition = content_disposition_with(type: disposition, filename: filename)
|
102
|
+
verified_key_with_expiration = ActiveStorage.verifier.generate(
|
103
|
+
{
|
104
|
+
key: key,
|
105
|
+
disposition: content_disposition,
|
106
|
+
content_type: content_type,
|
107
|
+
service_name: respond_to?(:name) ? name : 'db'
|
108
|
+
},
|
109
|
+
expires_in: expires_in,
|
110
|
+
purpose: :blob_key
|
111
|
+
)
|
112
|
+
|
113
|
+
current_uri = URI.parse(current_host)
|
114
|
+
url_helpers.service_url(
|
115
|
+
verified_key_with_expiration,
|
116
|
+
protocol: current_uri.scheme,
|
117
|
+
host: current_uri.host,
|
118
|
+
port: current_uri.port,
|
119
|
+
disposition: content_disposition,
|
120
|
+
content_type: content_type,
|
121
|
+
filename: filename
|
122
|
+
)
|
116
123
|
end
|
117
124
|
|
118
125
|
def ensure_integrity_of(key, checksum)
|
119
|
-
|
120
|
-
return if Digest::MD5.base64digest(file.data) == checksum
|
126
|
+
return if Digest::MD5.base64digest(object_for(key).data) == checksum
|
121
127
|
|
122
128
|
delete(key)
|
123
129
|
raise ActiveStorage::IntegrityError
|
124
130
|
end
|
125
131
|
|
132
|
+
def retrieve_file(key)
|
133
|
+
file = object_for(key)
|
134
|
+
raise(ActiveStorage::FileNotFoundError) unless file
|
135
|
+
|
136
|
+
file.data
|
137
|
+
end
|
138
|
+
|
139
|
+
def object_for(key, fields: nil)
|
140
|
+
as_file = fields ? ::ActiveStorageDB::File.select(*fields) : ::ActiveStorageDB::File
|
141
|
+
as_file.find_by(ref: key)
|
142
|
+
end
|
143
|
+
|
126
144
|
def stream(key)
|
127
|
-
size =
|
128
|
-
::ActiveStorageDB::File.select('OCTET_LENGTH(data) AS size').find_by(ref: key)&.size ||
|
129
|
-
raise(ActiveStorage::FileNotFoundError)
|
145
|
+
size = object_for(key, fields: 'OCTET_LENGTH(data) AS size')&.size || raise(ActiveStorage::FileNotFoundError)
|
130
146
|
(size / @chunk_size.to_f).ceil.times.each do |i|
|
131
|
-
range = (i * @chunk_size..(i + 1) * @chunk_size - 1)
|
147
|
+
range = (i * @chunk_size..((i + 1) * @chunk_size) - 1)
|
132
148
|
yield download_chunk(key, range)
|
133
149
|
end
|
134
150
|
end
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -1,31 +1,64 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
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
|
+
|
3
19
|
namespace :asdb do
|
4
|
-
desc 'ActiveStorageDB: list attachments'
|
20
|
+
desc 'ActiveStorageDB: list attachments ordered by blob id desc'
|
5
21
|
task list: [:environment] do |_t, _args|
|
6
|
-
::ActiveStorage::Blob.order(:
|
7
|
-
|
8
|
-
|
9
|
-
|
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)
|
10
28
|
end
|
11
29
|
end
|
12
30
|
|
13
|
-
desc 'ActiveStorageDB: download attachment'
|
14
|
-
task :
|
15
|
-
|
16
|
-
|
17
|
-
abort('Required arguments: source
|
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?
|
22
36
|
|
23
|
-
blob = ::ActiveStorage::Blob.
|
37
|
+
blob = ::ActiveStorage::Blob.find_by(id: blob_id)
|
24
38
|
abort('Source file not found') unless blob
|
25
39
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
30
63
|
end
|
31
64
|
end
|
metadata
CHANGED
@@ -1,43 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_storage_db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mattia Roccoberton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activestorage
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '6.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '6.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '6.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '6.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: appraisal
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.4'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: factory_bot_rails
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -69,6 +83,9 @@ files:
|
|
69
83
|
- config/routes.rb
|
70
84
|
- db/migrate/20200702202022_create_active_storage_db_files.rb
|
71
85
|
- lib/active_storage/service/db_service.rb
|
86
|
+
- lib/active_storage/service/db_service_rails60.rb
|
87
|
+
- lib/active_storage/service/db_service_rails61.rb
|
88
|
+
- lib/active_storage/service/db_service_rails70.rb
|
72
89
|
- lib/active_storage_db.rb
|
73
90
|
- lib/active_storage_db/engine.rb
|
74
91
|
- lib/active_storage_db/version.rb
|
@@ -76,7 +93,10 @@ files:
|
|
76
93
|
homepage: https://github.com/blocknotes/active_storage_db
|
77
94
|
licenses:
|
78
95
|
- MIT
|
79
|
-
metadata:
|
96
|
+
metadata:
|
97
|
+
homepage_uri: https://github.com/blocknotes/active_storage_db
|
98
|
+
source_code_uri: https://github.com/blocknotes/active_storage_db
|
99
|
+
rubygems_mfa_required: 'true'
|
80
100
|
post_install_message:
|
81
101
|
rdoc_options: []
|
82
102
|
require_paths:
|
@@ -85,14 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
105
|
requirements:
|
86
106
|
- - ">="
|
87
107
|
- !ruby/object:Gem::Version
|
88
|
-
version: 2.
|
108
|
+
version: 2.6.0
|
89
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
110
|
requirements:
|
91
111
|
- - ">="
|
92
112
|
- !ruby/object:Gem::Version
|
93
113
|
version: '0'
|
94
114
|
requirements: []
|
95
|
-
rubygems_version: 3.
|
115
|
+
rubygems_version: 3.1.6
|
96
116
|
signing_key:
|
97
117
|
specification_version: 4
|
98
118
|
summary: ActiveStorage DB Service
|