familia 2.0.0.pre3 → 2.0.0.pre5

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop_todo.yml +17 -17
  4. data/CLAUDE.md +3 -3
  5. data/Gemfile +5 -1
  6. data/Gemfile.lock +18 -3
  7. data/README.md +36 -157
  8. data/TEST_COVERAGE.md +40 -0
  9. data/docs/overview.md +359 -0
  10. data/docs/wiki/API-Reference.md +270 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +64 -0
  12. data/docs/wiki/Home.md +49 -0
  13. data/docs/wiki/Implementation-Guide.md +183 -0
  14. data/docs/wiki/Security-Model.md +143 -0
  15. data/lib/familia/base.rb +18 -27
  16. data/lib/familia/connection.rb +6 -5
  17. data/lib/familia/{datatype → data_type}/commands.rb +2 -5
  18. data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
  19. data/lib/familia/{datatype → data_type}/types/hashkey.rb +2 -2
  20. data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
  21. data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
  22. data/lib/familia/{datatype → data_type}/types/string.rb +2 -1
  23. data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
  24. data/lib/familia/{datatype.rb → data_type.rb} +10 -12
  25. data/lib/familia/encryption/manager.rb +102 -0
  26. data/lib/familia/encryption/provider.rb +49 -0
  27. data/lib/familia/encryption/providers/aes_gcm_provider.rb +103 -0
  28. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
  29. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +118 -0
  30. data/lib/familia/encryption/registry.rb +50 -0
  31. data/lib/familia/encryption.rb +178 -0
  32. data/lib/familia/encryption_request_cache.rb +68 -0
  33. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +153 -0
  34. data/lib/familia/features/encrypted_fields.rb +28 -0
  35. data/lib/familia/features/expiration.rb +107 -77
  36. data/lib/familia/features/quantization.rb +5 -9
  37. data/lib/familia/features/relatable_objects.rb +2 -4
  38. data/lib/familia/features/safe_dump.rb +14 -17
  39. data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
  40. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
  41. data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
  42. data/lib/familia/features/transient_fields.rb +47 -0
  43. data/lib/familia/features.rb +40 -24
  44. data/lib/familia/field_type.rb +270 -0
  45. data/lib/familia/horreum/connection.rb +8 -11
  46. data/lib/familia/horreum/{commands.rb → database_commands.rb} +7 -19
  47. data/lib/familia/horreum/definition_methods.rb +453 -0
  48. data/lib/familia/horreum/{class_methods.rb → management_methods.rb} +19 -229
  49. data/lib/familia/horreum/serialization.rb +46 -18
  50. data/lib/familia/horreum/settings.rb +10 -2
  51. data/lib/familia/horreum/utils.rb +9 -10
  52. data/lib/familia/horreum.rb +18 -10
  53. data/lib/familia/logging.rb +14 -14
  54. data/lib/familia/settings.rb +39 -3
  55. data/lib/familia/utils.rb +45 -0
  56. data/lib/familia/version.rb +1 -1
  57. data/lib/familia.rb +2 -1
  58. data/try/core/base_enhancements_try.rb +115 -0
  59. data/try/core/connection_try.rb +0 -1
  60. data/try/core/errors_try.rb +0 -1
  61. data/try/core/familia_extended_try.rb +3 -4
  62. data/try/core/familia_try.rb +0 -1
  63. data/try/core/pools_try.rb +2 -2
  64. data/try/core/secure_identifier_try.rb +0 -1
  65. data/try/core/settings_try.rb +0 -1
  66. data/try/core/utils_try.rb +0 -1
  67. data/try/{datatypes → data_types}/boolean_try.rb +1 -2
  68. data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
  69. data/try/{datatypes → data_types}/hash_try.rb +1 -2
  70. data/try/{datatypes → data_types}/list_try.rb +1 -2
  71. data/try/{datatypes → data_types}/set_try.rb +1 -2
  72. data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
  73. data/try/{datatypes → data_types}/string_try.rb +1 -2
  74. data/try/debugging/README.md +32 -0
  75. data/try/debugging/cache_behavior_tracer.rb +91 -0
  76. data/try/debugging/encryption_method_tracer.rb +138 -0
  77. data/try/debugging/provider_diagnostics.rb +110 -0
  78. data/try/edge_cases/hash_symbolization_try.rb +0 -1
  79. data/try/edge_cases/json_serialization_try.rb +0 -1
  80. data/try/edge_cases/reserved_keywords_try.rb +42 -11
  81. data/try/encryption/config_persistence_try.rb +192 -0
  82. data/try/encryption/encryption_core_try.rb +328 -0
  83. data/try/encryption/instance_variable_scope_try.rb +31 -0
  84. data/try/encryption/module_loading_try.rb +28 -0
  85. data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
  86. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
  87. data/try/encryption/roundtrip_validation_try.rb +28 -0
  88. data/try/encryption/secure_memory_handling_try.rb +125 -0
  89. data/try/features/encrypted_fields_core_try.rb +117 -0
  90. data/try/features/encrypted_fields_integration_try.rb +220 -0
  91. data/try/features/encrypted_fields_no_cache_security_try.rb +205 -0
  92. data/try/features/encrypted_fields_security_try.rb +370 -0
  93. data/try/features/encryption_fields/aad_protection_try.rb +53 -0
  94. data/try/features/encryption_fields/context_isolation_try.rb +120 -0
  95. data/try/features/encryption_fields/error_conditions_try.rb +116 -0
  96. data/try/features/encryption_fields/fresh_key_derivation_try.rb +122 -0
  97. data/try/features/encryption_fields/fresh_key_try.rb +163 -0
  98. data/try/features/encryption_fields/key_rotation_try.rb +117 -0
  99. data/try/features/encryption_fields/memory_security_try.rb +37 -0
  100. data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
  101. data/try/features/encryption_fields/nonce_uniqueness_try.rb +54 -0
  102. data/try/features/encryption_fields/thread_safety_try.rb +199 -0
  103. data/try/features/expiration_try.rb +0 -1
  104. data/try/features/feature_dependencies_try.rb +159 -0
  105. data/try/features/quantization_try.rb +0 -1
  106. data/try/features/real_feature_integration_try.rb +148 -0
  107. data/try/features/relatable_objects_try.rb +0 -1
  108. data/try/features/safe_dump_advanced_try.rb +0 -1
  109. data/try/features/safe_dump_try.rb +0 -1
  110. data/try/features/transient_fields/redacted_string_try.rb +248 -0
  111. data/try/features/transient_fields/refresh_reset_try.rb +164 -0
  112. data/try/features/transient_fields/simple_refresh_test.rb +50 -0
  113. data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
  114. data/try/features/transient_fields_core_try.rb +181 -0
  115. data/try/features/transient_fields_integration_try.rb +260 -0
  116. data/try/helpers/test_helpers.rb +42 -0
  117. data/try/horreum/base_try.rb +157 -3
  118. data/try/horreum/class_methods_try.rb +27 -36
  119. data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
  120. data/try/horreum/field_categories_try.rb +118 -0
  121. data/try/horreum/field_definition_try.rb +96 -0
  122. data/try/horreum/initialization_try.rb +0 -1
  123. data/try/horreum/relations_try.rb +0 -1
  124. data/try/horreum/serialization_persistent_fields_try.rb +165 -0
  125. data/try/horreum/serialization_try.rb +2 -3
  126. data/try/memory/memory_basic_test.rb +73 -0
  127. data/try/memory/memory_detailed_test.rb +121 -0
  128. data/try/memory/memory_docker_ruby_dump.sh +80 -0
  129. data/try/memory/memory_search_for_string.rb +83 -0
  130. data/try/memory/test_actual_redactedstring_protection.rb +38 -0
  131. data/try/models/customer_safe_dump_try.rb +0 -1
  132. data/try/models/customer_try.rb +0 -1
  133. data/try/models/datatype_base_try.rb +1 -2
  134. data/try/models/familia_object_try.rb +0 -1
  135. metadata +85 -18
