shiny_json_logic 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +2 -2
  5. data/lib/shiny_json_logic/comparisons/comparable.rb +15 -2
  6. data/lib/shiny_json_logic/engine.rb +12 -5
  7. data/lib/shiny_json_logic/numericals/min_max_collection.rb +21 -13
  8. data/lib/shiny_json_logic/operations/addition.rb +6 -2
  9. data/lib/shiny_json_logic/operations/all.rb +5 -3
  10. data/lib/shiny_json_logic/operations/and.rb +5 -2
  11. data/lib/shiny_json_logic/operations/base.rb +11 -4
  12. data/lib/shiny_json_logic/operations/coalesce.rb +5 -2
  13. data/lib/shiny_json_logic/operations/concatenation.rb +18 -5
  14. data/lib/shiny_json_logic/operations/division.rb +5 -2
  15. data/lib/shiny_json_logic/operations/exists.rb +8 -8
  16. data/lib/shiny_json_logic/operations/filter.rb +18 -5
  17. data/lib/shiny_json_logic/operations/inclusion.rb +16 -1
  18. data/lib/shiny_json_logic/operations/iterable/base.rb +30 -37
  19. data/lib/shiny_json_logic/operations/max.rb +1 -1
  20. data/lib/shiny_json_logic/operations/merge.rb +14 -3
  21. data/lib/shiny_json_logic/operations/min.rb +1 -1
  22. data/lib/shiny_json_logic/operations/missing.rb +34 -13
  23. data/lib/shiny_json_logic/operations/missing_some.rb +21 -5
  24. data/lib/shiny_json_logic/operations/modulo.rb +5 -2
  25. data/lib/shiny_json_logic/operations/none.rb +5 -3
  26. data/lib/shiny_json_logic/operations/or.rb +5 -2
  27. data/lib/shiny_json_logic/operations/preserve.rb +6 -4
  28. data/lib/shiny_json_logic/operations/product.rb +5 -2
  29. data/lib/shiny_json_logic/operations/reduce.rb +14 -19
  30. data/lib/shiny_json_logic/operations/some.rb +5 -1
  31. data/lib/shiny_json_logic/operations/subtraction.rb +5 -2
  32. data/lib/shiny_json_logic/operations/throw.rb +1 -1
  33. data/lib/shiny_json_logic/operations/try.rb +7 -3
  34. data/lib/shiny_json_logic/operations/val.rb +29 -8
  35. data/lib/shiny_json_logic/operations/var.rb +37 -11
  36. data/lib/shiny_json_logic/operator_solver.rb +44 -40
  37. data/lib/shiny_json_logic/scope_stack.rb +23 -53
  38. data/lib/shiny_json_logic/truthy.rb +10 -2
  39. data/lib/shiny_json_logic/utils/array.rb +1 -3
  40. data/lib/shiny_json_logic/version.rb +1 -1
  41. data/lib/shiny_json_logic.rb +1 -1
  42. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf0275f81efb49af13061d1c54c2dc273fb4686ad647a43f3ba4eafb069176f5
4
- data.tar.gz: d411aa52d5d3a235b5a9c783909acac800e10a620e0294f572ccbca9d7421223
3
+ metadata.gz: cb0d5385d70e0de02275f1a5a4ad0f8d6014c9f8030df64882cb7da1ff44942d
4
+ data.tar.gz: a608c50e2ef5acb46a348674df1ffeff928274f142f5d71ffbaf1101787b4149
5
5
  SHA512:
