Skip to content

Commit

Permalink
Merge pull request #39135 from fatkodima/active_storage-named-variants
Browse files Browse the repository at this point in the history
[ActiveStorage] Add ability to use pre-defined variants
  • Loading branch information
kaspth committed Jan 29, 2021
2 parents 51bab59 + a1408e7 commit 65979c6
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 5 deletions.
22 changes: 22 additions & 0 deletions activestorage/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
* Add ability to use pre-defined variants.

```ruby
class User < ActiveRecord::Base
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize: "100x100"
attachable.variant :medium, resize: "300x300", monochrome: true
end
end

class Gallery < ActiveRecord::Base
has_many_attached :photos do |attachable|
attachable.variant :thumb, resize: "100x100"
attachable.variant :medium, resize: "300x300", monochrome: true
end
end

<%= image_tag user.avatar.variant(:thumb) %>
```
*fatkodima*
* After setting `config.active_storage.resolve_model_to_route = :rails_storage_proxy`
`rails_blob_path` and `rails_representation_path` will generate proxy URLs by default.
Expand Down
17 changes: 17 additions & 0 deletions activestorage/app/models/active_storage/attachment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ def purge_later
blob&.purge_later
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
def analyze_blob_later
blob.analyze_later unless blob.analyzed?
Expand All @@ -53,6 +66,10 @@ def purge_dependent_blob_later
def dependent
record.attachment_reflections[name]&.options[:dependent]
end

def variants
record.attachment_reflections[name]&.variants
end
end

ActiveSupport.run_load_hooks :active_storage_attachment, ActiveStorage::Attachment
2 changes: 2 additions & 0 deletions activestorage/lib/active_storage/attached/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def #{name}=(attachable)
{ dependent: dependent, service_name: service },
self
)
yield reflection if block_given?
ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
end

Expand Down Expand Up @@ -178,6 +179,7 @@ def purge_later
{ dependent: dependent, service_name: service },
self
)
yield reflection if block_given?
ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
end

Expand Down
14 changes: 12 additions & 2 deletions activestorage/lib/active_storage/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@

module ActiveStorage
module Reflection
class HasAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
def variant(name, transformations)
variants[name] = transformations
end

def variants
@variants ||= {}
end
end

# Holds all the metadata about a has_one_attached attachment as it was
# specified in the Active Record class.
class HasOneAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
class HasOneAttachedReflection < HasAttachedReflection #:nodoc:
def macro
:has_one_attached
end
end

# Holds all the metadata about a has_many_attached attachment as it was
# specified in the Active Record class.
class HasManyAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
class HasManyAttachedReflection < HasAttachedReflection #:nodoc:
def macro
:has_many_attached
end
Expand Down
20 changes: 20 additions & 0 deletions activestorage/test/models/attached/many_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,26 @@ def highlights
assert_match(/Cannot configure service :unknown for User#featured_photos/, error.message)
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
def append_on_assign
ActiveStorage.replace_on_assign_to_many, previous = false, ActiveStorage.replace_on_assign_to_many
Expand Down
20 changes: 20 additions & 0 deletions activestorage/test/models/attached/one_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -609,4 +609,24 @@ def avatar

assert_match(/Cannot configure service :unknown for User#featured_photo/, error.message)
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
12 changes: 9 additions & 3 deletions activestorage/test/models/reflection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class ActiveStorage::ReflectionTest < ActiveSupport::TestCase

reflection = User.reflect_on_attachment(:cover_photo)
assert_equal :local, reflection.options[:service_name]

reflection = User.reflect_on_attachment(:avatar_with_variants)
assert_instance_of Hash, reflection.variants
end

test "reflection on a singular attachment with the same name as an attachment on another model" do
Expand All @@ -28,13 +31,16 @@ class ActiveStorage::ReflectionTest < ActiveSupport::TestCase

reflection = User.reflect_on_attachment(:vlogs)
assert_equal :local, reflection.options[:service_name]

reflection = User.reflect_on_attachment(:highlights_with_variants)
assert_instance_of Hash, reflection.variants
end

test "reflecting on all attachments" do
reflections = User.reflect_on_all_attachments.sort_by(&:name)
assert_equal [ User ], reflections.collect(&:active_record).uniq
assert_equal %i[ avatar cover_photo highlights vlogs ], reflections.collect(&:name)
assert_equal %i[ has_one_attached has_one_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 %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_one_attached has_many_attached has_many_attached has_many_attached ], reflections.collect(&:macro)
assert_equal [ :purge_later, :purge_later, false, :purge_later, :purge_later, false ], reflections.collect { |reflection| reflection.options[:dependent] }
end
end
6 changes: 6 additions & 0 deletions activestorage/test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,15 @@ class User < ActiveRecord::Base

has_one_attached :avatar
has_one_attached :cover_photo, dependent: false, service: :local
has_one_attached :avatar_with_variants do |attachable|
attachable.variant :thumb, resize: "100x100"
end

has_many_attached :highlights
has_many_attached :vlogs, dependent: false, service: :local
has_many_attached :highlights_with_variants do |attachable|
attachable.variant :thumb, resize: "100x100"
end
end

class Group < ActiveRecord::Base
Expand Down
27 changes: 27 additions & 0 deletions guides/source/active_storage_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,22 @@ class User < ApplicationRecord
end
```

You can configure specific variants per attachment by calling the `variant` method on yielded attachable object:

```ruby
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize: "100x100"
end
end
```

Call `avatar.variant(:thumb)` to get a thumb variant of an avatar:

```ruby
<%= image_tag user.avatar.variant(:thumb) %>
```
[`has_one_attached`]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Model.html#method-i-has_one_attached
[Attached::One#attach]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/One.html#method-i-attach
[Attached::One#attached?]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/One.html#method-i-attached-3F
Expand Down Expand Up @@ -406,10 +422,21 @@ class Message < ApplicationRecord
end
```
Configuring specific variants is done the same way as `has_one_attached`, by calling the `variant` method on the yielded attachable object:
```ruby
class Message < ApplicationRecord
has_many_attached :images do |attachable|
attachable.variant :thumb, resize: "100x100"
end
end
```
[`has_many_attached`]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Model.html#method-i-has_many_attached
[Attached::Many#attach]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Many.html#method-i-attach
[Attached::Many#attached?]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Many.html#method-i-attached-3F
### Attaching File/IO Objects
Sometimes you need to attach a file that doesn’t arrive via an HTTP request.
Expand Down

0 comments on commit 65979c6

Please sign in to comment.