Shopify / liquid

Liquid markup language. Safe, customer facing template language for flexible web apps.
https://shopify.github.io/liquid/
MIT License
11.11k stars 1.39k forks source link

New Object Feature brainstorm #1237

Open zrothberg opened 4 years ago

zrothberg commented 4 years ago

So quick rant and proposal followed by use cases and reasoning. I would like to propose a new feature I am going to refer to as "masks" for liquid objects.

What is a mask? A mask is a object modifier that takes a liquid object and returns a new masked one only using passed values and globals. The idea is to be able to create new attributes/modify existing ones for objects in a consistent, predictable, and repeatable manner. An example of this would be

mask product_with_avg_price:

     {{old_object}}
     {% assign new_object = {} %} #no idea what the correct syntax is here
     {{assign counter = 0}}
     {{assign pricing = 0 }}
     {for price in old_object.variants.variant.price}
          {counter = counter | plus 1}
          {pricing = pricing | plus price}
     {end for}
     {%assign return_object.avg_price = pricing | divide_by : counter %}
     {% return_pointer = new_object %}

products.liquid:

     {% mask "product_with_avg_price", old_object = product, return_pointer = new_object_pointer %} 

     {{new_object_pointer.avg_price}}

This would allow us to encapsulate website wide object logic into the objects themselves instead of into externally referenced snippets and render functions. Further this would cut down on the number of local variables in the template and snippet sections as the ones used to calculate values would be moved into the mask scope and could be discarded after calculation. I believe the masks could also be cached with a high hit rate and may help speed up page load if we where to restrict them to global scoped variables only. Most importantly this greatly increase developers flexibility within platforms using liquid. Being able to create new object scopes allows for a wide variety of applications without having to expose to much to external parties.

A few uses cases from our own code base in shopify. Product status, Product compare at price, Collection filter options, Custom Json objects, ect.

Product status and compare at price are values that need to be calculated for each product and are used anytime a product is shown on our site. Being able to ensure the same value we calculate at the homepage is the values we calculate at the collection page would be extremely helpful and creates enforceable consistency.

Now technically we can emulate some of this behavior right now with render but it would require a lot of reworking our snippets to make them load from product view down. As well as force us to either needlessly calculate values, have ugly if else statements to pass caller references to ensure we only calculate what we need to each time, or separate the calculations into different render snippets. Being able to mask our objects would be much more easily transferable to most liquid code as it would simply require you to replace the template and snippet file references. You would not even need to remove old calculations allowing for users to test the feature out in a piecemeal fashion.

Anyway let me know what yall think. I read a fair amount of the codebase here but I do not write in ruby so if I am missing some implementation details that would make this impossible or not very efficient I apologize. Really just looking for feedback and if this is something that seems interesting to do.

matthijskooijman commented 3 years ago

I've been looking at a similar problem recently. Looking at your suggestion, it seems quite specific, I wonder if what you're asking cannot be more generally fixed by:

IMHO, both of these things could also be independently useful (i.e. you might want to create an object at the top of your template and use it further below, or let an include return a string rather than an object).

Regarding your example:

matthijskooijman commented 3 years ago

Oh, just read up on render and include tags and it seems the include tag can already "return" objects, since its scope is shared with the parent scope (so any variables written to are visible in the parent scope). But include is deprecated for this very reason, so some way for render to return values in a controlled way would still make sense.