Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion active_storage_db.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

$:.push File.expand_path("lib", __dir__)
$LOAD_PATH.unshift File.expand_path("lib", __dir__)

# Maintain your gem's version:
require "active_storage_db/version"
Expand Down
12 changes: 2 additions & 10 deletions app/controllers/active_storage_db/files_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ def show
def update
if (token = decode_verified_token)
file_uploaded = upload_file(token)
head(file_uploaded ? :no_content : unprocessable)
head(file_uploaded ? :no_content : ActiveStorageDB::UNPROCESSABLE_STATUS)
else
head(:not_found)
end
rescue ActiveStorage::IntegrityError
head(unprocessable)
head(ActiveStorageDB::UNPROCESSABLE_STATUS)
end

private
Expand Down Expand Up @@ -60,13 +60,5 @@ def upload_file(token) # rubocop:disable Naming/PredicateMethod
db_service.upload(token[:key], request.body, checksum: token[:checksum])
true
end

def unprocessable
if Rails::VERSION::MAJOR > 7 || (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1)
:unprocessable_content
else
:unprocessable_entity
end
end
end
end
11 changes: 9 additions & 2 deletions lib/active_storage/service/db_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ class Service::DBService < Service

def initialize(public: false, **)
@chunk_size = [ENV.fetch("ASDB_CHUNK_SIZE") { 1.megabytes }.to_i, MINIMUM_CHUNK_SIZE].max
@max_size = ENV.fetch("ASDB_MAX_FILE_SIZE", nil)&.to_i
@public = public
end

def upload(key, io, checksum: nil, **)
instrument :upload, key: key, checksum: checksum do
data = io.read
if @max_size && data.bytesize > @max_size
raise ArgumentError, "File size exceeds the maximum allowed size of #{@max_size} bytes"
end

if checksum
digest = Digest::MD5.base64digest(data)
raise ActiveStorage::IntegrityError unless digest == checksum
Expand All @@ -50,6 +55,8 @@ def download(key, &block)

def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
# NOTE: from/size are derived from Range#begin and Range#size (always integers),
# so string interpolation into SQL is safe here.
from = range.begin + 1
size = range.size
args = adapter_sqlserver? || adapter_sqlite? ? "data, #{from}, #{size}" : "data FROM #{from} FOR #{size}"
Expand Down Expand Up @@ -81,7 +88,7 @@ def delete_prefixed(prefix)
def exist?(key)
instrument :exist, key: key do |payload|
comment = "DBService#exist?"
result = ::ActiveStorageDB::File.annotate(comment).where(ref: key).exists?
result = ::ActiveStorageDB::File.annotate(comment).exists?(ref: key)
payload[:exist] = result
result
end
Expand Down Expand Up @@ -172,7 +179,7 @@ def retrieve_file(key)
def object_for(key, fields: nil)
comment = "DBService#object_for"
scope = ::ActiveStorageDB::File.annotate(comment)
scope = scope.select(*fields) if fields
scope = scope.select(fields) if fields
scope.find_by(ref: key)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/active_storage/service/db_service_rails70.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def compose(source_keys, destination_key, **)
buffer = nil
comment = "DBService#compose"
source_keys.each do |source_key|
record = ::ActiveStorageDB::File.annotate(comment).find_by(ref: source_key)
record = ::ActiveStorageDB::File.annotate(comment).select(:data).find_by(ref: source_key)
raise ActiveStorage::FileNotFoundError unless record

if buffer
Expand Down
8 changes: 8 additions & 0 deletions lib/active_storage_db/engine.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# frozen_string_literal: true

module ActiveStorageDB
# :nocov:
UNPROCESSABLE_STATUS = if Rails::VERSION::MAJOR > 7 || (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1)
:unprocessable_content
else
:unprocessable_entity
end
# :nocov:

class Engine < ::Rails::Engine
isolate_namespace ActiveStorageDB
end
Expand Down
23 changes: 16 additions & 7 deletions lib/tasks/active_storage_db_tasks.rake
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,30 @@ module ActiveStorage
end

def print_blob(blob, digits: 0)
size = if blob.byte_size < 1024
"#{blob.byte_size}B".rjust(8)
else
"#{blob.byte_size / 1024}K".rjust(8)
end
size = format_size(blob.byte_size)
date = blob.created_at.strftime("%Y-%m-%d %H:%M")
puts "#{size} #{date} #{blob.id.to_s.rjust(digits)} #{blob.filename}"
end

def format_size(bytes)
if bytes >= 1.gigabyte
"#{(bytes / 1.gigabyte.to_f).round(1)}G".rjust(8)
elsif bytes >= 1.megabyte
"#{(bytes / 1.megabyte.to_f).round(1)}M".rjust(8)
elsif bytes >= 1.kilobyte
"#{bytes / 1024}K".rjust(8)
else
"#{bytes}B".rjust(8)
end
end
end
end

namespace :asdb do
desc "ActiveStorageDB: list attachments ordered by blob id desc"
task list: [:environment] do |_t, _args|
query = ActiveStorage::Blob.order(id: :desc).limit(100)
task :list, [:count] => [:environment] do |_t, args|
count = (args[:count] || 100).to_i
query = ActiveStorage::Blob.order(id: :desc).limit(count)
digits = query.ids.inject(0) { |ret, id|
size = id.to_s.size
[size, ret].max
Expand Down
8 changes: 2 additions & 6 deletions spec/dummy/app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def create
format.json { render :show, status: :created, location: @post }
else
format.html { render :new }
format.json { render json: { errors: @post.errors }, status: unprocessable }
format.json { render json: { errors: @post.errors }, status: ActiveStorageDB::UNPROCESSABLE_STATUS }
end
end
end
Expand All @@ -46,7 +46,7 @@ def update
format.json { render :show, status: :ok, location: @post }
else
format.html { render :edit }
format.json { render json: { errors: @post.errors }, status: unprocessable }
format.json { render json: { errors: @post.errors }, status: ActiveStorageDB::UNPROCESSABLE_STATUS }
end
end
end
Expand All @@ -70,8 +70,4 @@ def load_post
def post_params
params.fetch(:post) { {} }.permit!
end

def unprocessable
Rails::VERSION::MAJOR > 7 || (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) ? :unprocessable_content : :unprocessable_entity
end
end
27 changes: 14 additions & 13 deletions spec/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,34 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 20_220_202_010_101) do
ActiveRecord::Schema[7.1].define(version: 2026_03_21_000000) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end

create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.string "service_name", null: false
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.string "content_type"
t.datetime "created_at", null: false
t.string "filename", null: false
t.string "key", null: false
t.text "metadata"
t.string "service_name", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end

create_table "active_storage_db_files", force: :cascade do |t|
t.string "ref", null: false
t.binary "data", null: false
t.datetime "created_at", precision: nil, null: false
t.binary "data", null: false
t.string "ref", null: false
t.datetime "updated_at"
t.index ["ref"], name: "index_active_storage_db_files_on_ref", unique: true
end

Expand All @@ -47,10 +48,10 @@
end

create_table "posts", force: :cascade do |t|
t.string "title"
t.text "content"
t.boolean "published"
t.datetime "created_at", null: false
t.boolean "published"
t.string "title"
t.datetime "updated_at", null: false
end

Expand Down
Loading