diff --git a/json_logic.gemspec b/json_logic.gemspec index 0b60e28..d060ba6 100644 --- a/json_logic.gemspec +++ b/json_logic.gemspec @@ -20,9 +20,9 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 2.2' - spec.add_development_dependency 'bundler', '~> 2.0.1' - spec.add_development_dependency 'rake', '~> 10.0' - spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'bundler', '>= 2.0.1' + spec.add_development_dependency 'rake', '>= 10.0' + spec.add_development_dependency 'minitest', '>= 5.0' spec.add_development_dependency 'byebug' spec.add_development_dependency 'pry' spec.add_runtime_dependency 'backport_dig' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3') diff --git a/lib/json_logic.rb b/lib/json_logic.rb index f7b2e1f..3bb256b 100644 --- a/lib/json_logic.rb +++ b/lib/json_logic.rb @@ -5,33 +5,57 @@ module JSONLogic def self.apply(logic, data) if logic.is_a?(Array) - return logic.map { |val| apply(val, data) } - end - - return logic unless logic.is_a?(Hash) # pass-thru + logic.map do |val| + apply(val, data) + end + elsif !logic.is_a?(Hash) + # Pass-thru + logic + else + if data.is_a?(Hash) + data = data.stringify_keys + end + data ||= {} - data = data.stringify_keys if data.is_a?(Hash) # Stringify keys to keep out problems with symbol/string mismatch - operator, values = logic.first # unwrap single-key hash - values = [values] unless values.is_a?(Array) # syntactic sugar - Operation.perform(operator, values, data || {}) + operator, values = operator_and_values_from_logic(logic) + Operation.perform(operator, values, data) + end end + # Return a list of the non-literal data used. Eg, if the logic contains a {'var' => 'bananas'} operation, the result of + # uses_data on this logic will be a collection containing 'bananas' def self.uses_data(logic) collection = [] - operator, values = logic.first - values = [values] unless values.is_a?(Array) - if operator == 'var' - collection << values[0] - else - values.each { |val| - collection.concat(uses_data(val)) - } + if logic.kind_of?(Hash) || logic.kind_of?(Array) # If we are still dealing with logic, keep going. Else it's a value. + operator, values = operator_and_values_from_logic(logic) + + if operator == 'var' # TODO: It may be that non-var operators use data so we may want a flag or collection that indicates data use. + if values[0] != JSONLogic::ITERABLE_KEY + collection << values[0] + end + else + values.each do |val| + collection.concat(uses_data(val)) + end + end end return collection.uniq end + def self.operator_and_values_from_logic(logic) + # Unwrap single-key hash + operator, values = logic.first + + # Ensure values is an array + if !values.is_a?(Array) + values = [values] + end + + [operator, values] + end + def self.filter(logic, data) data.select { |d| apply(logic, d) } end diff --git a/lib/json_logic/operation.rb b/lib/json_logic/operation.rb index db6712d..5cec5b6 100644 --- a/lib/json_logic/operation.rb +++ b/lib/json_logic/operation.rb @@ -1,9 +1,22 @@ module JSONLogic + ITERABLE_KEY = "".freeze + class Operation LAMBDAS = { '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) + if !(d.is_a?(Hash) || d.is_a?(Array)) + d + else + if v == [JSONLogic::ITERABLE_KEY] + if d.is_a?(Array) + d + else + d[JSONLogic::ITERABLE_KEY] + end + else + d.deep_fetch(*v) + end + end end, 'missing' => ->(v, d) { v.select { |val| d.deep_fetch(val).nil? } }, 'missing_some' => ->(v, d) {