在我的rails 7应用程序中,products
可以有图像。起初我使用:
# products.rb
has_many_attached :images do |attachable|
attachable.variant :thumb, resize_to_limit: [100, 100]
end
现在,我需要每张图片都有一个标题。所以我创建了一个images
多态记录:
# products.rb
has_many :photos, as: :imageable, class_name: :Image, dependent: :destroy do
def attach(args)
build.attach(args)
end
end
# images.rb
has_one_attached :file
我的问题是:
是否可以在products.rb
文件中定义products
图像变体
例如,product
可能具有拇指变体(100x100(,而另一个模型,home
也可能具有has_many :images
,但具有<strong]介质>(200x200(变体
资源
https://www.bigbinary.com/blog/rails-7-adds-ability-to-use-predefined-variants
https://jonsully.net/blog/a-generic-image-wrapper-for-active-storage/
是的,这是可能的。反复阅读Jon Sullivan的帖子,为了能够定义父模型的变体,只需要做一些更改。
我在这里设置了一个回购示例。
这只是一个概念的证明,这还没有经过测试,我可能已经忘记了案例,等等…它似乎有效,但我不会把我的生产环境押在它上。
此外,该代码可能过于";聪明的";并使维护和调试变得更加困难。我怀疑有一种方法可以让这件事变得更简单,但没有花时间进一步挖掘。
无论如何,TLDR是,为了处理has_one和has_many关系之间的一些差异,我们在Imageable
关注点中创建了一个新方法,该方法深受Jon的启发,并对Image
类进行了一些调整:
# app/model/concerns/imageable.rb
module Imageable
extend ActiveSupport::Concern
class_methods do
def has_images(relation_name, **kwargs, &block)
has_many relation_name, as: :imageable, class_name: 'Image', dependent: :destroy
# this creates an has_one_attached relation, specific to this call of
# has_images, to which attachment options will be set. This allows to
# have distinct options set per call
image_attachment = "#{name.downcase}_#{relation_name.to_s.singularize}".to_sym
Image.has_one_attached image_attachment, **kwargs, &block
# not strictly necessary but allows for nicer code when building forms
accepts_nested_attributes_for relation_name
# We override attribute writers generated by `has_many` and `accepts_nested_attributes_for`
# to force the correct name attribute on Image
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{relation_name}=(images)
super(images.map {|image|
image.name = "#{image_attachment}"
image
})
end
# not strictly necessary but allows for nicer code when building forms
def #{relation_name}_attributes=(attrs)
super(attrs.transform_values {|image_attrs|
{name: "#{image_attachment}"}.merge!(image_attrs)
})
end
CODE
end
end
end
# app/models/image.rb
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
has_one_attached :file
delegate :persisted?, to: :imageable, allow_nil: true
delegate_missing_to :file
# The name attribute value is the name of the relation we create on Image
# when calling Imageable.has_images. We call the attr_reader created by
# the relation definition, or fallback to a generic attachment if no name
# exists (eg on Image.new)
def file
name ? public_send(name) : ActiveStorage::Attached::One.new('file', self)
end
def file=(attrs)
file.attach attrs unless attrs.blank?
end
end
您可以使用新定义的has_images
方法设置关系
# app/model/product.rb
class Product < ApplicationRecord
include Imageable
has_images :photos do |attachable|
attachable.variant :thumb, resize_to_limit: [100, 100]
attachable.variant :medium, resize_to_limit: [300, 300]
end
end
然后你可以在你的视图中使用这个,就像在博客文章中一样
<%# app/views/products/_form.html.erb %>
<div id="<%= dom_id product %>">
<p>
<strong>Images:</strong>
<% product.photos.each do |image| %>
<%= image_tag image.file.variant(:thumb) %>
<%= image_tag image.file.variant(:medium) %>
<% end %>
</p>
</div>
<%# app/views/products/_form.html.erb %>
<%= form_with(model: product) do |form| %>
<% if product.errors.any? %>
<div style="color: red">
<h2><%= pluralize(product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% product.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form.fields_for :photos do |photo_fields| %>
<div>
<%= photo_fields.label :preview %>
<br>
<%= image_tag(photo_fields.object.variant(:thumb)) if photo_fields.object.attached? %>
</div>
<div>
<%= photo_fields.file_field :file, direct_upload: true %>
</div>
<div>
<%= photo_fields.label :image_alt_text %><br>
<%= photo_fields.text_field :alt_text, required: true %>
</div>
<div>
<%= photo_fields.label :image_caption %><br>
<%= photo_fields.text_field :caption, required: true %>
</div>
<% end %>
<div>
<%= form.submit %>
</div>
<% end %>