shiny_json_logic 0.2.9 → 0.2.11

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile.lock +1 -1
  5. data/lib/shiny_json_logic/comparisons/comparable.rb +36 -0
  6. data/lib/shiny_json_logic/engine.rb +3 -4
  7. data/lib/shiny_json_logic/numericals/min_max_collection.rb +2 -2
  8. data/lib/shiny_json_logic/operations/addition.rb +1 -1
  9. data/lib/shiny_json_logic/operations/base.rb +3 -0
  10. data/lib/shiny_json_logic/operations/concatenation.rb +2 -2
  11. data/lib/shiny_json_logic/operations/different.rb +6 -34
  12. data/lib/shiny_json_logic/operations/division.rb +1 -1
  13. data/lib/shiny_json_logic/operations/double_not.rb +1 -1
  14. data/lib/shiny_json_logic/operations/equal.rb +4 -32
  15. data/lib/shiny_json_logic/operations/exists.rb +1 -1
  16. data/lib/shiny_json_logic/operations/greater.rb +4 -32
  17. data/lib/shiny_json_logic/operations/greater_equal.rb +4 -32
  18. data/lib/shiny_json_logic/operations/iterable/base.rb +1 -1
  19. data/lib/shiny_json_logic/operations/merge.rb +2 -2
  20. data/lib/shiny_json_logic/operations/missing.rb +3 -3
  21. data/lib/shiny_json_logic/operations/missing_some.rb +3 -2
  22. data/lib/shiny_json_logic/operations/modulo.rb +1 -1
  23. data/lib/shiny_json_logic/operations/preserve.rb +1 -1
  24. data/lib/shiny_json_logic/operations/product.rb +1 -1
  25. data/lib/shiny_json_logic/operations/smaller.rb +4 -32
  26. data/lib/shiny_json_logic/operations/smaller_equal.rb +4 -32
  27. data/lib/shiny_json_logic/operations/strict_different.rb +3 -3
  28. data/lib/shiny_json_logic/operations/strict_equal.rb +1 -1
  29. data/lib/shiny_json_logic/operations/subtraction.rb +1 -1
  30. data/lib/shiny_json_logic/operations/throw.rb +0 -1
  31. data/lib/shiny_json_logic/operations/try.rb +1 -3
  32. data/lib/shiny_json_logic/operations/val.rb +6 -4
  33. data/lib/shiny_json_logic/operations/var.rb +26 -2
  34. data/lib/shiny_json_logic/operator_solver.rb +1 -3
  35. data/lib/shiny_json_logic/scope_stack.rb +15 -13
  36. data/lib/shiny_json_logic/utils/array.rb +22 -0
  37. data/lib/shiny_json_logic/utils/indifferent_hash.rb +72 -0
  38. data/lib/shiny_json_logic/version.rb +1 -1
  39. data/lib/shiny_json_logic.rb +1 -1
  40. data/shiny_json_logic.gemspec +2 -2
  41. metadata +8 -6
  42. data/lib/core_ext/array.rb +0 -19
  43. data/lib/core_ext/hash.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecf7aea99599023d6adf2af12bbb99fe8a40d9d8bcce1c2410eb4a49337b08b6
4
- data.tar.gz: 73944111a179b14d04e401351519c46400294b39e95571b3f744225915f759a6
3
+ metadata.gz: f6c51d139183162260ba3b85195e9e8854da441bedad9ca5753785d2a781b9d4
4
+ data.tar.gz: 499f7e445cb8aadebb2397781c36b09722b688ee955994002963e38a0ce64b78
5
5
  SHA512:
6
- metadata.gz: 8676f95bd3ce898099859c0828fd56f0be608555ca89ecc3e133ffd4bedd435820c9ce58e15ecd2f4f44521514c1ddfbed3c8d235c4ca37acd1c87dc93bafc5c
7
- data.tar.gz: 91d46fb710614bcf75ae1a198f21f9fabd06875f89c48224413c36a04668894c5ee2276374d1a0f6ca9a7d7f858c4f68b737386ffd2b6c9cb7e819b4b75a6e5a
6
+ metadata.gz: 5723ddd51acda1014b43b9f84e4ce2054a7b9fc1ea6e86fecc7d30742bd8b6f51309d7d220f15a7546713a151be503977cc7452bac1953a08c21fd6efbd9d285
7
+ data.tar.gz: bd2182abf594716ee39c4b892dae1079969f5d49402c258ad136d8b935460f9cd88d924dc6ffb8aa9336328578157f34eddbd04dc4ce8b7310c91cd4fbd18f48
data/.gitignore CHANGED
@@ -21,3 +21,4 @@
21
21
 
22
22
  # Development context
23
23
  AGENTS_DEVELOPMENT.md
24
+ DOCUMENTATION.md
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.11] - 2026-02-09
6
+ ### Changed
7
+ - Removes monkey patches in favour of isolated modules.
8
+
9
+ ### Added
10
+ - Supports hashes with string or symbol access indifferently (e.g. `{"a" => 1}` can be accessed with `:a` or `"a"`).
11
+
5
12
  ## [0.2.9] - 2026-02-08
6
13
  ### Added
7
14
  - New specific error classes: `Errors::InvalidArguments`, `Errors::NotANumber`, `Errors::UnknownOperator`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiny_json_logic (0.2.9)
4
+ shiny_json_logic (0.2.11)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shiny_json_logic/numericals/numerify"
4
+
5
+ module ShinyJsonLogic
6
+ module Comparisons
7
+ module Comparable
8
+ include Numericals::Numerify
9
+
10
+ private
11
+
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)
14
+
15
+ if a.is_a?(String) && b.is_a?(String)
16
+ return a <=> b
17
+ end
18
+
19
+ num_a = numerify_for_comparison(a)
20
+ num_b = numerify_for_comparison(b)
21
+ return :nan if num_a.nil? || num_b.nil?
22
+
23
+ num_a <=> num_b
24
+ end
25
+
26
+ def numerify_for_comparison(value)
27
+ return value.to_f if value.is_a?(Numeric)
28
+ return 0.0 if value == false
29
+ return 1.0 if value == true
30
+ return 0.0 if value.nil?
31
+ return value.to_f if value.is_a?(String) && numeric_string?(value)
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,3 @@
1
- require "core_ext/array"
2
- require "core_ext/hash"
3
1
  require "shiny_json_logic/operator_solver"
4
2
 
5
3
  module ShinyJsonLogic
@@ -14,9 +12,10 @@ module ShinyJsonLogic
14
12
  return rule if rule.empty?
15
13
 
16
14
  operation, args = rule.to_a.first
17
- return rule unless operations.key?(operation)
15
+ operation_key = operation.to_s
16
+ return rule unless operations.key?(operation_key)
18
17
 
19
- operations.fetch(operation).new(args, scope_stack).call
18
+ operations.fetch(operation_key).new(args, scope_stack).call
20
19
  elsif rule.is_a?(Array)
21
20
  rule.map { |val| call(val) }
22
21
  else
@@ -14,12 +14,12 @@ module ShinyJsonLogic
14
14
 
15
15
  def collect_values
16
16
  result = []
17
- Array.wrap_nil(rules).each do |rule|
17
+ wrap_nil(rules).each do |rule|
18
18
  evaluated = evaluate(rule)
19
19
  # If rule was an operation (Hash), expand the result array
20
20
  # If rule was a literal array, it's invalid (will fail numeric check)
21
21
  if operation?(rule)
22
- Array.wrap_nil(evaluated).each { |val| result << val }
22
+ wrap_nil(evaluated).each { |val| result << val }
23
23
  else
24
24
  result << evaluated
25
25
  end
@@ -25,7 +25,7 @@ module ShinyJsonLogic
25
25
  private
26
26
 
27
27
  def each_operand
28
- Array.wrap_nil(rules).each do |rule|
28
+ wrap_nil(rules).each do |rule|
29
29
  yield numerify(evaluate(rule))
30
30
  end
31
31
  end
@@ -1,8 +1,11 @@
1
1
  require "shiny_json_logic/truthy"
2
+ require "shiny_json_logic/utils/array"
2
3
 
3
4
  module ShinyJsonLogic
4
5
  module Operations
5
6
  class Base
7
+ include Utils::Array
8
+
6
9
  def initialize(rules, scope_stack)
7
10
  @scope_stack = scope_stack
8
11
  @dynamic_args = operation?(rules)
@@ -5,9 +5,9 @@ module ShinyJsonLogic
5
5
  class Concatenation < Base
6
6
  def call
7
7
  result = []
8
- Array.wrap_nil(rules).each do |rule|
8
+ wrap_nil(rules).each do |rule|
9
9
  evaluated = evaluate(rule)
10
- Array.wrap_nil(evaluated).each { |v| result << v.to_s }
10
+ wrap_nil(evaluated).each { |v| result << v.to_s }
11
11
  end
12
12
  result.join
13
13
  end
@@ -1,17 +1,17 @@
1
1
  require "shiny_json_logic/operations/base"
2
2
  require "shiny_json_logic/numericals/with_error_handling"
3
- require "shiny_json_logic/numericals/numerify"
3
+ require "shiny_json_logic/comparisons/comparable"
4
4
 
5
5
  module ShinyJsonLogic
6
6
  module Operations
7
7
  class Different < Base
8
8
  include Numericals::WithErrorHandling
9
- include Numericals::Numerify
9
+ include Comparisons::Comparable
10
10
  raise_on_dynamic_args!
11
11
 
12
12
  def call
13
13
  return handle_invalid_args if dynamic_args?
14
- operands = Array.wrap_nil(rules)
14
+ operands = wrap_nil(rules)
15
15
  return handle_invalid_args if operands.length < 2
16
16
 
17
17
  prev = evaluate(operands[0])
@@ -19,39 +19,11 @@ module ShinyJsonLogic
19
19
  curr = evaluate(rule)
20
20
  result = compare(prev, curr)
21
21
  return handle_nan if result == :nan
22
- return false if result == 0 # Si son iguales, != es false
22
+ return false if result == 0
23
23
  prev = curr
