shiny_json_logic 0.2.15 → 0.2.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff0a154c1e7efab1b183eec02c91b413311ede5763a920563ff1e72eaac3aa2d
4
- data.tar.gz: 51ec7875a3f873d33463b416d415b94b5cf495715ff451735d68e1f9e50a918d
3
+ metadata.gz: 1a4ebb694ae1ef43b04f2ef7a750bc102f202cc2e9efa6e0f43361bc66b82d4f
4
+ data.tar.gz: e57b52493d8d60506c2ceb5c30bace49790bbd6fa5eb2e800f31fb4aae94bbd6
5
5
  SHA512:
6
- metadata.gz: 835be2a2c516c43edfe0c89736c68ca4792f12912e87d11b1325009e0ac1447ee5d94f4efcbe2adb3d1e53f01f22643b019e01b6874e8d743946faeb0c0911ce
7
- data.tar.gz: a3e86add800f4dbde67e1c566e094209a3a9c9c32debaa1ed6101e62ce0188d01a356a3108752f708ea00ff6f5c49da911f964fda9b87400e73314e628874300
6
+ metadata.gz: 2b336cd498e0286fe1a2f953fc43a8319b83b3792f4471ff8f78eed0dd7cb9174ade63f7fba91b7accca6eccedffb785fb413eae82de9d7f67611d5a5ea64959
7
+ data.tar.gz: 3f76cf1ac0abea0a1e04a2b6ed2c1b468a028018a471afb20e88b4031555bdadaf0f917f179a84a0c258f4404bf9d61e7ad36a9e07c660a5ebd0177f7236e324
data/CHANGELOG.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
- ## [0.2.15] - 2026-02-15
4
+ ## [0.2.16] - 2026-02-21
5
+ ### Changed
6
+ - Removed simple delegator to check indifferent access in favour of key transformation . This improves performance and reduces memory usage.
7
+ - Removes instantiation of engine in favor of static evaluation to improve performance & memory usage.
8
+
9
+ ## [0.2.15] - 2026-02-20
5
10
  ### Changed
6
11
  - Refactors operation solvers to improve performance.
7
12
  - Includes frozen string literals to optimize string handling.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiny_json_logic (0.2.15)
4
+ shiny_json_logic (0.2.16)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -3,35 +3,24 @@
3
3
  require "shiny_json_logic/operator_solver"
4
4
 
5
5
  module ShinyJsonLogic
6
- class Engine
7
- def initialize(rule, scope_stack)
8
- @rule = rule
9
- @scope_stack = scope_stack
10
- end
6
+ module Engine
7
+ OPERATIONS = OperatorSolver::SOLVERS
11
8
 
12
- def call(rule = self.rule)
9
+ def self.call(rule, scope_stack)
13
10
  if rule.is_a?(Hash)
14
11
  return rule if rule.empty?
15
12
 
16
13
  operation, args = rule.to_a.first
17
14
  operation_key = operation.to_s
18
15
 
19
- return rule unless operations.key?(operation_key)
16
+ return rule unless OPERATIONS.key?(operation_key)
20
17
 
21
- operations.fetch(operation_key).new(args, scope_stack).call
18
+ OPERATIONS.fetch(operation_key).new(args, scope_stack).call
22
19
  elsif rule.is_a?(Array)
23
- rule.map { |val| call(val) }
20
+ rule.map { |val| call(val, scope_stack) }
24
21
  else
25
22
  rule
26
23
  end
27
24
  end
28
-
29
- private
30
-
31
- attr_reader :rule, :scope_stack
32
-
33
- def operations
34
- OperatorSolver::SOLVERS
35
- end
36
25
  end
37
- end
26
+ end
@@ -29,7 +29,7 @@ module ShinyJsonLogic
29
29
  end
30
30
 
31
31
  def evaluate(rule)
32
- Engine.new(rule, scope_stack).call
32
+ Engine.call(rule, scope_stack)
33
33
  end
34
34
 
35
35
  def dynamic_args?
@@ -12,7 +12,7 @@ module ShinyJsonLogic
12
12
  private
13
13
 
14
14
  def on_each(item)
15
- Truthy.call(Engine.new(filter, scope_stack).call) ? item : nil
15
+ Truthy.call(Engine.call(filter, scope_stack)) ? item : nil
16
16
  end
17
17
 
18
18
  def on_after(results)
@@ -33,7 +33,7 @@ module ShinyJsonLogic
33
33
  private
34
34
 
35
35
  def evaluate(rule)
36
- Engine.new(rule, scope_stack).call
36
+ Engine.call(rule, scope_stack)
37
37
  end
38
38
  end
39
39
  end
