Detect `nil` and empty values in objects with `where` filter (#7580)

Merge pull request 7580
This commit is contained in:
Ashwin Maroli 2019-03-22 20:23:34 +05:30 committed by jekyllbot
parent 16c24d9125
commit 9240addcf0
5 changed files with 137 additions and 6 deletions

View File

@ -104,6 +104,21 @@ The default is `default`. They are as follows (with what they filter):
- `ascii`: spaces, non-alphanumeric, and non-ASCII characters
- `latin`: like `default`, except Latin characters are first transliterated (e.g. `àèïòü` to `aeiou`) {%- include docs_version_badge.html version="3.7.0" -%}.
### Detecting `nil` values with `where` filter {%- include docs_version_badge.html version="4.0.0" -%}
You can use the `where` filter to detect documents and pages with properties that are `nil` or `""`. For example,
```liquid
// Using `nil` to select posts that either do not have `my_prop` defined or `my_prop` has been set to `nil` explicitly.
{% raw %}{% assign filtered_posts = site.posts | where: 'my_prop', nil %}{% endraw %}
```
```liquid
// Using Liquid's special literal `empty` or `blank` to select posts that have `my_prop` set to an empty value.
{% raw %}{% assign filtered_posts = site.posts | where: 'my_prop', empty %}{% endraw %}
```
### Standard Liquid Filters
For your convenience, here is the list of all [Liquid filters]({{ page.shopify_filter_url }}) with links to examples in the official Liquid documentation.

View File

@ -107,3 +107,55 @@ Feature: Embed filters
Then I should get a zero exit status
And the _site directory should exist
And I should see exactly "The rule of 3: Fly, Run, Jump," in "_site/bird.html"
Scenario: Filter posts by given property and value
Given I have a _posts directory
And I have the following posts:
| title | date | content | property |
| Bird | 2019-03-13 | Chirp | [nature, sounds] |
| Cat | 2019-03-14 | Meow | [sounds] |
| Dog | 2019-03-15 | Bark | |
| Elephant | 2019-03-16 | Asiatic | wildlife |
| Goat | 2019-03-17 | Mountains | "" |
| Horse | 2019-03-18 | Mustang | [] |
| Iguana | 2019-03-19 | Reptile | {} |
| Jaguar | 2019-03-20 | Reptile | {foo: lorem, bar: nature} |
And I have a "string-value.md" page with content:
"""
{% assign pool = site.posts | reverse | where: 'property', 'wildlife' %}
{{ pool | map: 'title' | join: ', ' }}
"""
And I have a "string-value-array.md" page with content:
"""
{% assign pool = site.posts | reverse | where: 'property', 'sounds' %}
{{ pool | map: 'title' | join: ', ' }}
"""
And I have a "string-value-hash.md" page with content:
"""
{% assign pool = site.posts | reverse | where: 'property', 'nature' %}
{{ pool | map: 'title' | join: ', ' }}
"""
And I have a "nil-value.md" page with content:
"""
{% assign pool = site.posts | reverse | where: 'property', nil %}
{{ pool | map: 'title' | join: ', ' }}
"""
And I have an "empty-liquid-literal.md" page with content:
"""
{% assign pool = site.posts | reverse | where: 'property', empty %}
{{ pool | map: 'title' | join: ', ' }}
"""
And I have a "blank-liquid-literal.md" page with content:
"""
{% assign pool = site.posts | reverse | where: 'property', blank %}
{{ pool | map: 'title' | join: ', ' }}
"""
When I run jekyll build
Then I should get a zero exit status
And the _site directory should exist
And I should see exactly "<p>Elephant</p>" in "_site/string-value.html"
And I should see exactly "<p>Bird, Cat</p>" in "_site/string-value-array.html"
And I should see exactly "<p>Bird</p>" in "_site/string-value-hash.html"
And I should see exactly "<p>Dog</p>" in "_site/nil-value.html"
And I should see exactly "<p>Dog, Goat, Horse, Iguana</p>" in "_site/empty-liquid-literal.html"
And I should see exactly "<p>Dog, Goat, Horse, Iguana</p>" in "_site/blank-liquid-literal.html"

View File

@ -67,6 +67,17 @@ end
#
Given(%r!^I have an? "(.*)" page with content:$!) do |file, text|
File.write(file, <<~DATA)
---
---
#{text}
DATA
end
#
Given(%r!^I have an? (.*) directory$!) do |dir|
unless File.directory?(dir)
then FileUtils.mkdir_p(dir)

View File

@ -161,13 +161,15 @@ module Jekyll
# Filter an array of objects
#
# input - the object array
# property - property within each object to filter by
# value - desired value
# input - the object array.
# property - the property within each object to filter by.
# value - the desired value.
# Cannot be an instance of Array nor Hash since calling #to_s on them returns
# their `#inspect` string object.
#
# Returns the filtered array of objects
def where(input, property, value)
return input if property.nil? || value.nil?
return input if !property || value.is_a?(Array) || value.is_a?(Hash)
return input unless input.respond_to?(:select)
input = input.values if input.is_a?(Hash)
@ -182,8 +184,8 @@ module Jekyll
# stash or retrive results to return
@where_filter_cache[input_id][property][value] ||= begin
input.select do |object|
Array(item_property(object, property)).map!(&:to_s).include?(value.to_s)
end || []
compare_property_vs_target(item_property(object, property), value)
end.to_a
end
end
@ -323,6 +325,22 @@ module Jekyll
.map!(&:last)
end
# `where` filter helper
def compare_property_vs_target(property, target)
case target
when NilClass
return true if property.nil?
when Liquid::Expression::MethodLiteral # `empty` or `blank`
return true if Array(property).join == target.to_s
else
Array(property).each do |prop|
return true if prop.to_s == target.to_s
end
end
false
end
def item_property(item, property)
@item_property_cache ||= {}
@item_property_cache[property] ||= {}

View File

@ -844,6 +844,11 @@ class TestFilters < JekyllUnitTest
assert_equal 2, @filter.where(@array_of_objects, "color", "red").length
end
should "filter objects with null properties appropriately" do
array = [{}, { "color" => nil }, { "color" => "" }, { "color" => "text" }]
assert_equal 2, @filter.where(array, "color", nil).length
end
should "filter array properties appropriately" do
hash = {
"a" => { "tags"=>%w(x y) },
@ -862,6 +867,36 @@ class TestFilters < JekyllUnitTest
assert_equal 2, @filter.where(hash, "tags", "x").length
end
should "filter hash properties with null and empty values" do
hash = {
"a" => { "tags" => {} },
"b" => { "tags" => "" },
"c" => { "tags" => nil },
"d" => { "tags" => ["x", nil] },
"e" => { "tags" => [] },
"f" => { "tags" => "xtra" },
}
assert_equal [{ "tags" => nil }], @filter.where(hash, "tags", nil)
assert_equal(
[{ "tags" => "" }, { "tags" => ["x", nil] }],
@filter.where(hash, "tags", "")
)
# `{{ hash | where: 'tags', empty }}`
assert_equal(
[{ "tags" => {} }, { "tags" => "" }, { "tags" => nil }, { "tags" => [] }],
@filter.where(hash, "tags", Liquid::Expression::LITERALS["empty"])
)
# `{{ `hash | where: 'tags', blank }}`
assert_equal(
[{ "tags" => {} }, { "tags" => "" }, { "tags" => nil }, { "tags" => [] }],
@filter.where(hash, "tags", Liquid::Expression::LITERALS["blank"])
)
end
should "not match substrings" do
hash = {
"a" => { "category"=>"bear" },