24
24
  end
25
- true # Todos los pares consecutivos son diferentes
26
- end
27
-
28
- private
29
-
30
- def compare(a, b)
31
- # Arrays u objetos → NaN
32
- return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
33
-
34
- # Ambos strings → comparación directa
35
- if a.is_a?(String) && b.is_a?(String)
36
- return a <=> b
37
- end
38
-
39
- # Convertir a números para comparar
40
- num_a = numerify_for_compare(a)
41
- num_b = numerify_for_compare(b)
42
- return :nan if num_a.nil? || num_b.nil?
43
-
44
- num_a <=> num_b
45
- end
46
-
47
- def numerify_for_compare(value)
48
- return value.to_f if value.is_a?(Numeric)
49
- return 0.0 if value == false
50
- return 1.0 if value == true
51
- return 0.0 if value.nil?
52
- return value.to_f if value.is_a?(String) && numeric_string?(value)
53
- nil # String no numérica
25
+ true
54
26
  end
55
27
  end
56
28
  end
57
- end
29
+ end
@@ -9,7 +9,7 @@ module ShinyJsonLogic
9
9
  include Numericals::Numerify
10
10
 
11
11
  def call
12
- operands = Array.wrap_nil(rules)
12
+ operands = wrap_nil(rules)
13
13
  return handle_invalid_args if operands.empty?
14
14
 
15
15
  result = nil
@@ -5,7 +5,7 @@ module ShinyJsonLogic
5
5
  module Operations
6
6
  class DoubleNot < Base
7
7
  def call
8
- value = Array.wrap_nil(rules).first
8
+ value = wrap_nil(rules).first
9
9
  !!Truthy.call(evaluate(value))
10
10
  end
11
11
  end
@@ -1,17 +1,17 @@
1
1
  require "shiny_json_logic/operations/base"
2
2
  require "shiny_json_logic/numericals/with_error_handling"
3
- require "shiny_json_logic/numericals/numerify"
3
+ require "shiny_json_logic/comparisons/comparable"
4
4
 
5
5
  module ShinyJsonLogic
6
6
  module Operations
7
7
  class Equal < Base
8
8
  include Numericals::WithErrorHandling
9
- include Numericals::Numerify
9
+ include Comparisons::Comparable
10
10
  raise_on_dynamic_args!
11
11
 
12
12
  def call
13
13
  return handle_invalid_args if dynamic_args?
14
- operands = Array.wrap_nil(rules)
14
+ operands = wrap_nil(rules)
15
15
  return handle_invalid_args if operands.length < 2
16
16
 
17
17
  first = evaluate(operands[0])
@@ -23,34 +23,6 @@ module ShinyJsonLogic
23
23
  end
24
24
  true
25
25
  end
26
-
27
- private
28
-
29
- def compare(a, b)
30
- # Arrays u objetos → NaN
31
- return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
32
-
33
- # Ambos strings → comparación directa
34
- if a.is_a?(String) && b.is_a?(String)
35
- return a <=> b
36
- end
37
-
38
- # Convertir a números para comparar
39
- num_a = numerify_for_compare(a)
40
- num_b = numerify_for_compare(b)
41
- return :nan if num_a.nil? || num_b.nil?
42
-
43
- num_a <=> num_b
44
- end
45
-
46
- def numerify_for_compare(value)
47
- return value.to_f if value.is_a?(Numeric)
48
- return 0.0 if value == false
49
- return 1.0 if value == true
50
- return 0.0 if value.nil?
51
- return value.to_f if value.is_a?(String) && numeric_string?(value)
52
- nil # String no numérica
53
- end
54
26
  end
55
27
  end
56
- end
28
+ end
@@ -6,7 +6,7 @@ module ShinyJsonLogic
6
6
  def call
7
7
  current = data
8
8
 
9
- Array.wrap_nil(rules).each do |rule|
9
+ wrap_nil(rules).each do |rule|
10
10
  segment = evaluate(rule)
11
11
  return false unless current.key?(segment)
12
12
  current = current[segment]
@@ -1,17 +1,17 @@
1
1
  require "shiny_json_logic/operations/base"
2
2
  require "shiny_json_logic/numericals/with_error_handling"
3
- require "shiny_json_logic/numericals/numerify"
3
+ require "shiny_json_logic/comparisons/comparable"
4
4
 
5
5
  module ShinyJsonLogic
6
6
  module Operations
7
7
  class Greater < Base
8
8
  include Numericals::WithErrorHandling
9
- include Numericals::Numerify
9
+ include Comparisons::Comparable
10
10
  raise_on_dynamic_args!
11
11
 
12
12
  def call
13
13
  return handle_invalid_args if dynamic_args?
14
- operands = Array.wrap_nil(rules)
14
+ operands = wrap_nil(rules)
15
15
  return handle_invalid_args if operands.length < 2
16
16
 
17
17
  prev = evaluate(operands[0])
@@ -24,34 +24,6 @@ module ShinyJsonLogic
24
24
  end
25
25
  true
26
26
  end
