shiny_json_logic 0.2.6 → 0.2.7

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +131 -0
  3. data/CHANGELOG.md +6 -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 +1 -13
  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 +1 -1
  13. data/lib/shiny_json_logic/operations/and.rb +6 -1
  14. data/lib/shiny_json_logic/operations/base.rb +27 -0
  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 +48 -4
  18. data/lib/shiny_json_logic/operations/division.rb +24 -11
  19. data/lib/shiny_json_logic/operations/double_not.rb +1 -1
  20. data/lib/shiny_json_logic/operations/equal.rb +44 -1
  21. data/lib/shiny_json_logic/operations/exists.rb +2 -2
  22. data/lib/shiny_json_logic/operations/filter.rb +2 -0
  23. data/lib/shiny_json_logic/operations/greater.rb +45 -1
  24. data/lib/shiny_json_logic/operations/greater_equal.rb +45 -1
  25. data/lib/shiny_json_logic/operations/inclusion.rb +3 -1
  26. data/lib/shiny_json_logic/operations/iterable/base.rb +25 -16
  27. data/lib/shiny_json_logic/operations/map.rb +10 -0
  28. data/lib/shiny_json_logic/operations/max.rb +7 -1
  29. data/lib/shiny_json_logic/operations/merge.rb +3 -1
  30. data/lib/shiny_json_logic/operations/min.rb +7 -1
  31. data/lib/shiny_json_logic/operations/missing.rb +8 -2
  32. data/lib/shiny_json_logic/operations/missing_some.rb +6 -4
  33. data/lib/shiny_json_logic/operations/modulo.rb +22 -3
  34. data/lib/shiny_json_logic/operations/none.rb +1 -1
  35. data/lib/shiny_json_logic/operations/not.rb +2 -1
  36. data/lib/shiny_json_logic/operations/or.rb +6 -1
  37. data/lib/shiny_json_logic/operations/product.rb +29 -2
  38. data/lib/shiny_json_logic/operations/smaller.rb +45 -1
  39. data/lib/shiny_json_logic/operations/smaller_equal.rb +45 -1
  40. data/lib/shiny_json_logic/operations/strict_different.rb +22 -4
  41. data/lib/shiny_json_logic/operations/strict_equal.rb +15 -3
  42. data/lib/shiny_json_logic/operations/substring.rb +3 -2
  43. data/lib/shiny_json_logic/operations/subtraction.rb +21 -3
  44. data/lib/shiny_json_logic/operations/throw.rb +1 -1
  45. data/lib/shiny_json_logic/operations/val.rb +4 -2
  46. data/lib/shiny_json_logic/operations/var.rb +3 -2
  47. data/lib/shiny_json_logic/operator_solver.rb +1 -1
  48. data/lib/shiny_json_logic/version.rb +1 -1
  49. data/lib/shiny_json_logic.rb +3 -0
  50. data/results/ruby.json +1 -1
  51. data/shiny_json_logic.gemspec +5 -5
  52. 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: afe72b83c4cd5884c3718de47470d643c06448547400213242c05149115be095
4
+ data.tar.gz: e45b27746d1e8227a3760ee9c0a55d07dccd9503485a2934f2049ec38c9306b0
5
5
  SHA512:
6
- metadata.gz: d28d16b67aaa1aa1435e2babfc2bf4b600c059ee896d863d85d1ba5460397a6eab0c4825575046ca72d0b1f804176710817bedc47d576eb84859e58af6996853
7
- data.tar.gz: '099460668bdd8641e3f37d2ece887eca7100ccf3d7e5ade02c57317c2676a163dcf4ed2aedb417037f6779d133051ca07a7b785e98b3f5661b83259edfb2b0a3'
6
+ metadata.gz: d6798164621aea875879d1dbc9c0f4b548036e1a1bf18ba7bdc463740a55b92ccfca12bd3c6cd3315af0b1e94c5e3ad402014de80d510b2a4a4e6cb0f29f0d9a
7
+ data.tar.gz: d9a8eb8c365817e8e4a8a1cf109711a6dee51068d1275b04aa31e43448c43674eeaba7bae570f65615a8ef5b42fd1572b87eedd7075ff6bce705d814cfaa34ed
data/AGENTS.md ADDED
@@ -0,0 +1,131 @@
1
+ # Estado del Refactor: Eliminar Syntactic Sugar de Array.wrap_nil en Engine
2
+
3
+ ## Instrucciones para el Agente
4
+
5
+ **IMPORTANTE:** Antes de modificar cualquier fichero:
6
+ 1. Explicar qué cambio se va a hacer y por qué
7
+ 2. Preguntar al usuario si procedemos o no
8
+ 3. Solo modificar tras confirmación explícita
9
+
10
+ ## Contexto
11
+
12
+ Estamos refactorizando la implementación de JSONLogic para Ruby para eliminar el "syntactic sugar" que convertía automáticamente todos los argumentos a arrays en `engine.rb:20`.
13
+
14
+ ### Cambio Principal
15
+
16
+ **Antes:**
17
+ ```ruby
18
+ operation, raw_args = rule.to_a.first
19
+ args = Array.wrap_nil(raw_args)
20
+ solve(operation, args, data)
21
+ ```
22
+
23
+ **Después:**
24
+ ```ruby
25
+ operation, raw_args = rule.to_a.first
26
+ solve(operation, raw_args, data)
27
+ ```
28
+
29
+ ## Filosofía
30
+
31
+ En JSONLogic:
32
+ - Las operaciones deben ser O(n), nunca O(n²)
33
+ - Si un argumento es un hash con operador, se evalúa en el preprocesado de `base.rb`
34
+ - Si un argumento es un array, ES un array (no se aplana)
35
+ - Si un argumento es un valor unitario (número, string, boolean, nil, hash vacío), se maneja como tal
36
+
37
+ ## Progreso
38
+
39
+ ### Operaciones Arregladas
40
+ - `!` (not.rb) - Usa `rules.is_a?(Array) ? rules.first : rules`
41
+ - `+` (addition.rb) - Usa `Array.wrap_nil(rules).each`, sin aplanar resultado de evaluate
42
+ - `val` (val.rb) - Usa `Array.wrap_nil(rules)`
43
+ - `var` (var.rb) - Usa `Array.wrap_nil(rules)`
44
+ - `max` (max.rb) - Usa `Array.wrap_nil(rules).each`
45
+ - `cat` (concatenation.rb) - Usa `Array.wrap_nil(rules).each`
46
+ - `merge` (merge.rb) - Usa `Array.wrap_nil(rules).map`
47
+ - `exists` (exists.rb) - Usa `Array.wrap_nil(rules).each` (también eliminado O(n²))
48
+ - `missing` (missing.rb) - Usa `Array.wrap_nil(rules)` y quitado chequeo innecesario
49
+ - `-` (subtraction.rb) - Usa `Array.wrap_nil(rules)` para manejar argumentos directos
50
+ - `/` (division.rb) - Usa `Array.wrap_nil(rules)` para manejar argumentos directos
51
+ - `%` (modulo.rb) - Usa `Array.wrap_nil(rules)` para manejar argumentos directos
52
+ - `*` (product.rb) - Usa `Array.wrap_nil(rules)` para manejar argumentos directos
53
+ - `>` (greater.rb) - Reescrito con comparación de tipos, NaN para arrays/objetos
54
+ - `>=` (greater_equal.rb) - Reescrito con comparación de tipos
55
+ - `<` (smaller.rb) - Reescrito con comparación de tipos
56
+ - `<=` (smaller_equal.rb) - Reescrito con comparación de tipos
57
+ - `==` (equal.rb) - Reescrito con coerción de tipos y manejo de NaN
58
+ - `!=` (different.rb) - Reescrito con comparación consecutiva de pares
59
+ - `===` (strict_equal.rb) - Añadida validación de argumentos mínimos
60
+ - `!==` (strict_different.rb) - Reescrito con comparación consecutiva de pares
61
+
62
+ ### Módulo WithErrorHandling
63
+ Se centralizó el manejo de errores en `lib/shiny_json_logic/numericals/with_error_handling.rb`:
64
+ - `handle_nan` - Retorna error de tipo "NaN"
65
+ - `handle_invalid_args` - Retorna error de tipo "Invalid Arguments"
66
+ - Aliases para compatibilidad: `handle_invalid_operand`, `handle_no_operators`
67
+
68
+ ### Preprocesado en base.rb
69
+ Se añadió preprocesado que evalúa hashes con operadores antes de pasarlos a la operación:
70
+ ```ruby
71
+ def preprocess(rules)
72
+ if operation?(rules)
73
+ evaluate(rules)
74
+ else
75
+ rules
76
+ end
77
+ end
78
+ ```
79
+
80
+ ## Estado Actual
81
+
82
+ - **Tests internos (spec/):** 315/315 pasando (100%)
83
+ - **Compatibilidad estándar (bin/test.sh):** 1041/1126 (92.5%)
84
+ - **Objetivo:** 84% mínimo ✅ ALCANZADO
85
+
86
+ ## Operaciones Pendientes de Arreglar
87
+
88
+ Los fallos restantes (85) están principalmente en:
89
+
90
+ 1. **Operadores con argumentos dinámicos** - Casos donde se pasa un array dinámico via `preserve`
91
+ 2. **`all`, `some`, `none`** - Casos "Missing array returns error"
92
+ 3. **`and`, `or`, `if`** - Casos edge con argumentos no-array
93
+
94
+ ## Patrón de Arreglo
95
+
96
+ Para cada operación que falla, el patrón general es:
97
+
98
+ 1. Identificar dónde asume que `rules` es un array (ej: `rules.each`, `rules.first`, `rules[0]`)
99
+ 2. Envolver con `Array.wrap_nil(rules)` para manejar valores unitarios
100
+ 3. Incluir `Numericals::WithErrorHandling` para usar `handle_nan` y `handle_invalid_args`
101
+ 4. Verificar que no se esté haciendo aplanado innecesario (mantener O(n))
102
+
103
+ ## Cómo Continuar
104
+
105
+ 1. Correr `bundle exec rspec` para verificar tests internos
106
+ 2. Correr `bin/test.sh` para ver compatibilidad estándar
107
+ 3. Identificar operación con fallos
108
+ 4. Ver casos de test que fallan para esa operación
109
+ 5. Modificar la operación siguiendo el patrón
110
+ 6. Repetir
111
+
112
+ ## Comandos Útiles
113
+
114
+ ```bash
115
+ # Tests internos
116
+ bundle exec rspec
117
+
118
+ # Tests de compatibilidad estándar
119
+ bin/test.sh
120
+
121
+ # Ver fallos por operación
122
+ bin/test.sh 2>&1 | grep -B1 "FAILED" | grep "example" | head -50
123
+
124
+ # Probar operación específica
125
+ bundle exec ruby -e "
126
+ require 'bundler/setup'
127
+ require 'shiny_json_logic'
128
+ result = ShinyJsonLogic.apply({'operador' => args}, data)
129
+ puts result.inspect
130
+ "
131
+ ```
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.7] -2026-02-05
6
+ ### Changed
7
+ - Refactors internal architecture to always enable lazy loading
8
+ - Improves compatibility of arithmetic operators
9
+ - Improves compatibility of logical operators
10
+
5
11
  ## [0.2.6] - 2026-02-02
6
12
  ### Changed
7
13
  - 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.7)
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": "92.5% (1041/1126)",
5
+ "color": "yellow"
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
@@ -17,19 +17,7 @@ module ShinyJsonLogic
17
17
  return rule if rule.empty?
18
18
 
19
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
20
+ solve(operation, raw_args, data)
33
21
  elsif rule.is_a?(Array)
34
22
  rule.map { |val| call(val, data) }
35
23
  else
@@ -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
@@ -9,7 +9,7 @@ module ShinyJsonLogic
9
9
  def on_after(results)
10
10
  return false if results.empty?
11
11
 
12
- results.all? { |res| res == true }
12
+ results.all? { |res| Truthy.call(res) }
13
13
  end
14
14
  end
15
15
  end
@@ -7,7 +7,12 @@ module ShinyJsonLogic
7
7
  protected
8
8
 
9
9
  def run
10
- rules.reduce { |a, b| Truthy.call(a) ? b : a }
10
+ result = nil
11
+ rules.each do |rule|
12
+ result = evaluate(rule)
13
+ return result unless Truthy.call(result)
14
+ end
15
+ result
11
16
  end
12
17
  end
13
18
  end
@@ -6,6 +6,7 @@ module ShinyJsonLogic
6
6
  def initialize(context)
7
7
  @context = context
8
8
  @rules, @data, @errors = @context.values_at("rules", "data", "errors")
9
+ @rules = preprocess(@rules)
9
10
  end
10
11
 
11
12
  def call
@@ -24,6 +25,32 @@ module ShinyJsonLogic
24
25
  def deliver(result = nil)
25
26
  {"result" => result, "data" => self.data, "errors" => self.errors}
26
27
  end
28
+
29
+ # Evalúa un argumento raw usando el Engine
30
+ def evaluate(rule)
31
+ engine = Engine.new(rule, data)
32
+ result = engine.call
33
+ self.errors = [*errors, *engine.errors].uniq
34
+ result
35
+ end
36
+
37
+ private
38
+
39
+ # Pre-procesa las rules:
40
+ # - Si es un hash con operador, lo evalúa inmediatamente
41
+ # - Si es array o valor unitario, lo deja como está
42
+ def preprocess(rules)
43
+ if operation?(rules)
44
+ evaluate(rules)
45
+ else
46
+ rules
47
+ end
48
+ end
49
+
50
+ def operation?(value)
51
+ return false unless value.is_a?(Hash) && !value.empty?
52
+ OperatorSolver.new.operation?(value)
53
+ end
27
54
  end
28
55
  end
29
56
  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,56 @@
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
+
11
+ protected
12
+
13
+ def run
14
+ operands = Array.wrap_nil(rules)
15
+ return handle_invalid_args if operands.length < 2
16
+
17
+ prev = evaluate(operands[0])
18
+ operands[1..].each do |rule|
19
+ curr = evaluate(rule)
20
+ result = compare(prev, curr)
21
+ return handle_nan if result == :nan
22
+ return false if result == 0 # Si son iguales, != es false
23
+ prev = curr
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
10
54
  end
11
55
  end
12
56
  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,7 @@ module ShinyJsonLogic
7
7
  protected
8
8
 
9
9
  def run
10
- !!Truthy.call(rules.first)
10
+ !!Truthy.call(evaluate(rules.first))
11
11
  end
12
12
  end
13
13
  end
@@ -1,12 +1,55 @@
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
+
6
11
  protected
7
12
 
8
13
  def run
9
- rules.map(&:to_s).all? { |v| v == rules[0].to_s }
14
+ operands = Array.wrap_nil(rules)
15
+ return handle_invalid_args if operands.length < 2
16
+
17
+ first = evaluate(operands[0])
18
+ operands[1..].each do |rule|
19
+ curr = evaluate(rule)
20
+ result = compare(first, curr)
21
+ return handle_nan if result == :nan
22
+ return false unless result == 0
23
+ end
24
+ true
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
10
53
  end
11
54
  end
12
55
  end