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.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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 :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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
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.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`
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue