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

[ActiveStorage] Add ability to use pre-defined variants

This commit is contained in:
fatkodima 2020-05-03 22:03:46 +03:00
parent ea4acf540e
commit 1adea93265
8 changed files with 117 additions and 8 deletions

View file

@ -1,3 +1,25 @@
* Add ability to use pre-defined variants.
```ruby
class User < ActiveRecord::Base
has_one_attached :avatar, variants: {
thumb: { resize: "100x100" },
medium: { resize: "300x300", monochrome: true }
}
end
class Gallery < ActiveRecord::Base
has_many_attached :photos, variants: {
thumb: { resize: "100x100" },
medium: { resize: "300x300", monochrome: true }
}
end
<%= image_tag user.avatar.variant(:thumb) %>
```
*fatkodima*
* Add `config.active_storage.web_image_content_types` to allow applications * Add `config.active_storage.web_image_content_types` to allow applications
to add content types (like `image/webp`) in which variants can be processed, to add content types (like `image/webp`) in which variants can be processed,
instead of letting those images be converted to the fallback PNG format. instead of letting those images be converted to the fallback PNG format.

View file

@ -30,6 +30,19 @@ class ActiveStorage::Attachment < ActiveRecord::Base
blob&.purge_later blob&.purge_later
end end
def variant(transformations)
case transformations
when Symbol
variant_name = transformations
transformations = variants.fetch(variant_name) do
record_model_name = record.to_model.model_name.name
raise ArgumentError, "Cannot find variant :#{variant_name} for #{record_model_name}##{name}"
end
end
blob.variant(transformations)
end
private private
def identify_blob def identify_blob
blob.identify blob.identify
@ -51,6 +64,10 @@ class ActiveStorage::Attachment < ActiveRecord::Base
def dependent def dependent
record.attachment_reflections[name]&.options[:dependent] record.attachment_reflections[name]&.options[:dependent]
end end
def variants
record.attachment_reflections[name]&.options[:variants]
end
end end
ActiveSupport.run_load_hooks :active_storage_attachment, ActiveStorage::Attachment ActiveSupport.run_load_hooks :active_storage_attachment, ActiveStorage::Attachment

View file

@ -40,7 +40,7 @@ module ActiveStorage
# has_one_attached :avatar, service: :s3 # has_one_attached :avatar, service: :s3
# end # end
# #
def has_one_attached(name, dependent: :purge_later, service: nil) def has_one_attached(name, dependent: :purge_later, service: nil, variants: {})
validate_service_configuration(name, service) validate_service_configuration(name, service)
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1 generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
@ -72,7 +72,7 @@ module ActiveStorage
:has_one_attached, :has_one_attached,
name, name,
nil, nil,
{ dependent: dependent, service_name: service }, { dependent: dependent, service_name: service, variants: variants },
self self
) )
ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection) ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
@ -110,7 +110,7 @@ module ActiveStorage
# has_many_attached :photos, service: :s3 # has_many_attached :photos, service: :s3
# end # end
# #
def has_many_attached(name, dependent: :purge_later, service: nil) def has_many_attached(name, dependent: :purge_later, service: nil, variants: {})
validate_service_configuration(name, service) validate_service_configuration(name, service)
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1 generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
@ -159,7 +159,7 @@ module ActiveStorage
:has_many_attached, :has_many_attached,
name, name,
nil, nil,
{ dependent: dependent, service_name: service }, { dependent: dependent, service_name: service, variants: variants },
self self
) )
ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection) ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)

View file

@ -610,6 +610,26 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
assert_match(/Cannot configure service :unknown for User#featured_photos/, error.message) assert_match(/Cannot configure service :unknown for User#featured_photos/, error.message)
end end
test "creating variation by variation name" do
@user.highlights_with_variants.attach fixture_file_upload("racecar.jpg")
variant = @user.highlights_with_variants.first.variant(:thumb).processed
image = read_image(variant)
assert_equal "JPEG", image.type
assert_equal 100, image.width
assert_equal 67, image.height
end
test "raises error when unknown variant name is used" do
@user.highlights_with_variants.attach fixture_file_upload("racecar.jpg")
error = assert_raises ArgumentError do
@user.highlights_with_variants.first.variant(:unknown).processed
end
assert_match(/Cannot find variant :unknown for User#highlights_with_variants/, error.message)
end
private private
def append_on_assign def append_on_assign
ActiveStorage.replace_on_assign_to_many, previous = false, ActiveStorage.replace_on_assign_to_many ActiveStorage.replace_on_assign_to_many, previous = false, ActiveStorage.replace_on_assign_to_many

View file

@ -584,4 +584,24 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
assert_match(/Cannot configure service :unknown for User#featured_photo/, error.message) assert_match(/Cannot configure service :unknown for User#featured_photo/, error.message)
end end
test "creating variation by variation name" do
@user.avatar_with_variants.attach fixture_file_upload("racecar.jpg")
variant = @user.avatar_with_variants.variant(:thumb).processed
image = read_image(variant)
assert_equal "JPEG", image.type
assert_equal 100, image.width
assert_equal 67, image.height
end
test "raises error when unknown variant name is used" do
@user.avatar_with_variants.attach fixture_file_upload("racecar.jpg")
error = assert_raises ArgumentError do
@user.avatar_with_variants.variant(:unknown).processed
end
assert_match(/Cannot find variant :unknown for User#avatar_with_variants/, error.message)
end
end end

View file

@ -12,6 +12,9 @@ class ActiveStorage::ReflectionTest < ActiveSupport::TestCase
reflection = User.reflect_on_attachment(:cover_photo) reflection = User.reflect_on_attachment(:cover_photo)
assert_equal :local, reflection.options[:service_name] assert_equal :local, reflection.options[:service_name]
reflection = User.reflect_on_attachment(:avatar_with_variants)
assert_instance_of Hash, reflection.options[:variants]
end end
test "reflection on a singular attachment with the same name as an attachment on another model" do test "reflection on a singular attachment with the same name as an attachment on another model" do
@ -28,13 +31,16 @@ class ActiveStorage::ReflectionTest < ActiveSupport::TestCase
reflection = User.reflect_on_attachment(:vlogs) reflection = User.reflect_on_attachment(:vlogs)
assert_equal :local, reflection.options[:service_name] assert_equal :local, reflection.options[:service_name]
reflection = User.reflect_on_attachment(:highlights_with_variants)
assert_instance_of Hash, reflection.options[:variants]
end end
test "reflecting on all attachments" do test "reflecting on all attachments" do
reflections = User.reflect_on_all_attachments.sort_by(&:name) reflections = User.reflect_on_all_attachments.sort_by(&:name)
assert_equal [ User ], reflections.collect(&:active_record).uniq assert_equal [ User ], reflections.collect(&:active_record).uniq
assert_equal %i[ avatar cover_photo highlights vlogs ], reflections.collect(&:name) assert_equal %i[ avatar avatar_with_variants cover_photo highlights highlights_with_variants vlogs ], reflections.collect(&:name)
assert_equal %i[ has_one_attached has_one_attached has_many_attached has_many_attached ], reflections.collect(&:macro) assert_equal %i[ has_one_attached has_one_attached has_one_attached has_many_attached has_many_attached has_many_attached ], reflections.collect(&:macro)
assert_equal [ :purge_later, false, :purge_later, false ], reflections.collect { |reflection| reflection.options[:dependent] } assert_equal [ :purge_later, :purge_later, false, :purge_later, :purge_later, false ], reflections.collect { |reflection| reflection.options[:dependent] }
end end
end end

View file

@ -119,9 +119,11 @@ class User < ActiveRecord::Base
has_one_attached :avatar has_one_attached :avatar
has_one_attached :cover_photo, dependent: false, service: :local has_one_attached :cover_photo, dependent: false, service: :local
has_one_attached :avatar_with_variants, variants: { thumb: { resize: "100x100" } }
has_many_attached :highlights has_many_attached :highlights
has_many_attached :vlogs, dependent: false, service: :local has_many_attached :vlogs, dependent: false, service: :local
has_many_attached :highlights_with_variants, variants: { thumb: { resize: "100x100" } }
end end
class Group < ActiveRecord::Base class Group < ActiveRecord::Base

View file

@ -129,7 +129,7 @@ amazon:
secret_access_key: "" secret_access_key: ""
region: "" region: ""
bucket: "" bucket: ""
upload: upload:
server_side_encryption: "" # 'aws:kms' or 'AES256' server_side_encryption: "" # 'aws:kms' or 'AES256'
``` ```
@ -332,6 +332,20 @@ class User < ApplicationRecord
end end
``` ```
You can configure specific variants per attachment using the `variants` option:
```ruby
class User < ApplicationRecord
has_one_attached :avatar, variants: { thumb: { resize: "100x100" } }
end
```
Call `avatar.variant(:thumb)` to get a thumb variant of an avatar:
```ruby
<%= image_tag user.avatar.variant(:thumb) %>
```
### `has_many_attached` ### `has_many_attached`
The `has_many_attached` macro sets up a one-to-many relationship between records The `has_many_attached` macro sets up a one-to-many relationship between records
@ -382,6 +396,14 @@ class Message < ApplicationRecord
end end
``` ```
Configuring specific variants is done the same way as `has_one_attached`, by using the `variants` option:
```ruby
class Message < ApplicationRecord
has_many_attached :images, variants: { thumb: { resize: "100x100" } }
end
```
### Attaching File/IO Objects ### Attaching File/IO Objects
Sometimes you need to attach a file that doesnt arrive via an HTTP request. Sometimes you need to attach a file that doesnt arrive via an HTTP request.