active_storage_db 1.1.0 → 1.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23bbb0b9bf0759169020c1da8e4a8d5028938ffdb1b13dc4a356783da06eb868
4
- data.tar.gz: 1e1e1146a363bba15efe440cd82fbc4926763596746995f8696c24ca9cb9789c
3
+ metadata.gz: 1a465e1b93b8ab6436f800b2a0ff6e38f049fae621587924d9b2299f3fa06946
4
+ data.tar.gz: 78583f7c6aa00b0a8c7ab1d98f05d4d34e8ff8a1846f0ed70a0c3e16e97275c7
5
5
  SHA512:
6
- metadata.gz: cc1229568804d8a6c1a0912f8ddcf59895d12374a57b460a6f29c8bce1aac7216c936b6b8c255ea41563f39b22c748e34645bac787da24886d9c02bcd367adb3
7
- data.tar.gz: b85e8f1b41dfc2e01cfb566efd561a44606959cd1cb6b3a9cf718e4618b396949aad3fa87f8037ba61eda0789425bf9b4cf1031805d370c4702d2c973a5dfe5b
6
+ metadata.gz: 76ef220615b3e3704a99803a8bbe3af211855278d3532a97bbc9f325a5455ff393dde657180368d10bdecce8592afe99a41be7f6fe28e325d3671fa3b414087a
7
+ data.tar.gz: c555f306068194447e708c743abdc829f8043728585da32bbf504d689571d38a72725dbe97382665bd20fdaa95848b48ee018d45ab5d22692c1736ad9bcc59cb
data/README.md CHANGED
@@ -1,16 +1,19 @@
1
1
  # Active Storage DB
2
2
 
3
3
  [![gem version](https://badge.fury.io/rb/active_storage_db.svg)](https://badge.fury.io/rb/active_storage_db)
4
+ [![gem downloads](https://badgen.net/rubygems/dt/active_storage_db)](https://rubygems.org/gems/active_storage_db)
5
+ [![maintainability](https://api.codeclimate.com/v1/badges/92e1e703c308744a0f66/maintainability)](https://codeclimate.com/github/blocknotes/active_storage_db/maintainability)
6
+
4
7
  [![linters](https://github.com/blocknotes/active_storage_db/actions/workflows/linters.yml/badge.svg)](https://github.com/blocknotes/active_storage_db/actions/workflows/linters.yml)
5
- [![specs Postgres](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_postgres_70.yml/badge.svg)](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_postgres_70.yml)
6
- [![Specs MySQL](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_mysql_70.yml/badge.svg)](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_mysql_70.yml)
8
+ [![specs Postgres](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_postgres_71.yml/badge.svg)](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_postgres_71.yml)
9
+ [![specs MySQL](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_mysql_71.yml/badge.svg)](https://github.com/blocknotes/active_storage_db/actions/workflows/specs_mysql_71.yml)
7
10
 
8
11
  An Active Storage service upload/download plugin that stores files in a PostgreSQL or MySQL database.
9
12
 
10
13
  Main features:
11
- - supports Rails _6.0_, _6.1_ and _7.0_;
14
+ - attachment data stored in a binary field (or blob);
12
15
  - all service methods implemented;
13
- - attachment data stored in a binary field (or blob).
16
+ - supports Rails _6_ and _7_.
14
17
 
15
18
  Useful also with platforms like Heroku (due to their ephemeral file system).
16
19
 
@@ -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 :not_found
11
+ head(:not_found)
12
12
  end
13
13
  rescue ActiveStorage::FileNotFoundError
14
- head :not_found
14
+ head(:not_found)
15
15
  end
16
16
 
17
17
  def update
18
18
  if (token = decode_verified_token)
19
- if acceptable_content?(token)
20
- db_service.upload(token[:key], request.body, checksum: token[:checksum])
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 :not_found
22
+ head(:not_found)
26
23
  end
27
24
  rescue ActiveStorage::IntegrityError
28
- head :unprocessable_entity
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 db_service.download(key), options
53
+ send_data(db_service.download(key), options)
47
54
  end
48
55
 
49
- def decode_verified_token
50
- ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
51
- end
56
+ def upload_file(token, body:)
57
+ return false unless acceptable_content?(token)
52
58
 
53
- def acceptable_content?(token)
54
- token[:content_type] == request.content_mime_type && token[:content_length] == request.content_length
59
+ db_service.upload(token[:key], request.body, checksum: token[:checksum])
60
+ true
55
61
  end
56
62
  end
57
63
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageDB
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageDB
4
- class File < ActiveRecord::Base
4
+ class File < ApplicationRecord
5
5
  validates :ref,
6
6
  presence: true,
7
7
  allow_blank: false,
@@ -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
- t.datetime :created_at, null: false
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
@@ -36,18 +36,14 @@ module ActiveStorage
36
36
  end
37
37
  else
38
38
  instrument :download, key: key do
39
- record = ::ActiveStorageDB::File.find_by(ref: key)
40
- raise(ActiveStorage::FileNotFoundError) unless record
41
-
42
- record.data
39
+ retrieve_file(key)
43
40
  end
44
41
  end
45
42
  end
46
43
 
47
44
  def download_chunk(key, range)
48
45
  instrument :download_chunk, key: key, range: range do
49
- chunk_select = "SUBSTRING(data FROM #{range.begin + 1} FOR #{range.size}) AS chunk"
50
- record = ::ActiveStorageDB::File.select(chunk_select).find_by(ref: key)
46
+ record = object_for(key, fields: "SUBSTRING(data FROM #{range.begin + 1} FOR #{range.size}) AS chunk")
51
47
  raise(ActiveStorage::FileNotFoundError) unless record
52
48
 
53
49
  record.chunk
@@ -63,7 +59,7 @@ module ActiveStorage
63
59
 
64
60
  def delete_prefixed(prefix)
65
61
  instrument :delete_prefixed, prefix: prefix do
66
- ::ActiveStorageDB::File.where('ref LIKE ?', "#{prefix}%").destroy_all
62
+ ::ActiveStorageDB::File.where('ref LIKE ?', "#{ApplicationRecord.sanitize_sql_like(prefix)}%").destroy_all
67
63
  end
68
64
  end
69
65
 
@@ -127,17 +123,26 @@ module ActiveStorage
127
123
  end
128
124
 
129
125
  def ensure_integrity_of(key, checksum)
130
- file = ::ActiveStorageDB::File.find_by(ref: key)
131
- return if Digest::MD5.base64digest(file.data) == checksum
126
+ return if Digest::MD5.base64digest(object_for(key).data) == checksum
132
127
 
133
128
  delete(key)
134
129
  raise ActiveStorage::IntegrityError
135
130
  end
136
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
+
137
144
  def stream(key)
138
- size =
139
- ::ActiveStorageDB::File.select('OCTET_LENGTH(data) AS size').find_by(ref: key)&.size ||
140
- raise(ActiveStorage::FileNotFoundError)
145
+ size = object_for(key, fields: 'OCTET_LENGTH(data) AS size')&.size || raise(ActiveStorage::FileNotFoundError)
141
146
  (size / @chunk_size.to_f).ceil.times.each do |i|
142
147
  range = (i * @chunk_size..((i + 1) * @chunk_size) - 1)
143
148
  yield download_chunk(key, range)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageDB
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -20,7 +20,7 @@ namespace :asdb do
20
20
  desc 'ActiveStorageDB: list attachments ordered by blob id desc'
21
21
  task list: [:environment] do |_t, _args|
22
22
  query = ::ActiveStorage::Blob.order(id: :desc).limit(100)
23
- digits = Math.log(query.maximum(:id), 10).to_i + 1
23
+ digits = query.ids.inject(0) { |ret, id| size = id.to_s.size; [size, ret].max }
24
24
 
25
25
  ::ActiveStorage::Tasks.print_blob_header(digits: digits)
26
26
  query.each do |blob|
@@ -52,7 +52,7 @@ namespace :asdb do
52
52
 
53
53
  blobs = ::ActiveStorage::Blob.where('filename LIKE ?', "%#{filename}%").order(id: :desc)
54
54
  if blobs.any?
55
- digits = Math.log(blobs.first.id, 10).to_i + 1
55
+ digits = blobs.ids.inject(0) { |ret, id| size = id.to_s.size; [size, ret].max }
56
56
  ::ActiveStorage::Tasks.print_blob_header(digits: digits)
57
57
  blobs.each do |blob|
58
58
  ::ActiveStorage::Tasks.print_blob(blob, digits: digits)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_storage_db
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mattia Roccoberton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-04 00:00:00.000000000 Z
11
+ date: 2023-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activestorage
@@ -77,6 +77,7 @@ files:
77
77
  - README.md
78
78
  - Rakefile
79
79
  - app/controllers/active_storage_db/files_controller.rb
80
+ - app/models/active_storage_db/application_record.rb
80
81
  - app/models/active_storage_db/file.rb
81
82
  - config/initializers/inflections.rb
82
83
  - config/routes.rb
@@ -104,14 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
105
  requirements:
105
106
  - - ">="
106
107
  - !ruby/object:Gem::Version
107
- version: 2.6.0
108
+ version: 2.7.0
108
109
  required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  requirements:
110
111
  - - ">="
111
112
  - !ruby/object:Gem::Version
112
113
  version: '0'
113
114
  requirements: []
114
- rubygems_version: 3.1.6
115
+ rubygems_version: 3.4.19
115
116
  signing_key:
116
117
  specification_version: 4
117
118
  summary: ActiveStorage DB Service