Skip to content

Commit

Permalink
Add find, find_index, has, and reject filters to arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
karreiro committed Dec 3, 2024
1 parent 3d1e2d4 commit 93c412e
Show file tree
Hide file tree
Showing 5 changed files with 421 additions and 37 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ end
gemspec

gem "base64"
gem "webrick"

group :benchmark, :test do
gem 'benchmark-ips'
Expand Down
44 changes: 40 additions & 4 deletions example/server/example_servlet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def paragraph(p)

class Servlet < LiquidServlet
def index
{ 'date' => Time.now }
{ 'date' => Time.now, 'products' => products_list }
end

def products
Expand All @@ -29,11 +29,47 @@ def products

private

class Name < Liquid::Drop
attr_reader :raw, :origin

def initialize(raw, origin)
super()
@raw = raw
@origin = origin
end
end

class Price < Liquid::Drop
attr_reader :value, :unit

def initialize(value, unit = 'USD')
super()
@value = value
@unit = unit
end
end

class Product < Liquid::Drop
attr_reader :name, :price, :description

def initialize(name, origin, price, description)
super()
@name = Name.new(name, origin)
@price = Price.new(price)
@description = description
end
end

def products_list
# [
# Product.new('Draft', 'Arbor', 39900, 'the *arbor draft* is a excellent product'),
# Product.new('Element', 'Arbor', 40000, 'the *arbor element* rocks for freestyling'),
# Product.new('Diamond', 'Arbor', 59900, 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity')
# ]
[
{ 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }
{ 'name' => 'Arbor 2Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor 2Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor 2Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }
]
end

Expand Down
8 changes: 7 additions & 1 deletion example/server/templates/index.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@

<p>It is {{date}}</p>

<br>

<p>Check out the <a href="/products">Products</a> screen </p>
{{ products.size }}


<br><br>

{{ products | sum: 'price.value' }}
120 changes: 92 additions & 28 deletions lib/liquid/standardfilters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def sort(input, property = nil)
end
elsif ary.all? { |el| el.respond_to?(:[]) }
begin
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
ary.sort { |a, b| nil_safe_compare(fetch_property(a, property), fetch_property(b, property)) }
rescue TypeError
raise_property_error(property)
end
Expand Down Expand Up @@ -407,7 +407,7 @@ def sort_natural(input, property = nil)
end
elsif ary.all? { |el| el.respond_to?(:[]) }
begin
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
ary.sort { |a, b| nil_safe_casecmp(fetch_property(a, property), fetch_property(b, property)) }
rescue TypeError
raise_property_error(property)
end
Expand All @@ -424,29 +424,59 @@ def sort_natural(input, property = nil)
# @liquid_syntax array | where: string, string
# @liquid_return [array[untyped]]
def where(input, property, target_value = nil)
ary = InputIterator.new(input, context)
filter_array(input, property, target_value, :select)
end

if ary.empty?
[]
elsif target_value.nil?
ary.select do |item|
item[property]
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
else
ary.select do |item|
item[property] == target_value
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Filters an array to exclude items with a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | reject: string, string
# @liquid_return [array[untyped]]
def reject(input, property, target_value = nil)
filter_array(input, property, target_value, :reject)
end

# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Tests if any item in an array has a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | some: string, string
# @liquid_return [boolean]
def has(input, property, target_value = nil)
filter_array(input, property, target_value, :any?)
end

# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the first item in an array with a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | find: string, string
# @liquid_return [untyped]
def find(input, property, target_value = nil)
filter_array(input, property, target_value, :find)
end

# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the index of the first item in an array with a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | find_index: string, string
# @liquid_return [number]
def find_index(input, property, target_value = nil)
filter_array(input, property, target_value, :find_index)
end

# @liquid_public_docs
Expand All @@ -465,7 +495,7 @@ def uniq(input, property = nil)
[]
else
ary.uniq do |item|
item[property]
fetch_property(item, property)
rescue TypeError
raise_property_error(property)
rescue NoMethodError
Expand Down Expand Up @@ -501,7 +531,7 @@ def map(input, property)
if property == "to_liquid"
e
elsif e.respond_to?(:[])
r = e[property]
r = fetch_property(e, property)
r.is_a?(Proc) ? r.call : r
end
end
Expand All @@ -525,7 +555,7 @@ def compact(input, property = nil)
[]
else
ary.reject do |item|
item[property].nil?
fetch_property(item, property).nil?
rescue TypeError
raise_property_error(property)
rescue NoMethodError
Expand Down Expand Up @@ -899,7 +929,7 @@ def sum(input, property = nil)
if property.nil?
item
elsif item.respond_to?(:[])
item[property]
fetch_property(item, property)
else
0
end
Expand All @@ -918,6 +948,40 @@ def sum(input, property = nil)

attr_reader :context

def filter_array(input, property, target_value, method)
ary = InputIterator.new(input, context)

return [] if ary.empty?

ary.public_send(method) do |item|
if target_value.nil?
fetch_property(item, property)
else
fetch_property(item, property) == target_value
end
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end

def fetch_property(drop, property_or_keys)
# For backward compatibility - historically some drops may have
# properties containing dots in their names (e.g. "foo.bar").
# We first attempt to look up the exact key before falling
# back to treating dots as nested property access.
value = drop[property_or_keys]

return value if !value.nil? || !property_or_keys.is_a?(String)

keys = property_or_keys.split('.')
keys.reduce(drop) do |drop, key|
drop.respond_to?(:[]) ? drop[key] : drop
end
end

def raise_property_error(property)
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
end
Expand Down
Loading

0 comments on commit 93c412e

Please sign in to comment.