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
@@ -1,10 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# try/horreum/initialization_try.rb
|
2
|
+
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
3
5
|
|
4
6
|
Familia.debug = false
|
5
7
|
|
6
8
|
## Existing positional argument initialization still works
|
7
|
-
@customer1 = Customer.new 'tryouts-29@test.com', '', '', '', '
|
9
|
+
@customer1 = Customer.new 'tryouts-29@test.com', '', '', '', 'John Doe'
|
8
10
|
[@customer1.custid, @customer1.name]
|
9
11
|
#=> ["tryouts-29@test.com", "John Doe"]
|
10
12
|
|
@@ -38,8 +40,8 @@ Familia.debug = false
|
|
38
40
|
[@customer6.custid, @customer6.name, @customer6.email]
|
39
41
|
#=> ["save-test@test.com", "Save Test", "save@example.com"]
|
40
42
|
|
41
|
-
## Keyword initialization sets
|
42
|
-
@customer6.
|
43
|
+
## Keyword initialization sets identifier field correctly
|
44
|
+
@customer6.identifier
|
43
45
|
#=> "save-test@test.com"
|
44
46
|
|
45
47
|
## Mixed valid and nil values in keyword args (nil values stay nil)
|
@@ -52,7 +54,7 @@ Familia.debug = false
|
|
52
54
|
#=> "Jane Smith"
|
53
55
|
|
54
56
|
## to_a works correctly with keyword-initialized objects
|
55
|
-
@customer2.to_a[
|
57
|
+
@customer2.to_a[4] # name field should be the fifth field defined in the class
|
56
58
|
#=> "Jane Smith"
|
57
59
|
|
58
60
|
## Session has limited fields (only sessid defined)
|
@@ -76,7 +78,7 @@ Familia.debug = false
|
|
76
78
|
#=> ["api.example.com", "domain-test@test.com"]
|
77
79
|
|
78
80
|
## CustomDomain still works with positional args
|
79
|
-
@domain2 = CustomDomain.new('web.example.com', 'positional@test.com')
|
81
|
+
@domain2 = CustomDomain.new('', 'web.example.com', 'positional@test.com')
|
80
82
|
[@domain2.display_domain, @domain2.custid]
|
81
83
|
#=> ["web.example.com", "positional@test.com"]
|
82
84
|
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# try/horreum/relations_try.rb
|
2
|
+
# Test Horreum Database type relations functionality
|
3
|
+
|
4
|
+
require_relative '../../lib/familia'
|
5
|
+
require_relative '../helpers/test_helpers'
|
6
|
+
|
7
|
+
Familia.debug = false
|
8
|
+
|
9
|
+
class RelationsTestUser < Familia::Horreum
|
10
|
+
prefix 'relationstestuser'
|
11
|
+
identifier_field :userid
|
12
|
+
field :userid
|
13
|
+
field :name
|
14
|
+
list :sessions
|
15
|
+
set :tags
|
16
|
+
zset :scores
|
17
|
+
hashkey :preferences
|
18
|
+
end
|
19
|
+
|
20
|
+
class RelationsTestProduct < Familia::Horreum
|
21
|
+
prefix 'relationstestproduct'
|
22
|
+
identifier_field :productid
|
23
|
+
field :productid
|
24
|
+
field :title
|
25
|
+
list :reviews
|
26
|
+
set :categories
|
27
|
+
zset :ratings
|
28
|
+
hashkey :metadata
|
29
|
+
counter :views
|
30
|
+
end
|
31
|
+
|
32
|
+
@test_user = RelationsTestUser.new
|
33
|
+
@test_user.userid = "user123"
|
34
|
+
@test_user.name = "Test User"
|
35
|
+
|
36
|
+
@test_product = RelationsTestProduct.new
|
37
|
+
@test_product.productid = "prod456"
|
38
|
+
@test_product.title = "Test Product"
|
39
|
+
|
40
|
+
## Class knows about Database type relationships
|
41
|
+
RelationsTestUser.has_relations?
|
42
|
+
#=> true
|
43
|
+
|
44
|
+
## Class can list Database type definitions
|
45
|
+
related_fields = RelationsTestUser.related_fields
|
46
|
+
related_fields.keys.sort
|
47
|
+
#=> [:preferences, :scores, :sessions, :tags]
|
48
|
+
|
49
|
+
## Database type definitions are accessible
|
50
|
+
sessions_def = RelationsTestUser.related_fields[:sessions]
|
51
|
+
sessions_def.nil?
|
52
|
+
#=> false
|
53
|
+
|
54
|
+
## Can access different Database type instances
|
55
|
+
sessions = @test_user.sessions
|
56
|
+
tags = @test_user.tags
|
57
|
+
scores = @test_user.scores
|
58
|
+
prefs = @test_user.preferences
|
59
|
+
[sessions.class.name, tags.class.name, scores.class.name, prefs.class.name]
|
60
|
+
#=> ["Familia::List", "Familia::Set", "Familia::SortedSet", "Familia::HashKey"]
|
61
|
+
|
62
|
+
## Database types use correct dbkeys
|
63
|
+
@test_user.sessions.dbkey
|
64
|
+
#=> "relationstestuser:user123:sessions"
|
65
|
+
|
66
|
+
## Database types use correct dbkeys
|
67
|
+
@test_user.tags.dbkey
|
68
|
+
#=> "relationstestuser:user123:tags"
|
69
|
+
|
70
|
+
## Can work with List Database type
|
71
|
+
@test_user.sessions.clear
|
72
|
+
@test_user.sessions.push("session1", "session2")
|
73
|
+
@test_user.sessions.size
|
74
|
+
#=> 2
|
75
|
+
|
76
|
+
## Can work with Set Database type
|
77
|
+
@test_user.tags.clear
|
78
|
+
@test_user.tags.add("ruby", "valkey", "web")
|
79
|
+
@test_user.tags.size
|
80
|
+
#=> 3
|
81
|
+
|
82
|
+
## Can work with SortedSet Database type
|
83
|
+
@test_user.scores.clear
|
84
|
+
@test_user.scores.add(100, "level1")
|
85
|
+
@test_user.scores.add(200, "level2")
|
86
|
+
@test_user.scores.size
|
87
|
+
#=> 2
|
88
|
+
|
89
|
+
## Can work with HashKey Database type
|
90
|
+
@test_user.preferences.clear
|
91
|
+
@test_user.preferences.put("theme", "dark")
|
92
|
+
@test_user.preferences.put("lang", "en")
|
93
|
+
@test_user.preferences.size
|
94
|
+
#=> 2
|
95
|
+
|
96
|
+
## Clearing a counter returns false when not set yet
|
97
|
+
@test_product.views.clear
|
98
|
+
@test_product.views.clear
|
99
|
+
#=> false
|
100
|
+
|
101
|
+
## Clearing a counter returns true when it is set
|
102
|
+
@test_product.views.increment
|
103
|
+
@test_product.views.clear
|
104
|
+
#=> true
|
105
|
+
|
106
|
+
## Counter Database type works
|
107
|
+
@test_product.views.increment
|
108
|
+
@test_product.views.incrementby(5)
|
109
|
+
@test_product.views.value
|
110
|
+
#=> "6"
|
111
|
+
|
112
|
+
## Database types maintain parent reference
|
113
|
+
@test_user.sessions.parent == @test_user
|
114
|
+
#=> true
|
115
|
+
|
116
|
+
## Database types know their field name
|
117
|
+
@test_user.tags.keystring
|
118
|
+
#=> :tags
|
119
|
+
|
120
|
+
## Can check if Database types exist
|
121
|
+
@test_user.scores.add(50, "test")
|
122
|
+
before_exists = @test_user.scores.exists?
|
123
|
+
@test_user.scores.clear
|
124
|
+
after_exists = @test_user.scores.exists?
|
125
|
+
[before_exists, after_exists]
|
126
|
+
#=> [true, false]
|
127
|
+
|
128
|
+
## Can destroy individual Database types
|
129
|
+
@test_user.preferences.put("temp", "value")
|
130
|
+
@test_user.preferences.clear
|
131
|
+
@test_user.preferences.exists?
|
132
|
+
#=> false
|
133
|
+
|
134
|
+
## Parent object destruction does not clean up relations
|
135
|
+
@test_user.sessions.add("cleanup_test")
|
136
|
+
@test_user.destroy!
|
137
|
+
@test_user.sessions.exists?
|
138
|
+
#=> true
|
139
|
+
|
140
|
+
## If the parent instance is still in memory, can use it
|
141
|
+
## to access and clear the child field.
|
142
|
+
@test_user.sessions.clear
|
143
|
+
#=> true
|
144
|
+
|
145
|
+
|
146
|
+
@test_product.destroy!
|
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# try/horreum/serialization_try.rb
|
2
|
+
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
3
5
|
|
4
6
|
Familia.debug = false
|
5
7
|
|
@@ -19,16 +21,16 @@ Familia.debug = false
|
|
19
21
|
@customer.to_h[:name]
|
20
22
|
#=> "John Doe"
|
21
23
|
|
22
|
-
## to_h includes the
|
23
|
-
@customer.to_h[:
|
24
|
+
## to_h includes the custid field (using symbol keys)
|
25
|
+
@customer.to_h[:custid]
|
24
26
|
#=> "tryouts-28@onetimesecret.com"
|
25
27
|
|
26
28
|
## to_a returns field array in definition order
|
27
29
|
@customer.to_a.class
|
28
30
|
#=> Array
|
29
31
|
|
30
|
-
## to_a includes values in field order (name should be at index
|
31
|
-
@customer.to_a[
|
32
|
+
## to_a includes values in field order (name should be at index 4)
|
33
|
+
@customer.to_a[4]
|
32
34
|
#=> "John Doe"
|
33
35
|
|
34
36
|
## batch_update can update multiple fields atomically, to_h
|
@@ -68,7 +70,7 @@ Familia.debug = false
|
|
68
70
|
[@customer.name, @customer.email]
|
69
71
|
#=> ["Memory Only", "memory@test.com"]
|
70
72
|
|
71
|
-
## apply_fields doesn't persist to
|
73
|
+
## apply_fields doesn't persist to Database (2 of 2)
|
72
74
|
@customer.refresh!
|
73
75
|
[@customer.name, @customer.email]
|
74
76
|
#=> ["Bob Jones", "jane@example.com"]
|
@@ -103,8 +105,8 @@ Familia.debug = false
|
|
103
105
|
|
104
106
|
## transaction method works with block
|
105
107
|
result = @customer.transaction do |conn|
|
106
|
-
conn.hset @customer.
|
107
|
-
conn.hset @customer.
|
108
|
+
conn.hset @customer.dbkey, 'temp_field', 'temp_value'
|
109
|
+
conn.hset @customer.dbkey, 'another_field', 'another_value'
|
108
110
|
end
|
109
111
|
result.size
|
110
112
|
#=> 2
|
@@ -119,11 +121,11 @@ result = @customer.batch_update()
|
|
119
121
|
result.successful?
|
120
122
|
#=> true
|
121
123
|
|
122
|
-
## destroy! removes object from
|
124
|
+
## destroy! removes object from Database (1 of 2)
|
123
125
|
@customer.destroy!
|
124
126
|
#=> true
|
125
127
|
|
126
|
-
## After destroy!,
|
128
|
+
## After destroy!, dbkey no longer exists (2 of 2)
|
127
129
|
@customer.exists?
|
128
130
|
#=> false
|
129
131
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
|
3
|
+
# Test Horreum settings
|
4
|
+
group "Horreum Settings"
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@user_class = Class.new(Familia::Horreum) do
|
8
|
+
identifier :email
|
9
|
+
field :name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
try "database selection inheritance" do
|
14
|
+
@user_class.db 5
|
15
|
+
user = @user_class.new(email: "test@example.com")
|
16
|
+
|
17
|
+
user.db == 5
|
18
|
+
end
|
19
|
+
|
20
|
+
try "custom serialization methods" do
|
21
|
+
@user_class.dump_method :to_json
|
22
|
+
@user_class.load_method :from_json
|
23
|
+
user = @user_class.new(email: "test@example.com")
|
24
|
+
|
25
|
+
user.dump_method == :to_json &&
|
26
|
+
user.load_method == :from_json
|
27
|
+
end
|
28
|
+
|
29
|
+
try "redisdetails comprehensive state inspection" do
|
30
|
+
user = @user_class.new(email: "test@example.com", name: "Test")
|
31
|
+
details = user.redisdetails
|
32
|
+
|
33
|
+
details.is_a?(Hash) &&
|
34
|
+
details.key?(:key) &&
|
35
|
+
details.key?(:db)
|
36
|
+
end
|
37
|
+
|
38
|
+
try "redisuri generation with suffix" do
|
39
|
+
user = @user_class.new(email: "test@example.com")
|
40
|
+
uri = user.redisuri("suffix")
|
41
|
+
|
42
|
+
uri.include?("suffix")
|
43
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
|
3
|
+
# Test cross-component integration scenarios
|
4
|
+
group "Cross-Component Integration"
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@user_class = Class.new(Familia::Horreum) do
|
8
|
+
identifier :email
|
9
|
+
field :name
|
10
|
+
feature :expiration
|
11
|
+
feature :safe_dump
|
12
|
+
ttl 3600
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
try "Horreum with multiple features integration" do
|
17
|
+
user = @user_class.new(email: "test@example.com", name: "Integration Test")
|
18
|
+
user.save
|
19
|
+
|
20
|
+
# Test expiration feature
|
21
|
+
user.expire(1800)
|
22
|
+
ttl_works = user.realttl > 0
|
23
|
+
|
24
|
+
# Test safe_dump feature
|
25
|
+
safe_data = user.safe_dump
|
26
|
+
safe_dump_works = safe_data.is_a?(Hash)
|
27
|
+
|
28
|
+
ttl_works && safe_dump_works
|
29
|
+
ensure
|
30
|
+
user&.delete!
|
31
|
+
end
|
32
|
+
|
33
|
+
try "RedisType relations with Horreum expiration" do
|
34
|
+
user = @user_class.new(email: "test@example.com")
|
35
|
+
user.save
|
36
|
+
user.expire(1800)
|
37
|
+
|
38
|
+
# Create related RedisType
|
39
|
+
tags = user.set(:tags)
|
40
|
+
tags << "ruby" << "redis"
|
41
|
+
|
42
|
+
# Expiration should cascade
|
43
|
+
tags.exists? && user.exists?
|
44
|
+
ensure
|
45
|
+
user&.delete!
|
46
|
+
end
|
@@ -1,14 +1,15 @@
|
|
1
|
+
# try/models/customer_safedump_try.rb
|
1
2
|
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
4
5
|
|
5
6
|
# Setup
|
6
7
|
@now = Time.now.to_i
|
7
8
|
@customer = Customer.new
|
8
|
-
@customer.custid = "test@example.com"
|
9
|
-
@customer.email = "test@example.com"
|
9
|
+
@customer.custid = "test+customer_safedump@example.com"
|
10
|
+
@customer.email = "test+customer_safedump@example.com"
|
10
11
|
@customer.role = "user"
|
11
|
-
|
12
|
+
# No longer need to set key field - identifier computed from custid
|
12
13
|
@customer.planid = "basic"
|
13
14
|
@customer.created = @now
|
14
15
|
@customer.updated = @now
|
@@ -23,7 +24,7 @@ require_relative './test_helpers'
|
|
23
24
|
|
24
25
|
## Safe dump includes correct custid
|
25
26
|
@safe_dump[:custid]
|
26
|
-
#=> "test@example.com"
|
27
|
+
#=> "test+customer_safedump@example.com"
|
27
28
|
|
28
29
|
## Safe dump includes correct role
|
29
30
|
@safe_dump[:role]
|
@@ -45,7 +46,7 @@ require_relative './test_helpers'
|
|
45
46
|
@customer.secrets_created.increment
|
46
47
|
@safe_dump = @customer.safe_dump
|
47
48
|
@safe_dump[:secrets_created]
|
48
|
-
#=> "
|
49
|
+
#=> "1"
|
49
50
|
|
50
51
|
## Safe dump includes correct active status when verified and not reset requested
|
51
52
|
@safe_dump[:active]
|
@@ -84,3 +85,4 @@ require_relative './test_helpers'
|
|
84
85
|
|
85
86
|
# Teardown
|
86
87
|
@customer.destroy!
|
88
|
+
@customer.secrets_created.clear
|
@@ -1,6 +1,9 @@
|
|
1
|
+
# try/models/customer_try.rb
|
2
|
+
|
1
3
|
# Customer Tryouts
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
4
|
+
require_relative '../../lib/familia'
|
5
|
+
require_relative '../helpers/test_helpers'
|
6
|
+
|
4
7
|
|
5
8
|
# Setup
|
6
9
|
@now = Time.now.to_f
|
@@ -8,7 +11,7 @@ require_relative './test_helpers'
|
|
8
11
|
@customer.custid = "test@example.com"
|
9
12
|
@customer.email = "test@example.com"
|
10
13
|
@customer.role = "user"
|
11
|
-
|
14
|
+
# No longer need to set key field - identifier computed from custid
|
12
15
|
@customer.planid = "basic"
|
13
16
|
@customer.created = Time.now.to_i
|
14
17
|
@customer.updated = Time.now.to_i
|
@@ -99,24 +102,24 @@ exists = Customer.exists?("test@example.com")
|
|
99
102
|
@customer.destroy!
|
100
103
|
#=> false
|
101
104
|
|
102
|
-
## Customer.
|
103
|
-
Customer.
|
105
|
+
## Customer.logical_database returns the correct database number
|
106
|
+
Customer.logical_database
|
104
107
|
#=> 15
|
105
108
|
|
106
|
-
## Customer.
|
107
|
-
@customer.
|
109
|
+
## Customer.logical_database returns the correct database number
|
110
|
+
@customer.logical_database
|
108
111
|
#=> 15
|
109
112
|
|
110
|
-
## @customer.
|
111
|
-
@customer.
|
113
|
+
## @customer.dbclient.connection returns the correct database URI
|
114
|
+
@customer.dbclient.connection
|
112
115
|
#=> {:host=>"127.0.0.1", :port=>6379, :db=>15, :id=>"redis://127.0.0.1:6379/15", :location=>"127.0.0.1:6379"}
|
113
116
|
|
114
|
-
## @customer.
|
115
|
-
@customer.secrets_created.
|
117
|
+
## @customer.dbclient.uri returns the correct database URI
|
118
|
+
@customer.secrets_created.logical_database
|
116
119
|
#=> nil
|
117
120
|
|
118
|
-
## @customer.
|
119
|
-
@customer.secrets_created.
|
121
|
+
## @customer.dbclient.uri returns the correct database URI
|
122
|
+
@customer.secrets_created.dbclient.connection
|
120
123
|
#=> {:host=>"127.0.0.1", :port=>6379, :db=>15, :id=>"redis://127.0.0.1:6379/15", :location=>"127.0.0.1:6379"}
|
121
124
|
|
122
125
|
## Customer.url is nil by default
|
@@ -124,14 +127,14 @@ Customer.uri
|
|
124
127
|
#=> nil
|
125
128
|
|
126
129
|
## Customer.destroy! makes only one call to Redis
|
127
|
-
|
130
|
+
DatabaseCommandCounter.count_commands { @customer.destroy! }
|
128
131
|
#=> 1
|
129
132
|
|
130
|
-
## Customer.
|
131
|
-
Customer.instances.
|
133
|
+
## Customer.logical_database returns the correct database number
|
134
|
+
Customer.instances.logical_database
|
132
135
|
#=> nil
|
133
136
|
|
134
|
-
## Customer.
|
137
|
+
## Customer.logical_database returns the correct database number
|
135
138
|
Customer.instances.uri.to_s
|
136
139
|
#=> 'redis://127.0.0.1/15/'
|
137
140
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# try/datatypes/datatype_base_try.rb
|
2
|
+
|
3
|
+
# Test DataType base functionality
|
4
|
+
|
5
|
+
require_relative '../../lib/familia'
|
6
|
+
require_relative '../helpers/test_helpers'
|
7
|
+
|
8
|
+
Familia.debug = false
|
9
|
+
|
10
|
+
@sample_obj = Customer.new(custid: 'customer123', email: 'test@example.com')
|
11
|
+
|
12
|
+
## Customer has defined Database types
|
13
|
+
Customer.related_fields.keys.include?(:timeline)
|
14
|
+
#=> true
|
15
|
+
|
16
|
+
## Customer has defined Database types
|
17
|
+
Customer.related_fields.keys.include?(:stripe_customer)
|
18
|
+
#=> true
|
19
|
+
|
20
|
+
## Can access Database type instances
|
21
|
+
@sample_obj.timeline
|
22
|
+
#=:> Familia::SortedSet
|
23
|
+
|
24
|
+
## Database types have dbkey method
|
25
|
+
@sample_obj.timeline.dbkey
|
26
|
+
#=> "customer:customer123:timeline"
|
27
|
+
|
28
|
+
## Database types are frozen after creation
|
29
|
+
@sample_obj.timeline.frozen?
|
30
|
+
#=> true
|
31
|
+
|
32
|
+
## Can access hashkey Database type
|
33
|
+
@sample_obj ||= Customer.new(custid: 'customer123', email: 'test@example.com')
|
34
|
+
stripe_customer = @sample_obj.stripe_customer
|
35
|
+
stripe_customer.class.name
|
36
|
+
#=> "Familia::HashKey"
|
37
|
+
|
38
|
+
## DataType instances know their owner
|
39
|
+
@sample_obj.timeline.parent == @sample_obj
|
40
|
+
#=> true
|
41
|
+
|
42
|
+
## DataType instances know their field name
|
43
|
+
@sample_obj.timeline.keystring
|
44
|
+
#=> :timeline
|
45
|
+
|
46
|
+
## DataType has opts hash
|
47
|
+
@sample_obj.timeline.opts.class
|
48
|
+
#=> Hash
|
49
|
+
|
50
|
+
## DataType responds to Familia's modified Database commands
|
51
|
+
@sample_obj.timeline
|
52
|
+
#=/=> _.respond_to?(:zadd)
|
53
|
+
#==> _.respond_to?(:add)
|
54
|
+
#==> _.respond_to?(:clear)
|
55
|
+
#==> _.respond_to?(:exists?)
|
56
|
+
#=/=> _.respond_to?(:destroy!)
|
57
|
+
|
58
|
+
|
59
|
+
## Can check if DataType exists in Redis
|
60
|
+
timeline = @sample_obj.timeline
|
61
|
+
exists_before = timeline.exists?
|
62
|
+
[exists_before.class, [true, false].include?(exists_before)]
|
63
|
+
#=> [FalseClass, true]
|
64
|
+
|
65
|
+
## DataType has size/length methods
|
66
|
+
@sample_obj.timeline.respond_to?(:size)
|
67
|
+
#=> true
|
68
|
+
|
69
|
+
## DataType size returns integer
|
70
|
+
timeline = @sample_obj.timeline
|
71
|
+
timeline.size
|
72
|
+
#=:> Integer
|
73
|
+
|
74
|
+
## Different Database types have type-specific methods
|
75
|
+
stripe_customer = @sample_obj.stripe_customer
|
76
|
+
stripe_customer
|
77
|
+
#=/=> _.respond_to?(:hset)
|
78
|
+
#==> _.respond_to?(:put)
|
79
|
+
#==> _.respond_to?(:store)
|
80
|
+
#==> _.respond_to?(:[]=)
|
81
|
+
|
82
|
+
## Can get DataType default expiration
|
83
|
+
timeline = @sample_obj.timeline
|
84
|
+
default_expiration = timeline.default_expiration
|
85
|
+
[default_expiration.class, default_expiration >= -1]
|
86
|
+
#=> [Integer, true]
|
87
|
+
|
88
|
+
## DataType has logical_database method
|
89
|
+
timeline = @sample_obj.timeline
|
90
|
+
db = timeline.logical_database
|
91
|
+
db
|
92
|
+
#=:> NilClass
|
93
|
+
|
94
|
+
## DataType has uri method
|
95
|
+
timeline = @sample_obj.timeline
|
96
|
+
uri = timeline.uri
|
97
|
+
uri.class.name
|
98
|
+
#=> "URI::Redis"
|
99
|
+
|
100
|
+
# Cleanup
|
101
|
+
@sample_obj.destroy!
|
@@ -1,29 +1,31 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# try/models/familia_object_try.rb
|
2
|
+
|
3
|
+
require_relative '../../lib/familia'
|
4
|
+
require_relative '../helpers/test_helpers'
|
3
5
|
|
4
6
|
Familia.debug = false
|
5
7
|
|
6
|
-
@a =
|
8
|
+
@a = Session.new 'atoken'
|
7
9
|
|
8
10
|
## Familia.prefix
|
9
|
-
|
10
|
-
#=> :
|
11
|
+
Session.prefix
|
12
|
+
#=> :session
|
11
13
|
|
12
14
|
## Familia#identifier
|
13
15
|
@a.identifier
|
14
|
-
#=> 'atoken
|
16
|
+
#=> 'atoken'
|
15
17
|
|
16
18
|
## Familia.suffix
|
17
|
-
|
19
|
+
Session.suffix
|
18
20
|
#=> :object
|
19
21
|
|
20
|
-
## Familia#
|
21
|
-
@a.
|
22
|
-
#=> '
|
22
|
+
## Familia#dbkey
|
23
|
+
@a.dbkey
|
24
|
+
#=> 'session:atoken:object'
|
23
25
|
|
24
|
-
## Familia#
|
25
|
-
@a.
|
26
|
-
#=> '
|
26
|
+
## Familia#dbkey
|
27
|
+
@a.dbkey
|
28
|
+
#=> 'session:atoken:object'
|
27
29
|
|
28
30
|
## Familia#save
|
29
31
|
@cust = Customer.new :delano, "Delano Mandelbaum"
|
@@ -50,7 +52,7 @@ Customer.values.size
|
|
50
52
|
#=> 0
|
51
53
|
|
52
54
|
## Familia#save with an object that expires
|
53
|
-
obj = Session.new 'sessionid'
|
55
|
+
obj = Session.new 'sessionid'
|
54
56
|
obj.save
|
55
57
|
#=> true
|
56
58
|
|
@@ -58,8 +60,8 @@ obj.save
|
|
58
60
|
Customer.customers.class
|
59
61
|
#=> Familia::List
|
60
62
|
|
61
|
-
## Familia class
|
62
|
-
Customer.customers.
|
63
|
+
## Familia class dbkey
|
64
|
+
Customer.customers.dbkey
|
63
65
|
#=> 'customer:customers'
|
64
66
|
|
65
67
|
## Familia.class_list
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
# Performance benchmarks separate from stress tests
|
5
|
+
group "Performance Benchmarks"
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@user_class = Class.new(Familia::Horreum) do
|
9
|
+
identifier :email
|
10
|
+
field :name
|
11
|
+
field :data
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
try "serialization performance comparison" do
|
16
|
+
large_data = { items: (1..1000).to_a, metadata: "x" * 1000 }
|
17
|
+
|
18
|
+
json_time = Benchmark.realtime do
|
19
|
+
100.times { JSON.dump(large_data) }
|
20
|
+
end
|
21
|
+
|
22
|
+
familia_time = Benchmark.realtime do
|
23
|
+
100.times { Familia.distinguisher(large_data) }
|
24
|
+
end
|
25
|
+
|
26
|
+
json_time > 0 && familia_time > 0
|
27
|
+
end
|
28
|
+
|
29
|
+
try "bulk operations vs individual saves" do
|
30
|
+
users = 100.times.map { |i|
|
31
|
+
@user_class.new(email: "user#{i}@example.com", name: "User #{i}")
|
32
|
+
}
|
33
|
+
|
34
|
+
individual_time = Benchmark.realtime do
|
35
|
+
users.each(&:save)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Cleanup for next test
|
39
|
+
users.each(&:delete!)
|
40
|
+
|
41
|
+
individual_time > 0
|
42
|
+
end
|
43
|
+
|
44
|
+
try "Redis type access performance" do
|
45
|
+
user = @user_class.new(email: "perf@example.com")
|
46
|
+
user.save
|
47
|
+
|
48
|
+
access_time = Benchmark.realtime do
|
49
|
+
1000.times { user.set(:tags) }
|
50
|
+
end
|
51
|
+
|
52
|
+
access_time > 0
|
53
|
+
ensure
|
54
|
+
user&.delete!
|
55
|
+
end
|