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

Extend image_tag to accept ActiveStorage Attachments and Variants (#30084)

* Extend image_tag to accept ActiveStorage's Attachments and Variants

* Flip resolve_image_source around

* Add tests for the new use-cases of image_tag

* Remove the higher-level test

* Update image_tag documentation

* Add error states into the test suite

* Re-raise polymorhic_url's NoMethodError as ArgumentError

* delegate_missing_to will raise DelegationError instead of NoMethodError
This commit is contained in:
Anton Khamets 2017-08-07 17:38:51 +03:00 committed by David Heinemeier Hansson
parent df94b863c2
commit 7c89948c41
5 changed files with 66 additions and 6 deletions

View file

@ -200,7 +200,7 @@ module ActionView
end end
# Returns an HTML image tag for the +source+. The +source+ can be a full # Returns an HTML image tag for the +source+. The +source+ can be a full
# path or a file. # path, a file or an Active Storage attachment.
# #
# ==== Options # ==== Options
# #
@ -217,6 +217,8 @@ module ActionView
# #
# ==== Examples # ==== Examples
# #
# Assets (images that are part of your app):
#
# image_tag("icon") # image_tag("icon")
# # => <img alt="Icon" src="/assets/icon" /> # # => <img alt="Icon" src="/assets/icon" />
# image_tag("icon.png") # image_tag("icon.png")
@ -235,12 +237,21 @@ module ActionView
# # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x"> # # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x">
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw") # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw"> # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
#
# Active Storage (images that are uploaded by the users of your app):
#
# image_tag(user.avatar)
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" alt="Tiger" />
# image_tag(user.avatar.variant(resize: "100x100"))
# # => <img src="/rails/active_storage/variants/.../tiger.jpg" alt="Tiger" />
# image_tag(user.avatar.variant(resize: "100x100"), size: '100')
# # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" alt="Tiger" />
def image_tag(source, options = {}) def image_tag(source, options = {})
options = options.symbolize_keys options = options.symbolize_keys
check_for_image_tag_errors(options) check_for_image_tag_errors(options)
skip_pipeline = options.delete(:skip_pipeline) skip_pipeline = options.delete(:skip_pipeline)
src = options[:src] = path_to_image(source, skip_pipeline: skip_pipeline) src = options[:src] = resolve_image_source(source, skip_pipeline)
unless src.start_with?("cid:") || src.start_with?("data:") || src.blank? unless src.start_with?("cid:") || src.start_with?("data:") || src.blank?
options[:alt] = options.fetch(:alt) { image_alt(src) } options[:alt] = options.fetch(:alt) { image_alt(src) }
@ -364,6 +375,16 @@ module ActionView
end end
end end
def resolve_image_source(source, skip_pipeline)
if source.is_a?(Symbol) || source.is_a?(String)
path_to_image(source, skip_pipeline: skip_pipeline)
else
polymorphic_url(source)
end
rescue NoMethodError => e
raise ArgumentError, "Can't resolve image into URL: #{e}"
end
def extract_dimensions(size) def extract_dimensions(size)
size = size.to_s size = size.to_s
if /\A\d+x\d+\z/.match?(size) if /\A\d+x\d+\z/.match?(size)

View file

@ -565,9 +565,6 @@ class AssetTagHelperTest < ActionView::TestCase
def blank?; true; end def blank?; true; end
def to_s; "no-image-yet.png"; end def to_s; "no-image-yet.png"; end
end end
def test_image_tag_with_blank_placeholder
assert_equal '<img alt="" src="/images/no-image-yet.png" />', image_tag(PlaceholderImage.new, alt: "")
end
def test_image_path_with_blank_placeholder def test_image_path_with_blank_placeholder
assert_equal "/images/no-image-yet.png", image_path(PlaceholderImage.new) assert_equal "/images/no-image-yet.png", image_path(PlaceholderImage.new)
end end

View file

@ -80,7 +80,7 @@ Variation of image attachment:
```erb ```erb
<%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %> <%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %>
<%= image_tag url_for(user.avatar.variant(resize: "100x100")) %> <%= image_tag user.avatar.variant(resize: "100x100") %>
``` ```
## Installation ## Installation

View file

@ -0,0 +1,40 @@
require "test_helper"
require "database/setup"
class User < ActiveRecord::Base
has_one_attached :avatar
end
class ActiveStorage::ImageTagTest < ActionView::TestCase
tests ActionView::Helpers::AssetTagHelper
setup do
@blob = create_image_blob filename: "racecar.jpg"
end
test "blob" do
assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url @blob}" />), image_tag(@blob)
end
test "variant" do
variant = @blob.variant(resize: "100x100")
assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url variant}" />), image_tag(variant)
end
test "attachment" do
attachment = ActiveStorage::Attachment.new(blob: @blob)
assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url attachment}" />), image_tag(attachment)
end
test "error when attachment's empty" do
@user = User.create!(name: "DHH")
assert_not @user.avatar.attached?
assert_raises(ArgumentError) { image_tag(@user.avatar) }
end
test "error when object can't be resolved into url" do
unresolvable_object = ActionView::Helpers::AssetTagHelper
assert_raises(ArgumentError) { image_tag(unresolvable_object) }
end
end

View file

@ -273,6 +273,8 @@ class Module
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
if #{target}.respond_to?(method) if #{target}.respond_to?(method)
#{target}.public_send(method, *args, &block) #{target}.public_send(method, *args, &block)
elsif #{target}.nil?
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
else else
super super
end end