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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +13 -0
- data/.github/workflows/docs.yml +1 -1
- data/.gitignore +9 -9
- data/.rubocop.yml +19 -0
- data/.yardopts +22 -1
- data/CHANGELOG.md +247 -0
- data/CLAUDE.md +12 -59
- data/Gemfile.lock +1 -1
- data/README.md +62 -2
- data/changelog.d/README.md +77 -0
- data/docs/archive/.gitignore +2 -0
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +210 -0
- data/docs/archive/FAMILIA_TECHNICAL.md +823 -0
- data/docs/archive/FAMILIA_UPDATE.md +226 -0
- data/docs/archive/README.md +63 -0
- data/docs/guides/.gitignore +2 -0
- data/docs/{wiki → guides}/Home.md +1 -1
- data/docs/{wiki → guides}/Implementation-Guide.md +1 -1
- data/docs/{wiki → guides}/Relationships-Guide.md +103 -50
- data/docs/guides/relationships-methods.md +266 -0
- data/docs/migrating/.gitignore +2 -0
- data/docs/migrating/v2.0.0-pre.md +84 -0
- data/docs/migrating/v2.0.0-pre11.md +255 -0
- data/docs/migrating/v2.0.0-pre12.md +306 -0
- data/docs/migrating/v2.0.0-pre5.md +110 -0
- data/docs/migrating/v2.0.0-pre6.md +154 -0
- data/docs/migrating/v2.0.0-pre7.md +222 -0
- data/docs/overview.md +6 -7
- data/{examples/redis_command_validation_example.rb → docs/reference/auditing_database_commands.rb} +29 -32
- data/examples/{bit_encoding_integration.rb → permissions.rb} +30 -27
- data/examples/relationships.rb +205 -0
- data/examples/safe_dump.rb +281 -0
- data/familia.gemspec +4 -4
- data/lib/familia/base.rb +52 -0
- data/lib/familia/connection.rb +4 -21
- data/lib/familia/{encryption_request_cache.rb → encryption/request_cache.rb} +1 -1
- data/lib/familia/errors.rb +2 -0
- data/lib/familia/features/autoloader.rb +57 -0
- data/lib/familia/features/external_identifier.rb +310 -0
- data/lib/familia/features/object_identifier.rb +307 -0
- data/lib/familia/features/relationships/indexing.rb +160 -175
- data/lib/familia/features/relationships/membership.rb +16 -21
- data/lib/familia/features/relationships/tracking.rb +61 -21
- data/lib/familia/features/relationships.rb +15 -8
- data/lib/familia/features/safe_dump.rb +66 -72
- data/lib/familia/features.rb +93 -5
- data/lib/familia/horreum/subclass/definition.rb +49 -3
- data/lib/familia/horreum.rb +15 -24
- data/lib/familia/secure_identifier.rb +51 -75
- data/lib/familia/verifiable_identifier.rb +162 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +1 -0
- data/setup.cfg +5 -0
- data/try/core/secure_identifier_try.rb +47 -18
- data/try/core/verifiable_identifier_try.rb +171 -0
- data/try/features/{external_identifiers/external_identifiers_try.rb → external_identifier/external_identifier_try.rb} +25 -28
- data/try/features/feature_improvements_try.rb +126 -0
- data/try/features/{object_identifiers/object_identifiers_integration_try.rb → object_identifier/object_identifier_integration_try.rb} +28 -30
- data/try/features/{object_identifiers/object_identifiers_try.rb → object_identifier/object_identifier_try.rb} +13 -13
- data/try/features/real_feature_integration_try.rb +7 -6
- data/try/features/relationships/relationships_api_changes_try.rb +339 -0
- data/try/features/relationships/relationships_try.rb +6 -5
- data/try/features/safe_dump/safe_dump_try.rb +8 -9
- data/try/helpers/test_helpers.rb +17 -17
- metadata +62 -41
- data/examples/relationships_basic.rb +0 -273
- data/lib/familia/features/external_identifiers/external_identifier_field_type.rb +0 -120
- data/lib/familia/features/external_identifiers.rb +0 -111
- data/lib/familia/features/object_identifiers/object_identifier_field_type.rb +0 -91
- data/lib/familia/features/object_identifiers.rb +0 -194
- /data/docs/{wiki → guides}/API-Reference.md +0 -0
- /data/docs/{wiki → guides}/Connection-Pooling-Guide.md +0 -0
- /data/docs/{wiki → guides}/Encrypted-Fields-Overview.md +0 -0
- /data/docs/{wiki → guides}/Expiration-Feature-Guide.md +0 -0
- /data/docs/{wiki → guides}/Feature-System-Guide.md +0 -0
- /data/docs/{wiki → guides}/Features-System-Developer-Guide.md +0 -0
- /data/docs/{wiki → guides}/Field-System-Guide.md +0 -0
- /data/docs/{wiki → guides}/Quantization-Feature-Guide.md +0 -0
- /data/docs/{wiki → guides}/Security-Model.md +0 -0
- /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,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
|