6
- metadata.gz: a926de52b731c63259ba2a6a954e35dd0d94c133d78cfb4bfe8c7d862cf8f5d5f9e6a33ad8b9d63028d9cc9cfa97473f6fca92683c27a219bc5838bcd3d626eb
7
- data.tar.gz: 112ec835a660537716dd1c94cfa9c152584296a44b857d0161ac3a2dd69c6f2440fd2ccf15542907f2b7a83aeb38a99acce949ad06fb55be62ff20d4687ec55a
6
+ metadata.gz: eef55803823c85357152265d6b9050394151111b782c233c264857837a35e822149c4f93c792fd1f79b9917c1c6956c7237949dd11f5ba057908b9b0a31372ae
7
+ data.tar.gz: 2dc4f7ee486216838700097498ff5b545307780ba274fd869a4c40accd46799f1111dcdeb12b55518926de4bce1339c57bdf5d42ba581fc3e17c452aa5e42aa1
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
+ ## [0.3.6] - 2026-03-06
5
+ ### Changed
6
+ - Optimizes instantiation of scope stack for improved performance.
7
+ - Refactors min/max operations for improved performance.
8
+
9
+ ## [0.3.5] - 2026-03-06
10
+ ### Changed
11
+ - Reduces object allocations further in hot paths (iterator index tracking, early-exit via throw/catch, inline nil-wrapping, scope push simplification) for an additional ~12–18% throughput improvement over 0.3.4.
12
+ - Symbol values are now coerced to String before comparisons (`==`, `===`, `!=`, `!==`, `<`, `>`, `<=`, `>=`, `in`), so Ruby Symbol data round-trips cleanly through JSONLogic rules.
13
+
4
14
  ## [0.3.4] - 2026-03-06
5
15
  ### Changed
6
16
  - Reduces object allocations in hot paths for improved performance.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiny_json_logic (0.3.4)
4
+ shiny_json_logic (0.3.6)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Compatibility](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/luismoyano/shiny-json-logic-ruby/master/badges/compat.json)
4
4
  [![Gem Version](https://badge.fury.io/rb/shiny_json_logic.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/shiny_json_logic)
5
- ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.6-brightgreen)
5
+ ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.4-brightgreen)
6
6
 
7
7
  > **A boring, correct and production-ready JSONLogic implementation for Ruby. ✨**
8
8
 
@@ -15,7 +15,7 @@ This gem focuses on predictable behavior, strict spec alignment, high compatibil
15
15
  ## Why ShinyJsonLogic?
16
16
 
17
17
  - 🧩 **Zero runtime dependencies** (stdlib-only). Just plug & play!
18
- - 🕰️ **Ruby 2.6+ compatible**, one of the lowest minimum versions supported in the Ruby ecosystem.
18
+ - 🕰️ **Ruby 2.4+ compatible**, one of the lowest minimum versions supported in the Ruby ecosystem.
19
19
  - 🔧 **Actively maintained** and continuously improved.
20
20
  - 📊 **Highest JSONLogic compatibility in the Ruby ecosystem**, as measured against the official test suites.
21
21
 
@@ -10,7 +10,8 @@ module ShinyJsonLogic
10
10
  module_function
11
11
 
12
12
  def compare(a, b)
13
- return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
13
+ a = a.to_s if a.is_a?(Symbol)
14
+ b = b.to_s if b.is_a?(Symbol)
14
15
 
15
16
  if a.is_a?(String) && b.is_a?(String)
16
17
  return a <=> b
@@ -28,15 +29,24 @@ module ShinyJsonLogic
28
29
  return 0.0 if value == false
29
30
  return 1.0 if value == true
30
31
  return 0.0 if value.nil?
31
- return value.to_f if value.is_a?(String) && Numericals::Numerify.numeric_string?(value)
32
+
33
+ value = value.to_s if value.is_a?(Symbol)
34
+ return value.to_f if Numericals::Numerify.numeric_string?(value)
35
+
32
36
  nil
33
37
  end
34
38
 
35
39
  # Normalize numeric types for strict equality comparisons (=== semantics).
40
+ # Also coerces Symbol → String so :foo === "foo" holds.
36
41
  def cast(value)
42
+ value = value.to_s if value.is_a?(Symbol)
37
43
  value.is_a?(Numeric) ? value.to_f : value
38
44
  end
39
45
 
46
+ def comparable_type?(value)
47
+ value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(Symbol) || value == true || value == false || value.nil?
48
+ end
49
+
40
50
  # Shared loop for all chain-comparison operators.
41
51
  # Yields the compare result for each consecutive pair; block returns true to continue, false to short-circuit.
42
52
  # Returns true if all pairs pass, false otherwise. Raises on :nan or invalid args.
@@ -46,9 +56,12 @@ module ShinyJsonLogic
46
56
  raise Errors::InvalidArguments if n < 2