27
-
28
- private
29
-
30
- def compare(a, b)
31
- # Arrays u objetos → NaN
32
- return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
33
-
34
- # Ambos strings → comparación lexicográfica
35
- if a.is_a?(String) && b.is_a?(String)
36
- return a <=> b
37
- end
38
-
39
- # Convertir a números para comparar
40
- num_a = numerify_for_compare(a)
41
- num_b = numerify_for_compare(b)
42
- return :nan if num_a.nil? || num_b.nil?
43
-
44
- num_a <=> num_b
45
- end
46
-
47
- def numerify_for_compare(value)
48
- return value.to_f if value.is_a?(Numeric)
49
- return 0.0 if value == false
50
- return 1.0 if value == true
51
- return 0.0 if value.nil?
52
- return value.to_f if value.is_a?(String) && numeric_string?(value)
53
- nil # String no numérica
54
- end
55
27
  end
56
28
  end
57
- end
29
+ end
@@ -1,17 +1,17 @@
1
1
  require "shiny_json_logic/operations/base"
2
2
  require "shiny_json_logic/numericals/with_error_handling"
3
- require "shiny_json_logic/numericals/numerify"
3
+ require "shiny_json_logic/comparisons/comparable"
4
4
 
5
5
  module ShinyJsonLogic
6
6
  module Operations
7
7
  class GreaterEqual < Base
8
8
  include Numericals::WithErrorHandling
9
- include Numericals::Numerify
9
+ include Comparisons::Comparable
10
10
  raise_on_dynamic_args!
11
11
 
12
12
  def call
13
13
  return handle_invalid_args if dynamic_args?
14
- operands = Array.wrap_nil(rules)
14
+ operands = wrap_nil(rules)
15
15
  return handle_invalid_args if operands.length < 2
16
16
 
17
17
  prev = evaluate(operands[0])
@@ -24,34 +24,6 @@ module ShinyJsonLogic
24
24
  end
25
25
  true
26
26
  end
27
-
28
- private
29
-
30
- def compare(a, b)
31
- # Arrays u objetos → NaN
32
- return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
33
-
34
- # Ambos strings → comparación lexicográfica
35
- if a.is_a?(String) && b.is_a?(String)
36
- return a <=> b
37
- end
38
-
39
- # Convertir a números para comparar
40
- num_a = numerify_for_compare(a)
41
- num_b = numerify_for_compare(b)
42
- return :nan if num_a.nil? || num_b.nil?
43
-
44
- num_a <=> num_b
45
- end
46
-
47
- def numerify_for_compare(value)
48
- return value.to_f if value.is_a?(Numeric)
49
- return 0.0 if value == false
50
- return 1.0 if value == true
51
- return 0.0 if value.nil?
52
- return value.to_f if value.is_a?(String) && numeric_string?(value)
53
- nil # String no numérica
54
- end
55
27
  end
56
28
  end
57
- end
29
+ end
@@ -78,7 +78,7 @@ module ShinyJsonLogic
78
78
  if collection.nil?
79
79
  @collection = []
80
80
  else
81
- @collection = Array.wrap(evaluate(collection))
81
+ @collection = wrap(evaluate(collection))
82
82
  end
83
83
  end
84
84
 
@@ -4,8 +4,8 @@ module ShinyJsonLogic
4
4
  module Operations
5
5
  class Merge < Base
6
6
  def call
7
- Array.wrap_nil(rules).map do |rule|
8
- Array.wrap_nil(evaluate(rule))
7
+ wrap_nil(rules).map do |rule|
8
+ wrap_nil(evaluate(rule))
9
9
  end.reduce([], :+)
10
10
  end
11
11
  end
@@ -5,11 +5,11 @@ module ShinyJsonLogic
5
5
  module Operations
6
6
  class Missing < Base
7
7
  def call
8
- items = Array.wrap_nil(rules)
8
+ items = wrap_nil(rules)
9
9
  keys = []
10
10
  items.each do |rule|
11
11
  evaluated = evaluate(rule)
12
- keys.concat(Array.wrap_nil(evaluated))
12
+ keys.concat(wrap_nil(evaluated).map(&:to_s))
13
13
  end
14
14
  return keys unless data.is_a?(Hash)
15
15
 
@@ -21,7 +21,7 @@ module ShinyJsonLogic
21
21
  def deep_keys(hash)
22
22
  return unless hash.is_a?(Hash)
23
23
 
24
- hash.keys.map{|key| ([key] << deep_keys(hash[key])).compact.join(".") }
24
+ hash.keys.map{|key| ([key.to_s] << deep_keys(hash[key])).compact.join(".") }
25
25
  end
26
26
  end
27
27
  end
@@ -6,10 +6,11 @@ module ShinyJsonLogic
6
6
  class MissingSome < Missing
7
7
  def call
8
8
  min_required = evaluate(rules[0])
9
- keys = Array.wrap_nil(evaluate(rules[1]))
9
+ keys = wrap_nil(evaluate(rules[1])).map(&:to_s)
10
10
  return keys unless data.is_a?(Hash) && rules.is_a?(Array)
11
11
 