@@ -46,7 +46,7 @@ module ShinyJsonLogic
46
46
  private
47
47
 
48
48
  def on_each(_item)
49
- Engine.new(filter, scope_stack).call
49
+ Engine.call(filter, scope_stack)
50
50
  end
51
51
 
52
52
  def on_before_each(item, index = 0)
@@ -16,7 +16,7 @@ module ShinyJsonLogic
16
16
  private
17
17
 
18
18
  def on_each(item)
19
- Engine.new(item, scope_stack).call
19
+ Engine.call(item, scope_stack)
20
20
  end
21
21
 
22
22
  def on_after(results)
@@ -15,7 +15,7 @@ module ShinyJsonLogic
15
15
  initial_accumulator_rule = rules.is_a?(Array) ? rules[2] : nil
16
16
  super
17
17
  # Evaluate the initial accumulator value (third argument)
18
- @accumulator = Engine.new(initial_accumulator_rule, scope_stack).call
18
+ @accumulator = Engine.call(initial_accumulator_rule, scope_stack)
19
19
  end
20
20
 
21
21
  private
@@ -33,7 +33,7 @@ module ShinyJsonLogic
33
33
  end
34
34
 
35
35
  def on_each(_item)
36
- self.accumulator = Engine.new(filter, scope_stack).call
36
+ self.accumulator = Engine.call(filter, scope_stack)
37
37
  end
38
38
 
39
39
  def on_after(_results)
@@ -15,8 +15,7 @@ module ShinyJsonLogic
15
15
  end
16
16
 
17
17
  begin
18
- engine = Engine.new(item, scope_stack)
19
- result = engine.call
18
+ result = Engine.call(item, scope_stack)
20
19
 
21
20
  # Pop error contexts if we pushed them
22
21
  if last_error
@@ -46,8 +46,9 @@ module ShinyJsonLogic
46
46
  keys.reduce(data) do |obj, key|
47
47
  return nil if obj.nil?
48
48
 
49
- result = if obj.is_a?(Hash)
50
- obj[key]
49
+ if obj.is_a?(Hash)
50
+ # Normalize key to string for lookup (data is already string-keyed)
51
+ obj[key.to_s]
51
52
  elsif obj.is_a?(Array)
52
53
  # Convert string keys to integers for arrays
53
54
  index = key.is_a?(String) ? key.to_i : key
@@ -55,9 +56,6 @@ module ShinyJsonLogic
55
56
  else
56
57
  nil
57
58
  end
58
-
59
- # Wrap nested hashes for indifferent access
60
- result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
61
59
  end
62
60
  end
63
61
  end
@@ -31,14 +31,8 @@ module ShinyJsonLogic
31
31
  return nil if current.nil?
32
32
 
33
33
  if current.is_a?(Hash)
34
- # Check if key exists (string or symbol) and return value
35
- if current.key?(k)
36
- current[k]
37
- elsif current.key?(k.to_sym)
38
- current[k.to_sym]
39
- else
40
- nil
41
- end
34
+ # Data is already normalized to string keys
35
+ current[k]
42
36
  elsif current.is_a?(Array)
43
37
  current[k.to_i]
44
38
  else
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "shiny_json_logic/utils/indifferent_hash"
4
-
5
3
  module ShinyJsonLogic
6
4
  # Manages a stack of scopes for nested data access in iterators.
7
5
  #
@@ -15,17 +13,20 @@ module ShinyJsonLogic
15
13
  # {"val": [[1], "key"]} -> go up 1 level, access "key"
16
14
  # {"val": [[2], "key"]} -> go up 2 levels, access "key"
17
15
  #
16
+ # Note: Data is normalized to string keys upfront in ShinyJsonLogic.apply,
17
+ # so no indifferent access is needed here.
18
+ #
18
19
  class ScopeStack
19
20
  attr_reader :stack
20
21
 
21
22
  def initialize(root_data)
22
- @root_data = wrap_indifferent(root_data)
23
+ @root_data = root_data
23
24
  @stack = [{ data: @root_data, index: 0 }]
24
25
  end
25
26
 
26
27
  # Push a new scope onto the stack (when entering an iteration)
27
28
  def push(data, index: 0)
28
- stack.push({ data: wrap_indifferent(data), index: index })
29
+ stack.push({ data: data, index: index })
29
30
  end
30
31
 
31
32
  # Pop the top scope (when exiting an iteration)
@@ -67,8 +68,9 @@ module ShinyJsonLogic
67
68
  keys.reduce(data) do |obj, key|
68
69
  return nil if obj.nil?
69
70
 
