familia 2.0.0.pre8 → 2.0.0.pre10

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -0
  3. data/.github/workflows/docs.yml +1 -1
  4. data/.gitignore +9 -9
  5. data/.rubocop.yml +19 -0
  6. data/.yardopts +22 -1
  7. data/CHANGELOG.md +184 -0
  8. data/CLAUDE.md +8 -5
  9. data/Gemfile.lock +1 -1
  10. data/README.md +62 -2
  11. data/changelog.d/README.md +66 -0
  12. data/changelog.d/fragments/.keep +0 -0
  13. data/changelog.d/template.md.j2 +29 -0
  14. data/docs/archive/.gitignore +2 -0
  15. data/docs/archive/FAMILIA_RELATIONSHIPS.md +210 -0
  16. data/docs/archive/FAMILIA_TECHNICAL.md +823 -0
  17. data/docs/archive/FAMILIA_UPDATE.md +226 -0
  18. data/docs/archive/README.md +67 -0
  19. data/docs/guides/.gitignore +2 -0
  20. data/docs/{wiki → guides}/Relationships-Guide.md +103 -50
  21. data/docs/guides/relationships-methods.md +266 -0
  22. data/examples/relationships_basic.rb +90 -157
  23. data/familia.gemspec +4 -4
  24. data/lib/familia/connection.rb +4 -21
  25. data/lib/familia/features/relationships/indexing.rb +160 -175
  26. data/lib/familia/features/relationships/membership.rb +16 -21
  27. data/lib/familia/features/relationships/tracking.rb +61 -21
  28. data/lib/familia/features/relationships.rb +15 -8
  29. data/lib/familia/horreum/subclass/definition.rb +2 -0
  30. data/lib/familia/horreum.rb +15 -24
  31. data/lib/familia/version.rb +1 -1
  32. data/setup.cfg +12 -0
  33. data/try/features/relationships/relationships_api_changes_try.rb +339 -0
  34. data/try/features/relationships/relationships_try.rb +6 -5
  35. metadata +43 -30
  36. /data/docs/{wiki → guides}/API-Reference.md +0 -0
  37. /data/docs/{wiki → guides}/Connection-Pooling-Guide.md +0 -0
  38. /data/docs/{wiki → guides}/Encrypted-Fields-Overview.md +0 -0
  39. /data/docs/{wiki → guides}/Expiration-Feature-Guide.md +0 -0
  40. /data/docs/{wiki → guides}/Feature-System-Guide.md +0 -0
  41. /data/docs/{wiki → guides}/Features-System-Developer-Guide.md +0 -0
  42. /data/docs/{wiki → guides}/Field-System-Guide.md +0 -0
  43. /data/docs/{wiki → guides}/Home.md +0 -0
  44. /data/docs/{wiki → guides}/Implementation-Guide.md +0 -0
  45. /data/docs/{wiki → guides}/Quantization-Feature-Guide.md +0 -0
  46. /data/docs/{wiki → guides}/Security-Model.md +0 -0
  47. /data/docs/{wiki → guides}/Transient-Fields-Guide.md +0 -0