12
- present = keys & data.keys
12
+ data_keys = data.keys.map(&:to_s)
13
+ present = keys & data_keys
13
14
  present.size >= min_required ? [] : Missing.new(keys, scope_stack).call
14
15
  end
15
16
  end
@@ -9,7 +9,7 @@ module ShinyJsonLogic
9
9
  include Numericals::Numerify
10
10
 
11
11
  def call
12
- operands = Array.wrap_nil(rules)
12
+ operands = wrap_nil(rules)
13
13
  return handle_invalid_args if operands.empty?
14
14
 
15
15
  safe_arithmetic do
@@ -4,7 +4,7 @@ module ShinyJsonLogic
4
4
  module Operations
5
5
  class Preserve < Iterable::Base
6
6
  def initialize(rules, scope_stack)
7
- @collection = Array.wrap(rules) || []
7
+ @collection = wrap(rules) || []
8
8
  # Skip Iterable::Base initialization, go directly to Operations::Base
9
9
  # Preserve doesn't need the standard iterable setup (filter, collection from rules[0], etc.)
10
10
  @rules = rules
@@ -9,7 +9,7 @@ module ShinyJsonLogic
9
9
  include Numericals::Numerify
10
10
 
11
11
  def call
12
- operands = Array.wrap_nil(rules)
12
+ operands = wrap_nil(rules)
13
13
  return 1 if operands.empty?
14
14
 
15
15
  safe_arithmetic do
@@ -1,17 +1,17 @@
1
1
  require "shiny_json_logic/operations/base"
2
2
  require "shiny_json_logic/numericals/with_error_handling"
3
- require "shiny_json_logic/numericals/numerify"
3
+ require "shiny_json_logic/comparisons/comparable"
4
4
 
5
5
  module ShinyJsonLogic
6
6
  module Operations
7
7
  class Smaller < Base
8
8
  include Numericals::WithErrorHandling
9
- include Numericals::Numerify
9
+ include Comparisons::Comparable
10
10
  raise_on_dynamic_args!
11
11
 
12
12
  def call
13
13
  return handle_invalid_args if dynamic_args?
14
- operands = Array.wrap_nil(rules)
14
+ operands = wrap_nil(rules)
15
15
  return handle_invalid_args if operands.length < 2
16
16
 
17
17
  prev = evaluate(operands[0])
@@ -24,34 +24,6 @@ module ShinyJsonLogic
24
24
  end
25
25
  true
26
26
  end
27
-
28
- private
29
-
30
- def compare(a, b)
31
- # Arrays u objetos → NaN
32
- return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
33
-
34
- # Ambos strings → comparación lexicográfica
35
- if a.is_a?(String) && b.is_a?(String)
36
- return a <=> b
37
- end
38
-
39
- # Convertir a números para comparar
40
- num_a = numerify_for_compare(a)
41
- num_b = numerify_for_compare(b)
42
- return :nan if num_a.nil? || num_b.nil?
43
-
44
- num_a <=> num_b
45
- end
46
-
47
- def numerify_for_compare(value)
48
- return value.to_f if value.is_a?(Numeric)
49
- return 0.0 if value == false
50
- return 1.0 if value == true
51
- return 0.0 if value.nil?
52
- return value.to_f if value.is_a?(String) && numeric_string?(value)
53
- nil # String no numérica
54
- end
55
27
  end
56
28
  end
57
- end
29
+ end
@@ -1,17 +1,17 @@
1
1
  require "shiny_json_logic/operations/base"
2
2
  require "shiny_json_logic/numericals/with_error_handling"
3
- require "shiny_json_logic/numericals/numerify"
3
+ require "shiny_json_logic/comparisons/comparable"
4
4
 
5
5
  module ShinyJsonLogic
6
6
  module Operations
7
7
  class SmallerEqual < Base
8
8
  include Numericals::WithErrorHandling
9
- include Numericals::Numerify
9
+ include Comparisons::Comparable
10
10
  raise_on_dynamic_args!
11
11
 
12
12
  def call
13
13
  return handle_invalid_args if dynamic_args?
14
- operands = Array.wrap_nil(rules)
14
+ operands = wrap_nil(rules)
15
15
  return handle_invalid_args if operands.length < 2
16
16
 
17
17
  prev = evaluate(operands[0])
@@ -24,34 +24,6 @@ module ShinyJsonLogic
24
24
  end
25
25
  true
26
26
  end
27
-
28
- private
29
-
30
- def compare(a, b)
31
- # Arrays u objetos → NaN
32
- return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
33
-
34
- # Ambos strings → comparación lexicográfica
35
- if a.is_a?(String) && b.is_a?(String)
36
- return a <=> b
37
- end
38
-
39
- # Convertir a números para comparar
40
- num_a = numerify_for_compare(a)
41
- num_b = numerify_for_compare(b)
42
- return :nan if num_a.nil? || num_b.nil?
43
-
44
- num_a <=> num_b
45
- end
46
-
47
- def numerify_for_compare(value)
48
- return value.to_f if value.is_a?(Numeric)
49
- return 0.0 if value == false
50
- return 1.0 if value == true
51
- return 0.0 if value.nil?
52
- return value.to_f if value.is_a?(String) && numeric_string?(value)
53
- nil # String no numérica
54
- end
55
27
  end
