Repository: Shopify/liquid (Ruby)

Issue:
'first' filter does not render liquid
@dylanahsmith

Example: https://gist.github.com/alexcoco/473aad631353f8c55d82

It looks like it returns a string representation of the object instead of rendering it.

`first` doesn't appear to call `to_liquid` whereas the for loop renders (and subsequently calls `to_liquid` on) every object in the collection before entering the loop.

Presumably this is because Liquid does not render array elements until they're being iterated over? But if they're accessed without being iterated over then they're not rendered when accessed.

I can print out all methods for the string: https://gist.github.com/alexcoco/663b1a9bfd05d5b70fb6



Discussion:
Currently, the only way I'm able to get a property out of the first element in a collection is with this:

```
{% for item in collection %}{{ item.property}}{% break %}{% endfor %}
```

It's ugly but it works. `{{ collection | first.property }}` doesn't work: `{{ collection | first }}` is not rendered yet.

There are a couple ways you should be able to accomplish this.  The first is by accessing the item with an array index:

```
{{ collection[0].property }}
```

And the second, while overkill for such a simple example, may be more useful based on what you're trying to do:

```
{% assign item = collection[0] %} or {% assign item = collection|first %}
{{ item.property }}
```

Thanks, @nickpearson. Using the index works. But this still doesn't explain the different behaviour when using the `first` filter. Is is expected that what `first` returns is not rendered?

I think liquid just needs to call `.to_liquid` in these filters.  e.g.

``` ruby
    def first(array)
      array.first.to_liquid if array.respond_to?(:first)
    end
```

@dylanahsmith, yeah, I agree. That would make sense. Are there any other implications in doing this? i.e. is there a reason why this wasn't already the case?

@fw42 do you think we should call `.to_liquid` on the output of all liquid filters to avoid this type of error?

What is the desired end result?  It seems to me you'd never want to directly render a hash produced by to_liquid without getting a property from it or passing it to something like a `to_json` filter.  As shown in the gist linked to above, doing so outputs `nameGarfield` and `nameSimba` (when iterating over an array of `[{ 'name' => 'Garfield' }, { 'name' => 'Simba' }]`).

I'm not saying this shouldn't be done -- just trying to understand what it is intended to accomplish.

At first I wanted to grab a property from the first element but then I realized that `{{ collection | first }}` doesn't return something with liquid. @nickpearson, you pointed out that there are ways to access properties from the first element when using `collection[index]` and that works well.

My point now, though, is that for anyone else expecting to be able to get an element from a collection using the `first` filter, I think it would make much more sense for the return value to have `to_liquid` called on it. Otherwise, what use is the string `#<Animal:0x1085b8f48>` to me when I expect it to return something useable in Liquid?

I see what you're saying, but my point is that even if the `first` filter did call `to_liquid`, the output would be `nameGarfield`, which isn't very useful/usable except to a developer.

However, if you had an array of strings or numbers, then the `first` filter will work as you'd expect.  For example, if `collection` is `['Garfield', 'Simba']`, then `{{ collection | first }}` returns `Garfield`, not `#<String:0x...>`.

It seems a better option might be to implement an `inspect` filter, which simply calls `.inspect` on its input:

``` ruby
def inspect(input)
  input.inspect
end
```

This would allow you to do the following and get some meaningful/readable output:

```
{{ collection | first | inspect }} #=> '{"name"=>"Garfield"}'
```

This would also be useful in lots of other scenarios as well.  I have implemented this filter in my apps, and I use it all the time when writing Liquid template code.

:-1: to an inspect filter.  Calling `inspect` on a drop can easily leak sensitive information.

@dylanahsmith, I think you mentioned that allowing inspect may be dangerous?

You're right that if the collection contained simple types like strings or numbers then it would be okay. I guess since something like `{{ collection | first.property }}` doesn't make sense then there's never really a use case for needing `first` to return liquid.

I do still think that using `{{ collection[0].property }}` could definitely look a little better.

Also, I don't think that `{% assign item = collection|first %}` works. It assigns `collection` to `item` and passes `item` to first:

```
{% assign animal = animals|first %}Assign first:
{{ animal }} ... {{ animal.name }}
```

renders

```
Assign first:
#<Animal:0x105ab3028>#<Animal:0x105ab2fd8> ... 
```

@dylanahsmith, agreed.  I typed out a quick version for conversation without regard for the security implications.  Thanks for pointing this out.  I do still think it would be useful (for developer discoverability) to have a way of seeing what's inside a Liquid-accessible object, but that's probably outside the scope of the base Liquid project.

I don't really know why we have a first filter at all... What are we going to do with this PR?

If there's a way to find out if it's actually widely used that would be interesting. Maybe we can remove it in an upcoming release. Otherwise, we should be calling to_liquid on the result in case the first element isn't a string, number, hash, etc.

``` ruby
def first(array)
  array.first.try(:to_liquid) if array.respond_to?(:first)
end
```

`#try` in case the array is empty and first is `nil`.

Maybe we should do this more generically, i.e., no matter which filter you use, Liquid should always call to_liquid on the result (if it responds to it). Thoughts, @dylanahsmith?

@alexcoco, can you check if this is still an issue?

This wasn't a blocker, more of a concern. I think `first` and `last` still return the underlying objects rather than liquid though.




Patch:
diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb
index 73ffca05c..9ff7260f1 100644
--- a/lib/liquid/context.rb
+++ b/lib/liquid/context.rb
@@ -105,7 +105,7 @@ def handle_error(e)
     end
 
     def invoke(method, *args)
-      strainer.invoke(method, *args)
+      strainer.invoke(method, *args).to_liquid
     end
 
     # Push new local scope on the stack. use <tt>Context#stack</tt> instead


Test patch:
diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb
index ffa2ebb76..7e2cc6da7 100644
--- a/test/integration/standard_filter_test.rb
+++ b/test/integration/standard_filter_test.rb
@@ -220,6 +220,11 @@ def test_sort_works_on_enumerables
     assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
   end
 
+  def test_first_and_last_call_to_liquid
+    assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
+    assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
+  end
+
   def test_date
     assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
     assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
diff --git a/test/integration/variable_test.rb b/test/integration/variable_test.rb
index 37456cc78..bd9813165 100644
--- a/test/integration/variable_test.rb
+++ b/test/integration/variable_test.rb
@@ -9,6 +9,10 @@ def test_simple_variable
     assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
   end
 
+  def test_variable_render_calls_to_liquid
+    assert_template_result 'foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new
+  end
+
   def test_simple_with_whitespaces
     template = Template.parse(%|  {{ test }}  |)
     assert_equal '  worked  ', template.render!('test' => 'worked')
diff --git a/test/test_helper.rb b/test/test_helper.rb
index d513f6554..9c0daa99e 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -66,3 +66,9 @@ def with_error_mode(mode)
     end
   end
 end
+
+class ThingWithToLiquid
+  def to_liquid
+    'foobar'
+  end
+end