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.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 t.foreign_key :active_storage_blobs, column: :blob_id
end 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
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.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 t.foreign_key :active_storage_blobs, column: :blob_id
end 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
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 t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end 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| create_table "messages", force: :cascade do |t|
t.string "subject" t.string "subject"
t.datetime "created_at", precision: 6, null: false t.datetime "created_at", precision: 6, null: false
@ -69,4 +75,5 @@ ActiveRecord::Schema.define(version: 2019_03_17_200724) do
end end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" 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 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 `service_url` methods in favour of `url`.
Deprecate `Variant#service_url` and `Preview#service_url` to instead use Deprecate `Variant#service_url` and `Preview#service_url` to instead use

View file

@ -4,6 +4,9 @@ module ActiveStorage::Blob::Representable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do 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 has_one_attached :preview_image
end end
@ -27,7 +30,7 @@ module ActiveStorage::Blob::Representable
# variable, call ActiveStorage::Blob#variable?. # variable, call ActiveStorage::Blob#variable?.
def variant(transformations) def variant(transformations)
if variable? if variable?
ActiveStorage::Variant.new(self, transformations) variant_class.new(self, transformations)
else else
raise ActiveStorage::InvariableError raise ActiveStorage::InvariableError
end end
@ -90,4 +93,9 @@ module ActiveStorage::Blob::Representable
def representable? def representable?
variable? || previewable? variable? || previewable?
end end
private
def variant_class
ActiveStorage.track_variants ? ActiveStorage::VariantWithRecord : ActiveStorage::Variant
end
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) self.class.encode(transformations)
end end
def digest
Digest::SHA1.base64digest Marshal.dump(transformations)
end
private private
def transformer def transformer
if ActiveStorage.variant_processor 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.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 t.foreign_key :active_storage_blobs, column: :blob_id
end 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
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 :draw_routes, default: true
mattr_accessor :replace_on_assign_to_many, default: false mattr_accessor :replace_on_assign_to_many, default: false
mattr_accessor :track_variants, default: false
module Transformers module Transformers
extend ActiveSupport::Autoload 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.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.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
end end

View file

@ -5,6 +5,14 @@ require "database/setup"
require "minitest/mock" require "minitest/mock"
class ActiveStorage::VariantTest < ActiveSupport::TestCase 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 test "variations have the same key for different types of the same transformation" do
blob = create_file_blob(filename: "racecar.jpg") blob = create_file_blob(filename: "racecar.jpg")
variant_a = blob.variant(resize: "100x100") 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.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`. * `config.active_storage.draw_routes` can be used to toggle Active Storage route generation. The default is `true`.
### Results of `config.load_defaults` ### 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. `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: #### For '6.0', new defaults from previous versions below and:
- `config.autoloader`: `:zeitwerk` - `config.autoloader`: `:zeitwerk`

View file

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

View file

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