Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions lib/json_logic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ def self.filter(logic, data)
end

def self.add_operation(operator, function)
Operation.class.send(:define_method, operator) do |v, d|
function.call(v, d)
end
Operation.add_operation(operator, function)
end
end

Expand Down
15 changes: 10 additions & 5 deletions lib/json_logic/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,13 @@ 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) { !interpolated_block(v[1], d).nil? && interpolated_block(v[1], d).include?(v[0]) },
'cat' => ->(v, d) { v.map(&:to_s).join },
'log' => ->(v, d) { puts v }
}

CUSTOM_LAMBDAS = {}

def self.interpolated_block(block, data)
# Make sure the empty var is there to be used in iterator
JSONLogic.apply(block, data.is_a?(Hash) ? data.merge({"": data}) : { "": data })
Expand All @@ -132,23 +134,26 @@ def self.perform(operator, values, data)
interpolated.flatten!(1) if interpolated.size == 1 # [['A']] => ['A']

return LAMBDAS[operator.to_s].call(interpolated, data) if is_standard?(operator)
send(operator, interpolated, data)
return CUSTOM_LAMBDAS[operator.to_s].call(interpolated, data) if is_custom?(operator)
raise ArgumentError, "Unknown operator #{operator}"
end

def self.is_standard?(operator)
LAMBDAS.key?(operator.to_s)
end

def self.is_custom?(operator)
CUSTOM_LAMBDAS.key?(operator.to_s)
end

# Determine if values associated with operator need to be re-interpreted for each iteration(ie some kind of iterator)
# or if values can just be evaluated before passing in.
def self.is_iterable?(operator)
['filter', 'some', 'all', 'none', 'in', 'map', 'reduce'].include?(operator.to_s)
end

def self.add_operation(operator, function)
self.class.send(:define_method, operator) do |v, d|
function.call(v, d)
end
CUSTOM_LAMBDAS[operator.to_s] = function
end
end
end
28 changes: 24 additions & 4 deletions test/json_logic_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
require 'json_logic'

class JSONLogicTest < Minitest::Test
test_suite_url = 'http://jsonlogic.com/tests.json'
tests = JSON.parse(open(test_suite_url).read)
test_suite_url = 'https://jsonlogic.com/tests.json'
tests = begin
JSON.parse(open(test_suite_url).read)
rescue Errno::ENOENT
# Run a cached copy of the test suite if we can't reach the canonical version
JSON.parse(File.read(File.join(File.dirname(__FILE__), 'tests.json')))
end
count = 1
tests.each do |pattern|
next unless pattern.is_a?(Array)
Expand Down Expand Up @@ -44,10 +49,13 @@ def test_false_value
end

def test_add_operation
new_operation = ->(v, d) { v.map { |x| x + 5 } }
JSONLogic.add_operation('fives', new_operation)
rules = JSON.parse(%Q|{"fives": {"var": "num"}}|)
data = JSON.parse(%Q|{"num": 1}|)
assert_raises(ArgumentError, "Unknown operator fives") do
JSONLogic.apply(rules, data)
end
new_operation = ->(v, d) { v.map { |x| x + 5 } }
JSONLogic.add_operation('fives', new_operation)
assert_equal([6], JSONLogic.apply(rules, data))
end

Expand Down Expand Up @@ -99,6 +107,18 @@ def test_in_with_variable
)
end

def test_in_with_null
assert_equal false, JSONLogic.apply(
{
"in" => [
{"var" => "x"},
{"var" => "y"},
]
},
{ "x" => "foo", "y" => nil }
)
end

def test_filter_with_non_array
assert_equal [], JSONLogic.apply(
{
Expand Down
Loading