familia 1.2.1 → 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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +68 -0
  3. data/.github/workflows/docs.yml +64 -0
  4. data/.gitignore +4 -0
  5. data/.pre-commit-config.yaml +3 -1
  6. data/.rubocop.yml +16 -9
  7. data/.rubocop_todo.yml +177 -31
  8. data/.yardopts +9 -0
  9. data/CLAUDE.md +141 -0
  10. data/Gemfile +15 -2
  11. data/Gemfile.lock +76 -34
  12. data/README.md +39 -23
  13. data/bin/irb +3 -0
  14. data/docs/connection_pooling.md +317 -0
  15. data/familia.gemspec +9 -5
  16. data/lib/familia/base.rb +19 -9
  17. data/lib/familia/connection.rb +232 -65
  18. data/lib/familia/core_ext.rb +1 -1
  19. data/lib/familia/datatype/commands.rb +59 -0
  20. data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
  21. data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
  22. data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
  23. data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
  24. data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
  25. data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
  26. data/lib/familia/datatype.rb +243 -0
  27. data/lib/familia/errors.rb +5 -2
  28. data/lib/familia/features/expiration.rb +33 -34
  29. data/lib/familia/features/quantization.rb +9 -3
  30. data/lib/familia/features/safe_dump.rb +2 -3
  31. data/lib/familia/features.rb +2 -2
  32. data/lib/familia/horreum/class_methods.rb +97 -110
  33. data/lib/familia/horreum/commands.rb +46 -51
  34. data/lib/familia/horreum/connection.rb +82 -0
  35. data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
  36. data/lib/familia/horreum/serialization.rb +61 -198
  37. data/lib/familia/horreum/settings.rb +6 -17
  38. data/lib/familia/horreum/utils.rb +11 -10
  39. data/lib/familia/horreum.rb +69 -60
  40. data/lib/familia/logging.rb +12 -12
  41. data/lib/familia/multi_result.rb +72 -0
  42. data/lib/familia/refinements.rb +7 -44
  43. data/lib/familia/settings.rb +11 -11
  44. data/lib/familia/utils.rb +123 -90
  45. data/lib/familia/version.rb +4 -21
  46. data/lib/familia.rb +17 -12
  47. data/lib/middleware/database_middleware.rb +150 -0
  48. data/try/configuration/scenarios_try.rb +65 -0
  49. data/try/core/connection_try.rb +58 -0
  50. data/try/core/errors_try.rb +93 -0
  51. data/try/core/extensions_try.rb +26 -0
  52. data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
  53. data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
  54. data/try/core/middleware_try.rb +68 -0
  55. data/try/core/refinements_try.rb +39 -0
  56. data/try/core/settings_try.rb +76 -0
  57. data/try/core/tools_try.rb +54 -0
  58. data/try/core/utils_try.rb +189 -0
  59. data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
  60. data/try/datatypes/datatype_base_try.rb +69 -0
  61. data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
  62. data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
  63. data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
  64. data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
  65. data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
  66. data/try/edge_cases/empty_identifiers_try.rb +48 -0
  67. data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
  68. data/try/edge_cases/json_serialization_try.rb +85 -0
  69. data/try/edge_cases/race_conditions_try.rb +60 -0
  70. data/try/edge_cases/reserved_keywords_try.rb +59 -0
  71. data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
  72. data/try/edge_cases/ttl_side_effects_try.rb +51 -0
  73. data/try/features/expiration_try.rb +86 -0
  74. data/try/features/quantization_try.rb +90 -0
  75. data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
  76. data/try/features/safe_dump_try.rb +137 -0
  77. data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
  78. data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
  79. data/try/horreum/class_methods_try.rb +41 -0
  80. data/try/horreum/commands_try.rb +49 -0
  81. data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
  82. data/try/horreum/relations_try.rb +146 -0
  83. data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
  84. data/try/horreum/settings_try.rb +43 -0
  85. data/try/integration/cross_component_try.rb +46 -0
  86. data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
  87. data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
  88. data/try/models/datatype_base_try.rb +101 -0
  89. data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
  90. data/try/performance/benchmarks_try.rb +55 -0
  91. data/try/pooling/README.md +20 -0
  92. data/try/pooling/configurable_stress_test_try.rb +435 -0
  93. data/try/pooling/connection_pool_test_try.rb +273 -0
  94. data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  95. data/try/pooling/lib/connection_pool_metrics.rb +372 -0
  96. data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
  97. data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
  98. data/try/pooling/lib/visualize_stress_results.rb +434 -0
  99. data/try/pooling/pool_siege_try.rb +509 -0
  100. data/try/pooling/run_stress_tests_try.rb +482 -0
  101. data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
  102. data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
  103. data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
  104. data/try/prototypes/atomic_saves_v4.rb +105 -0
  105. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
  106. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  107. metadata +140 -43
  108. data/.github/workflows/ruby.yml +0 -71
  109. data/VERSION.yml +0 -4
  110. data/lib/familia/redistype/commands.rb +0 -59
  111. data/lib/familia/redistype.rb +0 -228
  112. data/lib/familia/tools.rb +0 -68
  113. data/lib/redis_middleware.rb +0 -109
  114. data/try/20_redis_type_try.rb +0 -70
  115. data/try/91_json_bug_try.rb +0 -86
