Skip to content

Commit 65979c6

Browse files
authored
Merge pull request rails#39135 from fatkodima/active_storage-named-variants
[ActiveStorage] Add ability to use pre-defined variants
2 parents 51bab59 + a1408e7 commit 65979c6

File tree

9 files changed

+135
-5
lines changed

9 files changed

+135
-5
lines changed

activestorage/CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
* Add ability to use pre-defined variants.
2+
3+
```ruby
4+
class User < ActiveRecord::Base
5+
has_one_attached :avatar do |attachable|
6+
attachable.variant :thumb, resize: "100x100"
7+
attachable.variant :medium, resize: "300x300", monochrome: true
8+
end
9+
end
10+
11+
class Gallery < ActiveRecord::Base
12+
has_many_attached :photos do |attachable|
13+
attachable.variant :thumb, resize: "100x100"
14+
attachable.variant :medium, resize: "300x300", monochrome: true
15+
end
16+
end
17+
18+
<%= image_tag user.avatar.variant(:thumb) %>
19+
```
20+
21+
*fatkodima*
22+
123
* After setting `config.active_storage.resolve_model_to_route = :rails_storage_proxy`
224
`rails_blob_path` and `rails_representation_path` will generate proxy URLs by default.
325

activestorage/app/models/active_storage/attachment.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ def purge_later
3737
blob&.purge_later
3838
end
3939

40+
def variant(transformations)
41+
case transformations
42+
when Symbol
43+
variant_name = transformations
44+
transformations = variants.fetch(variant_name) do
45+
record_model_name = record.to_model.model_name.name
46+
raise ArgumentError, "Cannot find variant :#{variant_name} for #{record_model_name}##{name}"
47+
end
48+
end
49+
50+
blob.variant(transformations)
51+
end
52+
4053
private
4154
def analyze_blob_later
4255
blob.analyze_later unless blob.analyzed?
@@ -53,6 +66,10 @@ def purge_dependent_blob_later
5366
def dependent
5467
record.attachment_reflections[name]&.options[:dependent]
5568
end
69+
70+
def variants
71+
record.attachment_reflections[name]&.variants
72+
end
5673
end
5774

5875
ActiveSupport.run_load_hooks :active_storage_attachment, ActiveStorage::Attachment

activestorage/lib/active_storage/attached/model.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def #{name}=(attachable)
8383
{ dependent: dependent, service_name: service },
8484
self
8585
)
86+
yield reflection if block_given?
8687
ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
8788
end
8889

@@ -178,6 +179,7 @@ def purge_later
178179
{ dependent: dependent, service_name: service },
179180
self
180181
)
182+
yield reflection if block_given?
181183
ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
182184
end
183185

activestorage/lib/active_storage/reflection.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,27 @@
22

33
module ActiveStorage
44
module Reflection
5+
class HasAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
6+
def variant(name, transformations)
7+
variants[name] = transformations
8+
end
9+
10+
def variants
11+
@variants ||= {}
12+
end
13+
end
14+
515
# Holds all the metadata about a has_one_attached attachment as it was
616
# specified in the Active Record class.
7-
class HasOneAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
17+
class HasOneAttachedReflection < HasAttachedReflection #:nodoc:
818
def macro
919
:has_one_attached
1020
end
1121
end
1222

1323
# Holds all the metadata about a has_many_attached attachment as it was
1424
# specified in the Active Record class.
15-
class HasManyAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
25+
class HasManyAttachedReflection < HasAttachedReflection #:nodoc:
1626
def macro
1727
:has_many_attached
1828
end

