diff --git a/app/concerns/liquid_interpolatable.rb b/app/concerns/liquid_interpolatable.rb
index dc8cb56c02..772d78f708 100644
--- a/app/concerns/liquid_interpolatable.rb
+++ b/app/concerns/liquid_interpolatable.rb
@@ -283,6 +283,27 @@ def as_object(object)
throw :as_object, object.as_json
end
+ # Group an array of items by a property
+ #
+ # Example usage:
+ #
+ # {% assign posts_by_author = site.posts | group_by: "author" %}
+ # {% for author in posts_by_author %}
+ #
{{author.name}}
+ # {% for post in author.items %}
+ # {{post.title}}
+ # {% endfor %}
+ # {% endfor %}
+ def group_by(input, property)
+ if input.respond_to?(:group_by)
+ input.group_by { |item| item[property] }.map do |value, items|
+ { 'name' => value, 'items' => items }
+ end
+ else
+ input
+ end
+ end
+
private
def logger
diff --git a/spec/concerns/liquid_interpolatable_spec.rb b/spec/concerns/liquid_interpolatable_spec.rb
index 6db0a66ca9..711c09566d 100644
--- a/spec/concerns/liquid_interpolatable_spec.rb
+++ b/spec/concerns/liquid_interpolatable_spec.rb
@@ -86,11 +86,11 @@ def @filter.to_xpath_roundtrip(string)
@agent.interpolation_context['s'] = 'http://example.com/dir/1?q=test'
end
- it 'should parse an abosule URI' do
+ it 'should parse an absolute URI' do
expect(@filter.to_uri('http://example.net/index.html', 'http://example.com/dir/1')).to eq(URI('http://example.net/index.html'))
end
- it 'should parse an abosule URI with a base URI specified' do
+ it 'should parse an absolute URI with a base URI specified' do
expect(@filter.to_uri('http://example.net/index.html', 'http://example.com/dir/1')).to eq(URI('http://example.net/index.html'))
end
@@ -98,7 +98,7 @@ def @filter.to_xpath_roundtrip(string)
expect(@filter.to_uri('foo/index.html', 'http://example.com/dir/1')).to eq(URI('http://example.com/dir/foo/index.html'))
end
- it 'should parse an abosule URI with a base URI specified' do
+ it 'should parse an absolute URI with a base URI specified' do
expect(@filter.to_uri('http://example.net/index.html', 'http://example.com/dir/1')).to eq(URI('http://example.net/index.html'))
end
@@ -394,4 +394,40 @@ def ensure_safety(obj)
expect(agent.interpolated['template']).to eq('38b98bc2625a8cac33369f6204e784482be5e172b242699406270856a841d1ec')
end
end
+
+ describe 'group_by' do
+ let(:events) do
+ [
+ { "date" => "2019-07-30", "type" => "Snap" },
+ { "date" => "2019-07-30", "type" => "Crackle" },
+ { "date" => "2019-07-29", "type" => "Pop" },
+ { "date" => "2019-07-29", "type" => "Bam" },
+ { "date" => "2019-07-29", "type" => "Pow" },
+ ]
+ end
+
+ it "should group an enumerable by the given attribute" do
+ expect(@filter.group_by(events, "date")).to eq(
+ [
+ {
+ "name" => "2019-07-30", "items" => [
+ { "date" => "2019-07-30", "type" => "Snap" },
+ { "date" => "2019-07-30", "type" => "Crackle" }
+ ]
+ },
+ {
+ "name" => "2019-07-29", "items" => [
+ { "date" => "2019-07-29", "type" => "Pop" },
+ { "date" => "2019-07-29", "type" => "Bam" },
+ { "date" => "2019-07-29", "type" => "Pow" }
+ ]
+ }
+ ]
+ )
+ end
+
+ it "should leave non-groupables alone" do
+ expect(@filter.group_by("some string", "anything")).to eq("some string")
+ end
+ end
end