-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Consolidate contextualization of inputs via the context #1537
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -187,16 +187,11 @@ def find_variable(key, raise_on_not_found: true) | |||||||||
# path and find_index() is optimized in MRI to reduce object allocation | ||||||||||
index = @scopes.find_index { |s| s.key?(key) } | ||||||||||
|
||||||||||
variable = if index | ||||||||||
if index | ||||||||||
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found) | ||||||||||
else | ||||||||||
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found) | ||||||||||
end | ||||||||||
|
||||||||||
variable = variable.to_liquid | ||||||||||
variable.context = self if variable.respond_to?(:context=) | ||||||||||
|
||||||||||
variable | ||||||||||
end | ||||||||||
|
||||||||||
def lookup_and_evaluate(obj, key, raise_on_not_found: true) | ||||||||||
|
@@ -207,9 +202,9 @@ def lookup_and_evaluate(obj, key, raise_on_not_found: true) | |||||||||
value = obj[key] | ||||||||||
|
||||||||||
if value.is_a?(Proc) && obj.respond_to?(:[]=) | ||||||||||
obj[key] = value.arity == 0 ? value.call : value.call(self) | ||||||||||
obj[key] = contextualize(value) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not changing the memoization in this PR as it let to too large of a change with even more unrelated code alterations. I feel we should look at:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would have been clearer, but achieve the same behaviour with more code. No strong opinion on my end.
Suggested change
|
||||||||||
else | ||||||||||
value | ||||||||||
contextualize(value) | ||||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
|
@@ -228,6 +223,20 @@ def tag_disabled?(tag_name) | |||||||||
@disabled_tags.fetch(tag_name, 0) > 0 | ||||||||||
end | ||||||||||
|
||||||||||
# Convert input objects into liquid aware representations | ||||||||||
# Procs will be resolved | ||||||||||
# Assigns the context (self) through context= | ||||||||||
def contextualize(object) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method name makes it seem like its primary purpose is to add a liquid context to an object. I had proposed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I get from the name that is sets Could we instead move the "contextualization" to |
||||||||||
if object.is_a?(Proc) | ||||||||||
object = object.arity == 0 ? object.call : object.call(self) | ||||||||||
end | ||||||||||
Comment on lines
+230
to
+232
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should add support for
Longer term, it would be better to reduce support for |
||||||||||
|
||||||||||
object = object.to_liquid | ||||||||||
object.context = self if object.respond_to?(:context=) | ||||||||||
|
||||||||||
object | ||||||||||
end | ||||||||||
|
||||||||||
protected | ||||||||||
|
||||||||||
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -582,8 +582,7 @@ def empty? | |
|
||
def each | ||
@input.each do |e| | ||
e = e.respond_to?(:to_liquid) ? e.to_liquid : e | ||
e.context = @context if e.respond_to?(:context=) | ||
e = @context.contextualize(e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As noted in the description, this change no longer exclude There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Seems like making the Combining refactoring and behaviour changes in the same PR can end up making the behaviour changes quite subtle. Instead, one PR could be based on another. E.g. the behaviour change PR could be done first, then the contextualize change can refactor it. Alternatively, the refactor PR could just replace the code that doesn't result in a behaviour change, then the behaviour change PR can use a new help method from the refactor PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can have a closer look. Making it unconditional was making a few tests of If we wanted to make the call unconditional in isolation, I will have to either:
I think going with 1 is a good tradeoff for now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's assume that a Proc type is the only non-liquid value that needs to be handled here. In that case, we can use a This deprecation should be communicated as deprecating support for having a Proc item in an enumerable. On the other hand, I think we should continue supporting a Proc value in liquid Hash objects and should make sure to memoize the result of evaluating the Proc. This means that the property lookup in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are other filters that support looking properties, which should be able to just use That helper method could be used in the |
||
yield(e) | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -34,6 +34,10 @@ def initialize(value:) | |||||
def registers | ||||||
@context.registers | ||||||
end | ||||||
|
||||||
def to_s | ||||||
"TestDrop(value:#{@value})" | ||||||
end | ||||||
end | ||||||
|
||||||
class TestModel | ||||||
|
@@ -467,10 +471,38 @@ def test_sort_calls_to_liquid | |||||
end | ||||||
|
||||||
def test_map_over_proc | ||||||
drop = TestDrop.new(value: "testfoo") | ||||||
drop = TestDrop.new(value: "123") | ||||||
p = proc { drop } | ||||||
templ = '{{ procs | map: "value" }}' | ||||||
assert_template_result("testfoo", templ, "procs" => [p]) | ||||||
assert_template_result("123", templ, "procs" => [p]) | ||||||
end | ||||||
|
||||||
def test_join_over_proc | ||||||
drop = TestDrop.new(value: "123") | ||||||
p = proc { drop } | ||||||
templ = '{{ procs | join }}' | ||||||
assert_template_result('TestDrop(value:123)', templ, "procs" => [p]) | ||||||
end | ||||||
|
||||||
def test_sort_over_proc | ||||||
drop = TestDrop.new(value: "123") | ||||||
p = proc { drop } | ||||||
templ = '{{ procs | sort: "value" }}' | ||||||
assert_template_result("TestDrop(value:123)", templ, "procs" => [p]) | ||||||
end | ||||||
|
||||||
def test_where_over_proc | ||||||
drop = TestDrop.new(value: "123") | ||||||
p = proc { drop } | ||||||
templ = '{{ procs | where: "value", "123" }}' | ||||||
assert_template_result("TestDrop(value:123)", templ, "procs" => [p]) | ||||||
end | ||||||
|
||||||
def test_uniq_over_proc | ||||||
drop = TestDrop.new(value: "123") | ||||||
p = proc { drop } | ||||||
templ = '{{ procs | uniq }}' | ||||||
assert_template_result("TestDrop(value:123)", templ, "procs" => [p]) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests are to cover the new behavior for Proc over filters where historically they would have raised on missing method |
||||||
end | ||||||
|
||||||
def test_map_over_drops_returning_procs | ||||||
|
@@ -888,7 +920,7 @@ def test_all_filters_never_raise_non_liquid_exception | |||||
{ foo: "bar" }, | ||||||
[{ "foo" => "bar" }, { "foo" => 123 }, { "foo" => nil }, { "foo" => true }, { "foo" => ["foo", "bar"] }], | ||||||
{ 1 => "bar" }, | ||||||
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }], | ||||||
["foo", 123, nil, true, false, ["foo"], { foo: "bar" }], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps the intention was to test against a drop instance here, which should be supported
Suggested change
|
||||||
] | ||||||
StandardFilters.public_instance_methods(false).each do |method| | ||||||
arg_count = @filters.method(method).arity | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -272,14 +272,36 @@ def test_multiple_named_cycles_with_names_from_context | |
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns) | ||
end | ||
|
||
def test_size_of_array | ||
assigns = { "array" => [1, 2, 3, 4] } | ||
assert_template_result('array has 4 elements', "array has {{ array.size }} elements", assigns) | ||
def test_command_methods_of_array | ||
assigns = { "array" => [11, 22, 33] } | ||
|
||
assert_template_result("3", "{{ array.size }}", assigns) | ||
|
||
assert_template_result("11", "{{ array.first }}", assigns) | ||
|
||
assert_template_result("33", "{{ array.last }}", assigns) | ||
end | ||
|
||
def test_command_methods_of_hash | ||
assigns = { "hash" => { a: 11, b: 22, c: 33 } } | ||
|
||
assert_template_result("3", "{{ hash.size }}", assigns) | ||
|
||
assert_template_result("a11", "{{ hash.first }}", assigns) | ||
assert_template_result("a", "{{ hash.first.first }}", assigns) | ||
assert_template_result("11", "{{ hash.first.last }}", assigns) | ||
|
||
assert_template_result("", "{{ hash.last }}", assigns) | ||
end | ||
|
||
def test_size_of_hash | ||
assigns = { "hash" => { a: 1, b: 2, c: 3, d: 4 } } | ||
assert_template_result('hash has 4 elements', "hash has {{ hash.size }} elements", assigns) | ||
def test_command_methods_with_proc | ||
skip("Liquid-C does not properly resolve Procs in with command methods") if ENV['LIQUID_C'] == '1' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Liquid-C does not pass this test. Skipping for now as I think we need to iterate further on Proc handling without blocking iterative work. |
||
|
||
assigns = { "array" => [proc { "test" }] } | ||
assert_template_result("test", "{{ array.first }}", assigns) | ||
|
||
assigns = { "hash" => { a: proc { "test" } } } | ||
assert_template_result("test", "{{ hash.first.last }}", assigns) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests are to cover the new behavior for Proc where historically they would have returned the Proc as string (eg.: |
||
end | ||
|
||
def test_illegal_symbols | ||
|
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.
Due to the potential "breaking change" in
StandardFilters
andVariableLookup
whereProc
are now properly handled, we might to look at this to be similar to #1527 flagged as deprecation for 6.0.I do not think anyone expected to have
Proc
to be exposed directly to templates.For standard filters specifically, that
respond_to?(:to_liquid)
might have left non-liquid aware object in by potential design error. I could see use cases leveraging this unexpectedly. They have an easy path forward to migrate, but a deprecation warning might be of interest here.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.
"contextualization" is a new concept, which makes this change log entry very unclear about what is being fixed. That seems like just a symptom of the fact that this PR combines multiple changes, a new feature (added method), behaviour fixes and some refactoring (which doesn't need to be in the changelog).