familia 2.0.0.pre8 → 2.0.0.pre12

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 (81) 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 +247 -0
  8. data/CLAUDE.md +12 -59
  9. data/Gemfile.lock +1 -1
  10. data/README.md +62 -2
  11. data/changelog.d/README.md +77 -0
  12. data/docs/archive/.gitignore +2 -0
  13. data/docs/archive/FAMILIA_RELATIONSHIPS.md +210 -0
  14. data/docs/archive/FAMILIA_TECHNICAL.md +823 -0
  15. data/docs/archive/FAMILIA_UPDATE.md +226 -0
  16. data/docs/archive/README.md +63 -0
  17. data/docs/guides/.gitignore +2 -0
  18. data/docs/{wiki → guides}/Home.md +1 -1
  19. data/docs/{wiki → guides}/Implementation-Guide.md +1 -1
  20. data/docs/{wiki → guides}/Relationships-Guide.md +103 -50
  21. data/docs/guides/relationships-methods.md +266 -0
  22. data/docs/migrating/.gitignore +2 -0
  23. data/docs/migrating/v2.0.0-pre.md +84 -0
  24. data/docs/migrating/v2.0.0-pre11.md +255 -0
  25. data/docs/migrating/v2.0.0-pre12.md +306 -0
  26. data/docs/migrating/v2.0.0-pre5.md +110 -0
  27. data/docs/migrating/v2.0.0-pre6.md +154 -0
  28. data/docs/migrating/v2.0.0-pre7.md +222 -0
  29. data/docs/overview.md +6 -7
  30. data/{examples/redis_command_validation_example.rb → docs/reference/auditing_database_commands.rb} +29 -32
  31. data/examples/{bit_encoding_integration.rb → permissions.rb} +30 -27
  32. data/examples/relationships.rb +205 -0
  33. data/examples/safe_dump.rb +281 -0
  34. data/familia.gemspec +4 -4
  35. data/lib/familia/base.rb +52 -0
  36. data/lib/familia/connection.rb +4 -21
  37. data/lib/familia/{encryption_request_cache.rb → encryption/request_cache.rb} +1 -1
  38. data/lib/familia/errors.rb +2 -0
  39. data/lib/familia/features/autoloader.rb +57 -0
  40. data/lib/familia/features/external_identifier.rb +310 -0
  41. data/lib/familia/features/object_identifier.rb +307 -0
  42. data/lib/familia/features/relationships/indexing.rb +160 -175
  43. data/lib/familia/features/relationships/membership.rb +16 -21
  44. data/lib/familia/features/relationships/tracking.rb +61 -21
  45. data/lib/familia/features/relationships.rb +15 -8
  46. data/lib/familia/features/safe_dump.rb +66 -72
  47. data/lib/familia/features.rb +93 -5
  48. data/lib/familia/horreum/subclass/definition.rb +49 -3
  49. data/lib/familia/horreum.rb +15 -24
  50. data/lib/familia/secure_identifier.rb +51 -75
  51. data/lib/familia/verifiable_identifier.rb +162 -0
  52. data/lib/familia/version.rb +1 -1
  53. data/lib/familia.rb +1 -0
  54. data/setup.cfg +5 -0
  55. data/try/core/secure_identifier_try.rb +47 -18
  56. data/try/core/verifiable_identifier_try.rb +171 -0
  57. data/try/features/{external_identifiers/external_identifiers_try.rb → external_identifier/external_identifier_try.rb} +25 -28
  58. data/try/features/feature_improvements_try.rb +126 -0
  59. data/try/features/{object_identifiers/object_identifiers_integration_try.rb → object_identifier/object_identifier_integration_try.rb} +28 -30
  60. data/try/features/{object_identifiers/object_identifiers_try.rb → object_identifier/object_identifier_try.rb} +13 -13
  61. data/try/features/real_feature_integration_try.rb +7 -6
  62. data/try/features/relationships/relationships_api_changes_try.rb +339 -0
  63. data/try/features/relationships/relationships_try.rb +6 -5
  64. data/try/features/safe_dump/safe_dump_try.rb +8 -9
  65. data/try/helpers/test_helpers.rb +17 -17
  66. metadata +62 -41
  67. data/examples/relationships_basic.rb +0 -273
  68. data/lib/familia/features/external_identifiers/external_identifier_field_type.rb +0 -120
  69. data/lib/familia/features/external_identifiers.rb +0 -111
  70. data/lib/familia/features/object_identifiers/object_identifier_field_type.rb +0 -91
  71. data/lib/familia/features/object_identifiers.rb +0 -194
  72. /data/docs/{wiki → guides}/API-Reference.md +0 -0
  73. /data/docs/{wiki → guides}/Connection-Pooling-Guide.md +0 -0
  74. /data/docs/{wiki → guides}/Encrypted-Fields-Overview.md +0 -0
  75. /data/docs/{wiki → guides}/Expiration-Feature-Guide.md +0 -0
  76. /data/docs/{wiki → guides}/Feature-System-Guide.md +0 -0
  77. /data/docs/{wiki → guides}/Features-System-Developer-Guide.md +0 -0
  78. /data/docs/{wiki → guides}/Field-System-Guide.md +0 -0
  79. /data/docs/{wiki → guides}/Quantization-Feature-Guide.md +0 -0
  80. /data/docs/{wiki → guides}/Security-Model.md +0 -0
  81. /data/docs/{wiki → guides}/Transient-Fields-Guide.md +0 -0