70
- result = if obj.is_a?(Hash)
71
- obj[key]
71
+ if obj.is_a?(Hash)
72
+ # Normalize key to string for lookup
73
+ obj[key.to_s]
72
74
  elsif obj.is_a?(Array)
73
75
  # Convert string keys to integers for arrays
74
76
  index = key.is_a?(String) ? key.to_i : key
@@ -76,19 +78,6 @@ module ShinyJsonLogic
76
78
  else
77
79
  nil
78
80
  end
79
-
80
- # Wrap nested hashes for indifferent access
81
- result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
82
- end
83
- end
84
-
85
- def wrap_indifferent(obj)
86
- if obj.is_a?(IndifferentHash)
87
- obj
88
- elsif obj.is_a?(Hash)
89
- IndifferentHash.new(obj)
90
- else
91
- obj
92
81
  end
93
82
  end
94
83
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShinyJsonLogic
4
- VERSION = "0.2.15"
4
+ VERSION = "0.2.16"
5
5
  end
@@ -13,9 +13,23 @@ module ShinyJsonLogic
13
13
  def self.apply(rule, data = {})
14
14
  validate_operators!(rule)
15
15
 
16
- scope_stack = ScopeStack.new(data || {})
17
- engine = Engine.new(rule, scope_stack)
18
- engine.call
16
+ normalized_data = deep_stringify_keys(data || {})
17
+ scope_stack = ScopeStack.new(normalized_data)
18
+ Engine.call(rule, scope_stack)
19
+ end
20
+
21
+ # Recursively converts all hash keys to strings
22
+ def self.deep_stringify_keys(obj)
23
+ case obj
24
+ when Hash
25
+ obj.each_with_object({}) do |(key, value), result|
26
+ result[key.to_s] = deep_stringify_keys(value)
27
+ end
28
+ when Array
29
+ obj.map { |item| deep_stringify_keys(item) }
30
+ else
31
+ obj
32
+ end
19
33
  end
20
34
 
21
35
  # Validates that all operations in the rule tree use known operators
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shiny_json_logic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.15
4
+ version: 0.2.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luis Moyano
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-20 00:00:00.000000000 Z
11
+ date: 2026-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -177,7 +177,6 @@ files:
177
177
  - lib/shiny_json_logic/scope_stack.rb
178
178
  - lib/shiny_json_logic/truthy.rb
179
179
  - lib/shiny_json_logic/utils/array.rb
180
- - lib/shiny_json_logic/utils/indifferent_hash.rb
181
180
  - lib/shiny_json_logic/version.rb
182
181
  - results/ruby.json
183
182
  - shiny_json_logic.gemspec
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "delegate"
4
-
5
- module ShinyJsonLogic
6
- class IndifferentHash < SimpleDelegator
7
- # Make is_a?(Hash) return true so existing code works
8
- def is_a?(klass)
9
- klass == Hash || super
10
- end
11
- alias kind_of? is_a?
12
-
13
- def [](key)
14
- obj = __getobj__
15
- return obj[key] if obj.key?(key)
16
-
17
- alt_key = alternate_key(key)
18
- return obj[alt_key] if alt_key && obj.key?(alt_key)
19
-
20
- nil
21
- end
22
-
23
- def fetch(key, *args, &block)
24
- obj = __getobj__
25
- return obj.fetch(key, *args, &block) if obj.key?(key)
26
-
27
- alt_key = alternate_key(key)
28
- return obj.fetch(alt_key, *args, &block) if alt_key && obj.key?(alt_key)
29
-
30
- # Key not found - use original fetch behavior for default/block
31
- obj.fetch(key, *args, &block)
32
- end
33
-
34
- def key?(key)
35
- obj = __getobj__
36
- return true if obj.key?(key)
37
-
38
- alt_key = alternate_key(key)
39
- alt_key && obj.key?(alt_key)
40
- end
41
-
42
- alias has_key? key?
43
- alias include? key?
44
- alias member? key?
45
-
46
- def dig(key, *rest)
47
- value = self[key]
48
- return value if rest.empty? || value.nil?
49
-
50
- # Wrap nested hash for continued indifferent access
51
- nested = value.is_a?(Hash) && !value.is_a?(IndifferentHash) ? IndifferentHash.new(value) : value
52
- nested.dig(*rest)
53
- end
54
-
55
- def deep_fetch(key, default = nil)
56
- keys = key.empty? ? [key] : key.to_s.split('.')
57
- value = dig(*keys) rescue default
58
- value.nil? ? default : value
59
- end
60
-
61
- private
62
-
63
- def alternate_key(key)
64
- case key
65
- when String
66
- key.to_sym
67
- when Symbol
68
- key.to_s
69
- end
70
- end
71
- end
72
- end