From f45a1d5fa835d1b488ba5770606512cc65d60b89 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 28 Jun 2022 20:59:35 -0300 Subject: [PATCH 1/2] improve code style to favor readability --- lib/core_ext/stringify_keys.rb | 2 ++ lib/json_logic.rb | 8 ++--- lib/json_logic/operation.rb | 53 ++++++++++++------------------ test/json_logic_test.rb | 59 +++++++++++++++++----------------- 4 files changed, 53 insertions(+), 69 deletions(-) diff --git a/lib/core_ext/stringify_keys.rb b/lib/core_ext/stringify_keys.rb index 08fb9b4..4572aec 100644 --- a/lib/core_ext/stringify_keys.rb +++ b/lib/core_ext/stringify_keys.rb @@ -2,10 +2,12 @@ class Hash # Stolen from ActiveSupport def transform_keys return enum_for(:transform_keys) { size } unless block_given? + result = {} each_key do |key| result[yield(key)] = self[key] end + result end diff --git a/lib/json_logic.rb b/lib/json_logic.rb index 3bb256b..913d8a6 100644 --- a/lib/json_logic.rb +++ b/lib/json_logic.rb @@ -5,16 +5,12 @@ module JSONLogic def self.apply(logic, data) if logic.is_a?(Array) - logic.map do |val| - apply(val, data) - end + logic.map { |val| apply(val, data) } elsif !logic.is_a?(Hash) # Pass-thru logic else - if data.is_a?(Hash) - data = data.stringify_keys - end + data = data.stringify_keys if data.is_a?(Hash) data ||= {} operator, values = operator_and_values_from_logic(logic) diff --git a/lib/json_logic/operation.rb b/lib/json_logic/operation.rb index 5cec5b6..93a9728 100644 --- a/lib/json_logic/operation.rb +++ b/lib/json_logic/operation.rb @@ -8,21 +8,17 @@ class Operation d else if v == [JSONLogic::ITERABLE_KEY] - if d.is_a?(Array) - d - else - d[JSONLogic::ITERABLE_KEY] - end + d.is_a?(Array) ? d : d[JSONLogic::ITERABLE_KEY] else d.deep_fetch(*v) end end end, 'missing' => ->(v, d) { v.select { |val| d.deep_fetch(val).nil? } }, - 'missing_some' => ->(v, d) { + 'missing_some' => ->(v, d) do present = v[1] & d.keys present.size >= v[0] ? [] : LAMBDAS['missing'].call(v[1], d) - }, + end, 'some' => -> (v,d) do return false unless v[0].is_a?(Array) @@ -38,27 +34,14 @@ class Operation end end, 'substr' => -> (v,d) do - limit = -1 - if v[2] - if v[2] < 0 - limit = v[2] - 1 - else - limit = v[1] + v[2] - 1 - end - end + return v[0][v[1]..-1] unless v[2] - v[0][v[1]..limit] + limit = v[2] < 0 ? v[2] - 1 : v[1] + v[2] - 1 + + v[0][v[1]..limit] end, 'none' => -> (v,d) do - - v[0].each do |val| - this_val_satisfies_condition = interpolated_block(v[1], val) - if this_val_satisfies_condition - return false - end - end - - return true + v[0].none? { |val| interpolated_block(v[1], val) } end, 'all' => -> (v,d) do # Difference between Ruby and JSONLogic spec ruby all? with empty array is true @@ -70,20 +53,24 @@ class Operation end, 'reduce' => -> (v,d) do return v[2] unless v[0].is_a?(Array) - v[0].inject(v[2]) { |acc, val| interpolated_block(v[1], { "current": val, "accumulator": acc })} + + v[0].inject(v[2]) do |acc, val| + interpolated_block(v[1], { "current": val, "accumulator": acc }) + end end, 'map' => -> (v,d) do return [] unless v[0].is_a?(Array) + v[0].map do |val| interpolated_block(v[1], val) end end, - 'if' => ->(v, d) { + 'if' => ->(v, d) do v.each_slice(2) do |condition, value| return condition if value.nil? return value if condition.truthy? end - }, + end, '==' => ->(v, d) { v[0].to_s == v[1].to_s }, '===' => ->(v, d) { v[0] == v[1] }, '!=' => ->(v, d) { v[0].to_s != v[1].to_s }, @@ -91,10 +78,10 @@ class Operation '!' => ->(v, d) { v[0].falsy? }, '!!' => ->(v, d) { v[0].truthy? }, 'or' => ->(v, d) { v.find(&:truthy?) || v.last }, - 'and' => ->(v, d) { + 'and' => ->(v, d) do result = v.find(&:falsy?) result.nil? ? v.last : result - }, + end, '?:' => ->(v, d) { LAMBDAS['if'].call(v, d) }, '>' => ->(v, d) { v.map(&:to_f).each_cons(2).all? { |i, j| i > j } }, '>=' => ->(v, d) { v.map(&:to_f).each_cons(2).all? { |i, j| i >= j } }, @@ -123,10 +110,10 @@ def self.perform(operator, values, data) # If iterable, we can only pre-fill the first element, the second one must be evaluated per element. # If not, we can prefill all. - if is_iterable?(operator) - interpolated = [JSONLogic.apply(values[0], data), *values[1..-1]] + interpolated = if is_iterable?(operator) + [JSONLogic.apply(values[0], data), *values[1..-1]] else - interpolated = values.map { |val| JSONLogic.apply(val, data) } + values.map { |val| JSONLogic.apply(val, data) } end interpolated.flatten!(1) if interpolated.size == 1 # [['A']] => ['A'] diff --git a/test/json_logic_test.rb b/test/json_logic_test.rb index 2f76271..75a23b7 100644 --- a/test/json_logic_test.rb +++ b/test/json_logic_test.rb @@ -28,19 +28,22 @@ class JSONLogicTest < Minitest::Test def test_filter filter = JSON.parse(%Q|{">": [{"var": "id"}, 1]}|) data = JSON.parse(%Q|[{"id": 1},{"id": 2}]|) - assert_equal([{'id' => 2}], JSONLogic.filter(filter, data)) + + assert_equal([{ 'id' => 2 }], JSONLogic.filter(filter, data)) end def test_symbol_operation - logic = {'==': [{var: "id"}, 1]} + logic = {'==': [{ var: "id" }, 1]} data = JSON.parse(%Q|{"id": 1}|) - assert_equal(true, JSONLogic.apply(logic, data)) + + assert JSONLogic.apply(logic, data) end def test_false_value - logic = {'==': [{var: "flag"}, false]} + logic = { '==': [{ var: "flag" }, false] } data = JSON.parse(%Q|{"flag": false}|) - assert_equal(true, JSONLogic.apply(logic, data)) + + assert JSONLogic.apply(logic, data) end def test_add_operation @@ -48,6 +51,7 @@ def test_add_operation JSONLogic.add_operation('fives', new_operation) rules = JSON.parse(%Q|{"fives": {"var": "num"}}|) data = JSON.parse(%Q|{"num": 1}|) + assert_equal([6], JSONLogic.apply(rules, data)) end @@ -63,36 +67,36 @@ def test_exponent_operation end def test_array_with_logic - assert_equal [1, 2, 3], JSONLogic.apply([1, {"var" => "x"}, 3], {"x" => 2}) + assert_equal [1, 2, 3], JSONLogic.apply([1, { "var" => "x" }, 3], { "x" => 2 }) assert_equal [42], JSONLogic.apply( { "if" => [ - {"var" => "x"}, - [{"var" => "y"}], + { "var" => "x" }, + [{ "var" => "y" }], 99 ] }, - { "x" => true, "y" => 42} + { "x" => true, "y" => 42 } ) end def test_in_with_variable - assert_equal true, JSONLogic.apply( + assert JSONLogic.apply( { "in" => [ - {"var" => "x"}, - {"var" => "x"} + { "var" => "x" }, + { "var" => "x" } ] }, - { "x" => "foo"} + { "x" => "foo" } ) - assert_equal false, JSONLogic.apply( + refute JSONLogic.apply( { "in" => [ - {"var" => "x"}, - {"var" => "y"}, + { "var" => "x" }, + { "var" => "y" } ] }, { "x" => "foo", "y" => "bar" } @@ -100,7 +104,7 @@ def test_in_with_variable end def test_filter_with_non_array - assert_equal [], JSONLogic.apply( + assert_empty JSONLogic.apply( { "filter" => [ { "var" => "x" }, @@ -115,8 +119,8 @@ def test_uses_data assert_equal ["x", "y"], JSONLogic.uses_data( { "in" => [ - {"var" => "x"}, - {"var" => "y"}, + { "var" => "x" }, + { "var" => "y" } ] } ) @@ -126,21 +130,16 @@ def test_uses_data_missing vars = JSONLogic.uses_data( { "in" => [ - {"var" => "x"}, - {"var" => "y"}, + { "var" => "x" }, + { "var" => "y" } ] } ) - provided_data_missing_y = { - x: 3, - } - - provided_data_missing_x = { - y: 4, - } + provided_data_missing_y = { x: 3 } + provided_data_missing_x = { y: 4 } - assert_equal ["y"], JSONLogic.apply({"missing": [vars]}, provided_data_missing_y) - assert_equal ["x"], JSONLogic.apply({"missing": [vars]}, provided_data_missing_x) + assert_equal ["y"], JSONLogic.apply({ "missing": [vars] }, provided_data_missing_y) + assert_equal ["x"], JSONLogic.apply({ "missing": [vars] }, provided_data_missing_x) end end From 13ad6dc45cd118722f285d7a7b0aeff381e6d7fd Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 29 Jun 2022 11:13:08 -0300 Subject: [PATCH 2/2] fix: fix in operation with non array --- lib/json_logic/operation.rb | 5 ++++- test/json_logic_test.rb | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/json_logic/operation.rb b/lib/json_logic/operation.rb index 93a9728..4127389 100644 --- a/lib/json_logic/operation.rb +++ b/lib/json_logic/operation.rb @@ -96,7 +96,10 @@ class Operation '%' => ->(v, d) { v.map(&:to_i).reduce(:%) }, '^' => ->(v, d) { v.map(&:to_f).reduce(:**) }, 'merge' => ->(v, d) { v.flatten }, - 'in' => ->(v, d) { interpolated_block(v[1], d).include? v[0] }, + 'in' => ->(v, d) do + result = interpolated_block(v[1], d)&.include? v[0] + result.nil? ? false : result + end, 'cat' => ->(v, d) { v.map(&:to_s).join }, 'log' => ->(v, d) { puts v } } diff --git a/test/json_logic_test.rb b/test/json_logic_test.rb index 75a23b7..0b05582 100644 --- a/test/json_logic_test.rb +++ b/test/json_logic_test.rb @@ -142,4 +142,11 @@ def test_uses_data_missing assert_equal ["y"], JSONLogic.apply({ "missing": [vars] }, provided_data_missing_y) assert_equal ["x"], JSONLogic.apply({ "missing": [vars] }, provided_data_missing_x) end + + def test_in_with_non_array + logic = { "in" => ["searchable_elem", { "var" => "non_array" }] } + + refute JSONLogic.apply(logic, { "non_array" => nil }) + refute JSONLogic.apply(logic, nil) + end end