activestorage/test/models/attached/many_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,26 @@ def highlights
628628
assert_match(/Cannot configure service :unknown for User#featured_photos/, error.message)
629629
end
630630

631+
test "creating variation by variation name" do
632+
@user.highlights_with_variants.attach fixture_file_upload("racecar.jpg")
633+
variant = @user.highlights_with_variants.first.variant(:thumb).processed
634+
635+
image = read_image(variant)
636+
assert_equal "JPEG", image.type
637+
assert_equal 100, image.width
638+
assert_equal 67, image.height
639+
end
640+
641+
test "raises error when unknown variant name is used" do
642+
@user.highlights_with_variants.attach fixture_file_upload("racecar.jpg")
643+
644+
error = assert_raises ArgumentError do
645+
@user.highlights_with_variants.first.variant(:unknown).processed
646+
end
647+
648+
assert_match(/Cannot find variant :unknown for User#highlights_with_variants/, error.message)
649+
end
650+
631651
private
632652
def append_on_assign
633653
ActiveStorage.replace_on_assign_to_many, previous = false, ActiveStorage.replace_on_assign_to_many

activestorage/test/models/attached/one_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,4 +609,24 @@ def avatar
609609

610610
assert_match(/Cannot configure service :unknown for User#featured_photo/, error.message)
611611
end
612+
613+
test "creating variation by variation name" do
614+
@user.avatar_with_variants.attach fixture_file_upload("racecar.jpg")
615+
variant = @user.avatar_with_variants.variant(:thumb).processed
616+
617+
image = read_image(variant)
618+
assert_equal "JPEG", image.type
619+
assert_equal 100, image.width
620+
assert_equal 67, image.height
621+
end
622+
623+
test "raises error when unknown variant name is used" do
624+
@user.avatar_with_variants.attach fixture_file_upload("racecar.jpg")
625+
626+
error = assert_raises ArgumentError do
627+
@user.avatar_with_variants.variant(:unknown).processed
628+
end
629+
630+
assert_match(/Cannot find variant :unknown for User#avatar_with_variants/, error.message)
631+
end
612632
end

activestorage/test/models/reflection_test.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class ActiveStorage::ReflectionTest < ActiveSupport::TestCase
1212

1313
reflection = User.reflect_on_attachment(:cover_photo)
1414
assert_equal :local, reflection.options[:service_name]
15+
16+
reflection = User.reflect_on_attachment(:avatar_with_variants)
17+
assert_instance_of Hash, reflection.variants
1518
end
1619

1720
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
2831

2932
reflection = User.reflect_on_attachment(:vlogs)
3033
assert_equal :local, reflection.options[:service_name]
34+
35+
reflection = User.reflect_on_attachment(:highlights_with_variants)
36+
assert_instance_of Hash, reflection.variants
3137
end
3238

3339
test "reflecting on all attachments" do
3440
reflections = User.reflect_on_all_attachments.sort_by(&:name)
3541
assert_equal [ User ], reflections.collect(&:active_record).uniq
36-
assert_equal %i[ avatar cover_photo highlights vlogs ], reflections.collect(&:name)
37-
assert_equal %i[ has_one_attached has_one_attached has_many_attached has_many_attached ], reflections.collect(&:macro)
38-
assert_equal [ :purge_later, false, :purge_later, false ], reflections.collect { |reflection| reflection.options[:dependent] }
42+
assert_equal %i[ avatar avatar_with_variants cover_photo highlights highlights_with_variants vlogs ], reflections.collect(&:name)
43+
assert_equal %i[ has_one_attached has_one_attached has_one_attached has_many_attached has_many_attached has_many_attached ], reflections.collect(&:macro)
44+
assert_equal [ :purge_later, :purge_later, false, :purge_later, :purge_later, false ], reflections.collect { |reflection| reflection.options[:dependent] }
3945
end
4046
end

activestorage/test/test_helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,15 @@ class User < ActiveRecord::Base
119119

120120
has_one_attached :avatar
121121
has_one_attached :cover_photo, dependent: false, service: :local
122+
has_one_attached :avatar_with_variants do |attachable|
123+
attachable.variant :thumb, resize: "100x100"
124+
end
122125

123126
has_many_attached :highlights
124127
has_many_attached :vlogs, dependent: false, service: :local
128+
has_many_attached :highlights_with_variants do |attachable|
129+
attachable.variant :thumb, resize: "100x100"
130+
end
125131
end
126132

127133
class Group < ActiveRecord::Base

guides/source/active_storage_overview.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,22 @@ class User < ApplicationRecord
352352
end
353353
```
354354

355+
You can configure specific variants per attachment by calling the `variant` method on yielded attachable object:
356+
357+
```ruby
358+
class User < ApplicationRecord
359+
has_one_attached :avatar do |attachable|
360+
attachable.variant :thumb, resize: "100x100"
361+
end
362+
end
363+
```
364+
365+
Call `avatar.variant(:thumb)` to get a thumb variant of an avatar:
366+
367+
```ruby
368+
<%= image_tag user.avatar.variant(:thumb) %>
369+
```
370+
355371
[`has_one_attached`]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Model.html#method-i-has_one_attached
356372
[Attached::One#attach]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/One.html#method-i-attach
357373
[Attached::One#attached?]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/One.html#method-i-attached-3F
@@ -406,10 +422,21 @@ class Message < ApplicationRecord
406422
end
407423
```
408424

425+
Configuring specific variants is done the same way as `has_one_attached`, by calling the `variant` method on the yielded attachable object:
426+
427+
```ruby
428+
class Message < ApplicationRecord
429+
has_many_attached :images do |attachable|
430+
attachable.variant :thumb, resize: "100x100"
431+
end
432+
end
433+
```
434+
409435
[`has_many_attached`]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Model.html#method-i-has_many_attached
410436
[Attached::Many#attach]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Many.html#method-i-attach
411437
[Attached::Many#attached?]: https://api.rubyonrails.org/classes/ActiveStorage/Attached/Many.html#method-i-attached-3F
412438

439+
413440
### Attaching File/IO Objects
414441

415442
Sometimes you need to attach a file that doesn’t arrive via an HTTP request.

0 commit comments

Comments
 (0)