toggly 0.1.0 → 0.2.1
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/CHANGELOG.md +13 -0
- data/README.md +2 -0
- data/lib/toggly/client.rb +1 -0
- data/lib/toggly/config.rb +2 -0
- data/lib/toggly/context.rb +8 -10
- data/lib/toggly/definitions_provider.rb +6 -1
- data/lib/toggly/entity_context.rb +95 -0
- data/lib/toggly/evaluation_engine.rb +47 -4
- data/lib/toggly/evaluators/context_property.rb +100 -0
- data/lib/toggly/feature_definition.rb +14 -3
- data/lib/toggly/registry.rb +1 -0
- data/lib/toggly/version.rb +1 -1
- data/lib/toggly.rb +3 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 661719eeb6fb2dec747b0f3bfd1dfa08349702573d583f72919abb6c5b6ae8d0
|
|
4
|
+
data.tar.gz: f1fdf90d3fa48390637f5ea000c21a6002f6764623ed562b822569f3e0e10862
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e6045eb73a91c81ccb3d9e7f3b4096958453f825025b18be81707beb6d96a6ca8f636ab1ee2e6b2852ccca5c7461d93009906de1967bf3a04a44e08425ce70d
|
|
7
|
+
data.tar.gz: c19d57ca41c649c359c29a0c4d6b5c8377d8cdcb03c4fdd1ff8020559d5d9788929aa9ee88e8281226e5e3175c1d8bb5647184c8d3d3c307a21ad6306c7f7546
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.1] - 2026-09-03
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Raise required Ruby version to 3.2+ (matches Gemfile.lock Bundler and CI).
|
|
13
|
+
|
|
14
|
+
## [0.2.0] - 2026-08-21
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- ContextProperty entity filters (`context_kind` / `context_requirement_type`) with operators eq, neq, gt, gte, lt, lte, in, contains. Fail closed. User filters AND entity filters; percentage stays user-only.
|
|
19
|
+
- `Toggly::EntityContext`, `Toggly.register_context`, and optional startup PUT `sdk/{appKey}/contexts` (`disable_entity_context_registration` to opt out).
|
|
20
|
+
|
|
8
21
|
## [0.1.0] - 2024-XX-XX
|
|
9
22
|
|
|
10
23
|
### Added
|
data/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Core Ruby SDK for [Toggly](https://toggly.io) feature flag management.
|
|
|
4
4
|
|
|
5
5
|
**Zero dependencies** - pure Ruby implementation.
|
|
6
6
|
|
|
7
|
+
Entity `ContextProperty` filters evaluate `Toggly::EntityContext` (`kind`, `key`, `attributes`) and are ANDed with user filters. `Toggly.register_context` optionally PUTs schemas to `sdk/{appKey}/contexts`.
|
|
8
|
+
|
|
7
9
|
## Installation
|
|
8
10
|
|
|
9
11
|
```ruby
|
data/lib/toggly/client.rb
CHANGED
data/lib/toggly/config.rb
CHANGED
|
@@ -35,6 +35,7 @@ module Toggly
|
|
|
35
35
|
|
|
36
36
|
# @return [Boolean] Enable WebSocket live updates (default: true)
|
|
37
37
|
attr_accessor :enable_live_updates
|
|
38
|
+
attr_accessor :disable_entity_context_registration
|
|
38
39
|
|
|
39
40
|
# @return [String] Application version
|
|
40
41
|
attr_accessor :app_version
|
|
@@ -73,6 +74,7 @@ module Toggly
|
|
|
73
74
|
@enable_undefined_in_dev = options[:enable_undefined_in_dev] || false
|
|
74
75
|
@disable_background_refresh = options[:disable_background_refresh] || false
|
|
75
76
|
@enable_live_updates = options.fetch(:enable_live_updates, true)
|
|
77
|
+
@disable_entity_context_registration = options.fetch(:disable_entity_context_registration, false)
|
|
76
78
|
@app_version = options[:app_version]
|
|
77
79
|
@instance_name = options[:instance_name]
|
|
78
80
|
@defaults = options[:defaults] || {}
|
data/lib/toggly/context.rb
CHANGED
|
@@ -11,18 +11,14 @@ module Toggly
|
|
|
11
11
|
# )
|
|
12
12
|
class Context
|
|
13
13
|
# @return [String, nil] User identity for percentage rollouts and targeting
|
|
14
|
-
|
|
14
|
+
# identity, groups, traits, and optional entity for ContextProperty filters
|
|
15
|
+
attr_reader :identity, :groups, :traits, :entity
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
attr_reader :groups
|
|
18
|
-
|
|
19
|
-
# @return [Hash<String, Object>] Custom traits for contextual targeting
|
|
20
|
-
attr_reader :traits
|
|
21
|
-
|
|
22
|
-
def initialize(identity: nil, groups: [], traits: {})
|
|
17
|
+
def initialize(identity: nil, groups: [], traits: {}, entity: nil)
|
|
23
18
|
@identity = identity&.to_s
|
|
24
19
|
@groups = Array(groups).map(&:to_s)
|
|
25
20
|
@traits = normalize_traits(traits)
|
|
21
|
+
@entity = entity
|
|
26
22
|
end
|
|
27
23
|
|
|
28
24
|
# Create a context with just an identity
|
|
@@ -80,7 +76,8 @@ module Toggly
|
|
|
80
76
|
Context.new(
|
|
81
77
|
identity: @identity,
|
|
82
78
|
groups: @groups,
|
|
83
|
-
traits: @traits.merge(normalize_traits(new_traits))
|
|
79
|
+
traits: @traits.merge(normalize_traits(new_traits)),
|
|
80
|
+
entity: @entity
|
|
84
81
|
)
|
|
85
82
|
end
|
|
86
83
|
|
|
@@ -92,7 +89,8 @@ module Toggly
|
|
|
92
89
|
Context.new(
|
|
93
90
|
identity: @identity,
|
|
94
91
|
groups: @groups + new_groups.flatten.map(&:to_s),
|
|
95
|
-
traits: @traits
|
|
92
|
+
traits: @traits,
|
|
93
|
+
entity: @entity
|
|
96
94
|
)
|
|
97
95
|
end
|
|
98
96
|
|
|
@@ -217,7 +217,12 @@ module Toggly
|
|
|
217
217
|
base = @config.definitions_url || @config.base_url
|
|
218
218
|
ws_url = base.gsub(%r{^https://}, "wss://").gsub(%r{^http://}, "ws://")
|
|
219
219
|
ws_url = ws_url.chomp("/")
|
|
220
|
-
|
|
220
|
+
params = {
|
|
221
|
+
"sdk" => "ruby",
|
|
222
|
+
"sdkVersion" => Toggly::VERSION
|
|
223
|
+
}
|
|
224
|
+
params["rev"] = @etag if @etag && !@etag.empty?
|
|
225
|
+
"#{ws_url}/#{@config.app_key}/ws?#{URI.encode_www_form(params)}"
|
|
221
226
|
end
|
|
222
227
|
|
|
223
228
|
def connect_websocket
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
# Entity context registry, mappers, and startup catalog PUT.
|
|
8
|
+
module Toggly
|
|
9
|
+
EntityContext = Struct.new(:kind, :key, :attributes, keyword_init: true) do
|
|
10
|
+
def attribute(name)
|
|
11
|
+
return nil unless attributes.is_a?(Hash)
|
|
12
|
+
|
|
13
|
+
return attributes[name] if attributes.key?(name)
|
|
14
|
+
return attributes[name.to_s] if attributes.key?(name.to_s)
|
|
15
|
+
|
|
16
|
+
attributes.each { |k, v| return v if k.to_s.casecmp?(name.to_s) }
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def attribute?(name)
|
|
21
|
+
return false unless attributes.is_a?(Hash)
|
|
22
|
+
|
|
23
|
+
attributes.key?(name) || attributes.key?(name.to_s) ||
|
|
24
|
+
attributes.keys.any? { |k| k.to_s.casecmp?(name.to_s) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
EntityContextPropertySchema = Struct.new(:name, :type, keyword_init: true)
|
|
29
|
+
EntityContextSchemaRegistration = Struct.new(:kind, :key_property, :display_name, :properties, keyword_init: true)
|
|
30
|
+
|
|
31
|
+
@entity_mappers = {}
|
|
32
|
+
@entity_schemas = {}
|
|
33
|
+
@entity_mutex = Mutex.new
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
def register_context(kind, schema: nil, &mapper)
|
|
37
|
+
@entity_mutex.synchronize do
|
|
38
|
+
@entity_mappers[kind] = mapper if mapper
|
|
39
|
+
if schema
|
|
40
|
+
schema.kind = kind
|
|
41
|
+
schema.display_name ||= kind
|
|
42
|
+
@entity_schemas[kind] = schema
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def map_entity(kind, entity)
|
|
48
|
+
mapper = @entity_mutex.synchronize { @entity_mappers[kind] }
|
|
49
|
+
return nil unless mapper
|
|
50
|
+
|
|
51
|
+
mapper.call(entity)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def entity_schemas
|
|
55
|
+
@entity_mutex.synchronize { @entity_schemas.values.dup }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def clear_entity_context_registrations
|
|
59
|
+
@entity_mutex.synchronize do
|
|
60
|
+
@entity_mappers.clear
|
|
61
|
+
@entity_schemas.clear
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def register_entity_contexts_at_startup(config)
|
|
66
|
+
return if config.respond_to?(:disable_entity_context_registration) && config.disable_entity_context_registration
|
|
67
|
+
return if config.app_key.nil? || config.app_key.empty?
|
|
68
|
+
|
|
69
|
+
regs = entity_schemas
|
|
70
|
+
return if regs.empty?
|
|
71
|
+
|
|
72
|
+
payload = {
|
|
73
|
+
contexts: regs.map do |r|
|
|
74
|
+
{
|
|
75
|
+
kind: r.kind,
|
|
76
|
+
keyProperty: r.key_property,
|
|
77
|
+
displayName: r.display_name || r.kind,
|
|
78
|
+
properties: Array(r.properties).map { |p| { name: p.name, type: p.type } }
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
}
|
|
82
|
+
uri = URI.join(config.base_url.end_with?("/") ? config.base_url : "#{config.base_url}/", "sdk/#{config.app_key}/contexts")
|
|
83
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
84
|
+
http.use_ssl = uri.scheme == "https"
|
|
85
|
+
http.open_timeout = config.http_timeout || 10
|
|
86
|
+
http.read_timeout = config.http_timeout || 10
|
|
87
|
+
req = Net::HTTP::Put.new(uri)
|
|
88
|
+
req["Content-Type"] = "application/json"
|
|
89
|
+
req.body = JSON.generate(payload)
|
|
90
|
+
http.request(req)
|
|
91
|
+
rescue StandardError
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -69,8 +69,53 @@ module Toggly
|
|
|
69
69
|
private
|
|
70
70
|
|
|
71
71
|
def evaluate_rules(definition, context, result = nil)
|
|
72
|
-
definition.rules.
|
|
73
|
-
|
|
72
|
+
entity_rules, user_rules = definition.rules.partition { |rule| Evaluators::ContextProperty.context_property?(rule) }
|
|
73
|
+
|
|
74
|
+
if entity_rules.any?
|
|
75
|
+
entity_ok = evaluate_entity_group(definition, entity_rules, context)
|
|
76
|
+
unless entity_ok
|
|
77
|
+
result&.reason = "entity_filters_failed"
|
|
78
|
+
return false
|
|
79
|
+
end
|
|
80
|
+
return true if user_rules.empty?
|
|
81
|
+
|
|
82
|
+
return evaluate_filter_group(user_rules, definition.requirement_type, context, definition.feature_key, result)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
sequential_rules(user_rules.empty? ? definition.rules : user_rules, definition, context, result)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def evaluate_entity_group(definition, rules, context)
|
|
89
|
+
entity = context&.entity
|
|
90
|
+
return false unless entity
|
|
91
|
+
|
|
92
|
+
results = rules.map { |rule| Evaluators::ContextProperty.evaluate_single(rule, entity) }
|
|
93
|
+
req = (definition.context_requirement_type || definition.requirement_type || "Any").to_s
|
|
94
|
+
req.casecmp?("All") ? results.all? : results.any?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def evaluate_filter_group(rules, requirement_type, context, feature_key, result)
|
|
98
|
+
req = (requirement_type || "Any").to_s
|
|
99
|
+
evaluations = rules.map do |rule|
|
|
100
|
+
rule_type = rule["type"] || rule[:type] || rule["name"] || rule[:name] || "always_on"
|
|
101
|
+
evaluator = @registry.get(rule_type)
|
|
102
|
+
next false unless evaluator
|
|
103
|
+
|
|
104
|
+
begin
|
|
105
|
+
value = evaluator.evaluate(rule, context, feature_key: feature_key)
|
|
106
|
+
value == true
|
|
107
|
+
rescue StandardError
|
|
108
|
+
false
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
passed = req.casecmp?("All") ? evaluations.all? : evaluations.any?
|
|
112
|
+
result&.reason = passed ? "rule_matched" : "no_match" if result
|
|
113
|
+
passed
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def sequential_rules(rules, definition, context, result)
|
|
117
|
+
rules.each_with_index do |rule, index|
|
|
118
|
+
rule_type = rule["type"] || rule[:type] || rule["name"] || rule[:name] || "always_on"
|
|
74
119
|
evaluator = @registry.get(rule_type)
|
|
75
120
|
|
|
76
121
|
unless evaluator
|
|
@@ -81,7 +126,6 @@ module Toggly
|
|
|
81
126
|
begin
|
|
82
127
|
evaluation = evaluator.evaluate(rule, context, feature_key: definition.feature_key)
|
|
83
128
|
|
|
84
|
-
# nil means continue to next rule
|
|
85
129
|
next if evaluation.nil?
|
|
86
130
|
|
|
87
131
|
if result
|
|
@@ -97,7 +141,6 @@ module Toggly
|
|
|
97
141
|
end
|
|
98
142
|
end
|
|
99
143
|
|
|
100
|
-
# No rules matched, default to enabled (since the feature itself is enabled)
|
|
101
144
|
result&.reason = "default_enabled"
|
|
102
145
|
true
|
|
103
146
|
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Toggly
|
|
4
|
+
module Evaluators
|
|
5
|
+
# Evaluator for ContextProperty entity filters. Fail closed.
|
|
6
|
+
class ContextProperty < Base
|
|
7
|
+
def self.type
|
|
8
|
+
"contextproperty"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def evaluate(rule, context, feature_key: nil)
|
|
12
|
+
entity = context&.entity
|
|
13
|
+
return false unless entity
|
|
14
|
+
|
|
15
|
+
self.class.evaluate_single(rule, entity)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.context_property?(rule)
|
|
19
|
+
type = rule["type"] || rule[:type] || rule["name"] || rule[:name]
|
|
20
|
+
type.to_s.downcase == "contextproperty"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.evaluate_single(rule, entity)
|
|
24
|
+
property = rule_lookup(rule, "Property")
|
|
25
|
+
operator = rule_lookup(rule, "Operator")
|
|
26
|
+
expected = rule_lookup(rule, "Value")
|
|
27
|
+
value_type = (rule_lookup(rule, "ValueType") || "string").to_s.downcase
|
|
28
|
+
return false if property.to_s.strip.empty? || operator.to_s.strip.empty? || expected.nil?
|
|
29
|
+
|
|
30
|
+
operator = operator.to_s.downcase
|
|
31
|
+
return false unless entity.attribute?(property)
|
|
32
|
+
|
|
33
|
+
actual = entity.attribute(property)
|
|
34
|
+
compare(actual, operator, expected.to_s, value_type)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.rule_lookup(rule, key)
|
|
38
|
+
params = rule["parameters"] || rule[:parameters] || rule
|
|
39
|
+
params[key] || params[key.to_s] || params[key.to_sym] ||
|
|
40
|
+
params.find { |k, _| k.to_s.casecmp?(key.to_s) }&.last
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.compare(actual, operator, expected, value_type)
|
|
44
|
+
case operator
|
|
45
|
+
when "eq"
|
|
46
|
+
actual.to_s.casecmp?(expected)
|
|
47
|
+
when "neq"
|
|
48
|
+
!actual.to_s.casecmp?(expected)
|
|
49
|
+
when "gt", "gte", "lt", "lte"
|
|
50
|
+
compare_ordered(actual, expected, value_type, operator)
|
|
51
|
+
when "in"
|
|
52
|
+
expected.split(",").map(&:strip).reject(&:empty?).any? { |c| c.casecmp?(actual.to_s) }
|
|
53
|
+
when "contains"
|
|
54
|
+
if value_type == "string[]"
|
|
55
|
+
Array(actual).any? { |v| v.to_s.casecmp?(expected) }
|
|
56
|
+
else
|
|
57
|
+
actual.to_s.downcase.include?(expected.downcase)
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.compare_ordered(actual, expected, value_type, operator)
|
|
65
|
+
if value_type == "datetime"
|
|
66
|
+
a = Time.parse(actual.to_s)
|
|
67
|
+
e = Time.parse(expected.to_s)
|
|
68
|
+
cmp = a <=> e
|
|
69
|
+
elsif value_type == "number"
|
|
70
|
+
a = parse_number(actual)
|
|
71
|
+
e = parse_number(expected)
|
|
72
|
+
return false if a.nil? || e.nil?
|
|
73
|
+
|
|
74
|
+
cmp = a <=> e
|
|
75
|
+
else
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
case operator
|
|
79
|
+
when "gt" then cmp.positive?
|
|
80
|
+
when "gte" then cmp >= 0
|
|
81
|
+
when "lt" then cmp.negative?
|
|
82
|
+
when "lte" then cmp <= 0
|
|
83
|
+
else false
|
|
84
|
+
end
|
|
85
|
+
rescue ArgumentError, TypeError
|
|
86
|
+
false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Strict numeric parse. Invalid strings must not coerce to 0.0.
|
|
90
|
+
def self.parse_number(value)
|
|
91
|
+
n = Float(value)
|
|
92
|
+
return nil unless n.finite?
|
|
93
|
+
|
|
94
|
+
n
|
|
95
|
+
rescue ArgumentError, TypeError
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -30,7 +30,7 @@ module Toggly
|
|
|
30
30
|
attr_reader :created_at
|
|
31
31
|
|
|
32
32
|
# @return [Time, nil] When the feature was last updated
|
|
33
|
-
attr_reader :updated_at
|
|
33
|
+
attr_reader :updated_at, :requirement_type, :context_kind, :context_requirement_type
|
|
34
34
|
|
|
35
35
|
# @return [String, nil] Feature description
|
|
36
36
|
attr_reader :description
|
|
@@ -38,6 +38,7 @@ module Toggly
|
|
|
38
38
|
# Feature types
|
|
39
39
|
TYPES = %w[Release Experiment Ops Permission].freeze
|
|
40
40
|
|
|
41
|
+
# rubocop:disable Metrics/ParameterLists
|
|
41
42
|
def initialize(
|
|
42
43
|
feature_key:,
|
|
43
44
|
feature_type: "Release",
|
|
@@ -46,7 +47,10 @@ module Toggly
|
|
|
46
47
|
metadata: {},
|
|
47
48
|
description: nil,
|
|
48
49
|
created_at: nil,
|
|
49
|
-
updated_at: nil
|
|
50
|
+
updated_at: nil,
|
|
51
|
+
requirement_type: "Any",
|
|
52
|
+
context_kind: nil,
|
|
53
|
+
context_requirement_type: nil
|
|
50
54
|
)
|
|
51
55
|
@feature_key = feature_key.to_s
|
|
52
56
|
@feature_type = validate_type(feature_type)
|
|
@@ -56,7 +60,11 @@ module Toggly
|
|
|
56
60
|
@description = description
|
|
57
61
|
@created_at = parse_time(created_at)
|
|
58
62
|
@updated_at = parse_time(updated_at)
|
|
63
|
+
@requirement_type = requirement_type || "Any"
|
|
64
|
+
@context_kind = context_kind
|
|
65
|
+
@context_requirement_type = context_requirement_type
|
|
59
66
|
end
|
|
67
|
+
# rubocop:enable Metrics/ParameterLists
|
|
60
68
|
|
|
61
69
|
# Create from a hash (e.g., from JSON)
|
|
62
70
|
#
|
|
@@ -84,7 +92,10 @@ module Toggly
|
|
|
84
92
|
metadata: hash[:metadata] || {},
|
|
85
93
|
description: hash[:description],
|
|
86
94
|
created_at: hash[:createdAt] || hash[:created_at],
|
|
87
|
-
updated_at: hash[:updatedAt] || hash[:updated_at]
|
|
95
|
+
updated_at: hash[:updatedAt] || hash[:updated_at],
|
|
96
|
+
requirement_type: hash[:requirementType] || hash[:requirement_type] || "Any",
|
|
97
|
+
context_kind: hash[:contextKind] || hash[:context_kind],
|
|
98
|
+
context_requirement_type: hash[:contextRequirementType] || hash[:context_requirement_type]
|
|
88
99
|
)
|
|
89
100
|
end
|
|
90
101
|
|
data/lib/toggly/registry.rb
CHANGED
data/lib/toggly/version.rb
CHANGED
data/lib/toggly.rb
CHANGED
|
@@ -12,6 +12,8 @@ require_relative "toggly/evaluators/percentage"
|
|
|
12
12
|
require_relative "toggly/evaluators/targeting"
|
|
13
13
|
require_relative "toggly/evaluators/time_window"
|
|
14
14
|
require_relative "toggly/evaluators/contextual_targeting"
|
|
15
|
+
require_relative "toggly/evaluators/context_property"
|
|
16
|
+
require_relative "toggly/entity_context"
|
|
15
17
|
require_relative "toggly/registry"
|
|
16
18
|
require_relative "toggly/evaluation_engine"
|
|
17
19
|
require_relative "toggly/snapshot_providers/base"
|
|
@@ -89,6 +91,7 @@ module Toggly
|
|
|
89
91
|
def reset!
|
|
90
92
|
@client&.close
|
|
91
93
|
@client = nil
|
|
94
|
+
clear_entity_context_registrations if respond_to?(:clear_entity_context_registrations)
|
|
92
95
|
end
|
|
93
96
|
end
|
|
94
97
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toggly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ops.ai
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: High-performance Ruby SDK for Toggly feature flag management. Works with
|
|
14
14
|
or without Toggly.io.
|
|
@@ -26,11 +26,13 @@ files:
|
|
|
26
26
|
- lib/toggly/config.rb
|
|
27
27
|
- lib/toggly/context.rb
|
|
28
28
|
- lib/toggly/definitions_provider.rb
|
|
29
|
+
- lib/toggly/entity_context.rb
|
|
29
30
|
- lib/toggly/errors.rb
|
|
30
31
|
- lib/toggly/evaluation_engine.rb
|
|
31
32
|
- lib/toggly/evaluators/always_off.rb
|
|
32
33
|
- lib/toggly/evaluators/always_on.rb
|
|
33
34
|
- lib/toggly/evaluators/base.rb
|
|
35
|
+
- lib/toggly/evaluators/context_property.rb
|
|
34
36
|
- lib/toggly/evaluators/contextual_targeting.rb
|
|
35
37
|
- lib/toggly/evaluators/percentage.rb
|
|
36
38
|
- lib/toggly/evaluators/targeting.rb
|
|
@@ -59,7 +61,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
59
61
|
requirements:
|
|
60
62
|
- - ">="
|
|
61
63
|
- !ruby/object:Gem::Version
|
|
62
|
-
version: 3.
|
|
64
|
+
version: 3.2.0
|
|
63
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
66
|
requirements:
|
|
65
67
|
- - ">="
|