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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.md +12 -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 +20 -24
- 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 +3 -1
- data/lib/shiny_json_logic/operations/and.rb +14 -1
- data/lib/shiny_json_logic/operations/base.rb +45 -4
- 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 +50 -4
- data/lib/shiny_json_logic/operations/division.rb +24 -11
- data/lib/shiny_json_logic/operations/double_not.rb +2 -1
- data/lib/shiny_json_logic/operations/equal.rb +46 -1
- data/lib/shiny_json_logic/operations/exists.rb +2 -2
- data/lib/shiny_json_logic/operations/filter.rb +5 -3
- data/lib/shiny_json_logic/operations/greater.rb +47 -1
- data/lib/shiny_json_logic/operations/greater_equal.rb +47 -1
- data/lib/shiny_json_logic/operations/if.rb +11 -4
- data/lib/shiny_json_logic/operations/inclusion.rb +3 -1
- data/lib/shiny_json_logic/operations/iterable/base.rb +52 -25
- data/lib/shiny_json_logic/operations/map.rb +11 -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 +3 -1
- data/lib/shiny_json_logic/operations/not.rb +2 -1
- data/lib/shiny_json_logic/operations/or.rb +14 -1
- data/lib/shiny_json_logic/operations/preserve.rb +10 -4
- data/lib/shiny_json_logic/operations/product.rb +29 -2
- data/lib/shiny_json_logic/operations/reduce.rb +14 -9
- data/lib/shiny_json_logic/operations/smaller.rb +47 -1
- data/lib/shiny_json_logic/operations/smaller_equal.rb +47 -1
- data/lib/shiny_json_logic/operations/some.rb +2 -0
- data/lib/shiny_json_logic/operations/strict_different.rb +24 -4
- data/lib/shiny_json_logic/operations/strict_equal.rb +17 -3
- data/lib/shiny_json_logic/operations/substring.rb +6 -3
- data/lib/shiny_json_logic/operations/subtraction.rb +21 -3
- data/lib/shiny_json_logic/operations/throw.rb +2 -10
- data/lib/shiny_json_logic/operations/try.rb +34 -11
- data/lib/shiny_json_logic/operations/val.rb +50 -3
- data/lib/shiny_json_logic/operations/var.rb +4 -3
- data/lib/shiny_json_logic/operator_solver.rb +3 -12
- data/lib/shiny_json_logic/scope_stack.rb +93 -0
- 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: e05288f3f43187927c102d03ce0cd653bf8762dd7e0adc40b29d27e0c4f7984f
|
|
4
|
+
data.tar.gz: 4c6e781dec321b4f928b9b1d786c708ffe39662cc22676cc5ab7d8228dff8aa8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0459cef93e4ce203e8ea1022c7bf7305aef736551e56bf3e8c47326ddcf95481232c788f5b3d5c96f25137df730b6cb5180ac3b7aadd696a1c4a0d9bea7e0aa3'
|
|
7
|
+
data.tar.gz: d3801f6f5cc0a0df664d829e352c934345305ce264a57bd39a7dd3f7e637963d402a0c7676e8c4f9fffa76fea7dffe6cca04d655bc38b79dfc781877faa616d5
|
data/.gitignore
CHANGED
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
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
|
@@ -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
|
-
|
|
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
|
|
19
|
+
def call(rule = self.rule)
|
|
16
20
|
if rule.is_a?(Hash)
|
|
17
21
|
return rule if rule.empty?
|
|
18
22
|
|
|
19
|
-
operation,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
|
47
|
-
context = {
|
|
48
|
-
|
|
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) &&
|
|
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
|
|
@@ -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
|
|
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
|
-
|
|
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, @
|
|
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, :
|
|
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, "
|
|
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,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,58 @@
|
|
|
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
|
+
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
|
-
|
|
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,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
|
-
|
|
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
|