Detect `nil` and empty values in objects with `where` filter (#7580)
Merge pull request 7580
This commit is contained in:
parent
16c24d9125
commit
9240addcf0
|
@ -104,6 +104,21 @@ The default is `default`. They are as follows (with what they filter):
|
||||||
- `ascii`: spaces, non-alphanumeric, and non-ASCII characters
|
- `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" -%}.
|
- `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
|
### 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.
|
For your convenience, here is the list of all [Liquid filters]({{ page.shopify_filter_url }}) with links to examples in the official Liquid documentation.
|
||||||
|
|
|
@ -107,3 +107,55 @@ Feature: Embed filters
|
||||||
Then I should get a zero exit status
|
Then I should get a zero exit status
|
||||||
And the _site directory should exist
|
And the _site directory should exist
|
||||||
And I should see exactly "The rule of 3: Fly, Run, Jump," in "_site/bird.html"
|
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"
|
||||||
|
|
|
@ -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|
|
Given(%r!^I have an? (.*) directory$!) do |dir|
|
||||||
unless File.directory?(dir)
|
unless File.directory?(dir)
|
||||||
then FileUtils.mkdir_p(dir)
|
then FileUtils.mkdir_p(dir)
|
||||||
|
|
|
@ -161,13 +161,15 @@ module Jekyll
|
||||||
|
|
||||||
# Filter an array of objects
|
# Filter an array of objects
|
||||||
#
|
#
|
||||||
# input - the object array
|
# input - the object array.
|
||||||
# property - property within each object to filter by
|
# property - the property within each object to filter by.
|
||||||
# value - desired value
|
# 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
|
# Returns the filtered array of objects
|
||||||
def where(input, property, value)
|
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)
|
return input unless input.respond_to?(:select)
|
||||||
|
|
||||||
input = input.values if input.is_a?(Hash)
|
input = input.values if input.is_a?(Hash)
|
||||||
|
@ -182,8 +184,8 @@ module Jekyll
|
||||||
# stash or retrive results to return
|
# stash or retrive results to return
|
||||||
@where_filter_cache[input_id][property][value] ||= begin
|
@where_filter_cache[input_id][property][value] ||= begin
|
||||||
input.select do |object|
|
input.select do |object|
|
||||||
Array(item_property(object, property)).map!(&:to_s).include?(value.to_s)
|
compare_property_vs_target(item_property(object, property), value)
|
||||||
end || []
|
end.to_a
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -323,6 +325,22 @@ module Jekyll
|
||||||
.map!(&:last)
|
.map!(&:last)
|
||||||
end
|
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)
|
def item_property(item, property)
|
||||||
@item_property_cache ||= {}
|
@item_property_cache ||= {}
|
||||||
@item_property_cache[property] ||= {}
|
@item_property_cache[property] ||= {}
|
||||||
|
|
|
@ -844,6 +844,11 @@ class TestFilters < JekyllUnitTest
|
||||||
assert_equal 2, @filter.where(@array_of_objects, "color", "red").length
|
assert_equal 2, @filter.where(@array_of_objects, "color", "red").length
|
||||||
end
|
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
|
should "filter array properties appropriately" do
|
||||||
hash = {
|
hash = {
|
||||||
"a" => { "tags"=>%w(x y) },
|
"a" => { "tags"=>%w(x y) },
|
||||||
|
@ -862,6 +867,36 @@ class TestFilters < JekyllUnitTest
|
||||||
assert_equal 2, @filter.where(hash, "tags", "x").length
|
assert_equal 2, @filter.where(hash, "tags", "x").length
|
||||||
end
|
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
|
should "not match substrings" do
|
||||||
hash = {
|
hash = {
|
||||||
"a" => { "category"=>"bear" },
|
"a" => { "category"=>"bear" },
|
||||||
|
|
Loading…
Reference in New Issue