diff --git a/Gemfile b/Gemfile
index 509316c1a..d7c005fff 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,6 +8,7 @@ end
gemspec
gem "base64"
+gem "webrick"
group :benchmark, :test do
gem 'benchmark-ips'
diff --git a/example/server/example_servlet.rb b/example/server/example_servlet.rb
index d2d4fd6a7..2a060f618 100644
--- a/example/server/example_servlet.rb
+++ b/example/server/example_servlet.rb
@@ -20,7 +20,7 @@ def paragraph(p)
class Servlet < LiquidServlet
def index
- { 'date' => Time.now }
+ { 'date' => Time.now, 'products' => products_list }
end
def products
@@ -29,11 +29,42 @@ 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
[
- { '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' }
+ 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')
]
end
diff --git a/example/server/templates/index.liquid b/example/server/templates/index.liquid
index 4872aa845..a326ceee5 100644
--- a/example/server/templates/index.liquid
+++ b/example/server/templates/index.liquid
@@ -2,5 +2,11 @@
It is {{date}}
+
-Check out the Products screen
+{{ products.size }}
+
+
+
+
+{{ products | sum: 'price.value' }}
diff --git a/lib/liquid/drop.rb b/lib/liquid/drop.rb
index b990630e4..e3aca3598 100644
--- a/lib/liquid/drop.rb
+++ b/lib/liquid/drop.rb
@@ -62,6 +62,19 @@ def to_s
alias_method :[], :invoke_drop
+ def dig(keys)
+ if self.class.invokable?(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.
+ return invoke_drop(keys)
+ end
+
+ keys = keys.to_s.split('.') unless keys.is_a?(Array)
+ keys.inject(self) { |obj, key| obj.respond_to?(:[]) ? obj[key] : nil }
+ end
+
# Check for method existence without invoking respond_to?, which creates symbols
def self.invokable?(method_name)
invokable_methods.include?(method_name.to_s)
diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb
index 2195f261b..27e4d9fdc 100644
--- a/lib/liquid/standardfilters.rb
+++ b/lib/liquid/standardfilters.rb
@@ -376,6 +376,12 @@ def sort(input, property = nil)
ary.sort do |a, b|
nil_safe_compare(a, b)
end
+ elsif ary.all? { |el| el.respond_to?(:dig) }
+ begin
+ ary.sort { |a, b| nil_safe_compare(a.dig(property), b.dig(property)) }
+ rescue TypeError
+ raise_property_error(property)
+ end
elsif ary.all? { |el| el.respond_to?(:[]) }
begin
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
@@ -405,6 +411,12 @@ def sort_natural(input, property = nil)
ary.sort do |a, b|
nil_safe_casecmp(a, b)
end
+ elsif ary.all? { |el| el.respond_to?(:dig) }
+ begin
+ ary.sort { |a, b| nil_safe_casecmp(a.dig(property), b.dig(property)) }
+ rescue TypeError
+ raise_property_error(property)
+ end
elsif ary.all? { |el| el.respond_to?(:[]) }
begin
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
@@ -495,7 +507,11 @@ def uniq(input, property = nil)
[]
else
ary.uniq do |item|
- item[property]
+ if item.respond_to?(:dig)
+ item.dig(property)
+ else
+ item[property]
+ end
rescue TypeError
raise_property_error(property)
rescue NoMethodError
@@ -530,6 +546,9 @@ def map(input, property)
if property == "to_liquid"
e
+ elsif e.respond_to?(:dig)
+ r = e.dig(property)
+ r.is_a?(Proc) ? r.call : r
elsif e.respond_to?(:[])
r = e[property]
r.is_a?(Proc) ? r.call : r
@@ -555,7 +574,11 @@ def compact(input, property = nil)
[]
else
ary.reject do |item|
- item[property].nil?
+ if item.respond_to?(:dig)
+ item.dig(property).nil?
+ else
+ item[property].nil?
+ end
rescue TypeError
raise_property_error(property)
rescue NoMethodError
@@ -928,6 +951,8 @@ def sum(input, property = nil)
values_for_sum = ary.map do |item|
if property.nil?
item
+ elsif item.respond_to?(:dig)
+ item.dig(property)
elsif item.respond_to?(:[])
item[property]
else
@@ -955,7 +980,13 @@ def filter_array(input, property, target_value, method)
ary.public_send(method) do |item|
if target_value.nil?
- item[property]
+ if item.respond_to?(:dig)
+ item.dig(property)
+ else
+ item[property]
+ end
+ elsif item.respond_to?(:dig)
+ item.dig(property) == target_value
else
item[property] == target_value
end