@@ -1,10 +1,12 @@
1
- require_relative '../lib/familia'
2
- require_relative './test_helpers'
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', '', '', '', '', 'John Doe'
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 key field correctly
42
- @customer6.key
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[5] # name field should be at index 5
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
- require_relative '../lib/familia'
2
- require_relative './test_helpers'
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 key field (using symbol keys)
23
- @customer.to_h[:key]
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 5)
31
- @customer.to_a[5]
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 Redis (2 of 2)
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.rediskey, 'temp_field', 'temp_value'
107
- conn.hset @customer.rediskey, 'another_field', 'another_value'
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 Redis (1 of 2)
124
+ ## destroy! removes object from Database (1 of 2)
123
125
  @customer.destroy!
124
126
  #=> true
125
127
 
126
- ## After destroy!, Redis key no longer exists (2 of 2)
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 '../lib/familia'
3
- require_relative './test_helpers'
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
- @customer.key = "abc123"
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
- #=> "2"
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 '../lib/familia'
3
- require_relative './test_helpers'
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
- @customer.key = "abc123"
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.db returns the correct database number
103
- Customer.db
105
+ ## Customer.logical_database returns the correct database number
106
+ Customer.logical_database
104
107
  #=> 15
105
108
 
106
- ## Customer.db returns the correct database number
107
- @customer.db
109
+ ## Customer.logical_database returns the correct database number
110
+ @customer.logical_database
108
111
  #=> 15
109
112
 
110
- ## @customer.redis.connection returns the correct redis URI
111
- @customer.redis.connection
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.redis.uri returns the correct redis URI
115
- @customer.secrets_created.db
117
+ ## @customer.dbclient.uri returns the correct database URI
118
+ @customer.secrets_created.logical_database
116
119
  #=> nil
117
120
 
118
- ## @customer.redis.uri returns the correct redis URI
119
- @customer.secrets_created.redis.connection
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
- RedisCommandCounter.count_commands { @customer.destroy! }
130
+ DatabaseCommandCounter.count_commands { @customer.destroy! }
128
131
  #=> 1
129
132
 
130
- ## Customer.db returns the correct database number
131
- Customer.instances.db
133
+ ## Customer.logical_database returns the correct database number
134
+ Customer.instances.logical_database
132
135
  #=> nil
133
136
 
134
- ## Customer.db returns the correct database number
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
- require_relative '../lib/familia'
2
- require_relative './test_helpers'
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 = Bone.new 'atoken', 'akey'
8
+ @a = Session.new 'atoken'
7
9
 
8
10
  ## Familia.prefix
9
- Bone.prefix
10
- #=> :bone
11
+ Session.prefix
12
+ #=> :session
11
13
 
12
14
  ## Familia#identifier
13
15
  @a.identifier
14
- #=> 'atoken:akey'
16
+ #=> 'atoken'
15
17
 
16
18
  ## Familia.suffix
17
- Bone.suffix
19
+ Session.suffix
18
20
  #=> :object
19
21
 
20
- ## Familia#rediskey
21
- @a.rediskey
22
- #=> 'bone:atoken:akey:object'
22
+ ## Familia#dbkey
23
+ @a.dbkey
24
+ #=> 'session:atoken:object'
23
25
 
24
- ## Familia#rediskey
25
- @a.rediskey
26
- #=> 'bone:atoken:akey:object'
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', :delano
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 rediskey
62
- Customer.customers.rediskey
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