47
57
 
48
58
  prev = Engine.call(operands[0], scope_stack)
59
+ raise Errors::NotANumber unless comparable_type?(prev)
60
+
49
61
  i = 1
50
62
  while i < n
51
63
  curr = Engine.call(operands[i], scope_stack)
64
+ raise Errors::NotANumber unless comparable_type?(curr)
52
65
  result = compare(prev, curr)
53
66
  raise Errors::NotANumber if result == :nan
54
67
  return false unless yield(result)
@@ -8,10 +8,9 @@ module ShinyJsonLogic
8
8
  OPERATIONS = OperatorSolver::SOLVERS
9
9
 
10
10
  def self.call(rule, scope_stack)
11
- if rule.is_a?(Utils::DataHash)
12
- rule
13
- elsif rule.is_a?(Hash)
14
- return rule if rule.empty?
11
+ if rule.is_a?(Hash)
12
+ # DataHash marks already-resolved user data — return as-is without dispatch
13
+ return rule if rule.is_a?(Utils::DataHash) || rule.empty?
15
14
 
16
15
  raise Errors::UnknownOperator if rule.size > 1
17
16
 
@@ -24,7 +23,15 @@ module ShinyJsonLogic
24
23
 
25
24
  op.call(args, scope_stack)
26
25
  elsif rule.is_a?(Array)
27
- rule.map { |val| call(val, scope_stack) }
26
+ # Use while loop instead of map avoids Enumerator overhead on Ruby 3.4 no-YJIT
27
+ n = rule.size
28
+ result = Array.new(n)
29
+ i = 0
30
+ while i < n
31
+ result[i] = call(rule[i], scope_stack)
32
+ i += 1
33
+ end
34
+ result
28
35
  else
29
36
  rule
30
37
  end
@@ -7,21 +7,29 @@ module ShinyJsonLogic
7
7
  module MinMaxCollection
8
8
  module_function
9
9
 
10
- def collect_numeric_values(rules, scope_stack)
11
- values = collect_values(rules, scope_stack)
12
- raise Errors::InvalidArguments if values.empty?
13
- raise Errors::InvalidArguments unless values.all? { |v| v.is_a?(Numeric) }
14
- values
15
- end
16
-
17
- def collect_values(rules, scope_stack)
18
- if Operations::Base.op?(rules)
19
- evaluated = Engine.call(rules, scope_stack)
20
- return Utils::Array.wrap_nil(evaluated)
10
+ def resolve(rules, scope_stack, op)
11
+ if rules.is_a?(Hash) && !rules.empty? && Engine::OPERATIONS.key?(rules.first[0].to_s)
12
+ items = Utils::Array.wrap_nil(Engine.call(rules, scope_stack))
13
+ evaluated = true
14
+ else
15
+ items = Utils::Array.wrap_nil(rules)
16
+ evaluated = false
21
17
  end
22
18
 
23
- wrapped = Utils::Array.wrap_nil(rules)
24
- wrapped.map { |rule| Engine.call(rule, scope_stack) }
19
+ raise Errors::InvalidArguments if items.empty?
20
+
21
+ best = evaluated ? items[0] : Engine.call(items[0], scope_stack)
22
+ raise Errors::InvalidArguments unless best.is_a?(Numeric)
23
+
24
+ i = 1
25
+ n = items.size
26
+ while i < n
27
+ v = evaluated ? items[i] : Engine.call(items[i], scope_stack)
28
+ raise Errors::InvalidArguments unless v.is_a?(Numeric)
29
+ best = v if op == :min ? v < best : v > best
30
+ i += 1
31
+ end
32
+ best
25
33
  end
26
34
  end
27
35
  end
@@ -11,10 +11,14 @@ module ShinyJsonLogic
11
11
 
12
12
  def self.execute(rules, scope_stack)
13
13
  safe_arithmetic do
14
+ operands = Utils::Array.wrap_nil(rules)
14
15
  result = 0.0
15
- Utils::Array.wrap_nil(rules).each do |rule|
16
- val = Numericals::Numerify.numerify(evaluate(rule, scope_stack))
16
+ i = 0
17
+ n = operands.size
18
+ while i < n
19
+ val = Numericals::Numerify.numerify(evaluate(operands[i], scope_stack))
17
20
  result += val.nil? ? 0 : val
21
+ i += 1
18
22
  end
19
23
  result
20
24
  end
@@ -8,10 +8,12 @@ module ShinyJsonLogic
8
8
  class All < Iterable::Base
9
9
  raise_on_dynamic_args!
10
10
 
11
- def self.on_after(results, _scope_stack)
12
- return false if results.empty?
11
+ def self.on_each(_item, filter, scope_stack)
12
+ throw(:early_return, false) unless Truthy.call(Engine.call(filter, scope_stack))
13
+ end
13
14
 
14
- results.all? { |res| Truthy.call(res) }
15
+ def self.on_after(results, _scope_stack)
16
+ results.empty? ? false : true
15
17
  end
16
18
  end
17
19
  end
@@ -15,9 +15,12 @@ module ShinyJsonLogic
15
15
  return false if rules.empty?
16
16
 
17
17
  result = nil
18
- rules.each do |rule|
19
- result = evaluate(rule, scope_stack)
18
+ i = 0
19
+ n = rules.size
20
+ while i < n
21
+ result = evaluate(rules[i], scope_stack)
20
22
  return result unless Truthy.call(result)
23
+ i += 1
21
24
  end
22
25
  result
23
26
  end
@@ -13,9 +13,14 @@ module ShinyJsonLogic
13
13
  end
14
14
 
15
15
  def self.resolve_rules(rules, scope_stack)
16
- dynamic = op?(rules)
17
- rules = Engine.call(rules, scope_stack) if dynamic
18
- raise Errors::InvalidArguments if dynamic && raise_on_dynamic_args?
16
+ # Inline op? check to avoid extra method-call overhead on the hot path.
17
+ # Most rule args are primitives/arrays — the is_a?(Hash) guard is cheap.
18
+ if rules.is_a?(Hash) && !rules.empty? && !rules.is_a?(Utils::DataHash)
19
+ key = rules.first[0]
20
+ return rules unless Engine::OPERATIONS.key?(key.to_s)
21
+ raise Errors::InvalidArguments if raise_on_dynamic_args?
22
+ return Engine.call(rules, scope_stack)
23
+ end
19
24
  rules
20
25
  end
21
26
 
@@ -36,7 +41,9 @@ module ShinyJsonLogic
36
41
  end
37
42
 
38
43
  def self.op?(value)
39
- return false unless value.is_a?(Hash) && !value.empty?
44
+ return false unless value.is_a?(Hash)
45
+ return false if value.empty?
46
+
40
47
  OperatorSolver.operation?(value)
41
48
  end
42
49
  end
@@ -6,9 +6,12 @@ module ShinyJsonLogic
6
6
  module Operations
7
7
  class Coalesce < Base
8
8
  def self.execute(rules, scope_stack)
9
- rules.each do |rule|
10
- result = evaluate(rule, scope_stack)
9
+ i = 0
10
+ n = rules.size
11
+ while i < n
12
+ result = evaluate(rules[i], scope_stack)
11
13
  return result unless result.nil?
14
+ i += 1
12
15
  end
13
16
  nil
14
17
  end
@@ -6,12 +6,25 @@ module ShinyJsonLogic
6
6
  module Operations
7
7
  class Concatenation < Base
8
8
  def self.execute(rules, scope_stack)
9
- result = []
10
- Utils::Array.wrap_nil(rules).each do |rule|
11
- evaluated = evaluate(rule, scope_stack)
12
- Utils::Array.wrap_nil(evaluated).each { |v| result << v.to_s }
9
+ result = +""
10
+ operands = Utils::Array.wrap_nil(rules)
11
+ i = 0
12
+ n = operands.size
13
+ while i < n
14
+ evaluated = evaluate(operands[i], scope_stack)
15
+ if evaluated.is_a?(Array)
16
+ j = 0
17
+ m = evaluated.size
18
+ while j < m
19
+ result << evaluated[j].to_s
20
+ j += 1
21
+ end
22
+ else
23
+ result << evaluated.to_s
24
+ end
25
+ i += 1
13
26
  end
14
- result.join
27
+ result
15
28
  end
16
29
  end
17
30
  end
@@ -15,14 +15,17 @@ module ShinyJsonLogic
15
15
 
16
16
  result = nil
17
17
  count = 0
18
+ i = 0
19
+ n = operands.size
18
20
 
19
21
  begin
20
- operands.each do |rule|
21
- evaluated = evaluate(rule, scope_stack)
22
+ while i < n
23
+ evaluated = evaluate(operands[i], scope_stack)
22
24
  num = Numericals::Numerify.numerify(evaluated)
23
25
  return handle_nan if num.nil?
24
26
  count += 1
25
27
  result = result.nil? ? num : result / num
28
+ i += 1
26
29
  end
27
30
  rescue TypeError
28
31
  return handle_nan
@@ -6,17 +6,17 @@ module ShinyJsonLogic
6
6
  module Operations
7
7
  class Exists < Base
8
8
  def self.execute(rules, scope_stack)
9
- current = scope_stack.current
10
-
11
- Utils::Array.wrap_nil(rules).each do |rule|
12
- segment = evaluate(rule, scope_stack)
13
- return false unless current.key?(segment)
9
+ current = scope_stack.last
10
+ operands = Utils::Array.wrap_nil(rules)
11
+ i = 0
12
+ n = operands.size
13
+ while i < n
14
+ segment = evaluate(operands[i], scope_stack)
15
+ return false unless current.is_a?(Hash) && current.key?(segment)
14
16
  current = current[segment]
17
+ i += 1
15
18
  end
16
-
17
19
  true
18
- rescue StandardError
19
- false
20
20
  end
21
21
  end
22
22
  end
@@ -9,12 +9,25 @@ module ShinyJsonLogic
9
9
  raise_on_nil_filter!
10
10
  raise_on_dynamic_args!
11
11
 
12
- def self.on_each(item, filter, scope_stack)
13
- Truthy.call(Engine.call(filter, scope_stack)) ? item : nil
14
- end
12
+ def self.call(rules, scope_stack)
13
+ rules = resolve_rules(rules, scope_stack)
14
+ filter = setup_filter(rules)
15
+ collection = setup_collection(rules, scope_stack)
15
16
 
16
- def self.on_after(results, _scope_stack)
17
- results.compact
17
+ result = []
18
+ i = 0
19
+ n = collection.size
20
+ while i < n
21
+ item = collection[i]
22
+ scope_stack << item
23
+ begin
24
+ result << item if Truthy.call(Engine.call(filter, scope_stack))
25
+ ensure
26
+ scope_stack.pop
27
+ end
28
+ i += 1
29
+ end
30
+ result
18
31
  end
19
32
  end
20
33
  end
@@ -8,7 +8,22 @@ module ShinyJsonLogic
8
8
  def self.execute(rules, scope_stack)
9
9
  needle = evaluate(rules.first, scope_stack)
10
10
  haystack = evaluate(rules.last, scope_stack)
11
- haystack.include?(needle)
11
+
12
+ # Normalize Symbols to String so :foo matches "foo" and vice-versa
13
+ needle = needle.to_s if needle.is_a?(Symbol)
14
+
15
+ if haystack.is_a?(Array)
16
+ i = 0
17
+ n = haystack.size
18
+ while i < n
19
+ el = haystack[i]
20
+ return true if (el.is_a?(Symbol) ? el.to_s : el) == needle
21
+ i += 1
22
+ end
23
+ false
24
+ else
25
+ haystack.include?(needle)
26
+ end
12
27
  end
13
28
  end
14
29
  end
@@ -14,46 +14,43 @@ module ShinyJsonLogic
14
14
  @raise_on_nil_filter
15
15
  end
16
16
 
17
- def self.call(rules, scope_stack)
18
- rules = resolve_rules(rules, scope_stack)
19
-
20
- collection, filter = setup_collection(rules, scope_stack)
21
-
22
- index_scope = { "index" => 0 }
23
- on_before(scope_stack)
24
- results = collection.each_with_index.each_with_object([]) do |(item, index), acc|
25
- index_scope["index"] = index
26
- scope_stack.push(index_scope, index: index)
27
- scope_stack.push(item, index: index)
28
- begin
29
- solved = on_each(item, filter, scope_stack)
30
- acc << solved
31
- scope_stack.pop
32
- scope_stack.pop
33
- rescue => e
34
- scope_stack.pop # item scope
35
- scope_stack.pop # iterator context scope
36
- raise e
37
- end
38
- end
39
- on_after(results, scope_stack)
17
+ def self.setup_filter(rules)
18
+ raise Errors::InvalidArguments unless rules.is_a?(Array)
19
+ filter = rules[1]
20
+ raise Errors::InvalidArguments if filter.nil? && raise_on_nil_filter?
21
+ filter
40
22
  end
41
23
 
42
24
  def self.setup_collection(rules, scope_stack)
43
- return handle_nil unless rules.is_a?(Array)
44
-
45
- filter = rules[1]
46
- return handle_nil if filter.nil? && raise_on_nil_filter?
25
+ collection_rule = rules.size > 0 ? rules[0] : rules
26
+ raise Errors::InvalidArguments if collection_rule.nil?
27
+ Utils::Array.wrap(Engine.call(collection_rule, scope_stack))
28
+ end
47
29
 
48
- collection_rule = rules.any? ? rules[0] : rules
49
- return handle_nil if collection_rule.nil?
30
+ def self.call(rules, scope_stack)
31
+ rules = resolve_rules(rules, scope_stack)
32
+ filter = setup_filter(rules)
33
+ collection = setup_collection(rules, scope_stack)
50
34
 
51
- collection = Utils::Array.wrap(Engine.call(collection_rule, scope_stack))
52
- [collection, filter]
35
+ early = catch(:early_return) do
36
+ results = []
37
+ i = 0
38
+ n = collection.size
39
+ while i < n
40
+ item = collection[i]
41
+ scope_stack << item
42
+ begin
43
+ results << on_each(item, filter, scope_stack)
44
+ ensure
45
+ scope_stack.pop
46
+ end
47
+ i += 1
48
+ end
49
+ on_after(results, scope_stack)
50
+ end
51
+ early
53
52
  end
54
53
 
55
- def self.on_before(_scope_stack); end
56
-
57
54
  def self.on_each(_item, filter, scope_stack)
58
55
  Engine.call(filter, scope_stack)
59
56
  end
@@ -61,10 +58,6 @@ module ShinyJsonLogic
61
58
  def self.on_after(results, _scope_stack)
62
59
  results
63
60
  end
64
-
65
- def self.handle_nil
66
- raise Errors::InvalidArguments
67
- end
68
61
  end
69
62
  end
70
63
  end
@@ -7,7 +7,7 @@ module ShinyJsonLogic
7
7
  module Operations
8
8
  class Max < Base
9
9
  def self.execute(rules, scope_stack)
10
- Numericals::MinMaxCollection.collect_numeric_values(rules, scope_stack).max
10
+ Numericals::MinMaxCollection.resolve(rules, scope_stack, :max)
11
11
  end
12
12
  end
13
13
  end
@@ -6,9 +6,20 @@ module ShinyJsonLogic
6
6
  module Operations
7
7
  class Merge < Base
8
8
  def self.execute(rules, scope_stack)
9
- Utils::Array.wrap_nil(rules).map do |rule|
10
- Utils::Array.wrap_nil(evaluate(rule, scope_stack))
11
- end.reduce([], :+)
9
+ result = []
10
+ operands = Utils::Array.wrap_nil(rules)
11
+ i = 0
12
+ n = operands.size
13
+ while i < n
14
+ evaluated = evaluate(operands[i], scope_stack)
15
+ if evaluated.is_a?(Array)
16
+ result.concat(evaluated)
17
+ else
18
+ result << evaluated
19
+ end
20
+ i += 1
21
+ end
22
+ result
12
23
  end
13
24
  end
14
25
  end
@@ -7,7 +7,7 @@ module ShinyJsonLogic
7
7
  module Operations
8
8
  class Min < Base
9
9
  def self.execute(rules, scope_stack)
10
- Numericals::MinMaxCollection.collect_numeric_values(rules, scope_stack).min
10
+ Numericals::MinMaxCollection.resolve(rules, scope_stack, :min)
11
11
  end
12
12
  end
13
13
  end
@@ -7,34 +7,55 @@ module ShinyJsonLogic
7
7
  module Operations
8
8
  class Missing < Base
9
9
  def self.execute(rules, scope_stack)
10
- items = Utils::Array.wrap_nil(rules)
10
+ wrapped = Utils::Array.wrap_nil(rules)
11
11
  keys = []
12
- items.each do |rule|
13
- evaluated = evaluate(rule, scope_stack)
14
- keys.concat(Utils::Array.wrap_nil(evaluated).map(&:to_s))
12
+ i = 0
13
+ n = wrapped.size
14
+ while i < n
15
+ evaluated = evaluate(wrapped[i], scope_stack)
16
+ if evaluated.is_a?(Array)
17
+ j = 0
18
+ m = evaluated.size
19
+ while j < m
20
+ keys << evaluated[j].to_s
21
+ j += 1
22
+ end
23
+ else
24
+ keys << evaluated.to_s
25
+ end
26
+ i += 1
15
27
  end
16
- current_data = scope_stack.current
28
+
29
+ current_data = scope_stack.last
17
30
  return keys unless current_data.is_a?(Hash)
18
31
 
19
- keys - deep_keys(current_data)
32
+ existing = {}
33
+ deep_keys(current_data, nil, existing)
34
+
35
+ result = []
36
+ i = 0
37
+ n = keys.size
38
+ while i < n
39
+ result << keys[i] unless existing.key?(keys[i])
40
+ i += 1
41
+ end
42
+ result
20
43
  end
21
44
 
22
- def self.deep_keys(hash, prefix = nil)
45
+ def self.deep_keys(hash, prefix, acc)
23
46
  return unless hash.is_a?(Hash)
24
47
 
25
- result = []
26
48
  hash.each do |key, val|
27
49
  key_s = key.to_s
28
50
  full_key = prefix ? "#{prefix}.#{key_s}" : key_s
29
- nested = deep_keys(val, full_key)
30
- if nested
31
- result.concat(nested)
51
+ if val.is_a?(Hash)
52
+ deep_keys(val, full_key, acc)
32
53
  else
33
- result << full_key
54
+ acc[full_key] = true
34
55
  end
35
56
  end
36
- result
37
57
  end
58
+
38
59
  private_class_method :deep_keys
39
60
  end
40
61
  end
@@ -8,13 +8,29 @@ module ShinyJsonLogic
8
8
  class MissingSome < Missing
9
9
  def self.execute(rules, scope_stack)
10
10
  min_required = evaluate(rules[0], scope_stack)
11
- keys = Utils::Array.wrap_nil(evaluate(rules[1], scope_stack)).map(&:to_s)
12
- current_data = scope_stack.current
11
+ raw_keys = evaluate(rules[1], scope_stack)
12
+ raw_keys_arr = wrap_nil(raw_keys)
13
+ keys = Array.new(raw_keys_arr.size)
14
+ i = 0
15
+ n = raw_keys_arr.size
16
+ while i < n
17
+ keys[i] = raw_keys_arr[i].to_s
18
+ i += 1
19
+ end
20
+
21
+ current_data = scope_stack.last
13
22
  return keys unless current_data.is_a?(Hash) && rules.is_a?(Array)
14
23
 
15
- data_keys = current_data.keys.map(&:to_s)
16
- present = keys & data_keys
17
- present.size >= min_required ? [] : Missing.execute(keys, scope_stack)
24
+ data_keys = current_data.keys
25
+ data_keys_s = Array.new(data_keys.size)
26
+ j = 0
27
+ m = data_keys.size
28
+ while j < m
29
+ data_keys_s[j] = data_keys[j].to_s
30
+ j += 1
31
+ end
32
+ present = keys & data_keys_s
33
+ present.size >= min_required ? [] : keys - present
18
34
  end
19
35
  end
20
36
  end