lib/json_logic/operation.rb in json_logic-0.1 vs lib/json_logic/operation.rb in json_logic-0.3

- old
+ new

@@ -1,14 +1,68 @@ module JSONLogic class Operation LAMBDAS = { - 'var' => ->(v, d) { d.deep_fetch(*v) }, + 'var' => ->(v, d) do + return d unless d.is_a?(Hash) or d.is_a?(Array) + return v == [""] ? (d.is_a?(Array) ? d : d[""]) : d.deep_fetch(*v) + end, 'missing' => ->(v, d) { v.select { |val| d.deep_fetch(val).nil? } }, 'missing_some' => ->(v, d) { present = v[1] & d.keys present.size >= v[0] ? [] : LAMBDAS['missing'].call(v[1], d) }, + 'some' => -> (v,d) do + v[0].any? do |val| + interpolated_block(v[1], val).truthy? + end + end, + 'filter' => -> (v,d) do + v[0].select do |val| + interpolated_block(v[1], val).truthy? + 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 + + 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 + end, + 'all' => -> (v,d) do + # Difference between Ruby and JSONLogic spec ruby all? with empty array is true + return false if v[0].empty? + + v[0].all? do |val| + interpolated_block(v[1], val) + end + 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 })} + 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) { v.each_slice(2) do |condition, value| return condition if value.nil? return value if condition.truthy? end @@ -40,10 +94,43 @@ 'in' => ->(v, d) { v[1].include? v[0] }, 'cat' => ->(v, d) { v.map(&:to_s).join }, 'log' => ->(v, d) { puts v } } + 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 }) + end + def self.perform(operator, values, data) - LAMBDAS[operator].call(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]] + else + interpolated = values.map { |val| JSONLogic.apply(val, data) } + end + + 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) + end + + def self.is_standard?(operator) + LAMBDAS.keys.include?(operator) + 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'].any? { |o| o == operator } + end + + def self.add_operation(operator, function) + self.class.send(:define_method, operator) do |v, d| + function.call(v, d) + end end end end