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:
parent
758e4f8406
commit
7d0327bbbf
17 changed files with 206 additions and 1 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
52
activestorage/test/models/variant_with_record_test.rb
Normal file
52
activestorage/test/models/variant_with_record_test.rb
Normal 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
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue