familia 2.0.0.pre10 → 2.0.0.pre13
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/.rubocop_todo.yml +2 -3
- data/CHANGELOG.rst +507 -0
- data/CLAUDE.md +5 -55
- data/Gemfile +1 -6
- data/Gemfile.lock +13 -7
- data/changelog.d/README.md +45 -34
- data/changelog.d/scriv.ini +5 -0
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +1 -1
- data/docs/archive/FAMILIA_UPDATE.md +1 -1
- data/docs/archive/README.md +15 -19
- data/docs/guides/Feature-System-Autoloading.md +228 -0
- data/docs/guides/Home.md +1 -1
- data/docs/guides/Implementation-Guide.md +1 -1
- data/docs/guides/relationships-methods.md +1 -1
- data/docs/guides/time-utilities.md +221 -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 +253 -0
- data/docs/migrating/v2.0.0-pre12.md +306 -0
- data/docs/migrating/v2.0.0-pre13.md +329 -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/autoloader/mega_customer/safe_dump_fields.rb +6 -0
- data/examples/autoloader/mega_customer.rb +17 -0
- data/examples/{bit_encoding_integration.rb → permissions.rb} +30 -27
- data/examples/{relationships_basic.rb → relationships.rb} +2 -3
- data/examples/safe_dump.rb +281 -0
- data/familia.gemspec +5 -4
- data/lib/familia/autoloader.rb +53 -0
- data/lib/familia/base.rb +57 -0
- data/lib/familia/data_type.rb +4 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -4
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/{encryption_request_cache.rb → encryption/request_cache.rb} +1 -1
- data/lib/familia/encryption.rb +1 -1
- data/lib/familia/errors.rb +5 -0
- data/lib/familia/features/autoloadable.rb +113 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +4 -2
- data/lib/familia/features/expiration.rb +4 -0
- data/lib/familia/features/external_identifier.rb +310 -0
- data/lib/familia/features/object_identifier.rb +307 -0
- data/lib/familia/features/quantization.rb +5 -0
- data/lib/familia/features/safe_dump.rb +74 -73
- data/lib/familia/features.rb +109 -17
- data/lib/familia/field_type.rb +2 -0
- data/lib/familia/horreum/core/serialization.rb +3 -3
- data/lib/familia/horreum/subclass/definition.rb +50 -7
- data/lib/familia/horreum.rb +2 -0
- data/lib/familia/json_serializer.rb +70 -0
- data/lib/familia/logging.rb +12 -10
- data/lib/familia/refinements/logger_trace.rb +57 -0
- data/lib/familia/refinements/snake_case.rb +40 -0
- data/lib/familia/refinements/time_utils.rb +248 -0
- data/lib/familia/refinements.rb +3 -49
- data/lib/familia/secure_identifier.rb +51 -75
- data/lib/familia/utils.rb +2 -0
- data/lib/familia/validation/{test_helpers.rb → validation_helpers.rb} +2 -2
- data/lib/familia/validation.rb +1 -1
- data/lib/familia/verifiable_identifier.rb +162 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +15 -2
- data/try/core/autoloader_try.rb +112 -0
- data/try/core/extensions_try.rb +38 -21
- data/try/core/familia_extended_try.rb +4 -3
- data/try/core/secure_identifier_try.rb +47 -18
- data/try/core/time_utils_try.rb +130 -0
- data/try/core/verifiable_identifier_try.rb +171 -0
- data/try/data_types/datatype_base_try.rb +3 -2
- data/try/features/autoloadable/autoloadable_try.rb +61 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +8 -3
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +59 -17
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +36 -12
- data/try/features/{external_identifiers/external_identifiers_try.rb → external_identifier/external_identifier_try.rb} +25 -28
- data/try/features/feature_improvements_try.rb +127 -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 +8 -7
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +111 -0
- data/try/features/safe_dump/safe_dump_try.rb +8 -9
- data/try/helpers/test_helpers.rb +41 -17
- data/try/integration/cross_component_try.rb +3 -1
- metadata +61 -26
- data/CHANGELOG.md +0 -184
- data/changelog.d/fragments/.keep +0 -0
- data/changelog.d/template.md.j2 +0 -29
- data/lib/familia/core_ext.rb +0 -135
- 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/setup.cfg +0 -12
@@ -0,0 +1,221 @@
|
|
1
|
+
# Time Utilities Guide
|
2
|
+
|
3
|
+
Familia provides a comprehensive time utilities refinement that adds convenient time conversion and age calculation methods to Ruby's `Numeric` and `String` classes.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The `Familia::Refinements::TimeUtils` module extends Ruby's built-in classes with intuitive time manipulation methods:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
using Familia::Refinements::TimeUtils
|
11
|
+
|
12
|
+
2.hours #=> 7200 (seconds)
|
13
|
+
"30m".in_seconds #=> 1800
|
14
|
+
timestamp.days_old #=> 5.2
|
15
|
+
```
|
16
|
+
|
17
|
+
## Time Constants
|
18
|
+
|
19
|
+
Familia uses **Gregorian year** calculations for consistent time conversions:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
PER_YEAR = 31_556_952.0 # 365.2425 days (accounts for leap years)
|
23
|
+
PER_MONTH = 2_629_746.0 # PER_YEAR / 12 (30.437 days)
|
24
|
+
PER_WEEK = 604_800.0 # 7 days
|
25
|
+
PER_DAY = 86_400.0 # 24 hours
|
26
|
+
```
|
27
|
+
|
28
|
+
### Why Gregorian Year?
|
29
|
+
|
30
|
+
The key design decision ensures **mathematical consistency**:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
12.months == 1.year #=> true (both equal 31,556,952 seconds)
|
34
|
+
```
|
35
|
+
|
36
|
+
This prevents subtle bugs where `12.months` and `1.year` would differ by ~5.8 hours.
|
37
|
+
|
38
|
+
## Basic Time Conversions
|
39
|
+
|
40
|
+
### Converting Numbers to Time Units
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
using Familia::Refinements::TimeUtils
|
44
|
+
|
45
|
+
# Singular and plural forms work identically
|
46
|
+
1.second #=> 1
|
47
|
+
30.seconds #=> 30
|
48
|
+
5.minutes #=> 300
|
49
|
+
2.hours #=> 7200
|
50
|
+
3.days #=> 259200
|
51
|
+
1.week #=> 604800
|
52
|
+
2.months #=> 5259492
|
53
|
+
1.year #=> 31556952
|
54
|
+
```
|
55
|
+
|
56
|
+
### Converting Time Units Back
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
7200.in_hours #=> 2.0
|
60
|
+
259200.in_days #=> 3.0
|
61
|
+
5259492.in_months #=> 2.0
|
62
|
+
31556952.in_years #=> 1.0
|
63
|
+
```
|
64
|
+
|
65
|
+
## String Time Parsing
|
66
|
+
|
67
|
+
Parse human-readable time strings:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
"30s".in_seconds #=> 30.0
|
71
|
+
"5m".in_seconds #=> 300.0
|
72
|
+
"2h".in_seconds #=> 7200.0
|
73
|
+
"1d".in_seconds #=> 86400.0
|
74
|
+
"1w".in_seconds #=> 604800.0
|
75
|
+
"2mo".in_seconds #=> 5259492.0
|
76
|
+
"1y".in_seconds #=> 31556952.0
|
77
|
+
```
|
78
|
+
|
79
|
+
### Supported String Formats
|
80
|
+
|
81
|
+
| Unit | Abbreviations | Example |
|
82
|
+
|------|---------------|---------|
|
83
|
+
| Microseconds | `us`, `μs`, `microsecond`, `microseconds` | `"500us"` |
|
84
|
+
| Milliseconds | `ms`, `millisecond`, `milliseconds` | `"250ms"` |
|
85
|
+
| Seconds | `s`, `second`, `seconds` | `"30s"` |
|
86
|
+
| Minutes | `m`, `minute`, `minutes` | `"15m"` |
|
87
|
+
| Hours | `h`, `hour`, `hours` | `"2h"` |
|
88
|
+
| Days | `d`, `day`, `days` | `"7d"` |
|
89
|
+
| Weeks | `w`, `week`, `weeks` | `"2w"` |
|
90
|
+
| Months | `mo`, `month`, `months` | `"6mo"` |
|
91
|
+
| Years | `y`, `year`, `years` | `"1y"` |
|
92
|
+
|
93
|
+
**Note**: Use `"mo"` for months to avoid confusion with `"m"` (minutes).
|
94
|
+
|
95
|
+
## Age Calculations
|
96
|
+
|
97
|
+
Calculate how old timestamps are:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# Basic age calculation
|
101
|
+
old_timestamp = 2.days.ago.to_i
|
102
|
+
old_timestamp.age_in(:days) #=> ~2.0
|
103
|
+
old_timestamp.age_in(:hours) #=> ~48.0
|
104
|
+
old_timestamp.age_in(:months) #=> ~0.066
|
105
|
+
|
106
|
+
# Convenience methods
|
107
|
+
old_timestamp.days_old #=> ~2.0
|
108
|
+
old_timestamp.hours_old #=> ~48.0
|
109
|
+
old_timestamp.minutes_old #=> ~2880.0
|
110
|
+
old_timestamp.weeks_old #=> ~0.28
|
111
|
+
old_timestamp.months_old #=> ~0.066
|
112
|
+
old_timestamp.years_old #=> ~0.005
|
113
|
+
|
114
|
+
# Calculate age from specific reference time
|
115
|
+
past_timestamp = 1.week.ago.to_i
|
116
|
+
reference_time = 3.days.ago
|
117
|
+
past_timestamp.age_in(:days, reference_time) #=> ~4.0
|
118
|
+
```
|
119
|
+
|
120
|
+
## Time Comparisons
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
timestamp = 2.hours.ago.to_i
|
124
|
+
|
125
|
+
# Check if older than duration
|
126
|
+
timestamp.older_than?(1.hour) #=> true
|
127
|
+
timestamp.older_than?(3.hours) #=> false
|
128
|
+
|
129
|
+
# Check if newer than duration (future)
|
130
|
+
future_timestamp = 1.hour.from_now.to_i
|
131
|
+
future_timestamp.newer_than?(30.minutes) #=> true
|
132
|
+
|
133
|
+
# Check if within duration of now (past or future)
|
134
|
+
timestamp.within?(3.hours) #=> true
|
135
|
+
timestamp.within?(1.hour) #=> false
|
136
|
+
```
|
137
|
+
|
138
|
+
## Practical Examples
|
139
|
+
|
140
|
+
### Cache Expiration
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
using Familia::Refinements::TimeUtils
|
144
|
+
|
145
|
+
class CacheEntry < Familia::Horreum
|
146
|
+
field :data
|
147
|
+
field :created_at
|
148
|
+
|
149
|
+
def expired?
|
150
|
+
created_at.to_i.older_than?(1.hour)
|
151
|
+
end
|
152
|
+
|
153
|
+
def cache_age
|
154
|
+
created_at.to_i.minutes_old
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
### Session Management
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
class UserSession < Familia::Horreum
|
163
|
+
field :last_active
|
164
|
+
|
165
|
+
def stale?
|
166
|
+
last_active.to_i.older_than?(30.minutes)
|
167
|
+
end
|
168
|
+
|
169
|
+
def session_duration
|
170
|
+
"Active for #{last_active.to_i.hours_old.round(1)} hours"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
### Data Retention
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
def cleanup_old_logs
|
179
|
+
Log.all.select do |log|
|
180
|
+
log.timestamp.to_i.older_than?(30.days)
|
181
|
+
end.each(&:destroy)
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
## Important Notes
|
186
|
+
|
187
|
+
### Calendar vs. Precise Time
|
188
|
+
|
189
|
+
Familia's time utilities use **average durations** suitable for:
|
190
|
+
- Age calculations
|
191
|
+
- Cache expiration
|
192
|
+
- Time-based cleanup
|
193
|
+
- General time arithmetic
|
194
|
+
|
195
|
+
For **calendar-aware operations** (exact months, leap years), use Ruby's `Date`/`Time` classes:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
# For average durations (Familia)
|
199
|
+
user_age = signup_date.to_i.months_old #=> 6.2
|
200
|
+
|
201
|
+
# For exact calendar operations (Ruby stdlib)
|
202
|
+
exact_months = Date.today.months_since(signup_date) #=> 6
|
203
|
+
```
|
204
|
+
|
205
|
+
### Thread Safety
|
206
|
+
|
207
|
+
All time utility methods are thread-safe and work with frozen objects.
|
208
|
+
|
209
|
+
## Migration from v1.x
|
210
|
+
|
211
|
+
If upgrading from earlier versions:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
# Old behavior (inconsistent)
|
215
|
+
12.months != 1.year # Different values
|
216
|
+
|
217
|
+
# New behavior (consistent)
|
218
|
+
12.months == 1.year # Same value: 31,556,952 seconds
|
219
|
+
```
|
220
|
+
|
221
|
+
Update any code that relied on the old 365-day year constant to expect the new Gregorian year values.
|
@@ -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,253 @@
|
|
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
|
+
# 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
|
+
class Customer < Familia::Horreum
|
116
|
+
# Features now available for use
|
117
|
+
feature :deprecated_fields
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
### After: Automatic Loading
|
122
|
+
```ruby
|
123
|
+
# customer/features.rb
|
124
|
+
|
125
|
+
class Customer < Familia::Horreum
|
126
|
+
module Features
|
127
|
+
include Familia::Autoloader
|
128
|
+
# Automatically discovers and loads all *.rb files from customer/features/
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Customer < Familia::Horreum
|
133
|
+
# Features automatically loaded and available
|
134
|
+
feature :deprecated_fields
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
**Directory structure this enables:**
|
139
|
+
```
|
140
|
+
models/
|
141
|
+
├── customer/
|
142
|
+
│ ├── features/
|
143
|
+
│ │ ├── deprecated_fields.rb # Standardized names!
|
144
|
+
│ │ ├── legacy_support.rb
|
145
|
+
│ │ └── stripe_integration.rb
|
146
|
+
│ └── features.rb # Include Autoloader here
|
147
|
+
├── session/
|
148
|
+
│ ├── features/
|
149
|
+
│ │ ├── deprecated_fields.rb # Same name, different implementation
|
150
|
+
│ │ └── expiration_hooks.rb
|
151
|
+
│ └── features.rb
|
152
|
+
└── customer.rb
|
153
|
+
```
|
154
|
+
|
155
|
+
## Field Definitions in Feature Modules
|
156
|
+
|
157
|
+
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.
|
158
|
+
|
159
|
+
### Example
|
160
|
+
```ruby
|
161
|
+
# features/common_fields.rb
|
162
|
+
|
163
|
+
module CommonFields
|
164
|
+
def self.included(base)
|
165
|
+
base.extend ClassMethods
|
166
|
+
end
|
167
|
+
|
168
|
+
module ClassMethods
|
169
|
+
# These field calls execute in the extending class's context
|
170
|
+
field :created
|
171
|
+
field :updated
|
172
|
+
field :version
|
173
|
+
|
174
|
+
def touch_updated
|
175
|
+
self.updated = Time.now.to_i
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
Familia::Base.add_feature self, :common_fields
|
180
|
+
end
|
181
|
+
|
182
|
+
# Usage
|
183
|
+
class Customer < Familia::Horreum
|
184
|
+
feature :common_fields
|
185
|
+
# Now has :created, :updated, :version fields and touch_updated class method
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
## Migration Steps
|
190
|
+
|
191
|
+
### 1. Update SafeDump Usage
|
192
|
+
Replace all `@safe_dump_fields` definitions with the new DSL:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
# Find and replace pattern:
|
196
|
+
# Old: @safe_dump_fields = [:field1, :field2, { field3: ->(obj) { ... } }]
|
197
|
+
# New: safe_dump_fields :field1, :field2, { field3: ->(obj) { ... } }
|
198
|
+
|
199
|
+
# Or use individual field definitions for better readability:
|
200
|
+
safe_dump_field :field1
|
201
|
+
safe_dump_field :field2
|
202
|
+
safe_dump_field :field3, ->(obj) { ... }
|
203
|
+
```
|
204
|
+
|
205
|
+
### 2. Set Up Auto-loading (Optional)
|
206
|
+
If you have project-specific features, set up auto-loading:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
# Create: models/[model_name]/features.rb
|
210
|
+
module YourProject
|
211
|
+
class ModelName < Familia::Horreum
|
212
|
+
module Features
|
213
|
+
include Familia::Autoloader
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Require this file before your model definitions
|
219
|
+
require_relative 'model_name/features'
|
220
|
+
```
|
221
|
+
|
222
|
+
### 3. Organize Features by Model (Optional)
|
223
|
+
Consider reorganizing shared feature names by model:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
# Before: features/customer_deprecated_fields.rb
|
227
|
+
# After: models/customer/features/deprecated_fields.rb
|
228
|
+
|
229
|
+
# This allows multiple models to have their own deprecated_fields.rb
|
230
|
+
```
|
231
|
+
|
232
|
+
### 4. Test Your Changes
|
233
|
+
Run your test suite to ensure all SafeDump functionality works correctly:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
# Verify SafeDump DSL works
|
237
|
+
model = YourModel.new(field1: 'value')
|
238
|
+
result = model.safe_dump
|
239
|
+
puts result.keys # Should include your defined fields
|
240
|
+
```
|
241
|
+
|
242
|
+
## Breaking Changes
|
243
|
+
|
244
|
+
1. **`@safe_dump_fields` no longer supported** - Must migrate to DSL methods
|
245
|
+
2. **SafeDump field order** - Fields are now returned in definition order via Hash keys (Ruby 1.9+ behavior)
|
246
|
+
|
247
|
+
## New Capabilities Unlocked
|
248
|
+
|
249
|
+
1. **Standardized feature names** across different models
|
250
|
+
2. **Cleaner SafeDump definitions** that can be easily moved to feature modules
|
251
|
+
3. **Automatic feature discovery** for better project organization
|
252
|
+
4. **Model-specific feature registries** for better namespace management
|
253
|
+
5. **Field definitions in feature modules** for shared functionality
|