shiny_json_logic 0.2.6 → 0.2.8

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +39 -3
  6. data/badges/compat.json +2 -2
  7. data/lib/core_ext/array.rb +5 -0
  8. data/lib/shiny_json_logic/engine.rb +20 -24
  9. data/lib/shiny_json_logic/numericals/numerify.rb +9 -9
  10. data/lib/shiny_json_logic/numericals/with_error_handling.rb +15 -10
  11. data/lib/shiny_json_logic/operations/addition.rb +15 -5
  12. data/lib/shiny_json_logic/operations/all.rb +3 -1
  13. data/lib/shiny_json_logic/operations/and.rb +14 -1
  14. data/lib/shiny_json_logic/operations/base.rb +45 -4
  15. data/lib/shiny_json_logic/operations/coalesce.rb +5 -1
  16. data/lib/shiny_json_logic/operations/concatenation.rb +6 -3
  17. data/lib/shiny_json_logic/operations/different.rb +50 -4
  18. data/lib/shiny_json_logic/operations/division.rb +24 -11
  19. data/lib/shiny_json_logic/operations/double_not.rb +2 -1
  20. data/lib/shiny_json_logic/operations/equal.rb +46 -1
  21. data/lib/shiny_json_logic/operations/exists.rb +2 -2
  22. data/lib/shiny_json_logic/operations/filter.rb +5 -3
  23. data/lib/shiny_json_logic/operations/greater.rb +47 -1
  24. data/lib/shiny_json_logic/operations/greater_equal.rb +47 -1
  25. data/lib/shiny_json_logic/operations/if.rb +11 -4
  26. data/lib/shiny_json_logic/operations/inclusion.rb +3 -1
  27. data/lib/shiny_json_logic/operations/iterable/base.rb +52 -25
  28. data/lib/shiny_json_logic/operations/map.rb +11 -0
  29. data/lib/shiny_json_logic/operations/max.rb +7 -1
  30. data/lib/shiny_json_logic/operations/merge.rb +3 -1
  31. data/lib/shiny_json_logic/operations/min.rb +7 -1
  32. data/lib/shiny_json_logic/operations/missing.rb +8 -2
  33. data/lib/shiny_json_logic/operations/missing_some.rb +6 -4
  34. data/lib/shiny_json_logic/operations/modulo.rb +22 -3
  35. data/lib/shiny_json_logic/operations/none.rb +3 -1
  36. data/lib/shiny_json_logic/operations/not.rb +2 -1
  37. data/lib/shiny_json_logic/operations/or.rb +14 -1
  38. data/lib/shiny_json_logic/operations/preserve.rb +10 -4
  39. data/lib/shiny_json_logic/operations/product.rb +29 -2
  40. data/lib/shiny_json_logic/operations/reduce.rb +14 -9
  41. data/lib/shiny_json_logic/operations/smaller.rb +47 -1
  42. data/lib/shiny_json_logic/operations/smaller_equal.rb +47 -1
  43. data/lib/shiny_json_logic/operations/some.rb +2 -0
  44. data/lib/shiny_json_logic/operations/strict_different.rb +24 -4
  45. data/lib/shiny_json_logic/operations/strict_equal.rb +17 -3
  46. data/lib/shiny_json_logic/operations/substring.rb +6 -3
  47. data/lib/shiny_json_logic/operations/subtraction.rb +21 -3
  48. data/lib/shiny_json_logic/operations/throw.rb +2 -10
  49. data/lib/shiny_json_logic/operations/try.rb +34 -11
  50. data/lib/shiny_json_logic/operations/val.rb +50 -3
  51. data/lib/shiny_json_logic/operations/var.rb +4 -3
  52. data/lib/shiny_json_logic/operator_solver.rb +3 -12
  53. data/lib/shiny_json_logic/scope_stack.rb +93 -0
  54. data/lib/shiny_json_logic/version.rb +1 -1
  55. data/lib/shiny_json_logic.rb +3 -0
  56. data/results/ruby.json +1 -1
  57. data/shiny_json_logic.gemspec +5 -5
  58. metadata +7 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c094c662a616d343c0d045d0c8da1173fb1f982c19b709ffc1e9c8550813092
4
- data.tar.gz: 872f71f3d52d7dc6d16a98e2fbf97cd50c07d9d901f7a2ac29ec831a86aa2621
3
+ metadata.gz: e05288f3f43187927c102d03ce0cd653bf8762dd7e0adc40b29d27e0c4f7984f
4
+ data.tar.gz: 4c6e781dec321b4f928b9b1d786c708ffe39662cc22676cc5ab7d8228dff8aa8
5
5
  SHA512:
6
- metadata.gz: d28d16b67aaa1aa1435e2babfc2bf4b600c059ee896d863d85d1ba5460397a6eab0c4825575046ca72d0b1f804176710817bedc47d576eb84859e58af6996853
7
- data.tar.gz: '099460668bdd8641e3f37d2ece887eca7100ccf3d7e5ade02c57317c2676a163dcf4ed2aedb417037f6779d133051ca07a7b785e98b3f5661b83259edfb2b0a3'
6
+ metadata.gz: '0459cef93e4ce203e8ea1022c7bf7305aef736551e56bf3e8c47326ddcf95481232c788f5b3d5c96f25137df730b6cb5180ac3b7aadd696a1c4a0d9bea7e0aa3'
7
+ data.tar.gz: d3801f6f5cc0a0df664d829e352c934345305ce264a57bd39a7dd3f7e637963d402a0c7676e8c4f9fffa76fea7dffe6cca04d655bc38b79dfc781877faa616d5
data/.gitignore CHANGED
@@ -18,3 +18,6 @@
18
18
 
19
19
  # Gem
20
20
  *.gem
21
+
22
+ # Development context
23
+ AGENTS_DEVELOPMENT.md
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.8] - 2026-02-07
6
+ ### Changed
7
+ - Improves error handling in try/throw/reduce operators
8
+ - Fixes error propagation in iterators (map/filter/all/some/none)
9
+ - Compatibility improved from 98.3% to 99.7%
10
+
11
+ ## [0.2.7] - 2026-02-05
12
+ ### Changed
13
+ - Refactors internal architecture to always enable lazy loading
14
+ - Improves compatibility of arithmetic operators
15
+ - Improves compatibility of logical operators
16
+
5
17
  ## [0.2.6] - 2026-02-02
6
18
  ### Changed
7
19
  - Fixes bug with iterable when rules are empty
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiny_json_logic (0.2.6)
4
+ shiny_json_logic (0.2.8)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -4,7 +4,7 @@
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
5
  ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7-brightgreen)
6
6
 
7
- > **A boring, correct and production-ready JSON Logic implementation for Ruby. ✨**
7
+ > **A boring, correct and production-ready JSONLogic implementation for Ruby. ✨**
8
8
 
9
9
  **ShinyJsonLogic** is a **pure Ruby**, **zero-dependency** JSON Logic implementation, designed to offer a reliable and well-tested engine for Ruby applications.
10
10
 
@@ -17,11 +17,15 @@ This gem focuses on predictable behavior, strict spec alignment, high compatibil
17
17
  - 🧩 **Zero runtime dependencies** (stdlib-only). Just plug & play!
18
18
  - 🕰️ **Ruby 2.7+ compatible**, one of the lowest minimum versions supported in the Ruby ecosystem.
19
19
  - 🔧 **Actively maintained** and continuously improved.
20
- - 📊 **Highest JSON Logic compatibility in the Ruby ecosystem**, as measured against the official test suites.
20
+ - 📊 **Highest JSONLogic compatibility in the Ruby ecosystem**, as measured against the official test suites.
21
21
  - ⭐ **Only Ruby implementation supporting the latest standard operators** (`val`, `exists`, `??`, `try`, `throw`)
22
22
 
23
23
  If you want JSON Logic to *just work* in Ruby, this is the safe default.
24
24
 
25
+ ---
26
+ # Test it for yourself!
27
+ Try it out in the sandbox at [jsonlogicruby.com](https://jsonlogicruby.com)
28
+
25
29
  ---
26
30
 
27
31
  ## Installation
@@ -52,6 +56,38 @@ require "shiny_json_logic"
52
56
 
53
57
  ---
54
58
 
59
+ ## Migrating from json-logic-ruby
60
+
61
+ If you're currently using [json-logic-ruby](https://github.com/bhgames/json-logic-ruby), migration is seamless.
62
+
63
+ ShinyJsonLogic provides `JsonLogic` and `JSONLogic` as aliases, so you only need to swap the gem in your Gemfile:
64
+
65
+ ```diff
66
+ - gem "json-logic-ruby"
67
+ + gem "shiny_json_logic"
68
+ ```
69
+
70
+ Your existing code will work without changes:
71
+
72
+ ```ruby
73
+ require "shiny_json_logic"
74
+
75
+ # Both of these work exactly as before:
76
+ JsonLogic.apply(rule, data)
77
+ JSONLogic.apply(rule, data)
78
+
79
+ # Or use the new module name:
80
+ ShinyJsonLogic.apply(rule, data)
81
+ ```
82
+
83
+ **Why should you give us a chance?**
84
+ - 🐛 Better spec compliance and fewer edge-case bugs
85
+ - ✨ Support for new operators (`val`, `exists`, `??`, `try`, `throw`, `preserve`)
86
+ - 🔧 Actively maintained
87
+ - 🧪 Higher test coverage against official JSONLogic test suites
88
+
89
+ ---
90
+
55
91
  ## Usage
56
92
 
57
93
  Basic usage is intentionally simple:
@@ -156,7 +192,7 @@ Install locally:
156
192
  ```bash
157
193
  bundle install
158
194
  ```
159
- How to run the compatibility tests:
195
+ How to run the compatibility test suite:
160
196
 
161
197
  ```bash
162
198
  bin/test.sh
data/badges/compat.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "label": "compat",
4
- "message": "83.9% (945/1126)",
5
- "color": "red"
4
+ "message": "99.7% (1123/1126)",
5
+ "color": "green"
6
6
  }
@@ -6,6 +6,11 @@ class Array
6
6
  [object]
7
7
  end
8
8
 
9
+ def self.wrap_nil(object)
10
+ return [nil] if object.nil?
11
+ wrap(object)
12
+ end
13
+
9
14
  def deep_fetch(index, default = nil)
10
15
  indexes = index.to_s.split('.').map(&:to_i)
11
16
  value = dig(*indexes) rescue default
@@ -1,37 +1,31 @@
1
1
  require "core_ext/array"
2
2
  require "core_ext/hash"
3
3
  require "shiny_json_logic/operator_solver"
4
+ require "shiny_json_logic/scope_stack"
4
5
 
5
6
  module ShinyJsonLogic
6
7
  class Engine
7
8
  attr_reader :errors
8
9
 
9
- def initialize(rule, data = {})
10
+ # Initialize with either:
11
+ # - Engine.new(rule, data) - creates a new scope_stack with data as root
12
+ # - Engine.new(rule, scope_stack: existing_stack) - uses existing scope_stack
13
+ def initialize(rule, data = nil, scope_stack: nil)
10
14
  @rule = rule
11
- @data = data || {}
12
15
  @errors = []
16
+ @scope_stack = scope_stack || ScopeStack.new(data || {})
13
17
  end
14
18
 
15
- def call(rule = self.rule, data = self.data)
19
+ def call(rule = self.rule)
16
20
  if rule.is_a?(Hash)
17
21
  return rule if rule.empty?
18
22
 
19
- operation, raw_args = rule.to_a.first
20
- if operations.collection_solvers.key?(operation)
21
- solve(operation, raw_args, data)
22
- else
23
- evaluated_args =
24
- if raw_args.is_a?(Array)
25
- raw_args.map { |val| call(val, data) }
26
- else
27
- call = call(raw_args, data)
28
- call.is_a?(Array) ? call : [call(raw_args, data)]
29
- end
30
-
31
- solve(operation, evaluated_args, data)
32
- end
23
+ operation, args = rule.to_a.first
24
+ return rule unless operations.solvers.key?(operation)
25
+
26
+ solve(operation, args)
33
27
  elsif rule.is_a?(Array)
34
- rule.map { |val| call(val, data) }
28
+ rule.map { |val| call(val) }
35
29
  else
36
30
  rule
37
31
  end
@@ -39,15 +33,17 @@ module ShinyJsonLogic
39
33
 
40
34
  private
41
35
 
42
- attr_reader :rule
43
- attr_accessor :data
36
+ attr_reader :rule, :scope_stack
44
37
  attr_writer :errors
45
38
 
46
- def solve(operation, args, initial_data)
47
- context = {"rules" => args, "data" => initial_data, "errors" => errors}
48
- result, data, errors = operations.solvers.fetch(operation).new(context).call.values_at("result", "data", "errors")
39
+ def solve(operation, args)
40
+ context = {
41
+ "rules" => args,
42
+ "errors" => errors,
43
+ "scope_stack" => scope_stack
44
+ }
45
+ result, errors = operations.solvers.fetch(operation).new(context).call.values_at("result", "errors")
49
46
  self.errors = [*self.errors, *errors].uniq
50
- self.data.merge data if self.data.is_a?(Hash) && data.is_a?(Hash)
51
47
 
52
48
  result
53
49
  end
@@ -1,25 +1,25 @@
1
1
  module ShinyJsonLogic
2
2
  module Numericals
3
3
  module Numerify
4
- def numerified
5
- @numerified ||=
6
- rules.map do |rule|
7
- numerify(rule)
8
- end
9
- end
10
-
11
4
  private
12
5
 
13
6
  def numerify(value)
14
7
  return value.to_f if value.is_a?(Numeric)
15
8
  return 0.0 if value == ""
16
- return value.to_f if value.is_a?(String) && value.match?(/\A[+-]?\d*.?\d+\z/)
9
+ return value.to_f if value.is_a?(String) && numeric_string?(value)
17
10
  return 0 if value == false
18
11
  return 1 if value == true
19
12
  return nil if value.nil?
20
13
 
21
14
  raise TypeError, "Cannot convert #{value.inspect} to a number"
22
15
  end
16
+
17
+ def numeric_string?(value)
18
+ Float(value)
19
+ true
20
+ rescue ArgumentError
21
+ false
22
+ end
23
23
  end
24
24
  end
25
- end
25
+ end
@@ -1,28 +1,33 @@
1
+ require "shiny_json_logic/errors/base"
2
+
1
3
  module ShinyJsonLogic
2
4
  module Numericals
3
5
  module WithErrorHandling
4
6
  def safe_arithmetic(&block)
5
7
  result = yield
6
- result.tap do |res|
7
- handle_invalid_operand if res.to_f.nan? || res == Float::INFINITY || res == -Float::INFINITY
8
+ if result.to_f.nan? || result == Float::INFINITY || result == -Float::INFINITY
9
+ return handle_nan
8
10
  end
11
+ result
9
12
  rescue TypeError
10
- handle_invalid_operand
13
+ handle_nan
11
14
  end
12
15
 
13
- def handle_invalid_operand
14
- self.data["type"] = "NaN"
16
+ def handle_nan
15
17
  error = ShinyJsonLogic::Errors::Base.new(type: "NaN")
16
- errors.push error
17
- return error.id
18
+ self.errors << error
19
+ error.id
18
20
  end
19
21
 
20
- def handle_no_operators
21
- error = Errors::Base.new(type: "Invalid Arguments")
22
+ def handle_invalid_args
23
+ error = ShinyJsonLogic::Errors::Base.new(type: "Invalid Arguments")
22
24
  self.errors << error
23
-
24
25
  error.id
25
26
  end
27
+
28
+ # Alias for backward compatibility
29
+ alias_method :handle_invalid_operand, :handle_nan
30
+ alias_method :handle_no_operators, :handle_invalid_args
26
31
  end
27
32
  end
28
33
  end
@@ -1,6 +1,6 @@
1
1
  require "shiny_json_logic/operations/base"
2
- require "shiny_json_logic/numericals/numerify"
3
2
  require "shiny_json_logic/numericals/with_error_handling"
3
+ require "shiny_json_logic/numericals/numerify"
4
4
 
5
5
  module ShinyJsonLogic
6
6
  module Operations
@@ -11,20 +11,30 @@ module ShinyJsonLogic
11
11
  protected
12
12
 
13
13
  def run
14
-
15
14
  safe_arithmetic do
16
- return 0 if numerified.empty?
15
+ result = 0.0
16
+ count = 0
17
+
18
+ each_operand do |num|
19
+ count += 1
20
+ result = result + num
21
+ end
17
22
 
18
- numerified.reduce(:+)
23
+ result
19
24
  end
20
25
  end
21
26
 
22
27
  private
23
28
 
29
+ def each_operand
30
+ Array.wrap_nil(rules).each do |rule|
31
+ yield numerify(evaluate(rule))
32
+ end
33
+ end
34
+
24
35
  def numerify(value)
25
36
  val = super
26
37
  return 0 if val.nil?
27
-
28
38
  val
29
39
  end
30
40
  end
@@ -4,12 +4,14 @@ require "shiny_json_logic/operations/iterable/base"
4
4
  module ShinyJsonLogic
5
5
  module Operations
6
6
  class All < Iterable::Base
7
+ raise_on_dynamic_args!
8
+
7
9
  private
8
10
 
9
11
  def on_after(results)
10
12
  return false if results.empty?
11
13
 
12
- results.all? { |res| res == true }
14
+ results.all? { |res| Truthy.call(res) }
13
15
  end
14
16
  end
15
17
  end
@@ -1,13 +1,26 @@
1
1
  require "shiny_json_logic/operations/base"
2
2
  require "shiny_json_logic/truthy"
3
+ require "shiny_json_logic/numericals/with_error_handling"
3
4
 
4
5
  module ShinyJsonLogic
5
6
  module Operations
6
7
  class And < Base
8
+ include Numericals::WithErrorHandling
9
+ raise_on_dynamic_args!
10
+
7
11
  protected
8
12
 
9
13
  def run
10
- rules.reduce { |a, b| Truthy.call(a) ? b : a }
14
+ return handle_invalid_args if dynamic_args?
15
+ return handle_invalid_args unless rules.is_a?(Array)
16
+ return false if rules.empty?
17
+
18
+ result = nil
19
+ rules.each do |rule|
20
+ result = evaluate(rule)
21
+ return result unless Truthy.call(result)
22
+ end
23
+ result
11
24
  end
12
25
  end
13
26
  end
@@ -5,7 +5,9 @@ module ShinyJsonLogic
5
5
  class Base
6
6
  def initialize(context)
7
7
  @context = context
8
- @rules, @data, @errors = @context.values_at("rules", "data", "errors")
8
+ @rules, @errors, @scope_stack = @context.values_at("rules", "errors", "scope_stack")
9
+ @dynamic_args = operation?(@rules)
10
+ @rules = pre_process(@rules)
9
11
  end
10
12
 
11
13
  def call
@@ -14,15 +16,54 @@ module ShinyJsonLogic
14
16
 
15
17
  protected
16
18
 
17
- attr_reader :context
18
- attr_accessor :rules, :data, :errors
19
+ attr_reader :context, :scope_stack
20
+ attr_accessor :rules, :errors
21
+
22
+ # Access current data through scope_stack
23
+ def data
24
+ scope_stack.current
25
+ end
19
26
 
20
27
  def run
21
28
  raise NotImplementedError
22
29
  end
23
30
 
24
31
  def deliver(result = nil)
25
- {"result" => result, "data" => self.data, "errors" => self.errors}
32
+ {"result" => result, "errors" => self.errors}
33
+ end
34
+
35
+ def evaluate(rule)
36
+ engine = Engine.new(rule, scope_stack: scope_stack)
37
+ result = engine.call
38
+ self.errors = [*errors, *engine.errors].uniq
39
+ result
40
+ end
41
+
42
+ def dynamic_args?
43
+ @dynamic_args && self.class.raise_on_dynamic_args?
44
+ end
45
+
46
+ def self.raise_on_dynamic_args!
47
+ @raise_on_dynamic_args = true
48
+ end
49
+
50
+ def self.raise_on_dynamic_args?
51
+ @raise_on_dynamic_args
52
+ end
53
+
54
+ private
55
+
56
+ def pre_process(rules)
57
+ if operation?(rules)
58
+ evaluate(rules)
59
+ else
60
+ rules
61
+ end
62
+ end
63
+
64
+ def operation?(value)
65
+ return false unless value.is_a?(Hash) && !value.empty?
66
+ OperatorSolver.new.operation?(value)
26
67
  end
27
68
  end
28
69
  end
@@ -6,7 +6,11 @@ module ShinyJsonLogic
6
6
  protected
7
7
 
8
8
  def run
9
- rules.compact.first
9
+ rules.each do |rule|
10
+ result = evaluate(rule)
11
+ return result unless result.nil?
12
+ end
13
+ nil
10
14
  end
11
15
  end
12
16
  end
@@ -6,9 +6,12 @@ module ShinyJsonLogic
6
6
  protected
7
7
 
8
8
  def run
9
- return rules.map(&:to_s).join if rules.is_a?(Array)
10
-
11
- rules
9
+ result = []
10
+ Array.wrap_nil(rules).each do |rule|
11
+ evaluated = evaluate(rule)
12
+ Array.wrap_nil(evaluated).each { |v| result << v.to_s }
13
+ end
14
+ result.join
12
15
  end
13
16
  end
14
17
  end
@@ -1,12 +1,58 @@
1
1
  require "shiny_json_logic/operations/base"
2
- require "shiny_json_logic/operations/equal"
2
+ require "shiny_json_logic/numericals/with_error_handling"
3
+ require "shiny_json_logic/numericals/numerify"
3
4
 
4
5
  module ShinyJsonLogic
5
6
  module Operations
6
7
  class Different < Base
7
- def call
8
- ctx = Operations::Equal.new(context).call
9
- {"result" => !ctx["result"], "data" => ctx["data"], "errors" => ctx["errors"]}
8
+ include Numericals::WithErrorHandling
9
+ include Numericals::Numerify
10
+ raise_on_dynamic_args!
11
+
12
+ protected
13
+
14
+ def run
15
+ return handle_invalid_args if dynamic_args?
16
+ operands = Array.wrap_nil(rules)
17
+ return handle_invalid_args if operands.length < 2
18
+
19
+ prev = evaluate(operands[0])
20
+ operands[1..].each do |rule|
21
+ curr = evaluate(rule)
22
+ result = compare(prev, curr)
23
+ return handle_nan if result == :nan
24
+ return false if result == 0 # Si son iguales, != es false
25
+ prev = curr
26
+ end
27
+ true # Todos los pares consecutivos son diferentes
28
+ end
29
+
30
+ private
31
+
32
+ def compare(a, b)
33
+ # Arrays u objetos → NaN
34
+ return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
35
+
36
+ # Ambos strings → comparación directa
37
+ if a.is_a?(String) && b.is_a?(String)
38
+ return a <=> b
39
+ end
40
+
41
+ # Convertir a números para comparar
42
+ num_a = numerify_for_compare(a)
43
+ num_b = numerify_for_compare(b)
44
+ return :nan if num_a.nil? || num_b.nil?
45
+
46
+ num_a <=> num_b
47
+ end
48
+
49
+ def numerify_for_compare(value)
50
+ return value.to_f if value.is_a?(Numeric)
51
+ return 0.0 if value == false
52
+ return 1.0 if value == true
53
+ return 0.0 if value.nil?
54
+ return value.to_f if value.is_a?(String) && numeric_string?(value)
55
+ nil # String no numérica
10
56
  end
11
57
  end
12
58
  end
@@ -11,23 +11,36 @@ module ShinyJsonLogic
11
11
  protected
12
12
 
13
13
  def run
14
- return handle_no_operators if rules.empty?
15
- return handle_nil_operands if rules.any?(&:nil?)
14
+ operands = Array.wrap_nil(rules)
15
+ return handle_invalid_args if operands.empty?
16
+
17
+ result = nil
18
+ count = 0
19
+
20
+ begin
21
+ each_operand(operands) do |num|
22
+ return handle_nan if num.nil?
23
+ count += 1
24
+ result = result.nil? ? num : result / num
25
+ end
26
+ rescue TypeError
27
+ return handle_nan
28
+ end
16
29
 
17
- safe_arithmetic do
18
- self.rules = [1, *rules] if rules.size < 2
30
+ return handle_invalid_args if count == 0
19
31
 
20
- numerified.reduce(:/)
21
- end
32
+ final_result = count == 1 ? 1.0 / result : result
33
+
34
+ safe_arithmetic { final_result }
22
35
  end
23
36
 
24
37
  private
25
38
 
26
- def handle_nil_operands
27
- error = Errors::Base.new(type: "NaN")
28
- self.errors << error
29
-
30
- error.id
39
+ def each_operand(operands)
40
+ operands.each do |rule|
41
+ evaluated = evaluate(rule)
42
+ yield numerify(evaluated)
43
+ end
31
44
  end
32
45
  end
33
46
  end
@@ -7,7 +7,8 @@ module ShinyJsonLogic
7
7
  protected
8
8
 
9
9
  def run
10
- !!Truthy.call(rules.first)
10
+ value = Array.wrap_nil(rules).first
11
+ !!Truthy.call(evaluate(value))
11
12
  end
12
13
  end
13
14
  end
@@ -1,12 +1,57 @@
1
1
  require "shiny_json_logic/operations/base"
2
+ require "shiny_json_logic/numericals/with_error_handling"
3
+ require "shiny_json_logic/numericals/numerify"
2
4
 
3
5
  module ShinyJsonLogic
4
6
  module Operations
5
7
  class Equal < Base
8
+ include Numericals::WithErrorHandling
9
+ include Numericals::Numerify
10
+ raise_on_dynamic_args!
11
+
6
12
  protected
7
13
 
8
14
  def run
9
- rules.map(&:to_s).all? { |v| v == rules[0].to_s }
15
+ return handle_invalid_args if dynamic_args?
16
+ operands = Array.wrap_nil(rules)
17
+ return handle_invalid_args if operands.length < 2
18
+
19
+ first = evaluate(operands[0])
20
+ operands[1..].each do |rule|
21
+ curr = evaluate(rule)
22
+ result = compare(first, curr)
23
+ return handle_nan if result == :nan
24
+ return false unless result == 0
25
+ end
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ def compare(a, b)
32
+ # Arrays u objetos → NaN
33
+ return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
34
+
35
+ # Ambos strings → comparación directa
36
+ if a.is_a?(String) && b.is_a?(String)
37
+ return a <=> b
38
+ end
39
+
40
+ # Convertir a números para comparar
41
+ num_a = numerify_for_compare(a)
42
+ num_b = numerify_for_compare(b)
43
+ return :nan if num_a.nil? || num_b.nil?
44
+
45
+ num_a <=> num_b
46
+ end
47
+
48
+ def numerify_for_compare(value)
49
+ return value.to_f if value.is_a?(Numeric)
50
+ return 0.0 if value == false
51
+ return 1.0 if value == true
52
+ return 0.0 if value.nil?
53
+ return value.to_f if value.is_a?(String) && numeric_string?(value)
54
+ nil # String no numérica
10
55
  end
11
56
  end
12
57
  end