familia 2.0.0.pre12 → 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 +1 -1
- data/Gemfile +1 -6
- data/Gemfile.lock +13 -7
- data/changelog.d/README.md +5 -5
- data/{setup.cfg → changelog.d/scriv.ini} +1 -1
- data/docs/guides/Feature-System-Autoloading.md +228 -0
- data/docs/guides/time-utilities.md +221 -0
- data/docs/migrating/v2.0.0-pre11.md +14 -16
- data/docs/migrating/v2.0.0-pre13.md +329 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +6 -0
- data/examples/autoloader/mega_customer.rb +17 -0
- data/familia.gemspec +1 -0
- data/lib/familia/autoloader.rb +53 -0
- data/lib/familia/base.rb +5 -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.rb +1 -1
- data/lib/familia/errors.rb +3 -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/quantization.rb +5 -0
- data/lib/familia/features/safe_dump.rb +7 -0
- data/lib/familia/features.rb +20 -16
- data/lib/familia/field_type.rb +2 -0
- data/lib/familia/horreum/core/serialization.rb +3 -3
- data/lib/familia/horreum/subclass/definition.rb +3 -4
- 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/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/version.rb +1 -1
- data/lib/familia.rb +15 -3
- 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/time_utils_try.rb +130 -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/feature_improvements_try.rb +2 -1
- data/try/features/real_feature_integration_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +111 -0
- data/try/helpers/test_helpers.rb +24 -0
- data/try/integration/cross_component_try.rb +3 -1
- metadata +33 -6
- data/CHANGELOG.md +0 -247
- data/lib/familia/core_ext.rb +0 -135
- data/lib/familia/features/autoloader.rb +0 -57
@@ -0,0 +1,228 @@
|
|
1
|
+
# Feature System Autoloading
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Familia's feature system includes an autoloading mechanism that automatically discovers and loads feature-specific extension files when features are included in your classes. This allows you to keep your main model files clean while organizing feature-specific configurations in separate files.
|
6
|
+
|
7
|
+
## The Problem It Solves
|
8
|
+
|
9
|
+
When you include a feature like `safe_dump` in a Familia class:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class User < Familia::Horreum
|
13
|
+
feature :safe_dump # This line should trigger autoloading
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
You want to be able to define the safe dump configuration in a separate file:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
# user/safe_dump_extensions.rb
|
21
|
+
class User
|
22
|
+
safe_dump_fields :name, :email # Don't dump :password
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
But there's a **timing problem**: when should this extension file be loaded?
|
27
|
+
|
28
|
+
## Why Standard `included` Hook Doesn't Work
|
29
|
+
|
30
|
+
The original approach tried to use the standard `included` hook:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
module SafeDump
|
34
|
+
def self.included(base)
|
35
|
+
# Try to autoload here - BUT THIS IS TOO EARLY!
|
36
|
+
autoload_files_for(base)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
**Problem**: This happens **during** the feature inclusion process, before the feature is fully set up. The class isn't in a stable state yet.
|
42
|
+
|
43
|
+
## The Solution: Post-Inclusion Hook
|
44
|
+
|
45
|
+
The `post_inclusion_autoload` system works in **two phases**:
|
46
|
+
|
47
|
+
### Phase 1: Feature System Hook
|
48
|
+
|
49
|
+
In `lib/familia/features.rb`, after including the feature module:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
def feature(feature_name, **options)
|
53
|
+
# ... setup code ...
|
54
|
+
|
55
|
+
include feature_class # Include the feature module
|
56
|
+
|
57
|
+
# NOW call the post-inclusion hook
|
58
|
+
if feature_class.respond_to?(:post_inclusion_autoload)
|
59
|
+
feature_class.post_inclusion_autoload(self, feature_name, options)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
### Phase 2: Autoloadable Implementation
|
65
|
+
|
66
|
+
The `post_inclusion_autoload` method in `lib/familia/features/autoloadable.rb`:
|
67
|
+
|
68
|
+
1. **Gets the source location** of the user's class file using Ruby's introspection:
|
69
|
+
```ruby
|
70
|
+
location_info = Module.const_source_location(base.name)
|
71
|
+
source_location = location_info&.first # e.g., "/path/to/models/user.rb"
|
72
|
+
```
|
73
|
+
|
74
|
+
2. **Calculates extension file paths** based on conventions:
|
75
|
+
```ruby
|
76
|
+
base_dir = File.dirname(location_path) # "/path/to/models"
|
77
|
+
model_name = base.name.snake_case # "user"
|
78
|
+
|
79
|
+
# Look for files like:
|
80
|
+
patterns = [
|
81
|
+
"/path/to/models/user/safe_dump_*.rb",
|
82
|
+
"/path/to/models/user/features/safe_dump_*.rb",
|
83
|
+
"/path/to/models/features/safe_dump_*.rb"
|
84
|
+
]
|
85
|
+
```
|
86
|
+
|
87
|
+
3. **Loads any matching files** found in those locations
|
88
|
+
|
89
|
+
## Why This Timing Matters
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class User < Familia::Horreum
|
93
|
+
feature :safe_dump # ← Timing is critical here
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
**What happens in order:**
|
98
|
+
|
99
|
+
1. `feature :safe_dump` is called
|
100
|
+
2. Feature system includes `Familia::Features::SafeDump` module
|
101
|
+
3. **Feature is now fully included and stable**
|
102
|
+
4. `post_inclusion_autoload` is called
|
103
|
+
5. Extension files are discovered and loaded
|
104
|
+
6. `safe_dump_fields :name, :email` executes in the extension file
|
105
|
+
|
106
|
+
## File Naming Conventions
|
107
|
+
|
108
|
+
The autoloading system looks for files matching these patterns (in order of precedence):
|
109
|
+
|
110
|
+
1. `{model_directory}/{model_name}/{feature_name}_*.rb`
|
111
|
+
2. `{model_directory}/{model_name}/features/{feature_name}_*.rb`
|
112
|
+
3. `{model_directory}/features/{feature_name}_*.rb`
|
113
|
+
|
114
|
+
### Examples
|
115
|
+
|
116
|
+
For a `User` class defined in `app/models/user.rb` with `feature :safe_dump`:
|
117
|
+
|
118
|
+
```
|
119
|
+
app/models/user/safe_dump_extensions.rb # ← Most specific
|
120
|
+
app/models/user/safe_dump_config.rb # ← Also matches pattern
|
121
|
+
app/models/user/features/safe_dump_*.rb # ← Feature subdirectory
|
122
|
+
app/models/features/safe_dump_*.rb # ← Shared feature configs
|
123
|
+
```
|
124
|
+
|
125
|
+
## Complete Example
|
126
|
+
|
127
|
+
### Main Model File
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# app/models/user.rb
|
131
|
+
class User < Familia::Horreum
|
132
|
+
field :name
|
133
|
+
field :email
|
134
|
+
field :password
|
135
|
+
field :created_at
|
136
|
+
|
137
|
+
feature :safe_dump # ← Triggers autoloading
|
138
|
+
feature :expiration
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
### Safe Dump Extensions
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
# app/models/user/safe_dump_extensions.rb
|
146
|
+
class User
|
147
|
+
# Configure which fields are safe to dump in API responses
|
148
|
+
safe_dump_fields :name, :email, :created_at
|
149
|
+
# Note: :password is intentionally excluded for security
|
150
|
+
|
151
|
+
def safe_dump_display_name
|
152
|
+
"#{name} (#{email})"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
### Expiration Extensions
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
# app/models/user/expiration_config.rb
|
161
|
+
class User
|
162
|
+
# Set default TTL for user objects
|
163
|
+
expires_in 30.days
|
164
|
+
|
165
|
+
# Custom expiration logic
|
166
|
+
def should_expire?
|
167
|
+
!active? && last_login_at < 90.days.ago
|
168
|
+
end
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
### Result
|
173
|
+
|
174
|
+
After loading, the `User` class has:
|
175
|
+
- `User.safe_dump_field_names` returns `[:name, :email, :created_at]`
|
176
|
+
- `User.ttl` returns the configured expiration
|
177
|
+
- All extension methods are available on instances
|
178
|
+
|
179
|
+
## Key Benefits
|
180
|
+
|
181
|
+
1. **Separation of Concerns**: Main model file focuses on core definition, extension files handle feature-specific configuration
|
182
|
+
|
183
|
+
2. **Convention Over Configuration**: No manual requires, just follow naming conventions
|
184
|
+
|
185
|
+
3. **Safe Timing**: Extension files load after the feature is fully set up
|
186
|
+
|
187
|
+
4. **Thread Safe**: No shared state between classes
|
188
|
+
|
189
|
+
5. **Discoverable**: Clear file organization makes extensions easy to find
|
190
|
+
|
191
|
+
## Why It's Better Than Alternatives
|
192
|
+
|
193
|
+
- **Manual requires**: Error-prone, verbose, easy to forget
|
194
|
+
- **Configuration blocks**: Clutters the main model file
|
195
|
+
- **Included hook**: Wrong timing, class not stable yet
|
196
|
+
- **Class_eval strings**: Complex, hard to debug and maintain
|
197
|
+
|
198
|
+
The `post_inclusion_autoload` system provides a clean, automatic, and safe way to extend feature behavior without polluting the main class definitions.
|
199
|
+
|
200
|
+
## Implementation Details
|
201
|
+
|
202
|
+
### Autoloadable Module
|
203
|
+
|
204
|
+
Features that support autoloading include the `Familia::Features::Autoloadable` module:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
module Familia::Features::SafeDump
|
208
|
+
include Familia::Features::Autoloadable # ← Enables autoloading
|
209
|
+
|
210
|
+
# Feature implementation...
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
### Anonymous Class Handling
|
215
|
+
|
216
|
+
The system gracefully handles edge cases:
|
217
|
+
|
218
|
+
- **Anonymous classes**: Classes without names (e.g., `Class.new`) are skipped
|
219
|
+
- **Eval contexts**: Classes defined in `eval` or irb are skipped
|
220
|
+
- **Missing files**: No errors if extension files don't exist
|
221
|
+
|
222
|
+
### Error Handling
|
223
|
+
|
224
|
+
- Missing extension files are silently ignored
|
225
|
+
- Syntax errors in extension files propagate normally
|
226
|
+
- `NameError` during constant resolution is caught and logged
|
227
|
+
|
228
|
+
This robust error handling ensures the autoloading system never breaks your application, even with unusual class definitions or missing files.
|
@@ -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.
|
@@ -102,7 +102,7 @@ end
|
|
102
102
|
|
103
103
|
### Before: Manual Loading
|
104
104
|
```ruby
|
105
|
-
#
|
105
|
+
# customer/features.rb
|
106
106
|
|
107
107
|
# Manual feature loading (copied from Familia)
|
108
108
|
features_dir = File.join(__dir__, 'features')
|
@@ -112,29 +112,26 @@ if Dir.exist?(features_dir)
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
feature :deprecated_fields
|
119
|
-
end
|
115
|
+
class Customer < Familia::Horreum
|
116
|
+
# Features now available for use
|
117
|
+
feature :deprecated_fields
|
120
118
|
end
|
121
119
|
```
|
122
120
|
|
123
121
|
### After: Automatic Loading
|
124
122
|
```ruby
|
125
|
-
#
|
126
|
-
|
123
|
+
# customer/features.rb
|
124
|
+
|
125
|
+
class Customer < Familia::Horreum
|
127
126
|
module Features
|
128
|
-
include Familia::
|
127
|
+
include Familia::Autoloader
|
129
128
|
# Automatically discovers and loads all *.rb files from customer/features/
|
130
129
|
end
|
131
130
|
end
|
132
131
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
feature :deprecated_fields
|
137
|
-
end
|
132
|
+
class Customer < Familia::Horreum
|
133
|
+
# Features automatically loaded and available
|
134
|
+
feature :deprecated_fields
|
138
135
|
end
|
139
136
|
```
|
140
137
|
|
@@ -162,6 +159,7 @@ Feature modules can now define fields directly in their `ClassMethods` modules.
|
|
162
159
|
### Example
|
163
160
|
```ruby
|
164
161
|
# features/common_fields.rb
|
162
|
+
|
165
163
|
module CommonFields
|
166
164
|
def self.included(base)
|
167
165
|
base.extend ClassMethods
|
@@ -210,9 +208,9 @@ If you have project-specific features, set up auto-loading:
|
|
210
208
|
```ruby
|
211
209
|
# Create: models/[model_name]/features.rb
|
212
210
|
module YourProject
|
213
|
-
|
211
|
+
class ModelName < Familia::Horreum
|
214
212
|
module Features
|
215
|
-
include Familia::
|
213
|
+
include Familia::Autoloader
|
216
214
|
end
|
217
215
|
end
|
218
216
|
end
|