data/docs/wiki/Home.md ADDED
@@ -0,0 +1,49 @@
1
+ # Familia Encrypted Fields Documentation
2
+
3
+ Welcome to the Familia encrypted fields feature documentation. This Wiki provides comprehensive guides for implementing field-level encryption in your Familia-based applications.
4
+
5
+ ## 📚 Documentation Structure
6
+
7
+ ### Essential Reading (Start Here)
8
+
9
+ 1. **[Encrypted Fields Overview](Encrypted-Fields-Overview.md)** - Quick introduction and basic usage
10
+
11
+ 2. **[Implementation Guide](Implementation-Guide.md)** - Configuration details and advanced usage
12
+
13
+ 3. **[API Reference](API-Reference.md)** - Class and method documentation
14
+
15
+ ### Deep Dives
16
+
17
+ 4. **[Security Model](Security-Model.md)** - Cryptographic design and Protected vs unprotected scenarios
18
+
19
+ ### Operations (As Needed)
20
+
21
+ 5. **[Migration Guide](Migration-Guide.md)** - Upgrading existing fields _(coming soon)_
22
+ 6. **[Key Management](Key-Management.md)** - Rotation and best practices _(coming soon)_
23
+
24
+ ## 🚀 Quick Start
25
+
26
+ ```ruby
27
+ # 1. Add encrypted field to your model
28
+ class User < Familia::Horreum
29
+ encrypted_field :secret_recipe
30
+ end
31
+
32
+ # 2. Configure encryption key
33
+ Familia.configure do |config|
34
+ config.encryption_keys = { v1: ENV['FAMILIA_ENCRYPTION_KEY'] }
35
+ config.current_key_version = :v1
36
+ end
37
+
38
+ # 3. Use like any other field
39
+ user = User.new(secret_recipe: "donna's cookies")
40
+ user.save
41
+ user.secret_recipe # => "donna's cookies" (automatically decrypted)
42
+ ```
43
+
44
+
45
+ ## Related Resources
46
+
47
+ - [Familia README](https://github.com/delano/familia) - Main project documentation
48
+ - [Issue #57](https://github.com/delano/familia/issues/57) - Original feature proposal
49
+ - [Issue #58](https://github.com/delano/familia/issues/58) - Wiki documentation tracking
@@ -0,0 +1,183 @@
1
+ # Implementation Guide
2
+
3
+ ## Architecture Overview
4
+
5
+ The encrypted fields feature extends Familia's existing field system with transformation hooks:
6
+
7
+ ```
8
+ User Input → Field Setter → Serialize Transform → Encryption → Redis
9
+ Redis → Decryption → Deserialize Transform → Field Getter → User Output
10
+ ```
11
+
12
+ ## Core Components
13
+
14
+ ### 1. Transform Hooks
15
+
16
+ Fields now support transform callbacks:
17
+
18
+ ```ruby
19
+ FieldDefinition = Data.define(
20
+ :field_name,
21
+ :method_name,
22
+ :serialize_transform, # Called before storage
23
+ :deserialize_transform # Called after retrieval
24
+ )
25
+ ```
26
+
27
+ ### 2. Encryption Module
28
+
29
+ Handles the cryptographic operations:
30
+
31
+ ```ruby
32
+ module Familia::Encryption
33
+ # Encrypts with field-specific derived key
34
+ def self.encrypt(plaintext, context:, additional_data: nil)
35
+
36
+ # Decrypts and verifies authenticity
37
+ def self.decrypt(ciphertext, context:, additional_data: nil)
38
+ end
39
+ ```
40
+
41
+ ### 3. Key Derivation
42
+
43
+ Each field gets a unique encryption key:
44
+
45
+ ```
46
+ Master Key + Field Context → HKDF/BLAKE2b → Field-Specific Key
47
+ ```
48
+
49
+ ## Implementation Steps
50
+
51
+ ### Step 1: Enable Encryption
52
+
53
+ ```ruby
54
+ class MyModel < Familia::Horreum
55
+ # Add the feature (optional if globally enabled)
56
+ feature :encryption
57
+
58
+ # Define encrypted fields
59
+ encrypted_field :sensitive_data
60
+ encrypted_field :api_key
61
+ end
62
+ ```
63
+
64
+ ### Step 2: Configure Keys
65
+
66
+ ```ruby
67
+ # config/initializers/familia.rb
68
+ Familia.configure do |config|
69
+ config.encryption_keys = {
70
+ v1: ENV['FAMILIA_ENCRYPTION_KEY_V1']
71
+ }
72
+ config.current_key_version = :v1
73
+ end
74
+
75
+ # Validate configuration at startup
76
+ Familia::Encryption.validate_configuration!
77
+ ```
78
+
79
+ ### Step 3: Generate Keys
80
+
81
+ ```bash
82
+ # Generate a secure key
83
+ $ familia encryption:generate_key --bits 256
84
+ # => base64_encoded_key_here
85
+
86
+ # Add to environment
87
+ $ echo "FAMILIA_ENCRYPTION_KEY_V1=base64_encoded_key_here" >> .env
88
+ ```
89
+
90
+ ## Advanced Usage
91
+
92
+ ### Custom Field Names
93
+
94
+ ```ruby
95
+ encrypted_field :favorite_snack, as: :top_secret_snack_preference
96
+ ```
97
+
98
+ ### Passphrase Protection
99
+
100
+ ```ruby
101
+ class Vault < Familia::Horreum
102
+ encrypted_field :secret
103
+
104
+ def unlock(passphrase)
105
+ # Passphrase becomes part of encryption context
106
+ self.secret(passphrase_value: passphrase)
107
+ end
108
+ end
109
+ ```
110
+
111
+ ### Batch Operations
112
+
113
+ ```ruby
114
+ # Efficient bulk encryption
115
+ customers = Customer.batch_create([
116
+ { email: 'user1@example.com', favorite_snack: 'chocolate chip cookies' },
117
+ { email: 'user2@example.com', favorite_snack: 'leftover pizza' }
118
+ ])
119
+ ```
120
+
121
+ ## Performance Optimization
122
+
123
+ ### Request-Scoped Key Caching
124
+
125
+ ```ruby
126
+ # Automatically enabled in web frameworks
127
+ # Manual control for other contexts:
128
+ Familia::Encryption.with_key_cache do
129
+ # All operations here share derived keys
130
+ Customer.find_each { |c| c.process }
131
+ end
132
+ ```
133
+
134
+ ### Memory Management
135
+
136
+ With libsodium installed:
137
+ - Keys are automatically wiped from memory
138
+ - Plaintext values cleared after use
139
+
140
+ ## Testing
141
+
142
+ ```ruby
143
+ # Test helper
144
+ RSpec.configure do |config|
145
+ config.include Familia::EncryptionTestHelpers
146
+
147
+ config.around(:each, :encryption) do |example|
148
+ with_test_encryption_keys { example.run }
149
+ end
150
+ end
151
+
152
+ # In tests
153
+ it "encrypts sensitive fields", :encryption do
154
+ user = User.create(favorite_snack: "leftover pizza")
155
+
156
+ # Verify encryption in Redis
157
+ raw_value = redis.hget(user.rediskey, "favorite_snack")
158
+ expect(raw_value).not_to include("leftover pizza")
159
+ expect(JSON.parse(raw_value)).to have_key("ciphertext")
160
+ end
161
+ ```
162
+
163
+ ## Troubleshooting
164
+
165
+ ### Common Issues
166
+
167
+ 1. **"No encryption key configured"**
168
+ - Ensure `FAMILIA_ENCRYPTION_KEY` is set
169
+ - Check `Familia.config.encryption_keys`
170
+
171
+ 2. **"Decryption failed"**
172
+ - Verify correct key version
173
+ - Check if data was encrypted with different key
174
+
175
+ 3. **Performance degradation**
176
+ - Enable key caching
177
+ - Consider installing libsodium gem
178
+
179
+ ## Next Steps
180
+
181
+ - [Security Model](Security-Model) - Understand the cryptographic design
182
+ - [Key Management](Key-Management) - Rotation and best practices
183
+ - [Migration Guide](Migration-Guide) - Upgrade existing fields
@@ -0,0 +1,143 @@
1
+ # Security Model
2
+
3
+ ## Cryptographic Design
4
+
5
+ ### Encryption Algorithms
6
+
7
+ **Primary (with libsodium):**
8
+ - Algorithm: XChaCha20-Poly1305
9
+ - Key Size: 256 bits
10
+ - Nonce Size: 192 bits
11
+ - Authentication Tag: 128 bits
12
+
13
+ **Fallback (with OpenSSL):**
14
+ - Algorithm: AES-256-GCM
15
+ - Key Size: 256 bits
16
+ - IV Size: 96 bits
17
+ - Authentication Tag: 128 bits
18
+
19
+ ### Key Derivation
20
+
21
+ Each field gets a unique key derived from the master key:
22
+
23
+ ```
24
+ Field Key = KDF(Master Key, Context)
25
+
26
+ Where Context = "ClassName:field_name:record_identifier"
27
+ ```
28
+
29
+ **KDF Functions:**
30
+ - Libsodium: BLAKE2b with personalization
31
+ - OpenSSL: HKDF-SHA256
32
+
33
+ ### Ciphertext Format
34
+
35
+ ```json
36
+ {
37
+ "library": "libsodium",
38
+ "algorithm": "xchacha20poly1305",
39
+ "nonce": "base64_encoded_nonce",
40
+ "ciphertext": "base64_encoded_ciphertext",
41
+ "key_version": "v1_2504"
42
+ }
43
+ ```
44
+
45
+ ## Threat Model
46
+
47
+ ### Protected Against
48
+
49
+ #### Database Compromise
50
+ - All sensitive fields encrypted with strong keys
51
+ - Attackers see only ciphertext
52
+
53
+ #### Field Value Swapping
54
+ - Field-specific key derivation prevents cross-field decryption
55
+ - Swapped values fail to decrypt
56
+
57
+ #### Replay Attacks
58
+ - Each encryption uses unique random nonce
59
+ - Old values remain valid but are distinct encryptions
60
+
61
+ #### Tampering
62
+ - Authenticated encryption (Poly1305/GCM)
63
+ - Modified ciphertext fails authentication
64
+
65
+ ### Not Protected Against
66
+
67
+ #### Application Memory Compromise
68
+ - Plaintext values exist in Ruby memory
69
+ - Mitigation: Use libsodium for memory wiping, minimize plaintext lifetime
70
+
71
+ #### Master Key Compromise
72
+ - All encrypted data compromised if keys obtained
73
+ - Mitigation: Secure key storage, regular rotation, hardware security modules
74
+
75
+ #### Side-Channel Attacks
76
+ - Key recovery through timing/power analysis
77
+ - Mitigation: Libsodium provides constant-time operations
78
+
79
+ ## Additional Security Features
80
+
81
+ ### Passphrase Protection
82
+
83
+ For ultra-sensitive fields, add user passphrases:
84
+
85
+ ```ruby
86
+ encrypted_field :love_letter
87
+
88
+ # Passphrase required for decryption
89
+ vault.love_letter(passphrase_value: user_passphrase)
90
+ ```
91
+
92
+ **How it works:**
93
+ 1. Passphrase hashed with SHA-256
94
+ 2. Hash included in Additional Authenticated Data (AAD)
95
+ 3. Wrong passphrase = authentication failure
96
+ 4. Passphrase never stored, only verified
97
+
98
+ ### Memory Safety
99
+
100
+ **With libsodium:**
101
+ - Automatic zeroing of sensitive memory
102
+ - Constant-time comparisons
103
+ - Protected memory pages when available
104
+
105
+ **Without libsodium:**
106
+ - Warning logged about reduced security
107
+ - Ruby GC may retain plaintext copies
108
+ - Timing attacks theoretically possible
109
+
110
+ ### RedactedString
111
+
112
+ Prevents accidental logging of sensitive data:
113
+
114
+ ```ruby
115
+ class RedactedString < String
116
+ def to_s
117
+ '[REDACTED]'
118
+ end
119
+
120
+ def inspect
121
+ '[REDACTED]'
122
+ end
123
+ end
124
+
125
+ # In logs:
126
+ logger.info "Love letter: #{user.love_letter}" # => "Love letter: [REDACTED]"
127
+ ```
128
+
129
+ ## Security Checklist
130
+
131
+ ### Development
132
+
133
+ - [ ] Never log plaintext sensitive fields
134
+ - [ ] Use RedactedString for extra protection
135
+ - [ ] Use libsodium for production when possible
136
+ - [ ] Validate encryption at startup
137
+ - [ ] Test encryption round-trips
138
+
139
+ ### Operations
140
+
141
+ - [ ] Regular key rotation schedule
142
+ - [ ] Monitor decryption failures
143
+ - [ ] Log field access patterns for auditing purposes
data/lib/familia/base.rb CHANGED
@@ -14,7 +14,8 @@ module Familia
14
14
  # @see Familia::DataType
15
15
  #
16
16
  module Base
17
- @features = nil
17
+ @features_available = nil
18
+ @feature_definitions = nil
18
19
  @dump_method = :to_json
19
20
  @load_method = :from_json
20
21
 
@@ -29,43 +30,33 @@ module Familia
29
30
  end
30
31
 
31
32
  class << self
32
- attr_reader :features
33
+ attr_reader :features_available, :feature_definitions
33
34
  attr_accessor :dump_method, :load_method
34
35
 
35
- def add_feature(klass, methname)
36
- @features ||= {}
37
- Familia.ld "[#{self}] Adding feature #{klass} as #{methname.inspect}"
36
+ def add_feature(klass, feature_name, depends_on: [])
37
+ @features_available ||= {}
38
+ Familia.ld "[#{self}] Adding feature #{klass} as #{feature_name.inspect}"
38
39
 
39
- features[methname] = klass
40
- end
41
- end
40
+ # Create field definition object
41
+ feature_def = FeatureDefinition.new(
42
+ name: feature_name,
43
+ depends_on: depends_on,
44
+ )
42
45
 
43
- # Base implementation of update_expiration that maintains API compatibility
44
- # with the :expiration feature's implementation.
45
- #
46
- # This is a no-op implementation that gets overridden by features like
47
- # :expiration. It accepts an optional default_expiration parameter to maintain interface
48
- # compatibility with the overriding implementations.
49
- #
50
- # @param default_expiration [Integer, nil] Time To Live in seconds (ignored in base implementation)
51
- # @return [nil] Always returns nil
52
- #
53
- # @note This is a no-op implementation. Classes that need expiration
54
- # functionality should include the :expiration feature.
55
- #
56
- def update_expiration(default_expiration: nil)
57
- Familia.ld "[update_expiration] Feature not enabled for #{self.class}. Key: #{dbkey} (caller: #{caller(1..1)})"
58
- nil
46
+ # Track field definitions after defining field methods
47
+ @feature_definitions ||= {}
48
+ @feature_definitions[feature_name] = feature_def
49
+
50
+ features_available[feature_name] = klass
51
+ end
59
52
  end
60
53
 
61
54
  def generate_id
62
- @identifier ||= Familia.generate_id
63
- @identifier
55
+ @identifier ||= Familia.generate_id # rubocop:disable Naming/MemoizedInstanceVariableName
64
56
  end
65
57
 
66
58
  def uuid
67
59
  @uuid ||= SecureRandom.uuid
68
- @uuid
69
60
  end
70
61
  end
71
62
  end
@@ -106,18 +106,19 @@ module Familia
106
106
  # Always pass normalized URI with database to provider
107
107
  # Provider MUST return connection already on the correct database
108
108
  parsed_uri = normalize_uri(uri)
109
- connection = connection_provider.call(parsed_uri.to_s)
109
+ client = connection_provider.call(parsed_uri.to_s)
110
110
 
111
111
  # In debug mode, verify the provider honored the contract
112
- if Familia.debug? && connection.respond_to?(:client)
113
- current_db = connection.client.db
112
+ if Familia.debug? && client.respond_to?(:client)
113
+ current_db = client.connection[:db]
114
114
  expected_db = parsed_uri.db || 0
115
+ Familia.ld "Connection provider returned client on DB #{current_db}, expected #{expected_db}"
115
116
  if current_db != expected_db
116
- Familia.warn "Connection provider returned connection on DB #{current_db}, expected #{expected_db}"
117
+ Familia.warn "Connection provider returned client on DB #{current_db}, expected #{expected_db}"
117
118
  end
118
119
  end
119
120
 
120
- return connection
121
+ return client
121
122
  end
122
123
 
123
124
  # Third priority: Fallback behavior or error
@@ -1,11 +1,9 @@
1
- # lib/familia/datatype/commands.rb
1
+ # lib/familia/data_type/commands.rb
2
2
 
3
3
  class Familia::DataType
4
-
5
4
  # Must be included in all DataType classes to provide Redis
6
5
  # commands. The class must have a dbkey method.
7
6
  module Commands
8
-
9
7
  def move(logical_database)
10
8
  dbclient.move dbkey, logical_database
11
9
  end
@@ -52,8 +50,7 @@ class Familia::DataType
52
50
  end
53
51
 
54
52
  def echo(meth, trace)
55
- dbclient.echo "[#{self.class}\##{meth}] #{trace} (#{@opts[:class]}\#)"
53
+ dbclient.echo "[#{self.class}##{meth}] #{trace} (#{@opts[:class]}#)"
56
54
  end
57
-
58
55
  end
59
56
  end
@@ -1,9 +1,7 @@
1
- # lib/familia/datatype/serialization.rb
1
+ # lib/familia/data_type/serialization.rb
2
2
 
3
3
  class Familia::DataType
4
-
5
4
  module Serialization
6
-
7
5
  # Serializes a value for storage in Redis.
8
6
  #
9
7
  # @param val [Object] The value to be serialized.
@@ -33,7 +31,7 @@ class Familia::DataType
33
31
 
34
32
  if opts[:class]
35
33
  prepared = Familia.distinguisher(opts[:class], strict_values: strict_values)
36
- Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared||'<nil>'}"
34
+ Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared || '<nil>'}"
37
35
  end
38
36
 
39
37
  if prepared.nil?
@@ -42,9 +40,12 @@ class Familia::DataType
42
40
  Familia.ld " from <#{val.class}> => <#{prepared.class}>"
43
41
  end
44
42
 
45
- Familia.trace :TOREDIS, dbclient, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>", caller(1..1) if Familia.debug?
43
+ if Familia.debug?
44
+ Familia.trace :TOREDIS, dbclient, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>",
45
+ caller(1..1)
46
+ end
46
47
 
47
- Familia.warn "[#{self.class}\#serialize_value] nil returned for #{opts[:class]}\##{name}" if prepared.nil?
48
+ Familia.warn "[#{self.class}#serialize_value] nil returned for #{opts[:class]}##{name}" if prepared.nil?
48
49
  prepared
49
50
  end
50
51
 
@@ -88,9 +89,7 @@ class Familia::DataType
88
89
  next if obj.nil?
89
90
 
90
91
  val = @opts[:class].send load_method, obj
91
- if val.nil?
92
- Familia.ld "[#{self.class}\#deserialize_values] nil returned for #{@opts[:class]}\##{name}"
93
- end
92
+ Familia.ld "[#{self.class}#deserialize_values] nil returned for #{@opts[:class]}##{name}" if val.nil?
94
93
 
95
94
  val
96
95
  rescue StandardError => e
@@ -125,5 +124,4 @@ class Familia::DataType
125
124
  ret&.first # return the object or nil
126
125
  end
127
126
  end
128
-
129
127
  end
@@ -1,4 +1,4 @@
1
- # lib/familia/datatype/types/hashkey.rb
1
+ # lib/familia/data_type/types/hashkey.rb
2
2
 
3
3
  module Familia
4
4
  class HashKey < DataType
@@ -55,7 +55,7 @@ module Familia
55
55
  end
56
56
 
57
57
  def hgetall
58
- dbclient.hgetall(dbkey).each_with_object({}) do |(k,v), ret|
58
+ dbclient.hgetall(dbkey).each_with_object({}) do |(k, v), ret|
59
59
  ret[k] = deserialize_value v
60
60
  end
61
61
  end
@@ -1,8 +1,7 @@
1
- # lib/familia/datatype/types/list.rb
1
+ # lib/familia/data_type/types/list.rb
2
2
 
3
3
  module Familia
4
4
  class List < DataType
5
-
6
5
  # Returns the number of elements in the list
7
6
  # @return [Integer] number of elements
8
7
  def element_count
@@ -91,36 +90,36 @@ module Familia
91
90
  rangeraw 0, count
92
91
  end
93
92
 
94
- def each(&blk)
95
- range.each(&blk)
93
+ def each(&)
94
+ range.each(&)
96
95
  end
97
96
 
98
- def each_with_index(&blk)
99
- range.each_with_index(&blk)
97
+ def each_with_index(&)
98
+ range.each_with_index(&)
100
99
  end
101
100
 
102
- def eachraw(&blk)
103
- rangeraw.each(&blk)
101
+ def eachraw(&)
102
+ rangeraw.each(&)
104
103
  end
105
104
 
106
- def eachraw_with_index(&blk)
107
- rangeraw.each_with_index(&blk)
105
+ def eachraw_with_index(&)
106
+ rangeraw.each_with_index(&)
108
107
  end
109
108
 
110
- def collect(&blk)
111
- range.collect(&blk)
109
+ def collect(&)
110
+ range.collect(&)
112
111
  end
113
112
 
114
- def select(&blk)
115
- range.select(&blk)
113
+ def select(&)
114
+ range.select(&)
116
115
  end
117
116
 
118
- def collectraw(&blk)
119
- rangeraw.collect(&blk)
117
+ def collectraw(&)
118
+ rangeraw.collect(&)
120
119
  end
121
120
 
122
- def selectraw(&blk)
123
- rangeraw.select(&blk)
121
+ def selectraw(&)
122
+ rangeraw.select(&)
124
123
  end
125
124
 
126
125
  def at(idx)
@@ -1,4 +1,4 @@
1
- # lib/familia/datatype/types/sorted_set.rb
1
+ # lib/familia/data_type/types/sorted_set.rb
2
2
 
3
3
  module Familia
4
4
  class SortedSet < DataType
@@ -99,36 +99,36 @@ module Familia
99
99
  revrangeraw 0, count, opts
100
100
  end
101
101
 
102
- def each(&blk)
103
- members.each(&blk)
102
+ def each(&)
103
+ members.each(&)
104
104
  end
105
105
 
106
- def each_with_index(&blk)
107
- members.each_with_index(&blk)
106
+ def each_with_index(&)
107
+ members.each_with_index(&)
108
108
  end
109
109
 
110
- def collect(&blk)
111
- members.collect(&blk)
110
+ def collect(&)
111
+ members.collect(&)
112
112
  end
113
113
 
114
- def select(&blk)
115
- members.select(&blk)
114
+ def select(&)
115
+ members.select(&)
116
116
  end
117
117
 
118
- def eachraw(&blk)
119
- membersraw.each(&blk)
118
+ def eachraw(&)
119
+ membersraw.each(&)
120
120
  end
121
121
 
122
- def eachraw_with_index(&blk)
123
- membersraw.each_with_index(&blk)
122
+ def eachraw_with_index(&)
123
+ membersraw.each_with_index(&)
124
124
  end
125
125
 
126
- def collectraw(&blk)
127
- membersraw.collect(&blk)
126
+ def collectraw(&)
127
+ membersraw.collect(&)
128
128
  end
129
129
 
130
- def selectraw(&blk)
131
- membersraw.select(&blk)
130
+ def selectraw(&)
131
+ membersraw.select(&)
132
132
  end
133
133
 
134
134
  def range(sidx, eidx, opts = {})
@@ -1,4 +1,4 @@
1
- # lib/familia/datatype/types/string.rb
1
+ # lib/familia/data_type/types/string.rb
2
2
 
3
3
  module Familia
4
4
  class String < DataType
@@ -25,6 +25,7 @@ module Familia
25
25
 
26
26
  def to_s
27
27
  return super if value.to_s.empty?
28
+
28
29
  value.to_s
29
30
  end
30
31