-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add #gallery
to Variant
and Product
#2337
Add #gallery
to Variant
and Product
#2337
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally think this is the most reasonable and sane way to start pulling paperclip out of core, as discussed elsewhere, so I'm 👍
core/app/models/spree/variant.rb
Outdated
# An Enumerable of media that goes along with a variant | ||
# Stores may not necessarily want to attach images to variants | ||
# This provides a more robust interface | ||
# NOTE: In the future this will return an empty array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would this be useful? What is a developer supposed to do with this information.
I assume this is for extension points, since we want to avoid class_eval
ing, maybe we should look at introducing a Spree::Config.variant_gallery_class
and a Spree::Config.product_gallery_class
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that idea of configurable image attachment classes. If we want extensions to be able to hook into we want to provide a real integration point.
Also I don’t think gallery
is the correct term. gallery_images
would follow the “plural is a collection” convention of Rails better. We could even override the images
getter. Btw. We need to deprecate the images =
setter as well or delegate this to the images provider class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I second John here and think we should introduce a real integration point for extensions.
core/app/models/spree/variant.rb
Outdated
# An Enumerable of media that goes along with a variant | ||
# Stores may not necessarily want to attach images to variants | ||
# This provides a more robust interface | ||
# NOTE: In the future this will return an empty array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that idea of configurable image attachment classes. If we want extensions to be able to hook into we want to provide a real integration point.
Also I don’t think gallery
is the correct term. gallery_images
would follow the “plural is a collection” convention of Rails better. We could even override the images
getter. Btw. We need to deprecate the images =
setter as well or delegate this to the images provider class.
@@ -0,0 +1,18 @@ | |||
module Spree | |||
module Gallery | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra empty line detected at module body beginning.
module Spree | ||
module Gallery | ||
class ProductGallery < Gallery::Base | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra empty line detected at class body beginning.
#gallery
to Variant
and Product
#gallery
to Variant
and Product
]
#gallery
to Variant
and Product
] #gallery
to Variant
and Product
#gallery
to Variant
and Product
#gallery
to Variant
and Product
describe '#primary_image' do | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 trailing blank lines detected.
describe '#primary_image' do | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 trailing blank lines detected.
private | ||
|
||
def product_images | ||
@variant.product.try(:variant_images) || [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this needs to be a try
. Is @variant
ever nil
?
@@ -0,0 +1,23 @@ | |||
module Spree |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure there's a need for this base class. It should be sufficient to duck-type have the two gallery classes "quack" similarly 🦆 🦆 🦆 🦆 🦆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the reason I would want the base class is so that it's obvious the interface that should be implemented for galleries in general. But I understand your point and can make the changes.
🦆 🦆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also don’t see a need for the base class. The two example implementations should be enough.
Everything else is 👌🏻
This is ready for another review. There have been a few changes since the last reviews. I have added When a variant has no images itself, why do we allow developers to choose to fallback to the first associated product image in some cases but not others? Could I remove the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think we should put all the image display logic into the gallery objects. They could defer from partial to partial and this screams for adding even more methods into that classes. Secondary_image? And what’s a best image anyways ;)
TL;DR let’s just remove them and be more explicit in the views. This might be not dry, but provides much more flexibility on the view layer, that is way easier to override and customize than a class. Also caching is much easier this way.
private | ||
|
||
def product_images | ||
@variant.product.variant_images |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t see a need for this private method as long as we don’t access it from two different methods at least.
# @return [Spree::Image] best image for the variant gallery | ||
def best_image | ||
@variant.images.first || product_images.first || Spree::Image.new | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not remove these image collector methods from here and move into the views. This might be more duplication, but is also more flexible. Although I think the naming is difficult and misleading. Let’s not make these decisions here, but in the views. They are easier to override in a per use case basis.
@@ -0,0 +1,21 @@ | |||
require 'rails_helper' | |||
|
|||
RSpec.describe Spree::ImagesHelper, type: :helper do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation detected.
#gallery
to Variant
and Product
#gallery
to Variant
and Product
#gallery
to Variant
and Product
#gallery
to Variant
and Product
core/app/models/spree/product.rb
Outdated
@@ -267,6 +267,8 @@ def total_on_hand | |||
# variants. If all else fails, will return a new image object. | |||
# @return [Spree::Image] the image to display | |||
def display_image | |||
Spree::Deprecation.warn('Spree::Product#display_image is DEPRECATED. ' \ | |||
'Choose an image from Spree::Product#gallery instead.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unexpected token tSTRING
core/app/models/spree/product.rb
Outdated
@@ -267,6 +267,8 @@ def total_on_hand | |||
# variants. If all else fails, will return a new image object. | |||
# @return [Spree::Image] the image to display | |||
def display_image | |||
Spree::Deprecation.warn('Spree::Product#display_image is DEPRECATED. ' \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bare backslash only allowed before newline
I think this is good to go. I've added |
Thomas and I discussed over this, and we think this is not the right abstraction (uh)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, so now we have an abstraction inbetween Spree::Variant
and the views, but the views still need to know Paperclip's API in order to display the images.
I would prefer if the galleries would return a simple list of paths or URLs that the views can use.
Something like this:
module Spree
class Variant
class Gallery
def initialize(variant)
@variant = variant
end
def image_urls(options)
@variant.images.map { |image| image.attachment(options) }
end
end
end
end
This would require us to change partials, too, but we would really lose the Paperclip API in the view layer, which without a change as radical as that we would not.
end | ||
|
||
it_behaves_like 'a gallery' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra empty line detected at block body end.
Thank you for the feedback.
Thanks to @derrickp for his input on this. The variant and product galleries return an Enumerable of images (things that must respond to Instead of having complex fallback rules, both the variant and product galleries can return empty Enumerables. Our image partials in the frontend/backend have historically dealt with ** If we are fine with removing this modal in the admin we can get rid of I'm interested in hearing what we think of where this is going. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now starting to look really good in my view. I have two minor nits regarding the URL used in views, which with the PR changes from :mini
to whatever is the default.
<% if itemprop %> | ||
<%= image_tag image.attachment(size), itemprop: itemprop %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not pass the :mini
parameter to the image. Maybe it should.
<% if image && image.attachment? %> | ||
<%= image_tag image.attachment(size) %> | ||
<% if image %> | ||
<%= image_tag image %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also missing the :mini
option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This partial is given an image url (the caller has already decided the size and retrieved the relevant url). I've updated the naming to be more clear.
Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should try to decouple from how an extension implements urls as much as possible. Currently the objects interacting with the gallery need to know to much implementation details. Preferably the gallery classes would only return images urls. Extensions would then implement gallery classes.
# | ||
# @return [Enumerable<Spree::Image>] all images in the gallery | ||
def images | ||
@images ||= (@product.images + @product.variant_images).uniq |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need to merge these two collections, because @product.variant_images
already includes images of the master variant - what are the images returned by @product.images
@@ -28,7 +28,9 @@ | |||
<td class="no-padding" rowspan="<%= row_count %>"> | |||
<div class='variant-container'> | |||
<div class='variant-image'> | |||
<%= render 'spree/admin/shared/image', image: variant.display_image(fallback: false), size: :small %> | |||
<%= render 'spree/admin/shared/image', | |||
image: variant.gallery.images.first.try(:url, :small), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing you're passing into the partial as image
is actually an url
. Shouldn't we rename the variable as well?
@@ -4,7 +4,9 @@ | |||
data-variant-id="<%= item.variant.id %>" | |||
> | |||
<td class="item-image align-center"> | |||
<%= render 'spree/admin/shared/image', image: item.variant.display_image, size: :mini %> | |||
<%= render 'spree/admin/shared/image', | |||
image: (item.variant.gallery.images.first || item.variant.product.gallery.images.first).try(:url, :mini), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense to have a first_image_url
method in the gallery classes.
item.variant.gallery.first_image_url(size: :mini) || item.variant.product.gallery.first_image_url(size: :mini)
core/app/models/spree/image.rb
Outdated
end | ||
|
||
def url(size) | ||
attachment.url(size) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not able to decouple from paperclip if we have this method inside of here. It would be better to put this url retrieving logic inside the gallery classes. Extensions like solidus_paperclip
would then implement such gallery classes.
<% if itemprop %> | ||
<%= image_tag image.attachment(size), itemprop: itemprop %> | ||
<%= image_tag image_url, itemprop: itemprop %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something to note here. We don't use an image's alt
attribute in either frontend or backend. Would being able to access image.alt
here be an argument for using a custom image class like I suggest in this PR comment? That image class could have a strict interface #url(size)
, #id
(for that single-use modal in backend/image/_image_row
), #viewable_id
, and #alt
.
Is there a reason why we haven't historically been rendering an image's alt
attribute? @mamhoff @tvdeyen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would being able to access image.alt here be an argument for using a custom image class
Yes! A separate image class (Picture
maybe?) makes total sense to abstract the presenter API 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why we haven't historically been rendering an image's alt attribute?
Not sure. We should render it (at least in the frontend).
end | ||
|
||
it_behaves_like 'a gallery' | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unexpected token kEND
end | ||
|
||
it_behaves_like 'a gallery' | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unexpected token kEND
core/app/models/spree/picture.rb
Outdated
end | ||
|
||
# Used by the API endpoints that expect Paperclip attributes | ||
def image |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use attr_reader to define trivial reader methods.
I have this to the point where we don't reference Paperclip specific calls anywhere in the views. We do continue to use Paperclip specific logic in the solidus_api controller for images, and in the actual form for uploading images. We can probably deal with these when someone goes to pull Paperclip into an extension. |
This is ready for another review. I plan on transitioning I am pretty happy with how this looks; the current state of the PR would make it easy for someone to pull out Paperclip, the only places that would need special attention are the places I mentioned in a previous comment. Very happy to discuss this either here or by DM on Slack if any of this isn't clear. |
Could we possibly take another look at this? With Rails 5.2/ActiveStorage close to release I think there should be some consideration to get Paperclip out. |
Paperclip is deprecated, we need to remove it from being stock Solidus. This becomes a problem because we have very specific Paperclip method calls in our view layer, and generally are too tightly coupled to everything Paperclip related. Having a pluggable gallery implementation for products and variants gives us better control of how we send things to the view layer and can hide implementation details of specific image uploading libraries. If a developer wants to plug a different image library they need to write a custom gallery implementation that returns a collection of images that respond to the same interface as `Spree::Image`. See PR 2337 for more context.
Image uploading extensions can define their own galleries (that quack like these ones) and use them in `Spree::Product` and `Spree::Variant`.
The view layer in Solidus uses too many many Paperclip specific calls. In order to remove Paperclip from core (since it is no longer being maintained) we need to move those calls out of the view layer and into here. The idea being that a different image provider need only to give the view layer images that respond to the same interface defined here. The image galleries (like Spree::Gallery::ProductGallery) should respond to `#images` with a collection of objects that respond to `#id`, `#filename`, `#url(size)`, and `#viewable_id`. Instead of removing Spree::Image and creating a new class to represent this image class for the view layer, we can just transition Spree::Image into that role.
This isn't being used anywhere in the project.
`Product` and `Variant` should not know the implementation details of their images. When they have that knowledge we end up coupling the image uploading library (like Paperclip) way too tightly to these core models. Developers are able to write their own custom gallery classes that respond to a single `#images` method that returns a collection of images that implement the interface defined in `Spree::Image`.
Doing this allows a developer to plug in different galleries for variants and products and not need to modify existing core models to change image providers. This change is motivated by the need to remove the deprecated Paperclip project from Solidus. See PR 2337 for more context.
Instead of calling a Paperclip specific method (`#attachment`) we instead want to use the more generic interface that we have now defined in `Spree::Image`. This way as long as our variant and product galleries return images that respond to a common interface we can swap out our image uploading libraries as we wish. The behaviour of this partial remains the same.
Instead of calling a Paperclip specific method (`#attachment`) we instead want to use the more generic interface that we have now defined in `Spree::Image`. This way as long as our variant and product galleries return images that respond to a common interface we can swap out our image uploading libraries as we wish.
Doing this allows a developer to plug in different galleries for variants and products and not need to modify existing core models to change image providers. This change is motivated by the need to remove the deprecated Paperclip project from Solidus. See PR 2337 for more context.
Doing this allows a developer to plug in different galleries for variants and products and not need to modify existing core models to change image providers. This change is motivated by the need to remove the deprecated Paperclip project from Solidus. See PR 2337 for more context.
Doing this allows a developer to plug in different galleries for variants and products and not need to modify existing core models to change image providers. This change is motivated by the need to remove the deprecated Paperclip project from Solidus. See PR 2337 for more context.
Products and variants should not need to worry about providing images to the view layer. They should instead push that responsibility to their galleries. Images should be picked from `#gallery.images` collection.
With Paperclip officially being deprecated can we take a serious look at merging this? When this is merged it will make extracting Paperclip into an extension much easier. I also think this is a better general approach to dealing with images in core/the view layer. @tvdeyen feedback would be appreciated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good abstraction that's waiting too long now. Sorry for keeping you waiting for this.
Things I would like to see in future PRs:
- Remove the
has_many :images
relation fromSpree::Product
andSpree::Variant
- Remove
Spree::Image
- Implement a presenter class for
#gallery.images
(Spree::GalleryPicture
|Spree::GalleryImage
)
🎉 🎁 |
Paperclip is deprecated and we need to remove it from stock Solidus.
In order to remove Paperclip properly we need to add extension points so that we can plug a different image uploading library (like Active Storage). Adding galleries to
Product
andVariant
will give us more control over what we send to the view layer, and protect our core models from image implementation details.To plug this interface a developer needs to create custom galleries for both
Variant
andProduct
and make sure that the#images
method of those return a collection of images that respond to the new "presentational" image interface the frontend expects. This PR includes changes toSpree::Image
which transitions it into a presentation role for Paperclip. Specifically, the presentational image interface that the view layer expects is:Before Paperclip can be extracted into its own extension properly we need this PR to be merged.
Much of the work around the the
Gallery
classes are heavily influenced by/verbatim the work Clarke did in #625.