56
28
  end
57
- end
29
+ end
@@ -9,16 +9,16 @@ module ShinyJsonLogic
9
9
 
10
10
  def call
11
11
  return handle_invalid_args if dynamic_args?
12
- operands = Array.wrap_nil(rules)
12
+ operands = wrap_nil(rules)
13
13
  return handle_invalid_args if operands.length < 2
14
14
 
15
15
  prev = cast(evaluate(operands[0]))
16
16
  operands[1..].each do |rule|
17
17
  curr = cast(evaluate(rule))
18
- return false if curr == prev # Si son iguales, !== es false
18
+ return false if curr == prev
19
19
  prev = curr
20
20
  end
21
- true # Todos los pares consecutivos son diferentes
21
+ true
22
22
  end
23
23
 
24
24
  private
@@ -9,7 +9,7 @@ module ShinyJsonLogic
9
9
 
10
10
  def call
11
11
  return handle_invalid_args if dynamic_args?
12
- operands = Array.wrap_nil(rules)
12
+ operands = wrap_nil(rules)
13
13
  return handle_invalid_args if operands.length < 2
14
14
 
15
15
  first = cast(evaluate(operands[0]))
@@ -9,7 +9,7 @@ module ShinyJsonLogic
9
9
  include Numericals::Numerify
10
10
 
11
11
  def call
12
- operands = Array.wrap_nil(rules)
12
+ operands = wrap_nil(rules)
13
13
  return handle_invalid_args if operands.empty?
14
14
 
15
15
  safe_arithmetic do
@@ -15,7 +15,6 @@ module ShinyJsonLogic
15
15
 
16
16
  extracted_type = error_type.is_a?(Hash) && error_type.key?("type") ? error_type["type"] : error_type
17
17
  extracted_type = self.data["type"] if extracted_type.nil?
18
- self.data["type"] = extracted_type unless extracted_type.nil?
19
18
 
20
19
  error = Errors::Base.new(type: extracted_type)
21
20
 
@@ -1,10 +1,8 @@
1
- require "core_ext/array"
2
-
3
1
  module ShinyJsonLogic
4
2
  module Operations
5
3
  class Try < Base
6
4
  def call
7
- items = Array.wrap_nil(rules)
5
+ items = wrap_nil(rules)
8
6
  last_error = nil
9
7
 
10
8
  items.each do |item|
@@ -4,7 +4,7 @@ module ShinyJsonLogic
4
4
  module Operations
5
5
  class Val < Base
6
6
  def call
7
- raw_keys = Array.wrap_nil(rules)
7
+ raw_keys = wrap_nil(rules)
8
8
 
9
9
  # {"val": []} or {"val": null} - return current scope
10
10
  if raw_keys.empty? || raw_keys == [nil]
@@ -44,16 +44,18 @@ module ShinyJsonLogic
44
44
  keys.reduce(data) do |obj, key|
45
45
  return nil if obj.nil?
46
46
 
47
- case obj
48
- when Hash
47
+ result = if obj.is_a?(Hash)
49
48
  obj[key]
50
- when Array
49
+ elsif obj.is_a?(Array)
51
50
  # Convert string keys to integers for arrays
52
51
  index = key.is_a?(String) ? key.to_i : key
53
52
  obj[index]
54
53
  else
55
54
  nil
56
55
  end
56
+
57
+ # Wrap nested hashes for indifferent access
58
+ result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
57
59
  end
58
60
  end
59
61
  end
@@ -5,16 +5,40 @@ module ShinyJsonLogic
5
5
  module Operations
6
6
  class Var < Base
7
7
  def call
8
- items = Array.wrap_nil(rules)
8
+ items = wrap_nil(rules)
9
9
  key = evaluate(items[0])
10
10
  default = items[1] ? evaluate(items[1]) : nil
11
11
 
12
12
  return data if key.nil? || key == ""
13
13
 
14
- data&.deep_fetch(*Array.wrap(key)) || default
14
+ fetch_value(data, key) || default
15
15
  rescue
16
16
  default || data
17
17
  end
18
+
19
+ private
20
+
21
+ def fetch_value(obj, key)
22
+ return nil if obj.nil?
23
+
24
+ # Split dot-separated keys
25
+ keys = key.to_s.split('.')
26
+
27
+ keys.reduce(obj) do |current, k|
28
+ return nil if current.nil?
29
+
30
+ if current.is_a?(Hash)
31
+ # Try string key first, then symbol key for indifferent access
32
+ result = current[k]
33
+ result = current[k.to_sym] if result.nil? && !current.key?(k)
34
+ result
35
+ elsif current.is_a?(Array)
36
+ current[k.to_i]
37
+ else
38
+ nil
39
+ end
40
+ end
41
+ end
18
42
  end
19
43
  end
20
44
  end
@@ -1,5 +1,3 @@
1
- require "core_ext/array"
2
- require "core_ext/hash"
3
1
  Dir[File.join(__dir__, "operations/**/*.rb")].each do |file|