@@ -0,0 +1,823 @@
1
+ # Familia v2.0.0-pre Series Technical Reference
2
+
3
+ **Familia** is a Ruby ORM for Redis/Valkey providing object mapping, relationships, and advanced features like encryption, connection pooling, and permission systems. This technical reference covers the major classes, methods, and usage patterns introduced in the v2.0.0-pre series.
4
+
5
+ ---
6
+
7
+ ## Core Architecture
8
+
9
+ ### Base Classes
10
+
11
+ #### `Familia::Horreum` - Primary ORM Base Class
12
+ The main base class for Redis-backed objects, similar to ActiveRecord models.
13
+
14
+ ```ruby
15
+ class User < Familia::Horreum
16
+ # Basic field definitions
17
+ field :name, :email, :created_at
18
+
19
+ # Redis data types as instance variables
20
+ list :sessions # Redis list
21
+ set :tags # Redis set
22
+ sorted_set :scores # Redis sorted set
23
+ hash :settings # Redis hash
24
+ end
25
+ ```
26
+
27
+ **Key Methods:**
28
+ - `save` - Persist object to Redis
29
+ - `save_if_not_exists` - Conditional persistence (v2.0.0-pre6)
30
+ - `load` - Load object from Redis
31
+ - `exists?` - Check if object exists in Redis
32
+ - `destroy` - Remove object from Redis
33
+
34
+ #### `Familia::DataType` - Redis Data Type Wrapper
35
+ Base class for Redis data type implementations.
36
+
37
+ **Registered Types:**
38
+ - `String` - Redis strings
39
+ - `List` - Redis lists
40
+ - `Set` - Redis sets
41
+ - `SortedSet` - Redis sorted sets
42
+ - `HashKey` - Redis hashes
43
+ - `Counter` - Atomic counters
44
+ - `Lock` - Distributed locks
45
+
46
+ ---
47
+
48
+ ## Feature System (v2.0.0-pre5+)
49
+
50
+ ### Feature Architecture
51
+ Modular system for extending Horreum classes with reusable functionality.
52
+
53
+ ```ruby
54
+ class Customer < Familia::Horreum
55
+ feature :expiration # TTL management
56
+ feature :safe_dump # API-safe serialization
57
+ feature :encrypted_fields # Field encryption
58
+ feature :transient_fields # Non-persistent fields
59
+ feature :relationships # Object relationships
60
+
61
+ field :name, :email
62
+ encrypted_field :api_key
63
+ transient_field :password
64
+ end
65
+ ```
66
+
67
+ ### Built-in Features
68
+
69
+ #### 1. Expiration Feature
70
+ TTL (Time To Live) management with cascading expiration.
71
+
72
+ ```ruby
73
+ class Session < Familia::Horreum
74
+ feature :expiration
75
+
76
+ field :user_id, :token
77
+ default_expiration 24.hours
78
+
79
+ # Cascade expiration to related objects
80
+ cascade_expiration_to :user_activity
81
+ end
82
+
83
+ session = Session.new(user_id: 123, token: "abc123")
84
+ session.save
85
+ session.expire_in(1.hour) # Set custom expiration
86
+ session.ttl # Check remaining time
87
+ ```
88
+
89
+ #### 2. SafeDump Feature
90
+ API-safe serialization excluding sensitive fields.
91
+
92
+ ```ruby
93
+ class User < Familia::Horreum
94
+ feature :safe_dump
95
+
96
+ field :name, :email, :password_hash
97
+ safe_dump_fields :name, :email # Only these fields in safe_dump
98
+ end
99
+
100
+ user = User.new(name: "Alice", email: "alice@example.com", password_hash: "secret")
101
+ user.safe_dump # => {"name" => "Alice", "email" => "alice@example.com"}
102
+ ```
103
+
104
+ #### 3. Encrypted Fields Feature (v2.0.0-pre5)
105
+ Transparent field-level encryption with multiple providers.
106
+
107
+ ```ruby
108
+ # Configuration
109
+ Familia.configure do |config|
110
+ config.encryption_keys = {
111
+ v1: ENV['FAMILIA_ENCRYPTION_KEY'],
112
+ v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
113
+ }
114
+ config.current_key_version = :v2
115
+ end
116
+
117
+ class Vault < Familia::Horreum
118
+ feature :encrypted_fields
119
+
120
+ field :name # Plaintext
121
+ encrypted_field :secret_key # Encrypted with XChaCha20-Poly1305
122
+ encrypted_field :api_token # Field-specific key derivation
123
+ encrypted_field :private_data # Transparent access
124
+ end
125
+
126
+ vault = Vault.new(
127
+ name: "Production Secrets",
128
+ secret_key: "super-secret-123",
129
+ api_token: "sk-1234567890"
130
+ )
131
+ vault.save
132
+
133
+ # Transparent access - automatically encrypted/decrypted
134
+ vault.secret_key # => "super-secret-123" (decrypted on access)
135
+ ```
136
+
137
+ **Encryption Providers:**
138
+ - **XChaCha20-Poly1305** (preferred) - Requires `rbnacl` gem
139
+ - **AES-256-GCM** (fallback) - Uses OpenSSL, no dependencies
140
+
141
+ **Security Features:**
142
+ - Field-specific key derivation for domain separation
143
+ - Key versioning and rotation support
144
+ - Memory-safe key handling
145
+ - Configurable encryption algorithms
146
+
147
+ #### 4. Transient Fields Feature (v2.0.0-pre5)
148
+ Non-persistent fields with memory-safe handling.
149
+
150
+ ```ruby
151
+ class LoginForm < Familia::Horreum
152
+ feature :transient_fields
153
+
154
+ field :username # Persistent
155
+ transient_field :password # Never stored in Redis
156
+ transient_field :csrf_token # Runtime only
157
+ end
158
+
159
+ form = LoginForm.new(username: "alice", password: "secret123")
160
+ form.save # Only saves 'username', password is transient
161
+
162
+ form.password.class # => Familia::Features::TransientFields::RedactedString
163
+ form.password.to_s # => "[REDACTED]" (safe for logging)
164
+ form.password.reveal # => "secret123" (explicit access)
165
+ ```
166
+
167
+ **RedactedString** - Security wrapper preventing accidental exposure:
168
+ - `to_s` returns "[REDACTED]"
169
+ - `inspect` returns "[REDACTED]"
170
+ - `reveal` method for explicit access
171
+ - Safe for logging and serialization
172
+
173
+ #### 5. Relationships Feature (v2.0.0-pre7)
174
+ Comprehensive object relationship system with three relationship types.
175
+
176
+ ```ruby
177
+ class Customer < Familia::Horreum
178
+ feature :relationships
179
+
180
+ identifier_field :custid
181
+ field :custid, :name, :email
182
+
183
+ # Collections for storing related object IDs
184
+ set :domains # Simple set
185
+ sorted_set :activity # Scored/sorted collection
186
+
187
+ # Indexed lookups (O(1) hash-based)
188
+ indexed_by :email_lookup, field: :email
189
+
190
+ # Global tracking with scoring
191
+ tracked_in :all_customers, type: :sorted_set, score: :created_at
192
+ end
193
+
194
+ class Domain < Familia::Horreum
195
+ feature :relationships
196
+
197
+ identifier_field :domain_id
198
+ field :domain_id, :name, :status
199
+
200
+ # Bidirectional membership
201
+ member_of Customer, :domains, type: :set
202
+
203
+ # Conditional tracking with lambda scoring
204
+ tracked_in :active_domains, type: :sorted_set,
205
+ score: ->(domain) { domain.status == 'active' ? Time.now.to_i : 0 }
206
+ end
207
+ ```
208
+
209
+ **Relationship Operations:**
210
+
211
+ ```ruby
212
+ # Create objects
213
+ customer = Customer.new(custid: "cust123", name: "Acme Corp")
214
+ domain = Domain.new(domain_id: "dom456", name: "acme.com", status: "active")
215
+
216
+ # Establish bidirectional relationship
217
+ domain.add_to_customer_domains(customer.custid)
218
+ customer.domains.add(domain.identifier)
219
+
220
+ # Query relationships
221
+ domain.in_customer_domains?(customer.custid) # => true
222
+ customer.domains.member?(domain.identifier) # => true
223
+
224
+ # Indexed lookups
225
+ Customer.add_to_email_lookup(customer)
226
+ found_id = Customer.email_lookup.get(customer.email) # O(1) lookup
227
+
228
+ # Global tracking
229
+ Customer.add_to_all_customers(customer)
230
+ recent = Customer.all_customers.range_by_score(
231
+ (Time.now - 24.hours).to_i, '+inf'
232
+ )
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Connection Management (v2.0.0-pre+)
238
+
239
+ ### Connection Provider Pattern
240
+ Flexible connection pooling with provider-based architecture.
241
+
242
+ ```ruby
243
+ # Basic Redis connection
244
+ Familia.configure do |config|
245
+ config.redis_uri = "redis://localhost:6379/0"
246
+ end
247
+
248
+ # Connection pooling with ConnectionPool gem
249
+ require 'connection_pool'
250
+
251
+ Familia.connection_provider = lambda do |uri|
252
+ parsed = URI.parse(uri)
253
+ pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
254
+
255
+ @pools ||= {}
256
+ @pools[pool_key] ||= ConnectionPool.new(size: 10, timeout: 5) do
257
+ Redis.new(
258
+ host: parsed.host,
259
+ port: parsed.port,
260
+ db: parsed.db || 0,
261
+ connect_timeout: 1,
262
+ read_timeout: 1,
263
+ write_timeout: 1
264
+ )
265
+ end
266
+
267
+ @pools[pool_key].with { |conn| yield conn if block_given?; conn }
268
+ end
269
+ ```
270
+
271
+ ### Multi-Database Support
272
+ Configure different logical databases for different models.
273
+
274
+ ```ruby
275
+ class User < Familia::Horreum
276
+ logical_database 0 # Use database 0
277
+ field :name, :email
278
+ end
279
+
280
+ class Session < Familia::Horreum
281
+ logical_database 1 # Use database 1
282
+ field :user_id, :token
283
+ end
284
+
285
+ class Cache < Familia::Horreum
286
+ logical_database 2 # Use database 2
287
+ field :key, :value
288
+ end
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Advanced Relationship Patterns
294
+
295
+ ### Permission-Encoded Relationships (v2.0.0-pre7)
296
+ Combine timestamps with permission bits for access control.
297
+
298
+ ```ruby
299
+ class Document < Familia::Horreum
300
+ feature :relationships
301
+
302
+ identifier_field :doc_id
303
+ field :doc_id, :title, :content
304
+
305
+ # Permission constants (bit flags)
306
+ READ = 1 # 001
307
+ WRITE = 2 # 010
308
+ DELETE = 4 # 100
309
+ ADMIN = 8 # 1000
310
+ end
311
+
312
+ class UserDocumentAccess
313
+ # Encode timestamp + permissions into sorted set score
314
+ def self.encode_score(timestamp, permissions)
315
+ "#{timestamp}.#{permissions}".to_f
316
+ end
317
+
318
+ def self.decode_score(score)
319
+ parts = score.to_s.split('.')
320
+ timestamp = parts[0].to_i
321
+ permissions = parts[1] ? parts[1].to_i : 0
322
+ [timestamp, permissions]
323
+ end
324
+
325
+ # Check if user has specific permission
326
+ def self.has_permission?(permissions, required)
327
+ (permissions & required) != 0
328
+ end
329
+ end
330
+
331
+ # Usage example
332
+ user_id = "user123"
333
+ doc_id = "doc456"
334
+ timestamp = Time.now.to_i
335
+
336
+ # Grant read + write permissions
337
+ permissions = Document::READ | Document::WRITE # 3
338
+ score = UserDocumentAccess.encode_score(timestamp, permissions)
339
+
340
+ # Store in sorted set (user_id -> score with permissions)
341
+ user_documents = Familia::DataType::SortedSet.new("user:#{user_id}:documents")
342
+ user_documents.add(doc_id, score)
343
+
344
+ # Query with permission filtering
345
+ docs_with_write = user_documents.select do |doc_id, score|
346
+ _, permissions = UserDocumentAccess.decode_score(score)
347
+ UserDocumentAccess.has_permission?(permissions, Document::WRITE)
348
+ end
349
+ ```
350
+
351
+ ### Time-Series Relationships
352
+ Track relationships over time with timestamp-based scoring.
353
+
354
+ ```ruby
355
+ class ActivityTracker < Familia::Horreum
356
+ feature :relationships
357
+
358
+ # Track user activities with timestamps
359
+ tracked_in :user_activities, type: :sorted_set,
360
+ score: ->(activity) { activity.created_at }
361
+
362
+ # Track by activity type
363
+ tracked_in :activity_by_type, type: :sorted_set,
364
+ score: ->(activity) { "#{activity.activity_type}:#{activity.created_at}".hash }
365
+
366
+ field :user_id, :activity_type, :data, :created_at
367
+ end
368
+
369
+ # Query recent activities (last hour)
370
+ hour_ago = (Time.now - 1.hour).to_i
371
+ recent_activities = ActivityTracker.user_activities.range_by_score(
372
+ hour_ago, '+inf', limit: [0, 50]
373
+ )
374
+
375
+ # Get activities by type in time range
376
+ login_activities = ActivityTracker.activity_by_type.range_by_score(
377
+ "login:#{hour_ago}".hash, "login:#{Time.now.to_i}".hash
378
+ )
379
+ ```
380
+
381
+ ---
382
+
383
+ ## Data Type Usage Patterns
384
+
385
+ ### Advanced Sorted Set Operations
386
+ Leverage Redis sorted sets for rankings, time series, and scored data.
387
+
388
+ ```ruby
389
+ class Leaderboard < Familia::Horreum
390
+ identifier_field :game_id
391
+ field :game_id, :name
392
+ sorted_set :scores
393
+ end
394
+
395
+ leaderboard = Leaderboard.new(game_id: "game1", name: "Daily Challenge")
396
+
397
+ # Add player scores
398
+ leaderboard.scores.add("player1", 1500)
399
+ leaderboard.scores.add("player2", 2300)
400
+ leaderboard.scores.add("player3", 1800)
401
+
402
+ # Get top 10 players
403
+ top_players = leaderboard.scores.range(0, 9, with_scores: true, order: 'DESC')
404
+ # => [["player2", 2300.0], ["player3", 1800.0], ["player1", 1500.0]]
405
+
406
+ # Get player rank
407
+ rank = leaderboard.scores.rank("player1", order: 'DESC') # => 2 (0-indexed)
408
+
409
+ # Get score range
410
+ mid_tier = leaderboard.scores.range_by_score(1000, 2000, with_scores: true)
411
+
412
+ # Increment score atomically
413
+ leaderboard.scores.increment("player1", 100) # Add 100 to existing score
414
+ ```
415
+
416
+ ### List-Based Queues and Feeds
417
+ Use Redis lists for queues, feeds, and ordered data.
418
+
419
+ ```ruby
420
+ class TaskQueue < Familia::Horreum
421
+ identifier_field :queue_name
422
+ field :queue_name
423
+ list :tasks
424
+ end
425
+
426
+ class ActivityFeed < Familia::Horreum
427
+ identifier_field :user_id
428
+ field :user_id
429
+ list :activities
430
+ end
431
+
432
+ # Task queue operations
433
+ queue = TaskQueue.new(queue_name: "email_processing")
434
+
435
+ # Add tasks to queue (right push)
436
+ queue.tasks.push({
437
+ type: "send_email",
438
+ recipient: "user@example.com",
439
+ template: "welcome"
440
+ }.to_json)
441
+
442
+ # Process tasks (left pop - FIFO)
443
+ next_task = queue.tasks.pop # Atomic pop from left
444
+ task_data = JSON.parse(next_task) if next_task
445
+
446
+ # Activity feed with size limit
447
+ feed = ActivityFeed.new(user_id: "user123")
448
+
449
+ # Add activity (keep last 100)
450
+ feed.activities.unshift("User logged in at #{Time.now}")
451
+ feed.activities.trim(0, 99) # Keep only last 100 items
452
+
453
+ # Get recent activities
454
+ recent = feed.activities.range(0, 9) # Get 10 most recent
455
+ ```
456
+
457
+ ### Hash-Based Configuration Storage
458
+ Store structured configuration and key-value data.
459
+
460
+ ```ruby
461
+ class UserPreferences < Familia::Horreum
462
+ identifier_field :user_id
463
+ field :user_id
464
+ hash :settings
465
+ hash :feature_flags
466
+ end
467
+
468
+ prefs = UserPreferences.new(user_id: "user123")
469
+
470
+ # Set individual preferences
471
+ prefs.settings.set("theme", "dark")
472
+ prefs.settings.set("notifications", "true")
473
+ prefs.settings.set("timezone", "UTC-5")
474
+
475
+ # Batch set multiple values
476
+ prefs.feature_flags.update({
477
+ "beta_ui" => "true",
478
+ "new_dashboard" => "false",
479
+ "advanced_features" => "true"
480
+ })
481
+
482
+ # Get preferences
483
+ theme = prefs.settings.get("theme") # => "dark"
484
+ all_settings = prefs.settings.all # => Hash of all settings
485
+
486
+ # Check feature flags
487
+ beta_enabled = prefs.feature_flags.get("beta_ui") == "true"
488
+ ```
489
+
490
+ ---
491
+
492
+ ## Error Handling and Validation
493
+
494
+ ### Connection Error Handling
495
+ Robust error handling for Redis connection issues.
496
+
497
+ ```ruby
498
+ class ResilientService < Familia::Horreum
499
+ field :name, :data
500
+
501
+ def self.with_fallback(&block)
502
+ retries = 3
503
+ begin
504
+ yield
505
+ rescue Redis::ConnectionError, Redis::TimeoutError => e
506
+ retries -= 1
507
+ if retries > 0
508
+ sleep(0.1 * (4 - retries)) # Exponential backoff
509
+ retry
510
+ else
511
+ Familia.warn "Redis operation failed after retries: #{e.message}"
512
+ nil # Return nil or handle gracefully
513
+ end
514
+ end
515
+ end
516
+
517
+ def save_with_fallback
518
+ self.class.with_fallback { save }
519
+ end
520
+ end
521
+ ```
522
+
523
+ ### Data Validation Patterns
524
+ Implement validation in model classes.
525
+
526
+ ```ruby
527
+ class User < Familia::Horreum
528
+ field :email, :username, :age
529
+
530
+ def valid?
531
+ errors.clear
532
+ validate_email
533
+ validate_username
534
+ validate_age
535
+ errors.empty?
536
+ end
537
+
538
+ def errors
539
+ @errors ||= []
540
+ end
541
+
542
+ private
543
+
544
+ def validate_email
545
+ unless email&.include?('@')
546
+ errors << "Email must be valid"
547
+ end
548
+ end
549
+
550
+ def validate_username
551
+ if username.nil? || username.length < 3
552
+ errors << "Username must be at least 3 characters"
553
+ end
554
+ end
555
+
556
+ def validate_age
557
+ unless age.is_a?(Integer) && age > 0
558
+ errors << "Age must be a positive integer"
559
+ end
560
+ end
561
+ end
562
+
563
+ # Usage
564
+ user = User.new(email: "invalid", username: "ab", age: -5)
565
+ if user.valid?
566
+ user.save
567
+ else
568
+ puts "Validation errors: #{user.errors.join(', ')}"
569
+ end
570
+ ```
571
+
572
+ ---
573
+
574
+ ## Performance Optimization
575
+
576
+ ### Batch Operations
577
+ Minimize Redis round trips with batch operations.
578
+
579
+ ```ruby
580
+ # Instead of multiple individual operations
581
+ users = []
582
+ 100.times do |i|
583
+ user = User.new(name: "User #{i}", email: "user#{i}@example.com")
584
+ users << user
585
+ end
586
+
587
+ # Use Redis pipelining for batch saves
588
+ User.transaction do |redis|
589
+ users.each do |user|
590
+ # All operations batched in transaction
591
+ user.object.set_all(user.to_hash)
592
+ User.email_index.set(user.email, user.identifier)
593
+ end
594
+ end
595
+ ```
596
+
597
+ ### Memory Optimization
598
+ Efficient memory usage patterns.
599
+
600
+ ```ruby
601
+ class CacheEntry < Familia::Horreum
602
+ feature :expiration
603
+
604
+ field :key, :value, :created_at
605
+ default_expiration 1.hour
606
+
607
+ # Use shorter field names to reduce memory
608
+ field :k, :v, :c # Instead of key, value, created_at
609
+
610
+ # Compress large values
611
+ def value=(val)
612
+ @value = val.length > 1000 ? Zlib.deflate(val) : val
613
+ end
614
+
615
+ def value
616
+ val = @value || ""
617
+ val.start_with?("\x78\x9c") ? Zlib.inflate(val) : val
618
+ rescue Zlib::DataError
619
+ @value # Return original if decompression fails
620
+ end
621
+ end
622
+ ```
623
+
624
+ ### Connection Pool Sizing
625
+ Configure connection pools based on application needs.
626
+
627
+ ```ruby
628
+ # High-throughput application
629
+ Familia.connection_provider = lambda do |uri|
630
+ ConnectionPool.new(size: 25, timeout: 5) do
631
+ Redis.new(uri, connect_timeout: 0.1, read_timeout: 1, write_timeout: 1)
632
+ end.with { |conn| yield conn if block_given?; conn }
633
+ end
634
+
635
+ # Memory-constrained environment
636
+ Familia.connection_provider = lambda do |uri|
637
+ ConnectionPool.new(size: 5, timeout: 10) do
638
+ Redis.new(uri, connect_timeout: 2, read_timeout: 5, write_timeout: 5)
639
+ end.with { |conn| yield conn if block_given?; conn }
640
+ end
641
+ ```
642
+
643
+ ---
644
+
645
+ ## Migration and Upgrading
646
+
647
+ ### From v1.x to v2.0.0-pre
648
+ Key changes and migration steps.
649
+
650
+ ```ruby
651
+ # OLD v1.x syntax
652
+ class User < Familia
653
+ identifier :email
654
+ string :name
655
+ list :sessions
656
+ end
657
+
658
+ # NEW v2.0.0-pre syntax
659
+ class User < Familia::Horreum
660
+ identifier_field :email # Updated method name
661
+ field :name # Generic field method
662
+ list :sessions # Data types unchanged
663
+ end
664
+
665
+ # Feature activation (NEW)
666
+ class User < Familia::Horreum
667
+ feature :expiration # Explicit feature activation
668
+ feature :safe_dump
669
+
670
+ identifier_field :email
671
+ field :name
672
+ list :sessions
673
+
674
+ default_expiration 24.hours # Feature-specific methods
675
+ safe_dump_fields :name # Feature-specific methods
676
+ end
677
+ ```
678
+
679
+ ### Encryption Migration
680
+ Migrating existing fields to encrypted storage.
681
+
682
+ ```ruby
683
+ # Step 1: Add feature without changing existing fields
684
+ class User < Familia::Horreum
685
+ feature :encrypted_fields # Add feature
686
+
687
+ field :name, :email
688
+ field :api_key # Still plaintext during migration
689
+ end
690
+
691
+ # Step 2: Migrate data with dual read/write
692
+ class User < Familia::Horreum
693
+ feature :encrypted_fields
694
+
695
+ field :name, :email
696
+ encrypted_field :api_key # Now encrypted
697
+
698
+ # Temporary migration method
699
+ def migrate_api_key!
700
+ if raw_api_key = object.get("api_key") # Read old plaintext
701
+ self.api_key = raw_api_key # Write as encrypted
702
+ object.delete("api_key") # Remove plaintext
703
+ save
704
+ end
705
+ end
706
+ end
707
+
708
+ # Step 3: Run migration for all users
709
+ User.all.each(&:migrate_api_key!)
710
+ ```
711
+
712
+ ---
713
+
714
+ ## Testing Patterns
715
+
716
+ ### Test Helpers and Utilities
717
+ Common patterns for testing Familia applications.
718
+
719
+ ```ruby
720
+ # test_helper.rb
721
+ require 'familia'
722
+
723
+ # Use separate Redis database for tests
724
+ Familia.configure do |config|
725
+ config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:6379/15')
726
+ end
727
+
728
+ module TestHelpers
729
+ def setup_redis
730
+ # Clear test database
731
+ Familia.connection.flushdb
732
+ end
733
+
734
+ def teardown_redis
735
+ Familia.connection.flushdb
736
+ end
737
+
738
+ def create_test_user(**attrs)
739
+ User.new({
740
+ email: "test@example.com",
741
+ name: "Test User",
742
+ created_at: Time.now.to_i
743
+ }.merge(attrs))
744
+ end
745
+ end
746
+
747
+ # In test files
748
+ class UserTest < Minitest::Test
749
+ include TestHelpers
750
+
751
+ def setup
752
+ setup_redis
753
+ end
754
+
755
+ def teardown
756
+ teardown_redis
757
+ end
758
+
759
+ def test_user_creation
760
+ user = create_test_user(name: "Alice")
761
+ user.save
762
+
763
+ assert user.exists?
764
+ assert_equal "Alice", user.name
765
+ end
766
+
767
+ def test_relationships
768
+ user = create_test_user
769
+ user.save
770
+
771
+ domain = Domain.new(domain_id: "test_domain", name: "test.com")
772
+ domain.save
773
+
774
+ # Test relationship
775
+ domain.add_to_user_domains(user.identifier)
776
+ assert domain.in_user_domains?(user.identifier)
777
+ end
778
+ end
779
+ ```
780
+
781
+ ---
782
+
783
+ ## Resources and References
784
+
785
+ ### Key Configuration
786
+ Essential configuration options for Familia v2.0.0-pre.
787
+
788
+ ```ruby
789
+ Familia.configure do |config|
790
+ # Basic Redis connection
791
+ config.redis_uri = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
792
+
793
+ # Connection provider for pooling (optional)
794
+ config.connection_provider = MyConnectionProvider
795
+
796
+ # Encryption configuration (for encrypted_fields feature)
797
+ config.encryption_keys = {
798
+ v1: ENV['FAMILIA_ENCRYPTION_KEY_V1'],
799
+ v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
800
+ }
801
+ config.current_key_version = :v2
802
+
803
+ # Debugging and logging
804
+ config.debug = ENV['FAMILIA_DEBUG'] == 'true'
805
+ config.enable_database_logging = ENV['FAMILIA_LOG_REDIS'] == 'true'
806
+ end
807
+ ```
808
+
809
+ ### Documentation Links
810
+ - [Familia Repository](https://github.com/delano/familia)
811
+ - [Wiki Home](docs/wiki/Home.md)
812
+ - [Feature System Guide](docs/wiki/Feature-System-Guide.md)
813
+ - [Relationships Guide](docs/wiki/Relationships-Guide.md)
814
+ - [Encrypted Fields Overview](docs/wiki/Encrypted-Fields-Overview.md)
815
+ - [Connection Pooling Guide](docs/wiki/Connection-Pooling-Guide.md)
816
+
817
+ ### Version Information
818
+ - **Current Version**: v2.0.0.pre6 (as of version.rb)
819
+ - **Target Version**: v2.0.0.pre7 (relationships release)
820
+ - **Ruby Compatibility**: 3.0+ (3.4+ recommended for optimal threading)
821
+ - **Redis Compatibility**: 6.0+ (Valkey compatible)
822
+
823
+ This technical reference covers the major components and usage patterns available in Familia v2.0.0-pre series. For complete API documentation, see the generated YARD docs and wiki guides.