Permalink
Browse files

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
  • Loading branch information...
samdoiron committed Sep 13, 2018
1 parent 70e7571 commit 842986a9721de11e71387732be51951285225977
Showing with 104 additions and 0 deletions.
  1. +32 −0 lib/liquid/standardfilters.rb
  2. +72 −0 test/integration/standard_filter_test.rb
@@ -151,6 +151,20 @@ def sort_natural(input, property = nil)
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 @@ def each
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

@@ -558,6 +558,78 @@ def test_date_raises_nothing
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)

0 comments on commit 842986a

Please sign in to comment.