familia 1.2.3 → 2.0.0.pre.pre
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/.github/workflows/ci.yml +68 -0
- data/.github/workflows/docs.yml +64 -0
- data/.gitignore +3 -0
- data/.pre-commit-config.yaml +3 -1
- data/.rubocop.yml +16 -9
- data/.rubocop_todo.yml +177 -31
- data/.yardopts +9 -0
- data/CLAUDE.md +141 -0
- data/Gemfile +15 -2
- data/Gemfile.lock +61 -61
- data/README.md +39 -23
- data/bin/irb +3 -0
- data/docs/connection_pooling.md +317 -0
- data/familia.gemspec +8 -5
- data/lib/familia/base.rb +19 -9
- data/lib/familia/connection.rb +232 -65
- data/lib/familia/core_ext.rb +1 -1
- data/lib/familia/datatype/commands.rb +59 -0
- data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
- data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
- data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
- data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
- data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
- data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
- data/lib/familia/datatype.rb +243 -0
- data/lib/familia/errors.rb +5 -2
- data/lib/familia/features/expiration.rb +33 -34
- data/lib/familia/features/quantization.rb +9 -3
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features.rb +2 -2
- data/lib/familia/horreum/class_methods.rb +97 -130
- data/lib/familia/horreum/commands.rb +46 -51
- data/lib/familia/horreum/connection.rb +82 -0
- data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
- data/lib/familia/horreum/serialization.rb +61 -198
- data/lib/familia/horreum/settings.rb +6 -17
- data/lib/familia/horreum/utils.rb +11 -10
- data/lib/familia/horreum.rb +69 -60
- data/lib/familia/logging.rb +12 -12
- data/lib/familia/multi_result.rb +72 -0
- data/lib/familia/refinements.rb +7 -44
- data/lib/familia/settings.rb +11 -11
- data/lib/familia/utils.rb +123 -90
- data/lib/familia/version.rb +4 -21
- data/lib/familia.rb +17 -12
- data/lib/middleware/database_middleware.rb +150 -0
- data/try/configuration/scenarios_try.rb +65 -0
- data/try/core/connection_try.rb +58 -0
- data/try/core/errors_try.rb +93 -0
- data/try/core/extensions_try.rb +26 -0
- data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
- data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
- data/try/core/middleware_try.rb +68 -0
- data/try/core/refinements_try.rb +39 -0
- data/try/core/settings_try.rb +76 -0
- data/try/core/tools_try.rb +54 -0
- data/try/core/utils_try.rb +189 -0
- data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
- data/try/datatypes/datatype_base_try.rb +69 -0
- data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
- data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
- data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
- data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
- data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
- data/try/edge_cases/empty_identifiers_try.rb +48 -0
- data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -8
- data/try/edge_cases/json_serialization_try.rb +85 -0
- data/try/edge_cases/race_conditions_try.rb +60 -0
- data/try/edge_cases/reserved_keywords_try.rb +59 -0
- data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +63 -60
- data/try/edge_cases/ttl_side_effects_try.rb +51 -0
- data/try/features/expiration_try.rb +86 -0
- data/try/features/quantization_try.rb +90 -0
- data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
- data/try/features/safe_dump_try.rb +137 -0
- data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
- data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
- data/try/horreum/class_methods_try.rb +41 -0
- data/try/horreum/commands_try.rb +49 -0
- data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
- data/try/horreum/relations_try.rb +146 -0
- data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
- data/try/horreum/settings_try.rb +43 -0
- data/try/integration/cross_component_try.rb +46 -0
- data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
- data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
- data/try/models/datatype_base_try.rb +101 -0
- data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
- data/try/performance/benchmarks_try.rb +55 -0
- data/try/pooling/README.md +20 -0
- data/try/pooling/configurable_stress_test_try.rb +435 -0
- data/try/pooling/connection_pool_test_try.rb +273 -0
- data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- data/try/pooling/lib/connection_pool_metrics.rb +372 -0
- data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
- data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
- data/try/pooling/lib/visualize_stress_results.rb +434 -0
- data/try/pooling/pool_siege_try.rb +509 -0
- data/try/pooling/run_stress_tests_try.rb +482 -0
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
- data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
- data/try/prototypes/atomic_saves_v4.rb +105 -0
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- metadata +124 -38
- data/.github/workflows/ruby.yml +0 -71
- data/VERSION.yml +0 -4
- data/lib/familia/redistype/commands.rb +0 -59
- data/lib/familia/redistype.rb +0 -228
- data/lib/familia/tools.rb +0 -68
- data/lib/redis_middleware.rb +0 -109
- data/try/20_redis_type_try.rb +0 -70
- data/try/91_json_bug_try.rb +0 -86
@@ -0,0 +1,93 @@
|
|
1
|
+
# try/core/errors_try.rb
|
2
|
+
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
5
|
+
|
6
|
+
Familia.debug = false
|
7
|
+
|
8
|
+
# Test Familia error classes and exception handling
|
9
|
+
|
10
|
+
## Familia::Problem is base error class
|
11
|
+
Familia::Problem.new.class.superclass
|
12
|
+
#=> RuntimeError
|
13
|
+
|
14
|
+
## NoIdentifier error can be raised
|
15
|
+
begin
|
16
|
+
raise Familia::NoIdentifier, "Missing identifier"
|
17
|
+
rescue Familia::NoIdentifier => e
|
18
|
+
e.class
|
19
|
+
end
|
20
|
+
#=> Familia::NoIdentifier
|
21
|
+
|
22
|
+
## NonUniqueKey error can be raised
|
23
|
+
begin
|
24
|
+
raise Familia::NonUniqueKey, "Duplicate key"
|
25
|
+
rescue Familia::NonUniqueKey => e
|
26
|
+
e.class
|
27
|
+
end
|
28
|
+
#=> Familia::NonUniqueKey
|
29
|
+
|
30
|
+
## HighRiskFactor error stores value
|
31
|
+
begin
|
32
|
+
raise Familia::HighRiskFactor.new("dangerous_value")
|
33
|
+
rescue Familia::HighRiskFactor => e
|
34
|
+
e.value
|
35
|
+
end
|
36
|
+
#=> "dangerous_value"
|
37
|
+
|
38
|
+
## HighRiskFactor error has custom message
|
39
|
+
begin
|
40
|
+
raise Familia::HighRiskFactor.new(123)
|
41
|
+
rescue Familia::HighRiskFactor => e
|
42
|
+
e.message.include?("High risk factor")
|
43
|
+
end
|
44
|
+
#=> true
|
45
|
+
|
46
|
+
## NotConnected error stores URI
|
47
|
+
test_uri = URI.parse('redis://localhost:6379')
|
48
|
+
begin
|
49
|
+
raise Familia::NotConnected.new(test_uri)
|
50
|
+
rescue Familia::NotConnected => e
|
51
|
+
e.uri.to_s
|
52
|
+
end
|
53
|
+
#=> "redis://localhost"
|
54
|
+
|
55
|
+
## NotConnected error has custom message
|
56
|
+
test_uri = URI.parse('redis://localhost:6379')
|
57
|
+
begin
|
58
|
+
raise Familia::NotConnected.new(test_uri)
|
59
|
+
rescue Familia::NotConnected => e
|
60
|
+
e.message.include?("No client for")
|
61
|
+
end
|
62
|
+
#> true
|
63
|
+
|
64
|
+
## KeyNotFoundError stores key
|
65
|
+
begin
|
66
|
+
raise Familia::KeyNotFoundError.new("missing:key")
|
67
|
+
rescue Familia::KeyNotFoundError => e
|
68
|
+
e.key
|
69
|
+
end
|
70
|
+
#=> "missing:key"
|
71
|
+
|
72
|
+
## KeyNotFoundError has custom message
|
73
|
+
begin
|
74
|
+
raise Familia::KeyNotFoundError.new("test:key")
|
75
|
+
rescue Familia::KeyNotFoundError => e
|
76
|
+
e.message.include?("Key not found in Redis")
|
77
|
+
end
|
78
|
+
#=> true
|
79
|
+
|
80
|
+
## KeyNotFoundError has custom message again
|
81
|
+
raise Familia::KeyNotFoundError.new("test:key")
|
82
|
+
#=!> error.message.include?("Key not found in Redis")
|
83
|
+
#=!> error.class == Familia::KeyNotFoundError
|
84
|
+
|
85
|
+
## All error classes inherit from Problem
|
86
|
+
[
|
87
|
+
Familia::NoIdentifier,
|
88
|
+
Familia::NonUniqueKey,
|
89
|
+
Familia::HighRiskFactor,
|
90
|
+
Familia::NotConnected,
|
91
|
+
Familia::KeyNotFoundError
|
92
|
+
].all? { |klass| klass.superclass == Familia::Problem }
|
93
|
+
##=> true
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
|
3
|
+
# Test core extensions
|
4
|
+
group "Core Extensions"
|
5
|
+
|
6
|
+
try "String time parsing with in_seconds" do
|
7
|
+
"60s".in_seconds == 60 &&
|
8
|
+
"5m".in_seconds == 300 &&
|
9
|
+
"2h".in_seconds == 7200 &&
|
10
|
+
"1d".in_seconds == 86400
|
11
|
+
end
|
12
|
+
|
13
|
+
try "Time::Units conversions" do
|
14
|
+
1.second == 1 &&
|
15
|
+
1.minute == 60 &&
|
16
|
+
1.hour == 3600 &&
|
17
|
+
1.day == 86400 &&
|
18
|
+
1.week == 604800
|
19
|
+
end
|
20
|
+
|
21
|
+
try "Numeric extensions to_ms and to_bytes" do
|
22
|
+
1000.to_ms == 1000 &&
|
23
|
+
1.to_bytes == 1 &&
|
24
|
+
1024.to_bytes == "1.0 KiB" &&
|
25
|
+
(1024 * 1024).to_bytes == "1.0 MiB"
|
26
|
+
end
|
@@ -1,28 +1,29 @@
|
|
1
|
+
# try/core/familia_extended_try.rb
|
1
2
|
|
2
3
|
require 'time'
|
3
4
|
|
4
|
-
require_relative '
|
5
|
-
require_relative '
|
5
|
+
require_relative '../../lib/familia'
|
6
|
+
require_relative '../helpers/test_helpers'
|
6
7
|
|
7
|
-
## Has all
|
8
|
-
registered_types = Familia::
|
8
|
+
## Has all datatype relativess
|
9
|
+
registered_types = Familia::DataType.registered_types.keys
|
9
10
|
registered_types.collect(&:to_s).sort
|
10
11
|
#=> ["counter", "hash", "hashkey", "list", "lock", "set", "sorted_set", "string", "zset"]
|
11
12
|
|
12
|
-
## Familia created class methods for
|
13
|
+
## Familia created class methods for datatype list class
|
13
14
|
Familia::Horreum::ClassMethods.public_method_defined? :list?
|
14
15
|
#=> true
|
15
16
|
|
16
|
-
## Familia created class methods for
|
17
|
+
## Familia created class methods for datatype list class
|
17
18
|
Familia::Horreum::ClassMethods.public_method_defined? :list
|
18
19
|
#=> true
|
19
20
|
|
20
|
-
## Familia created class methods for
|
21
|
+
## Familia created class methods for datatype list class
|
21
22
|
Familia::Horreum::ClassMethods.public_method_defined? :lists
|
22
23
|
#=> true
|
23
24
|
|
24
|
-
## A Familia object knows its
|
25
|
-
Bone.
|
25
|
+
## A Familia object knows its datatype relatives
|
26
|
+
Bone.related_fields.is_a?(Hash) && Bone.related_fields.has_key?(:owners)
|
26
27
|
#=> true
|
27
28
|
|
28
29
|
## A Familia object knows its lists
|
@@ -33,7 +34,7 @@ Bone.lists.size
|
|
33
34
|
Bone.list? :owners
|
34
35
|
#=> true
|
35
36
|
|
36
|
-
## A Familia object can get a specific
|
37
|
+
## A Familia object can get a specific datatype relatives def
|
37
38
|
definition = Bone.list :owners
|
38
39
|
definition.klass
|
39
40
|
#=> Familia::List
|
@@ -1,12 +1,14 @@
|
|
1
|
+
# try/core/familia_try.rb
|
1
2
|
|
2
|
-
|
3
|
-
require_relative '
|
3
|
+
|
4
|
+
require_relative '../../lib/familia'
|
5
|
+
require_relative '../helpers/test_helpers'
|
4
6
|
|
5
7
|
#Familia.apiversion = 'v1'
|
6
8
|
|
7
9
|
|
8
10
|
## Check for help class
|
9
|
-
Bone.
|
11
|
+
Bone.related_fields.keys # consistent b/c hashes are ordered
|
10
12
|
#=> [:owners, :tags, :metrics, :props, :value]
|
11
13
|
|
12
14
|
## Familia has a uri
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
|
3
|
+
# Test Redis middleware components
|
4
|
+
group "Redis Middleware"
|
5
|
+
|
6
|
+
try "RedisLogger logs commands with timing" do
|
7
|
+
# Mock Redis client with middleware
|
8
|
+
class MockRedis
|
9
|
+
attr_reader :logged_commands
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@logged_commands = []
|
13
|
+
extend RedisLogger
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(key)
|
17
|
+
log_command("GET", key) { "test_value" }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def log_command(cmd, *args)
|
23
|
+
start_time = Time.now
|
24
|
+
result = yield
|
25
|
+
duration = Time.now - start_time
|
26
|
+
@logged_commands << { command: cmd, args: args, duration: duration }
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
redis = MockRedis.new
|
32
|
+
result = redis.get("test_key")
|
33
|
+
|
34
|
+
result == "test_value" &&
|
35
|
+
redis.logged_commands.length == 1 &&
|
36
|
+
redis.logged_commands.first[:command] == "GET"
|
37
|
+
end
|
38
|
+
|
39
|
+
try "RedisCommandCounter tracks command metrics" do
|
40
|
+
# Mock counter implementation
|
41
|
+
counter = RedisCommandCounter.new
|
42
|
+
|
43
|
+
counter.increment("GET")
|
44
|
+
counter.increment("SET")
|
45
|
+
counter.increment("GET")
|
46
|
+
|
47
|
+
counter.count("GET") == 2 &&
|
48
|
+
counter.count("SET") == 1 &&
|
49
|
+
counter.total == 3
|
50
|
+
rescue NameError
|
51
|
+
# Skip if RedisCommandCounter not available
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
try "Command counting with count_commands utility" do
|
56
|
+
redis = Familia.redis
|
57
|
+
|
58
|
+
count = count_commands do
|
59
|
+
redis.set("test_key", "value")
|
60
|
+
redis.get("test_key")
|
61
|
+
redis.del("test_key")
|
62
|
+
end
|
63
|
+
|
64
|
+
count >= 3 # At least 3 commands executed
|
65
|
+
rescue NameError, NoMethodError
|
66
|
+
# Skip if count_commands not available
|
67
|
+
true
|
68
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
|
3
|
+
# Test Familia refinements
|
4
|
+
group "Familia Refinements"
|
5
|
+
|
6
|
+
using Familia::FlexibleHashAccess
|
7
|
+
|
8
|
+
try "FlexibleHashAccess allows string/symbol key interchange" do
|
9
|
+
hash = { name: "test", "email" => "test@example.com" }
|
10
|
+
|
11
|
+
hash[:name] == "test" &&
|
12
|
+
hash["name"] == "test" &&
|
13
|
+
hash[:email] == "test@example.com" &&
|
14
|
+
hash["email"] == "test@example.com"
|
15
|
+
end
|
16
|
+
|
17
|
+
try "LoggerTraceRefinement adds trace level when FAMILIA_TRACE enabled" do
|
18
|
+
old_env = ENV['FAMILIA_TRACE']
|
19
|
+
ENV['FAMILIA_TRACE'] = '1'
|
20
|
+
|
21
|
+
logger = Logger.new(STDOUT)
|
22
|
+
logger.respond_to?(:trace)
|
23
|
+
ensure
|
24
|
+
ENV['FAMILIA_TRACE'] = old_env
|
25
|
+
end
|
26
|
+
|
27
|
+
try "FAMILIA_TRACE environment control" do
|
28
|
+
old_env = ENV['FAMILIA_TRACE']
|
29
|
+
|
30
|
+
ENV['FAMILIA_TRACE'] = '1'
|
31
|
+
trace_enabled = ENV['FAMILIA_TRACE']
|
32
|
+
|
33
|
+
ENV['FAMILIA_TRACE'] = nil
|
34
|
+
trace_disabled = ENV['FAMILIA_TRACE']
|
35
|
+
|
36
|
+
trace_enabled == '1' && trace_disabled.nil?
|
37
|
+
ensure
|
38
|
+
ENV['FAMILIA_TRACE'] = old_env
|
39
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# try/core/settings_try.rb
|
2
|
+
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
5
|
+
|
6
|
+
Familia.debug = false
|
7
|
+
|
8
|
+
# Test Familia configuration and settings management
|
9
|
+
|
10
|
+
## Familia has default delim
|
11
|
+
Familia.delim
|
12
|
+
#=> ":"
|
13
|
+
|
14
|
+
## Can set custom delim
|
15
|
+
Familia.delim('|')
|
16
|
+
Familia.delim
|
17
|
+
#=> "|"
|
18
|
+
|
19
|
+
## Familia has default suffix
|
20
|
+
Familia.suffix
|
21
|
+
#=> :object
|
22
|
+
|
23
|
+
## Can set custom suffix
|
24
|
+
Familia.suffix(:data)
|
25
|
+
Familia.suffix
|
26
|
+
#=> :data
|
27
|
+
|
28
|
+
## Familia has default TTL
|
29
|
+
Familia.default_expiration
|
30
|
+
#=> 0
|
31
|
+
|
32
|
+
## Can set default expiration value
|
33
|
+
Familia.default_expiration(3600)
|
34
|
+
Familia.default_expiration
|
35
|
+
#=> 3600.0
|
36
|
+
|
37
|
+
## Familia has default database
|
38
|
+
Familia.logical_database
|
39
|
+
#=> nil
|
40
|
+
|
41
|
+
## Can set database number
|
42
|
+
Familia.logical_database(2)
|
43
|
+
Familia.logical_database
|
44
|
+
#=> 2
|
45
|
+
|
46
|
+
## Familia has default prefix
|
47
|
+
Familia.prefix
|
48
|
+
#=> nil
|
49
|
+
|
50
|
+
## Can set custom prefix
|
51
|
+
Familia.prefix('app')
|
52
|
+
Familia.prefix
|
53
|
+
#=> "app"
|
54
|
+
|
55
|
+
## default_suffix method returns suffix
|
56
|
+
Familia.default_suffix
|
57
|
+
#=> :data
|
58
|
+
|
59
|
+
## Setting values with nil preserves current value
|
60
|
+
current_default_expiration = Familia.default_expiration
|
61
|
+
Familia.default_expiration(nil)
|
62
|
+
Familia.default_expiration
|
63
|
+
#=> 3600.0
|
64
|
+
|
65
|
+
## Setting delim with nil preserves current value
|
66
|
+
current_delim = Familia.delim
|
67
|
+
Familia.delim(nil)
|
68
|
+
Familia.delim
|
69
|
+
#=> "|"
|
70
|
+
|
71
|
+
# Cleanup - restore defaults
|
72
|
+
Familia.delim(':')
|
73
|
+
Familia.suffix(:object)
|
74
|
+
Familia.default_expiration(0)
|
75
|
+
Familia.logical_database(nil)
|
76
|
+
Familia.prefix(nil)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
|
3
|
+
# Test Familia::Tools - key migration and utility functions
|
4
|
+
group "Familia::Tools"
|
5
|
+
|
6
|
+
try "move_keys across Redis instances" do
|
7
|
+
source_redis = Redis.new(db: 10)
|
8
|
+
dest_redis = Redis.new(db: 11)
|
9
|
+
|
10
|
+
# Setup test data
|
11
|
+
source_redis.set("test:key1", "value1")
|
12
|
+
source_redis.set("test:key2", "value2")
|
13
|
+
|
14
|
+
# Move keys
|
15
|
+
moved = Familia::Tools.move_keys(source_redis, dest_redis, "test:*")
|
16
|
+
|
17
|
+
moved == 2 &&
|
18
|
+
dest_redis.get("test:key1") == "value1" &&
|
19
|
+
!source_redis.exists?("test:key1")
|
20
|
+
ensure
|
21
|
+
source_redis&.flushdb
|
22
|
+
dest_redis&.flushdb
|
23
|
+
end
|
24
|
+
|
25
|
+
try "rename with transformation block" do
|
26
|
+
redis = Familia.redis
|
27
|
+
redis.set("old:key1", "value1")
|
28
|
+
redis.set("old:key2", "value2")
|
29
|
+
|
30
|
+
renamed = Familia::Tools.rename(redis, "old:*") { |key| key.gsub("old:", "new:") }
|
31
|
+
|
32
|
+
renamed == 2 &&
|
33
|
+
redis.get("new:key1") == "value1" &&
|
34
|
+
!redis.exists?("old:key1")
|
35
|
+
ensure
|
36
|
+
redis&.del("old:key1", "old:key2", "new:key1", "new:key2")
|
37
|
+
end
|
38
|
+
|
39
|
+
try "get_any retrieves values regardless of type" do
|
40
|
+
redis = Familia.redis
|
41
|
+
redis.set("string_key", "string_value")
|
42
|
+
redis.hset("hash_key", "field", "hash_value")
|
43
|
+
redis.lpush("list_key", "list_value")
|
44
|
+
|
45
|
+
string_val = Familia::Tools.get_any(redis, "string_key")
|
46
|
+
hash_val = Familia::Tools.get_any(redis, "hash_key")
|
47
|
+
list_val = Familia::Tools.get_any(redis, "list_key")
|
48
|
+
|
49
|
+
string_val == "string_value" &&
|
50
|
+
hash_val.is_a?(Hash) &&
|
51
|
+
list_val.is_a?(Array)
|
52
|
+
ensure
|
53
|
+
redis&.del("string_key", "hash_key", "list_key")
|
54
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# try/core/utils_try.rb
|
2
|
+
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
5
|
+
|
6
|
+
Familia.debug = false
|
7
|
+
|
8
|
+
# Test Familia utility methods and helpers
|
9
|
+
|
10
|
+
##
|
11
|
+
## ID Generation
|
12
|
+
##
|
13
|
+
|
14
|
+
## Familia.generate_id
|
15
|
+
Familia.respond_to?(:generate_id)
|
16
|
+
#=> true
|
17
|
+
|
18
|
+
## Can generate a default base-36 ID
|
19
|
+
id = Familia.generate_id
|
20
|
+
[id.class, id.length > 10, id.match?(/^[a-z0-9]+$/)]
|
21
|
+
#=> [String, true, true]
|
22
|
+
|
23
|
+
## Generated IDs are unique
|
24
|
+
[Familia.generate_id == Familia.generate_id]
|
25
|
+
#=> [false]
|
26
|
+
|
27
|
+
## Can generate an ID with a custom base (hex)
|
28
|
+
hex_id = Familia.generate_id(16)
|
29
|
+
[hex_id.class, hex_id.length == 64, hex_id.match?(/^[a-f0-9]+$/)]
|
30
|
+
#=> [String, true, true]
|
31
|
+
|
32
|
+
## Familia.generate_trace_id
|
33
|
+
Familia.respond_to?(:generate_trace_id)
|
34
|
+
#=> true
|
35
|
+
|
36
|
+
## Can generate a default base-36 trace ID
|
37
|
+
trace_id = Familia.generate_trace_id
|
38
|
+
[trace_id.class, trace_id.length > 5, trace_id.length < 20]
|
39
|
+
#=> [String, true, true]
|
40
|
+
|
41
|
+
## Can generate a trace ID with a custom base (hex)
|
42
|
+
hex_trace_id = Familia.generate_trace_id(16)
|
43
|
+
[hex_trace_id.class, hex_trace_id.length == 16]
|
44
|
+
#=> [String, true]
|
45
|
+
|
46
|
+
## Familia.generate_hex_id
|
47
|
+
Familia.respond_to?(:generate_hex_id)
|
48
|
+
#=> true
|
49
|
+
|
50
|
+
## Can generate a 256-bit hex ID
|
51
|
+
hex_id = Familia.generate_hex_id
|
52
|
+
[hex_id.class, hex_id.length == 64, hex_id.match?(/^[a-f0-9]+$/)]
|
53
|
+
#=> [String, true, true]
|
54
|
+
|
55
|
+
## Familia.generate_hex_trace_id
|
56
|
+
Familia.respond_to?(:generate_hex_trace_id)
|
57
|
+
#=> true
|
58
|
+
|
59
|
+
## Can generate a 64-bit hex trace ID
|
60
|
+
hex_trace_id = Familia.generate_hex_trace_id
|
61
|
+
[hex_trace_id.class, hex_trace_id.length == 16, hex_trace_id.match?(/^[a-f0-9]+$/)]
|
62
|
+
#=> [String, true, true]
|
63
|
+
|
64
|
+
|
65
|
+
##
|
66
|
+
## ID Shortening
|
67
|
+
##
|
68
|
+
|
69
|
+
## Familia.shorten_to_external_id
|
70
|
+
Familia.respond_to?(:shorten_to_external_id)
|
71
|
+
#=> true
|
72
|
+
|
73
|
+
## Can shorten hex ID to external ID (128 bits)
|
74
|
+
hex_id = Familia.generate_hex_id
|
75
|
+
external_id = Familia.shorten_to_external_id(hex_id)
|
76
|
+
[external_id.class, external_id.length < hex_id.length]
|
77
|
+
#=> [String, true]
|
78
|
+
|
79
|
+
## Can shorten hex ID to external ID with custom base (hex)
|
80
|
+
hex_id = Familia.generate_hex_id
|
81
|
+
hex_external_id = Familia.shorten_to_external_id(hex_id, base: 16)
|
82
|
+
[hex_external_id.class, hex_external_id.length == 32]
|
83
|
+
#=> [String, true]
|
84
|
+
|
85
|
+
## Familia.shorten_to_trace_id
|
86
|
+
Familia.respond_to?(:shorten_to_trace_id)
|
87
|
+
#=> true
|
88
|
+
|
89
|
+
## Can shorten hex ID to trace ID (64 bits)
|
90
|
+
hex_id = Familia.generate_hex_id
|
91
|
+
trace_id = Familia.shorten_to_trace_id(hex_id)
|
92
|
+
[trace_id.class, trace_id.length < hex_id.length]
|
93
|
+
#=> [String, true]
|
94
|
+
|
95
|
+
## Can shorten hex ID to trace ID with custom base (hex)
|
96
|
+
hex_id = Familia.generate_hex_id
|
97
|
+
hex_trace_id = Familia.shorten_to_trace_id(hex_id, base: 16)
|
98
|
+
[hex_trace_id.class, hex_trace_id.length == 16]
|
99
|
+
#=> [String, true]
|
100
|
+
|
101
|
+
## Shortened IDs are deterministic
|
102
|
+
hex_id = Familia.generate_hex_id
|
103
|
+
id1 = Familia.shorten_to_external_id(hex_id)
|
104
|
+
id2 = Familia.shorten_to_external_id(hex_id)
|
105
|
+
id1 == id2
|
106
|
+
#=> true
|
107
|
+
|
108
|
+
##
|
109
|
+
## Key helpers
|
110
|
+
##
|
111
|
+
Familia.delim(':') # Ensure default
|
112
|
+
|
113
|
+
## Can join values with delimiter
|
114
|
+
result = Familia.join('user', '123', 'sessions')
|
115
|
+
result
|
116
|
+
#=> "user:123:sessions"
|
117
|
+
|
118
|
+
## Can join with custom delimiter
|
119
|
+
original_delim = Familia.delim
|
120
|
+
Familia.delim('|')
|
121
|
+
result = Familia.join('a', 'b', 'c')
|
122
|
+
[result, Familia.delim]
|
123
|
+
#=> ["a|b|c", "|"]
|
124
|
+
|
125
|
+
## Can split values with custom delimiter
|
126
|
+
parts = Familia.split('a|b|c')
|
127
|
+
parts
|
128
|
+
#=> ["a", "b", "c"]
|
129
|
+
|
130
|
+
## Resets delimiter for remaining tests
|
131
|
+
Familia.delim(':')
|
132
|
+
#=> ":"
|
133
|
+
|
134
|
+
## Can split with default delimiter after reset
|
135
|
+
parts = Familia.split('user:123:data')
|
136
|
+
parts
|
137
|
+
#=> ["user", "123", "data"]
|
138
|
+
|
139
|
+
## Can create dbkey with default delimiter
|
140
|
+
key = Familia.dbkey('v1', 'customer', 'email')
|
141
|
+
key
|
142
|
+
#=> "v1:customer:email"
|
143
|
+
|
144
|
+
##
|
145
|
+
## Other Utilities
|
146
|
+
##
|
147
|
+
|
148
|
+
## qstamp returns integer timestamp by default
|
149
|
+
stamp = Familia.qstamp
|
150
|
+
stamp.class
|
151
|
+
#=> Integer
|
152
|
+
|
153
|
+
## qstamp with pattern returns formatted string
|
154
|
+
formatted = Familia.qstamp(3600, pattern: '%Y%m%d%H')
|
155
|
+
[formatted.class, formatted.length == 10]
|
156
|
+
#=> [String, true]
|
157
|
+
|
158
|
+
## qstamp works with custom time
|
159
|
+
test_time = Time.utc(2023, 6, 15, 14, 30, 0)
|
160
|
+
custom_stamp = Familia.qstamp(3600, time: test_time)
|
161
|
+
Time.at(custom_stamp).utc.hour
|
162
|
+
#=> 14
|
163
|
+
|
164
|
+
## distinguisher handles basic types
|
165
|
+
str_result = Familia.distinguisher('test')
|
166
|
+
int_result = Familia.distinguisher(123)
|
167
|
+
sym_result = Familia.distinguisher(:symbol)
|
168
|
+
[str_result, int_result, sym_result]
|
169
|
+
#=> ["test", "123", "symbol"]
|
170
|
+
|
171
|
+
## distinguisher raises error for high-risk types with strict mode
|
172
|
+
begin
|
173
|
+
Familia.distinguisher(true, strict_values: true)
|
174
|
+
rescue Familia::HighRiskFactor => e
|
175
|
+
e.class
|
176
|
+
end
|
177
|
+
#=> Familia::HighRiskFactor
|
178
|
+
|
179
|
+
## distinguisher allows high-risk types with non-strict mode
|
180
|
+
result = Familia.distinguisher(false, strict_values: false)
|
181
|
+
result
|
182
|
+
#=> "false"
|
183
|
+
|
184
|
+
# Cleanup - restore defaults, leave nothing but footprints
|
185
|
+
Familia.delim(':')
|
186
|
+
Familia.suffix(:object)
|
187
|
+
Familia.default_expiration(0)
|
188
|
+
Familia.logical_database(nil)
|
189
|
+
Familia.prefix(nil)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# try/datatypes/base_try.rb
|
2
|
+
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
5
|
+
|
6
|
+
@limiter1 = Limiter.new :requests
|
7
|
+
|
8
|
+
## Database Types are unique per instance of a Familia class
|
9
|
+
@a = Bone.new 'atoken1', :name1
|
10
|
+
@b = Bone.new 'atoken2', :name2
|
11
|
+
p [@a.object_id, @b.object_id]
|
12
|
+
p [@a.owners.parent.class, @b.owners.parent.class]
|
13
|
+
p [@a.owners.parent.object_id, @b.owners.parent.object_id]
|
14
|
+
p [@a.owners.dbkey, @b.owners.dbkey]
|
15
|
+
p [@a.token, @b.token]
|
16
|
+
p [@a.name, @b.name]
|
17
|
+
@a.owners.dbkey.eql?(@b.owners.dbkey)
|
18
|
+
#=> false
|
19
|
+
|
20
|
+
## Database Types are frozen
|
21
|
+
@a.owners.frozen?
|
22
|
+
#=> true
|
23
|
+
|
24
|
+
## Limiter#qstamp
|
25
|
+
@limiter1.counter.qstamp(10.minutes, '%H:%M', 1302468980)
|
26
|
+
##=> '20:50'
|
27
|
+
|
28
|
+
## Database Types can be stored to quantized stamp suffix
|
29
|
+
@limiter1.counter.dbkey
|
30
|
+
##=> "v1:limiter:requests:counter:20:50"
|
31
|
+
|
32
|
+
## Limiter#qstamp as a number
|
33
|
+
@limiter2 = Limiter.new :requests
|
34
|
+
p [@limiter1.default_expiration, @limiter2.default_expiration]
|
35
|
+
p [@limiter1.counter.parent.default_expiration, @limiter2.counter.parent.default_expiration]
|
36
|
+
@limiter2.counter.qstamp(10.minutes, pattern: nil, time: 1302468980)
|
37
|
+
#=> 1302468600
|
38
|
+
|
39
|
+
## Database Types can be stored to quantized numeric suffix. This
|
40
|
+
## tryouts is disabled b/c `DataType#dbkey` takes no args
|
41
|
+
## and relies on the `class Limiter` definition in test_helpers.rb
|
42
|
+
## for the `:quantize` option. The quantized suffix for the Limiter
|
43
|
+
## class is `'%H:%M'` so its dbkeys will always look like that.
|
44
|
+
@limiter2.counter.dbkey
|
45
|
+
##=> "v1:limiter:requests:counter:1302468600"
|
46
|
+
|
47
|
+
## Increment counter
|
48
|
+
@limiter1.counter.delete!
|
49
|
+
@limiter1.counter.increment
|
50
|
+
#=> 1
|
51
|
+
|
52
|
+
## Check counter default_expiration
|
53
|
+
@limiter1.counter.default_expiration
|
54
|
+
#=> 3600.0
|
55
|
+
|
56
|
+
## Check limiter default_expiration
|
57
|
+
@limiter1.default_expiration
|
58
|
+
#=> 1800.0
|
59
|
+
|
60
|
+
## Check default_expiration for a different instance
|
61
|
+
## (this exists to make sure options are cloned for each instance)
|
62
|
+
@limiter3 = Limiter.new :requests
|
63
|
+
@limiter3.counter.default_expiration
|
64
|
+
#=> 3600.0
|
65
|
+
|
66
|
+
## Check current_expiration
|
67
|
+
sleep 1 # Database default_expirations are in seconds so we can't wait any less time than this (without mocking)
|
68
|
+
@limiter1.counter.current_expiration
|
69
|
+
#=> 3600-1
|