4
2
  require file
5
3
  end
@@ -7,7 +5,7 @@ end
7
5
  module ShinyJsonLogic
8
6
  class OperatorSolver
9
7
  def operation?(value)
10
- value.keys.any? { |key| solvers.key?(key) }
8
+ value.keys.any? { |key| solvers.key?(key.to_s) }
11
9
  end
12
10
 
13
11
  def solvers
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "shiny_json_logic/utils/indifferent_hash"
4
+
3
5
  module ShinyJsonLogic
4
6
  # Manages a stack of scopes for nested data access in iterators.
5
7
  #
@@ -17,14 +19,13 @@ module ShinyJsonLogic
17
19
  attr_reader :stack
18
20
 
19
21
  def initialize(root_data)
20
- # Deep dup the root data to prevent mutations from affecting scope resolution
21
- @root_snapshot = deep_dup(root_data)
22
- @stack = [{ data: @root_snapshot, index: 0 }]
22
+ @root_data = wrap_indifferent(root_data)
23
+ @stack = [{ data: @root_data, index: 0 }]
23
24
  end
24
25
 
25
26
  # Push a new scope onto the stack (when entering an iteration)
26
27
  def push(data, index: 0)
27
- stack.push({ data: data, index: index })
28
+ stack.push({ data: wrap_indifferent(data), index: index })
28
29
  end
29
30
 
30
31
  # Pop the top scope (when exiting an iteration)
@@ -66,25 +67,26 @@ module ShinyJsonLogic
66
67
  keys.reduce(data) do |obj, key|
67
68
  return nil if obj.nil?
68
69
 
69
- case obj
70
- when Hash
70
+ result = if obj.is_a?(Hash)
71
71
  obj[key]
72
- when Array
72
+ elsif obj.is_a?(Array)
73
73
  # Convert string keys to integers for arrays
74
74
  index = key.is_a?(String) ? key.to_i : key
75
75
  obj[index]
76
76
  else
77
77
  nil
78
78
  end
79
+
80
+ # Wrap nested hashes for indifferent access
81
+ result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
79
82
  end
80
83
  end
81
84
 
82
- def deep_dup(obj)
83
- case obj
84
- when Hash
85
- obj.transform_values { |v| deep_dup(v) }
86
- when Array
87
- obj.map { |v| deep_dup(v) }
85
+ def wrap_indifferent(obj)
86
+ if obj.is_a?(IndifferentHash)
87
+ obj
88
+ elsif obj.is_a?(Hash)
89
+ IndifferentHash.new(obj)
88
90
  else
89
91
  obj
90
92
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShinyJsonLogic
4
+ module Utils
5
+ module Array
6
+ module_function
7
+
8
+ def wrap(object)
9
+ return [] if object.nil?
10
+ return object.to_ary || [object] if object.respond_to?(:to_ary)
11
+
12
+ [object]
13
+ end
14
+
15
+ def wrap_nil(object)
16
+ return [nil] if object.nil?
17
+
18
+ wrap(object)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module ShinyJsonLogic
6
+ class IndifferentHash < SimpleDelegator
7
+ # Make is_a?(Hash) return true so existing code works
8
+ def is_a?(klass)
9
+ klass == Hash || super
10
+ end
11
+ alias kind_of? is_a?
12
+
13
+ def [](key)
14
+ obj = __getobj__
15
+ return obj[key] if obj.key?(key)
16
+
17
+ alt_key = alternate_key(key)
18
+ return obj[alt_key] if alt_key && obj.key?(alt_key)
19
+
20
+ nil
21
+ end
22
+
23
+ def fetch(key, *args, &block)
24
+ obj = __getobj__
25
+ return obj.fetch(key, *args, &block) if obj.key?(key)
26
+
27
+ alt_key = alternate_key(key)
28
+ return obj.fetch(alt_key, *args, &block) if alt_key && obj.key?(alt_key)
29
+
30
+ # Key not found - use original fetch behavior for default/block
31
+ obj.fetch(key, *args, &block)
32
+ end
33
+
34
+ def key?(key)
35
+ obj = __getobj__
36
+ return true if obj.key?(key)
37
+
38
+ alt_key = alternate_key(key)
39
+ alt_key && obj.key?(alt_key)
40
+ end
41
+
42
+ alias has_key? key?
43
+ alias include? key?
44
+ alias member? key?
45
+
46
+ def dig(key, *rest)
47
+ value = self[key]
48
+ return value if rest.empty? || value.nil?
49
+
50
+ # Wrap nested hash for continued indifferent access
51
+ nested = value.is_a?(Hash) && !value.is_a?(IndifferentHash) ? IndifferentHash.new(value) : value
52
+ nested.dig(*rest)
53
+ end
54
+
55
+ def deep_fetch(key, default = nil)
56
+ keys = key.empty? ? [key] : key.to_s.split('.')
57
+ value = dig(*keys) rescue default
58
+ value.nil? ? default : value
59
+ end
60
+
61
+ private
62
+
63
+ def alternate_key(key)
64
+ case key
65
+ when String
66
+ key.to_sym
67
+ when Symbol
68
+ key.to_s
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module ShinyJsonLogic
2
- VERSION = "0.2.9"
2
+ VERSION = "0.2.11"
3
3
  end
