From c4c75d77283218f6822f84f54fc29433b0d452b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= <9662430+andershagbard@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:09:43 +0200 Subject: [PATCH] Add to_array filter Template authors currently have no direct way to turn a scalar into an array or guarantee an empty array, and have started relying on quirks of other filters to get one, e.g. `{% assign new_array = null | sort %}`. This adds an explicit filter: arrays pass through unchanged, blank values (nil, false, empty/whitespace strings, empty arrays) become an empty array, and scalars are wrapped in a single-element array. Hashes and other non-scalar objects raise a Liquid error rather than being silently wrapped. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011cLgiFwd9vkUSmwvATqxre --- lib/liquid/condition.rb | 18 +----------------- lib/liquid/standardfilters.rb | 17 +++++++++++++++++ lib/liquid/utils.rb | 18 ++++++++++++++++++ test/integration/standard_filter_test.rb | 20 ++++++++++++++++++++ 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index 9d55c42b3..73e651393 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -143,24 +143,8 @@ def call_method_literal(literal, value) end end - # Implement blank? semantics matching ActiveSupport - # blank? returns true for nil, false, empty strings, whitespace-only strings, - # empty arrays, and empty hashes def liquid_blank?(value) - case value - when NilClass, FalseClass - true - when TrueClass, Numeric - false - when String - # Blank if empty or whitespace only (matches ActiveSupport) - value.empty? || value.match?(/\A\s*\z/) - when Array, Hash - value.empty? - else - # Fall back to empty? if available, otherwise false - value.respond_to?(:empty?) ? value.empty? : false - end + Liquid::Utils.blank?(value) end # Implement empty? semantics diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index ed6141566..d488e4e6b 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -315,6 +315,23 @@ def squish(input) Utils.to_s(input).strip.gsub(/\s+/, ' ') end + # @liquid_public_docs + # @liquid_type filter + # @liquid_category array + # @liquid_summary + # Converts a value into an array. Arrays are returned unchanged, blank values (`nil`, `false`, + # empty or whitespace-only strings, and empty arrays) become an empty array, and any other + # scalar value is wrapped in a single-element array. + # @liquid_syntax variable | to_array + # @liquid_return [array[untyped]] + def to_array(input) + return input if input.is_a?(Array) + return [] if Utils.blank?(input) + return [input] if input.is_a?(String) || input.is_a?(Numeric) || input.is_a?(TrueClass) || input.respond_to?(:strftime) + + raise Liquid::ArgumentError, "cannot convert object into array" + end + # @liquid_public_docs # @liquid_type filter # @liquid_category string diff --git a/lib/liquid/utils.rb b/lib/liquid/utils.rb index 084739a21..fe9d95bfa 100644 --- a/lib/liquid/utils.rb +++ b/lib/liquid/utils.rb @@ -85,6 +85,24 @@ def self.to_date(obj) nil end + # Implement blank? semantics matching ActiveSupport + # blank? returns true for nil, false, empty strings, whitespace-only strings, + # empty arrays, and empty hashes + def self.blank?(value) + case value + when NilClass, FalseClass + true + when TrueClass, Numeric + false + when String + value.empty? || value.match?(/\A\s*\z/) + when Array, Hash + value.empty? + else + value.respond_to?(:empty?) ? value.empty? : false + end + end + def self.to_liquid_value(obj) # Enable "obj" to represent itself as a primitive value like integer, string, or boolean return obj.to_liquid_value if obj.respond_to?(:to_liquid_value) diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index 94097ae1f..ef8789951 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -171,6 +171,26 @@ def test_squish_filter assert_equal("", Liquid::Template.parse('{{ " " | squish }}').render) end + def test_to_array + assert_equal(['abc'], @filters.to_array('abc')) + assert_equal([1], @filters.to_array(1)) + assert_equal([1, 2, 3], @filters.to_array([1, 2, 3])) + assert_equal([true], @filters.to_array(true)) + end + + def test_to_array_with_blank_values + assert_equal([], @filters.to_array(nil)) + assert_equal([], @filters.to_array(false)) + assert_equal([], @filters.to_array('')) + assert_equal([], @filters.to_array(' ')) + assert_equal([], @filters.to_array([])) + end + + def test_to_array_with_object_raises + assert_raises(Liquid::ArgumentError) { @filters.to_array({ 'a' => 1 }) } + assert_raises(Liquid::ArgumentError) { @filters.to_array(TestDrop.new(value: 'x')) } + end + def test_escape assert_equal('<strong>', @filters.escape('')) assert_equal('1', @filters.escape(1))