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