@@ -30,7 +30,7 @@ module ShinyJsonLogic
30
30
  operation, args = rule.first
31
31
 
32
32
  # Check if operation is known
33
- unless OperatorSolver.new.solvers.key?(operation)
33
+ unless OperatorSolver.new.solvers.key?(operation.to_s)
34
34
  raise Errors::UnknownOperator
35
35
  end
36
36
 
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = "Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps, Ruby 2.7+, high spec alignment."
13
13
  spec.description = %q{
14
- ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained, test-driven, and designed for strict spec alignment and predictable behavior. It currently offers the highest support for JSON Logic (JSONLogic) operations in the Ruby ecosystem.
14
+ ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained, test-driven, and designed for strict spec alignment and predictable behavior. It currently offers the highest support for JSON Logic (JSONLogic) operations in the Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a concise overview of JsonLogic in the ruby ecosystem.
15
15
  }
16
16
 
17
17
  spec.homepage = "https://jsonlogicruby.com"
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.metadata = {
23
23
  "homepage_uri" => spec.homepage,
24
24
  "source_code_uri" => "https://github.com/luismoyano/shiny-json-logic-ruby",
25
- "documentation_uri" => "https://github.com/luismoyano/shiny-json-logic-ruby#readme#readme",
25
+ "documentation_uri" => "https://jsonlogicruby.com/docs",
26
26
  "changelog_uri" => "https://github.com/luismoyano/shiny-json-logic-ruby/blob/master/CHANGELOG.md",
27
27
  "rubygems_mfa_required" => "true"
28
28
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shiny_json_logic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.2.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luis Moyano
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-07 00:00:00.000000000 Z
11
+ date: 2026-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -98,7 +98,8 @@ description: "\n ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency imple
98
98
  of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained,
99
99
  test-driven, and designed for strict spec alignment and predictable behavior. It
100
100
  currently offers the highest support for JSON Logic (JSONLogic) operations in the
101
- Ruby ecosystem.\n "
101
+ Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a
102
+ concise overview of JsonLogic in the ruby ecosystem.\n "
102
103
  email:
103
104
  - moyano@hey.com
104
105
  executables: []
@@ -126,9 +127,8 @@ files:
126
127
  - bin/console
127
128
  - bin/setup
128
129
  - bin/test.sh
129
- - lib/core_ext/array.rb
130
- - lib/core_ext/hash.rb
131
130
  - lib/shiny_json_logic.rb
131
+ - lib/shiny_json_logic/comparisons/comparable.rb
132
132
  - lib/shiny_json_logic/engine.rb
133
133
  - lib/shiny_json_logic/errors/base.rb
134
134
  - lib/shiny_json_logic/errors/invalid_arguments.rb
@@ -181,6 +181,8 @@ files:
181
181
  - lib/shiny_json_logic/operator_solver.rb
182
182
  - lib/shiny_json_logic/scope_stack.rb
183
183
  - lib/shiny_json_logic/truthy.rb
184
+ - lib/shiny_json_logic/utils/array.rb
185
+ - lib/shiny_json_logic/utils/indifferent_hash.rb
184
186
  - lib/shiny_json_logic/version.rb
185
187
  - results/ruby.json
186
188
  - shiny_json_logic.gemspec
@@ -190,7 +192,7 @@ licenses:
190
192
  metadata:
191
193
  homepage_uri: https://jsonlogicruby.com
192
194
  source_code_uri: https://github.com/luismoyano/shiny-json-logic-ruby
193
- documentation_uri: https://github.com/luismoyano/shiny-json-logic-ruby#readme#readme
195
+ documentation_uri: https://jsonlogicruby.com/docs
194
196
  changelog_uri: https://github.com/luismoyano/shiny-json-logic-ruby/blob/master/CHANGELOG.md
195
197
  rubygems_mfa_required: 'true'
196
198
  post_install_message:
@@ -1,19 +0,0 @@
1
- class Array
2
- def self.wrap(object)
3
- return [] if object.nil?
4
- return object.to_ary || [object] if object.respond_to?(:to_ary)
5
-
6
- [object]
7
- end
8
-
9
- def self.wrap_nil(object)
10
- return [nil] if object.nil?
11
- wrap(object)
12
- end
13
-
14
- def deep_fetch(index, default = nil)
15
- indexes = index.to_s.split('.').map(&:to_i)
16
- value = dig(*indexes) rescue default
17
- value.nil? ? default : value # value can be false (Boolean)
18
- end
19
- end
data/lib/core_ext/hash.rb DELETED
@@ -1,7 +0,0 @@
1
- class Hash
2
- def deep_fetch(key, default = nil)
3
- keys = key.empty? ? [key] : key.to_s.split('.')
4
- value = dig(*keys) rescue default
5
- value.nil? ? default : value # value can be false (Boolean)
6
- end
7
- end