@@ -0,0 +1,266 @@
1
+ # Relationship Methods
2
+
3
+ Here are the methods automatically generated for each relationship type in the new clean API:
4
+
5
+ ## member_of Relationships
6
+
7
+ When you declare:
8
+ ```ruby
9
+ class Domain < Familia::Horreum
10
+ member_of Customer, :domains
11
+ end
12
+ ```
13
+
14
+ **Generated methods on Domain instances:**
15
+ - `add_to_customer_domains(customer)` - Add this domain to customer's domains collection
16
+ - `remove_from_customer_domains(customer)` - Remove this domain from customer's domains collection
17
+ - `in_customer_domains?(customer)` - Check if this domain is in customer's domains collection
18
+
19
+ **Collection << operator support:**
20
+ ```ruby
21
+ customer.domains << domain # Clean Ruby-like syntax (equivalent to domain.add_to_customer_domains(customer))
22
+ ```
23
+
24
+ The method names follow the pattern: `{action}_to_{lowercase_class_name}_{collection_name}`
25
+
26
+ ## tracked_in Relationships
27
+
28
+ ### Class-Level Tracking (class_tracked_in)
29
+ When you declare:
30
+ ```ruby
31
+ class Customer < Familia::Horreum
32
+ class_tracked_in :all_customers, score: :created_at
33
+ end
34
+ ```
35
+
36
+ **Generated class methods:**
37
+ - `Customer.add_to_all_customers(customer)` - Add customer to class-level tracking
38
+ - `Customer.remove_from_all_customers(customer)` - Remove customer from class-level tracking
39
+ - `Customer.all_customers` - Access the sorted set collection directly
40
+
41
+ **Automatic behavior:**
42
+ - Objects are automatically added to class-level tracking collections when saved
43
+ - No manual calls required for basic tracking
44
+
45
+ ### Relationship Tracking (tracked_in with parent class)
46
+ When you declare:
47
+ ```ruby
48
+ class User < Familia::Horreum
49
+ tracked_in Team, :active_users, score: :last_seen
50
+ end
51
+ ```
52
+
53
+ **Generated methods:**
54
+ - Team instance methods for managing the active_users collection
55
+ - Automatic score calculation based on the provided lambda or field
56
+
57
+ ## indexed_by Relationships
58
+
59
+ The `indexed_by` method creates Redis hash-based indexes for O(1) field lookups with automatic management.
60
+
61
+ ### Class-Level Indexing (class_indexed_by)
62
+ When you declare:
63
+ ```ruby
64
+ class Customer < Familia::Horreum
65
+ class_indexed_by :email, :email_lookup
66
+ end
67
+ ```
68
+
69
+ **Generated methods:**
70
+ - **Instance methods**: `customer.add_to_class_email_lookup`, `customer.remove_from_class_email_lookup`
71
+ - **Class methods**: `Customer.email_lookup` (returns hash), `Customer.find_by_email(email)`
72
+
73
+ **Automatic behavior:**
74
+ - Objects are automatically added to class-level indexes when saved
75
+ - Index updates happen transparently on field changes
76
+
77
+ Redis key pattern: `customer:email_lookup`
78
+
79
+ ### Relationship-Scoped Indexing (indexed_by with parent:)
80
+ When you declare:
81
+ ```ruby
82
+ class Domain < Familia::Horreum
83
+ indexed_by :name, :domain_index, parent: Customer
84
+ end
85
+ ```
86
+
87
+ **Generated class methods on Customer:**
88
+ - `Customer#find_by_name(domain_name)` - Find domain by name within this customer
89
+ - `Customer#find_all_by_name(domain_names)` - Find multiple domains by names
90
+
91
+ Redis key pattern: `domain:domain_index` (all stored at class level for consistency)
92
+
93
+ ### When to Use Each Context
94
+ - **Class-level context (`class_indexed_by`)**: Use for system-wide lookups where the field value should be unique across all instances
95
+ - Examples: email addresses, usernames, API keys
96
+ - **Relationship context (`parent:` parameter)**: Use for relationship-scoped lookups where the field value is unique within a specific context
97
+ - Examples: domain names per customer, project names per team
98
+
99
+ ## Complete Example
100
+
101
+ From the relationships example file, you can see the new clean API in action:
102
+
103
+ ```ruby
104
+ # Domain declares membership in Customer collections
105
+ class Domain < Familia::Horreum
106
+ member_of Customer, :domains
107
+ class_tracked_in :active_domains, score: -> { status == 'active' ? Time.now.to_i : 0 }
108
+ end
109
+
110
+ class Customer < Familia::Horreum
111
+ class_indexed_by :email, :email_lookup
112
+ class_tracked_in :all_customers, score: :created_at
113
+ end
114
+ ```
115
+
116
+ **Usage with automatic behavior:**
117
+ ```ruby
118
+ # Create and save objects (automatic indexing and tracking)
119
+ customer = Customer.new(email: "admin@acme.com", name: "Acme Corp")
120
+ customer.save # Automatically added to email_lookup and all_customers
121
+
122
+ domain = Domain.new(name: "acme.com", status: "active")
123
+ domain.save # Automatically added to active_domains
124
+
125
+ # Clean relationship syntax
126
+ customer.domains << domain # Ruby-like collection syntax
127
+
128
+ # Query relationships
129
+ domain.in_customer_domains?(customer) # => true
130
+ customer.domains.member?(domain.identifier) # => true
131
+
132
+ # O(1) lookups with automatic management
133
+ found_id = Customer.email_lookup.get("admin@acme.com")
134
+ ```
135
+
136
+ ## Method Naming Conventions
137
+
138
+ The relationship system uses consistent naming patterns:
139
+ - **member_of**: `{add_to|remove_from|in}_#{parent_class.downcase}_#{collection_name}`
140
+ - **class_tracked_in**: `{add_to|remove_from}_#{collection_name}` (class methods)
141
+ - **class_indexed_by**: `{add_to|remove_from}_class_#{index_name}` (instance methods)
142
+ - **indexed_by with parent**: `{add_to|remove_from}_#{parent_class.downcase}_#{index_name}` (instance methods)
143
+
144
+ ## Key Benefits
145
+
146
+ - **Automatic management**: Save operations update indexes and tracking automatically
147
+ - **Ruby-idiomatic**: Use `<<` operator for natural collection syntax
148
+ - **Consistent storage**: All indexes stored at class level for architectural simplicity
149
+ - **Clean API**: Removed complex global vs parent conditionals for simpler method generation
150
+
151
+
152
+ ## Context Parameter Usage Patterns
153
+
154
+ The `context` parameter in `indexed_by` is a fundamental architectural decision that determines index scope and ownership. Here are practical patterns for when to use each approach:
155
+
156
+ ### Global Context Pattern
157
+ Use `class_indexed_by` when field values should be unique system-wide:
158
+
159
+ ```ruby
160
+ class User < Familia::Horreum
161
+ feature :relationships
162
+
163
+ identifier_field :user_id
164
+ field :user_id, :email, :username
165
+
166
+ # System-wide unique email lookup
167
+ class_indexed_by :email, :email_lookup
168
+ class_indexed_by :username, :username_lookup
169
+ end
170
+
171
+ # Usage:
172
+ user.add_to_global_email_lookup
173
+ found_user_id = User.email_lookup.get("john@example.com")
174
+ ```
175
+
176
+ **Redis keys generated**: `global:email_lookup`, `global:username_lookup`
177
+
178
+ ### Parent Context Pattern
179
+ Use `parent: SomeClass` when field values are unique within a specific parent context:
180
+
181
+ ```ruby
182
+ class Customer < Familia::Horreum
183
+ feature :relationships
184
+
185
+ identifier_field :custid
186
+ field :custid, :name
187
+ sorted_set :domains
188
+ end
189
+
190
+ class Domain < Familia::Horreum
191
+ feature :relationships
192
+
193
+ identifier_field :domain_id
194
+ field :domain_id, :name, :subdomain
195
+
196
+ # Domains are unique per customer (customer can't have duplicate domain names)
197
+ indexed_by :name, :domain_index, parent: Customer
198
+ indexed_by :subdomain, :subdomain_index, parent: Customer
199
+ end
200
+
201
+ # Usage:
202
+ customer = Customer.new(custid: "cust_123")
203
+ customer.find_by_name("example.com") # Find domain within this customer
204
+ customer.find_all_by_subdomain(["www", "api"]) # Find multiple subdomains
205
+ ```
206
+
207
+ **Redis keys generated**: `customer:cust_123:domain_index`, `customer:cust_123:subdomain_index`
208
+
209
+ ### Mixed Pattern Example
210
+ A real-world example showing both patterns:
211
+
212
+ ```ruby
213
+ class ApiKey < Familia::Horreum
214
+ feature :relationships
215
+
216
+ identifier_field :key_id
217
+ field :key_id, :key_hash, :name, :scope
218
+
219
+ # API key hashes must be globally unique
220
+ class_indexed_by :key_hash, :global_key_lookup
221
+
222
+ # But key names can be reused across different customers
223
+ indexed_by :name, :customer_key_lookup, parent: Customer
224
+ indexed_by :scope, :scope_lookup, parent: Customer
225
+ end
226
+
227
+ # Usage examples:
228
+ # Global lookup (system-wide unique)
229
+ ApiKey.key_lookup.get("sha256:abc123...")
230
+
231
+ # Scoped lookup (unique per customer)
232
+ customer = Customer.new(custid: "cust_456")
233
+ customer.find_by_name("production-api-key")
234
+ customer.find_all_by_scope(["read", "write"])
235
+ ```
236
+
237
+ ### Migrating Guide
238
+ If you have existing code with old syntax, here's how to update it:
239
+
240
+ ```ruby
241
+ # ❌ Old syntax (pre-refactoring)
242
+ indexed_by :email_lookup, field: :email
243
+ indexed_by :email, :email_lookup, context: :global
244
+ tracked_in :global, :all_users, score: :created_at
245
+
246
+ # ✅ New syntax - Class-level scope
247
+ class_indexed_by :email, :email_lookup
248
+ class_tracked_in :all_users, score: :created_at
249
+
250
+ # ✅ New syntax - Relationship scope
251
+ indexed_by :email, :customer_email_lookup, parent: Customer
252
+ tracked_in Customer, :user_activity, score: :last_seen
253
+ ```
254
+
255
+ **Key Changes**:
256
+ 1. **Class-level relationships**: Use `class_` prefix (`class_tracked_in`, `class_indexed_by`)
257
+ 2. **Relationship-scoped**: Use `parent:` parameter instead of `:global` symbol
258
+ 3. **Automatic management**: Objects automatically added to class-level collections on save
259
+ 4. **Clean syntax**: Collections support `<<` operator for Ruby-like relationship building
260
+ 5. **Simplified storage**: All indexes stored at class level (parent is conceptual only)
261
+
262
+ **Behavioral Changes**:
263
+ - Save operations now automatically update indexes and class-level tracking
264
+ - No more manual `add_to_*` calls required for basic functionality
265
+ - `<<` operator works naturally with all collection types
266
+ - Method generation simplified without complex global/parent conditionals
@@ -0,0 +1,2 @@
1
+ # Even all wiki markdown docs get the blues
2
+ !*.md
@@ -0,0 +1,84 @@
1
+ # Migrating Guide: v1.x to v2.0.0-pre
2
+
3
+ This guide covers migrating from Familia v1.x to the v2.0.0-pre foundation release.
4
+
5
+ ## Overview
6
+
7
+ The v2.0.0-pre series represents a major modernization of Familia with:
8
+ - Complete API redesign for clarity and consistency
9
+ - Valkey compatibility alongside Redis support
10
+ - Ruby 3.4+ modernization with improved thread safety
11
+ - New connection pooling architecture
12
+
13
+ ## Step-by-Step Migration
14
+
15
+ ### 1. Update Connection Configuration
16
+
17
+ **Before (v1.x):**
18
+ ```ruby
19
+ Familia.connect('redis://localhost:6379/0')
20
+ ```
21
+
22
+ **After (v2.0.0-pre):**
23
+ ```ruby
24
+ Familia.configure do |config|
25
+ config.redis_uri = 'redis://localhost:6379/0'
26
+ config.connection_pool = true
27
+ end
28
+ ```
29
+
30
+ ### 2. Migrate Identifier Declarations
31
+
32
+ **Before (v1.x):**
33
+ ```ruby
34
+ class User < Familia::Base
35
+ identifier :user_id
36
+ end
37
+ ```
38
+
39
+ **After (v2.0.0-pre):**
40
+ ```ruby
41
+ class User < Familia::Horreum
42
+ identifier_field :user_id
43
+ end
44
+ ```
45
+
46
+ ### 3. Update Feature Activations
47
+
48
+ **Before (v1.x):**
49
+ ```ruby
50
+ class User < Familia::Base
51
+ include Familia::Features::Expiration
52
+ end
53
+ ```
54
+
55
+ **After (v2.0.0-pre):**
56
+ ```ruby
57
+ class User < Familia::Horreum
58
+ feature :expiration
59
+ end
60
+ ```
61
+
62
+ ### 4. Review Method Calls
63
+
64
+ Several methods were renamed for consistency:
65
+
66
+ | v1.x Method | v2.0.0-pre Method | Notes |
67
+ |-------------|------------------|-------|
68
+ | `delete` | `destroy` | More semantic naming |
69
+ | `exists` | `exists?` | Ruby predicate convention |
70
+ | `dump` | `serialize` | Clearer intent |
71
+
72
+ ## Breaking Changes
73
+
74
+ - `Familia::Base` replaced by `Familia::Horreum`
75
+ - Connection configuration moved to block-based setup
76
+ - Feature inclusion changed from `include` to `feature` declarations
77
+ - Several method names updated for consistency
78
+
79
+ ## Next Steps
80
+
81
+ After completing the foundation migration:
82
+ 1. Review [Security Feature Adoption](v2.0.0-pre5.md) for encrypted fields
83
+ 2. See [Architecture Migration](v2.0.0-pre6.md) for persistence improvements
84
+ 3. Explore [Relationships Migration](v2.0.0-pre7.md) for the new relationship system
@@ -0,0 +1,255 @@
1
+ # Migrating Guide: v2.0.0-pre11
2
+
3
+ This version introduces significant improvements to Familia's feature system, making it easier to organize and use features across complex projects.
4
+
5
+ ## Enhanced Feature System
6
+
7
+ ### Model-Specific Feature Registration
8
+
9
+ Previously, all features were registered globally. Now you can register features specific to individual model classes, allowing for better organization and namespace management.
10
+
11
+ #### Before
12
+ ```ruby
13
+ # Global feature registration only
14
+ module MyProjectFeature
15
+ # Feature implementation
16
+ end
17
+ Familia::Base.add_feature MyProjectFeature, :my_project_feature
18
+
19
+ class Customer < Familia::Horreum
20
+ feature :my_project_feature
21
+ end
22
+
23
+ class Session < Familia::Horreum
24
+ feature :my_project_feature # Same global feature
25
+ end
26
+ ```
27
+
28
+ #### After
29
+ ```ruby
30
+ # Model-specific feature registration
31
+ module CustomerSpecificFeature
32
+ # Feature implementation
33
+ end
34
+
35
+ # Register feature only for Customer and its subclasses
36
+ Customer.add_feature CustomerSpecificFeature, :customer_specific
37
+
38
+ class Customer < Familia::Horreum
39
+ feature :customer_specific # Available via Customer's registry
40
+ end
41
+
42
+ class PremiumCustomer < Customer
43
+ feature :customer_specific # Inherited via ancestry chain
44
+ end
45
+
46
+ class Session < Familia::Horreum
47
+ # feature :customer_specific # Not available - would raise error
48
+ end
49
+ ```
50
+
51
+ **Benefits:**
52
+ - Features can have the same name across different model hierarchies
53
+ - Standardized naming: `deprecated_fields.rb` instead of `customer_deprecated_fields.rb`
54
+ - Natural inheritance through Ruby's class hierarchy
55
+
56
+ ## SafeDump DSL Improvements
57
+
58
+ The new DSL replaces the brittle `@safe_dump_fields` class instance variable pattern with clean, explicit methods.
59
+
60
+ ### Before
61
+ ```ruby
62
+ class Customer < Familia::Horreum
63
+ feature :safe_dump
64
+
65
+ # Brittle - hard to move to feature modules, confusing syntax
66
+ @safe_dump_fields = [
67
+ :custid,
68
+ :email,
69
+ { active: ->(obj) { obj.active? } },
70
+ { display_name: ->(obj) { "#{obj.name} (#{obj.custid})" } }
71
+ ]
72
+ end
73
+ ```
74
+
75
+ ### After
76
+ ```ruby
77
+ class Customer < Familia::Horreum
78
+ feature :safe_dump
79
+
80
+ # Clean DSL - easy to understand and organize
81
+ safe_dump_field :custid
82
+ safe_dump_field :email
83
+ safe_dump_field :active, ->(obj) { obj.active? }
84
+ safe_dump_field :display_name, ->(obj) { "#{obj.name} (#{obj.custid})" }
85
+
86
+ # Or define multiple fields at once
87
+ safe_dump_fields :created, :updated, { status: ->(obj) { obj.role } }
88
+ end
89
+ ```
90
+
91
+ **New methods available:**
92
+ - `safe_dump_field(name, callable = nil)` - Define a single field
93
+ - `safe_dump_fields(*fields)` - Define multiple fields or get field names
94
+ - `safe_dump_field_names` - Get array of field names
95
+ - `safe_dump_field_map` - Get the internal callable map
96
+
97
+ **Backward Compatibility:**
98
+ - `set_safe_dump_fields(*fields)` - Legacy setter method (still works)
99
+ - The old `@safe_dump_fields` pattern is no longer supported
100
+
101
+ ## Auto-loading Features
102
+
103
+ ### Before: Manual Loading
104
+ ```ruby
105
+ # apps/api/v2/models/customer/features.rb
106
+
107
+ # Manual feature loading (copied from Familia)
108
+ features_dir = File.join(__dir__, 'features')
109
+ if Dir.exist?(features_dir)
110
+ Dir.glob(File.join(features_dir, '*.rb')).each do |feature_file|
111
+ require_relative feature_file
112
+ end
113
+ end
114
+
115
+ module V2
116
+ class Customer < Familia::Horreum
117
+ # Features now available for use
118
+ feature :deprecated_fields
119
+ end
120
+ end
121
+ ```
122
+
123
+ ### After: Automatic Loading
124
+ ```ruby
125
+ # apps/api/v2/models/customer/features.rb
126
+ module V2::Customer
127
+ module Features
128
+ include Familia::Features::Autoloader
129
+ # Automatically discovers and loads all *.rb files from customer/features/
130
+ end
131
+ end
132
+
133
+ module V2
134
+ class Customer < Familia::Horreum
135
+ # Features automatically loaded and available
136
+ feature :deprecated_fields
137
+ end
138
+ end
139
+ ```
140
+
141
+ **Directory structure this enables:**
142
+ ```
143
+ models/
144
+ ├── customer/
145
+ │ ├── features/
146
+ │ │ ├── deprecated_fields.rb # Standardized names!
147
+ │ │ ├── legacy_support.rb
148
+ │ │ └── stripe_integration.rb
149
+ │ └── features.rb # Include Autoloader here
150
+ ├── session/
151
+ │ ├── features/
152
+ │ │ ├── deprecated_fields.rb # Same name, different implementation
153
+ │ │ └── expiration_hooks.rb
154
+ │ └── features.rb
155
+ └── customer.rb
156
+ ```
157
+
158
+ ## Field Definitions in Feature Modules
159
+
160
+ Feature modules can now define fields directly in their `ClassMethods` modules. When a class extends the module, the field definitions execute in the extending class's context.
161
+
162
+ ### Example
163
+ ```ruby
164
+ # features/common_fields.rb
165
+ module CommonFields
166
+ def self.included(base)
167
+ base.extend ClassMethods
168
+ end
169
+
170
+ module ClassMethods
171
+ # These field calls execute in the extending class's context
172
+ field :created
173
+ field :updated
174
+ field :version
175
+
176
+ def touch_updated
177
+ self.updated = Time.now.to_i
178
+ end
179
+ end
180
+
181
+ Familia::Base.add_feature self, :common_fields
182
+ end
183
+
184
+ # Usage
185
+ class Customer < Familia::Horreum
186
+ feature :common_fields
187
+ # Now has :created, :updated, :version fields and touch_updated class method
188
+ end
189
+ ```
190
+
191
+ ## Migration Steps
192
+
193
+ ### 1. Update SafeDump Usage
194
+ Replace all `@safe_dump_fields` definitions with the new DSL:
195
+
196
+ ```ruby
197
+ # Find and replace pattern:
198
+ # Old: @safe_dump_fields = [:field1, :field2, { field3: ->(obj) { ... } }]
199
+ # New: safe_dump_fields :field1, :field2, { field3: ->(obj) { ... } }
200
+
201
+ # Or use individual field definitions for better readability:
202
+ safe_dump_field :field1
203
+ safe_dump_field :field2
204
+ safe_dump_field :field3, ->(obj) { ... }
205
+ ```
206
+
207
+ ### 2. Set Up Auto-loading (Optional)
208
+ If you have project-specific features, set up auto-loading:
209
+
210
+ ```ruby
211
+ # Create: models/[model_name]/features.rb
212
+ module YourProject
213
+ module ModelName
214
+ module Features
215
+ include Familia::Features::Autoloader
216
+ end
217
+ end
218
+ end
219
+
220
+ # Require this file before your model definitions
221
+ require_relative 'model_name/features'
222
+ ```
223
+
224
+ ### 3. Organize Features by Model (Optional)
225
+ Consider reorganizing shared feature names by model:
226
+
227
+ ```ruby
228
+ # Before: features/customer_deprecated_fields.rb
229
+ # After: models/customer/features/deprecated_fields.rb
230
+
231
+ # This allows multiple models to have their own deprecated_fields.rb
232
+ ```
233
+
234
+ ### 4. Test Your Changes
235
+ Run your test suite to ensure all SafeDump functionality works correctly:
236
+
237
+ ```ruby
238
+ # Verify SafeDump DSL works
239
+ model = YourModel.new(field1: 'value')
240
+ result = model.safe_dump
241
+ puts result.keys # Should include your defined fields
242
+ ```
243
+
244
+ ## Breaking Changes
245
+
246
+ 1. **`@safe_dump_fields` no longer supported** - Must migrate to DSL methods
247
+ 2. **SafeDump field order** - Fields are now returned in definition order via Hash keys (Ruby 1.9+ behavior)
248
+
249
+ ## New Capabilities Unlocked
250
+
251
+ 1. **Standardized feature names** across different models
252
+ 2. **Cleaner SafeDump definitions** that can be easily moved to feature modules
253
+ 3. **Automatic feature discovery** for better project organization
254
+ 4. **Model-specific feature registries** for better namespace management
255
+ 5. **Field definitions in feature modules** for shared functionality