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.
- checksums.yaml +4 -4
- data/AGENTS.md +131 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +39 -3
- data/badges/compat.json +2 -2
- data/lib/core_ext/array.rb +5 -0
- data/lib/shiny_json_logic/engine.rb +1 -13
- data/lib/shiny_json_logic/numericals/numerify.rb +9 -9
- data/lib/shiny_json_logic/numericals/with_error_handling.rb +15 -10
- data/lib/shiny_json_logic/operations/addition.rb +15 -5
- data/lib/shiny_json_logic/operations/all.rb +1 -1
- data/lib/shiny_json_logic/operations/and.rb +6 -1
- data/lib/shiny_json_logic/operations/base.rb +27 -0
- data/lib/shiny_json_logic/operations/coalesce.rb +5 -1
- data/lib/shiny_json_logic/operations/concatenation.rb +6 -3
- data/lib/shiny_json_logic/operations/different.rb +48 -4
- data/lib/shiny_json_logic/operations/division.rb +24 -11
- data/lib/shiny_json_logic/operations/double_not.rb +1 -1
- data/lib/shiny_json_logic/operations/equal.rb +44 -1
- data/lib/shiny_json_logic/operations/exists.rb +2 -2
- data/lib/shiny_json_logic/operations/filter.rb +2 -0
- data/lib/shiny_json_logic/operations/greater.rb +45 -1
- data/lib/shiny_json_logic/operations/greater_equal.rb +45 -1
- data/lib/shiny_json_logic/operations/inclusion.rb +3 -1
- data/lib/shiny_json_logic/operations/iterable/base.rb +25 -16
- data/lib/shiny_json_logic/operations/map.rb +10 -0
- data/lib/shiny_json_logic/operations/max.rb +7 -1
- data/lib/shiny_json_logic/operations/merge.rb +3 -1
- data/lib/shiny_json_logic/operations/min.rb +7 -1
- data/lib/shiny_json_logic/operations/missing.rb +8 -2
- data/lib/shiny_json_logic/operations/missing_some.rb +6 -4
- data/lib/shiny_json_logic/operations/modulo.rb +22 -3
- data/lib/shiny_json_logic/operations/none.rb +1 -1
- data/lib/shiny_json_logic/operations/not.rb +2 -1
- data/lib/shiny_json_logic/operations/or.rb +6 -1
- data/lib/shiny_json_logic/operations/product.rb +29 -2
- data/lib/shiny_json_logic/operations/smaller.rb +45 -1
- data/lib/shiny_json_logic/operations/smaller_equal.rb +45 -1
- data/lib/shiny_json_logic/operations/strict_different.rb +22 -4
- data/lib/shiny_json_logic/operations/strict_equal.rb +15 -3
- data/lib/shiny_json_logic/operations/substring.rb +3 -2
- data/lib/shiny_json_logic/operations/subtraction.rb +21 -3
- data/lib/shiny_json_logic/operations/throw.rb +1 -1
- data/lib/shiny_json_logic/operations/val.rb +4 -2
- data/lib/shiny_json_logic/operations/var.rb +3 -2
- data/lib/shiny_json_logic/operator_solver.rb +1 -1
- data/lib/shiny_json_logic/version.rb +1 -1
- data/lib/shiny_json_logic.rb +3 -0
- data/results/ruby.json +1 -1
- data/shiny_json_logic.gemspec +5 -5
- metadata +7 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: afe72b83c4cd5884c3718de47470d643c06448547400213242c05149115be095
|
|
4
|
+
data.tar.gz: e45b27746d1e8227a3760ee9c0a55d07dccd9503485a2934f2049ec38c9306b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://badge.fury.io/rb/shiny_json_logic)
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
> **A boring, correct and production-ready
|
|
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
|
|
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
|
|
195
|
+
How to run the compatibility test suite:
|
|
160
196
|
|
|
161
197
|
```bash
|
|
162
198
|
bin/test.sh
|
data/badges/compat.json
CHANGED
data/lib/core_ext/array.rb
CHANGED
|
@@ -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
|
-
|
|
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) &&
|
|
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.
|
|
7
|
-
|
|
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
|
-
|
|
13
|
+
handle_nan
|
|
11
14
|
end
|
|
12
15
|
|
|
13
|
-
def
|
|
14
|
-
self.data["type"] = "NaN"
|
|
16
|
+
def handle_nan
|
|
15
17
|
error = ShinyJsonLogic::Errors::Base.new(type: "NaN")
|
|
16
|
-
errors
|
|
17
|
-
|
|
18
|
+
self.errors << error
|
|
19
|
+
error.id
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
def
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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,9 +6,12 @@ module ShinyJsonLogic
|
|
|
6
6
|
protected
|
|
7
7
|
|
|
8
8
|
def run
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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/
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
15
|
-
return
|
|
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
|
-
|
|
18
|
-
self.rules = [1, *rules] if rules.size < 2
|
|
30
|
+
return handle_invalid_args if count == 0
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|