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