1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Track Active Storage variants in the database

This commit is contained in:
George Claghorn 2019-12-05 22:21:24 -05:00 committed by George Claghorn
parent 758e4f8406
commit 7d0327bbbf
17 changed files with 206 additions and 1 deletions

View file

@ -24,5 +24,13 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
create_table :active_storage_variant_records do |t|
t.belongs_to :blob, null: false, index: false
t.string :variation_digest, null: false
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end

View file

@ -24,5 +24,13 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
create_table :active_storage_variant_records do |t|
t.belongs_to :blob, null: false, index: false
t.string :variation_digest, null: false
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end

View file

@ -44,6 +44,12 @@ ActiveRecord::Schema.define(version: 2019_03_17_200724) do
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", force: :cascade do |t|
t.integer "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_on_blob_id"
end
create_table "messages", force: :cascade do |t|
t.string "subject"
t.datetime "created_at", precision: 6, null: false
@ -69,4 +75,5 @@ ActiveRecord::Schema.define(version: 2019_03_17_200724) do
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
end

View file

@ -1,3 +1,7 @@
* Variants are tracked in the database to avoid existence checks in the storage service.
*George Claghorn*
* Deprecate `service_url` methods in favour of `url`.
Deprecate `Variant#service_url` and `Preview#service_url` to instead use

View file

@ -4,6 +4,9 @@ module ActiveStorage::Blob::Representable
extend ActiveSupport::Concern
included do
has_many :variant_records, class_name: "ActiveStorage::VariantRecord", dependent: false
before_destroy { variant_records.destroy_all if ActiveStorage.track_variants }
has_one_attached :preview_image
end
@ -27,7 +30,7 @@ module ActiveStorage::Blob::Representable
# variable, call ActiveStorage::Blob#variable?.
def variant(transformations)
if variable?
ActiveStorage::Variant.new(self, transformations)
variant_class.new(self, transformations)
else
raise ActiveStorage::InvariableError
end
@ -90,4 +93,9 @@ module ActiveStorage::Blob::Representable
def representable?
variable? || previewable?
end
private
def variant_class
ActiveStorage.track_variants ? ActiveStorage::VariantWithRecord : ActiveStorage::Variant
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
class ActiveStorage::VariantRecord < ActiveRecord::Base
self.table_name = "active_storage_variant_records"
belongs_to :blob
has_one_attached :image
end

View file

@ -0,0 +1,63 @@
# frozen_string_literal: true
class ActiveStorage::VariantWithRecord
WEB_IMAGE_CONTENT_TYPES = %w[ image/png image/jpeg image/jpg image/gif ]
attr_reader :blob, :variation
def initialize(blob, variation)
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
end
def processed
process
self
end
def process
transform_blob { |image| create_or_find_record(image: image) } unless processed?
end
def processed?
record.present?
end
def image
record&.image
end
def url(**options)
image&.url(**options)
end
alias_method :service_url, :url
deprecate service_url: :url
private
def transform_blob
blob.open do |input|
if blob.content_type.in?(WEB_IMAGE_CONTENT_TYPES)
variation.transform(input) do |output|
yield io: output, filename: blob.filename, content_type: blob.content_type
end
else
variation.transform(input, format: "png") do |output|
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
end
end
end
end
def create_or_find_record(image:)
@record =
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
blob.variant_records.create_or_find_by!(variation_digest: variation.digest) do |record|
record.image.attach(image)
end
end
end
def record
@record ||= blob.variant_records.find_by(variation_digest: variation.digest)
end
end

View file

@ -58,6 +58,10 @@ class ActiveStorage::Variation
self.class.encode(transformations)
end
def digest
Digest::SHA1.base64digest Marshal.dump(transformations)
end
private
def transformer
if ActiveStorage.variant_processor

View file

@ -23,5 +23,13 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
create_table :active_storage_variant_records do |t|
t.belongs_to :blob, null: false, index: false
t.string :variation_digest, null: false
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end

View file

@ -0,0 +1,11 @@
class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
def up
create_table :active_storage_variant_records do |t|
t.belongs_to :blob, null: false, index: false
t.string :variation_digest, null: false
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end

View file

@ -63,6 +63,7 @@ module ActiveStorage
mattr_accessor :draw_routes, default: true
mattr_accessor :replace_on_assign_to_many, default: false
mattr_accessor :track_variants, default: false
module Transformers
extend ActiveSupport::Autoload

View file

@ -84,6 +84,7 @@ module ActiveStorage
ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
ActiveStorage.track_variants = app.config.active_storage.track_variants || false
end
end

View file

@ -5,6 +5,14 @@ require "database/setup"
require "minitest/mock"
class ActiveStorage::VariantTest < ActiveSupport::TestCase
setup do
@was_tracking, ActiveStorage.track_variants = ActiveStorage.track_variants, false
end
teardown do
ActiveStorage.track_variants = @was_tracking
end
test "variations have the same key for different types of the same transformation" do
blob = create_file_blob(filename: "racecar.jpg")
variant_a = blob.variant(resize: "100x100")

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
require "test_helper"
require "database/setup"
class ActiveStorage::VariantWithRecordTest < ActiveSupport::TestCase
setup do
@was_tracking, ActiveStorage.track_variants = ActiveStorage.track_variants, true
end
teardown do
ActiveStorage.track_variants = @was_tracking
end
test "generating a resized variation of a JPEG blob" do
blob = create_file_blob(filename: "racecar.jpg")
variant = blob.variant(resize: "100x100")
assert_difference -> { blob.variant_records.count }, +1 do
variant.process
end
assert_match(/racecar\.jpg/, variant.url)
image = read_image(variant.image)
assert_equal 100, image.width
assert_equal 67, image.height
record = blob.variant_records.last
assert_equal variant.variation.digest, record.variation_digest
end
test "serving a previously-generated resized variation of a JPEG blob" do
blob = create_file_blob(filename: "racecar.jpg")
assert_difference -> { blob.variant_records.count } do
blob.variant(resize: "100x100").process
end
variant = blob.variant(resize: "100x100")
assert_no_difference -> { blob.variant_records.count } do
variant.process
end
assert_match(/racecar\.jpg/, variant.url)
image = read_image(variant.image)
assert_equal 100, image.width
assert_equal 67, image.height
end
end

View file

@ -889,12 +889,19 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla
* `config.active_storage.replace_on_assign_to_many` determines whether assigning to a collection of attachments declared with `has_many_attached` replaces any existing attachments or appends to them. The default is `true`.
* `config.active_storage.track_variants` determines whether variants are recorded in the database. The default is `true`.
* `config.active_storage.draw_routes` can be used to toggle Active Storage route generation. The default is `true`.
### Results of `config.load_defaults`
`config.load_defaults` sets new defaults up to and including the version passed. Such that passing, say, '6.0' also gets the new defaults from every version before it.
#### For '6.1', new defaults from previous versions below and:
- `config.active_record.has_many_inversing`: `true`
- `config.active_storage.track_variants`: `true`
#### For '6.0', new defaults from previous versions below and:
- `config.autoloader`: `:zeitwerk`

View file

@ -159,6 +159,10 @@ module Rails
if respond_to?(:active_record)
active_record.has_many_inversing = true
end
if respond_to?(:active_storage)
active_storage.track_variants = true
end
else
raise "Unknown version #{target_version.to_s.inspect}"
end

View file

@ -8,3 +8,6 @@
# Support for inversing belongs_to -> has_many Active Record associations.
# Rails.application.config.active_record.has_many_inversing = true
# Track Active Storage variants in the database.
# Rails.application.config.active_storage.track_variants = true