From 842986a9721de11e71387732be51951285225977 Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 13 Sep 2018 16:37:38 -0400 Subject: [PATCH] Add `where` filter to standard filters Users of Liquid will often wish to filter an array to only those items that match a certain criteria. For example, showing "pinned" messages at the top of a list. Example usage: `{{ comments | where: "pinned" | first }}` or `{{ products | where: "category", "kitchen" }}` * Add where filter to standard filters * Add tests for new where functionality --- lib/liquid/standardfilters.rb | 32 +++++++++++ test/integration/standard_filter_test.rb | 72 ++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index c5dbcb8..29c2cad 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -151,6 +151,20 @@ module Liquid end end + # Filter the elements of an array to those with a certain property value. + # By default the target is any truthy value. + def where(input, property, target_value = nil) + ary = InputIterator.new(input) + + if ary.empty? + [] + elsif ary.first.respond_to?(:[]) && target_value.nil? + ary.where_present(property) + elsif ary.first.respond_to?(:[]) + ary.where(property, target_value) + end + end + # Remove duplicate elements from an array # provide optional property with which to determine uniqueness def uniq(input, property = nil) @@ -429,6 +443,24 @@ module Liquid yield(e.respond_to?(:to_liquid) ? e.to_liquid : e) end end + + def where(property, target_value) + select do |item| + item[property] == target_value + end + rescue TypeError + # Cannot index with the given property type (eg. indexing integers with strings + # which are only allowed to be indexed by other integers). + raise ArgumentError.new("cannot select the property `#{property}`") + end + + def where_present(property) + select { |item| item[property] } + rescue TypeError + # Cannot index with the given property type (eg. indexing integers with strings + # which are only allowed to be indexed by other integers). + raise ArgumentError.new("cannot select the property `#{property}`") + end end end diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index d39472e..75e6e8d 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -558,6 +558,78 @@ class StandardFiltersTest < Minitest::Test assert_template_result('abc', "{{ 'abc' | date: '%D' }}") end + def test_where + input = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "beta", "ok" => false }, + { "handle" => "gamma", "ok" => false }, + { "handle" => "delta", "ok" => true } + ] + + expectation = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "delta", "ok" => true } + ] + + assert_equal expectation, @filters.where(input, "ok", true) + assert_equal expectation, @filters.where(input, "ok") + end + + def test_where_no_key_set + input = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "beta" }, + { "handle" => "gamma" }, + { "handle" => "delta", "ok" => true } + ] + + expectation = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "delta", "ok" => true } + ] + + assert_equal expectation, @filters.where(input, "ok", true) + assert_equal expectation, @filters.where(input, "ok") + end + + def test_where_non_array_map_input + assert_equal [{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok") + assert_equal [], @filters.where({ "a" => "not ok" }, "a", "ok") + end + + def test_where_indexable_but_non_map_value + assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) } + assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") } + end + + def test_where_non_boolean_value + input = [ + { "message" => "Bonjour!", "language" => "French" }, + { "message" => "Hello!", "language" => "English" }, + { "message" => "Hallo!", "language" => "German" } + ] + + assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French") + assert_equal [{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German") + assert_equal [{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English") + end + + def test_where_array_of_only_unindexable_values + assert_nil @filters.where([nil], "ok", true) + assert_nil @filters.where([nil], "ok") + end + + def test_where_no_target_value + input = [ + { "foo" => false }, + { "foo" => true }, + { "foo" => "for sure" }, + { "bar" => true } + ] + + assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo") + end + private def with_timezone(tz)