better_model 1.0.0 โ 1.2.0
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/README.md +427 -85
- data/lib/better_model/archivable.rb +10 -10
- data/lib/better_model/predicable.rb +39 -52
- data/lib/better_model/sortable.rb +4 -0
- data/lib/better_model/state_transition.rb +106 -0
- data/lib/better_model/stateable/configurator.rb +300 -0
- data/lib/better_model/stateable/errors.rb +45 -0
- data/lib/better_model/stateable/guard.rb +87 -0
- data/lib/better_model/stateable/transition.rb +143 -0
- data/lib/better_model/stateable.rb +354 -0
- data/lib/better_model/traceable.rb +498 -0
- data/lib/better_model/validatable/business_rule_validator.rb +47 -0
- data/lib/better_model/validatable/configurator.rb +245 -0
- data/lib/better_model/validatable/order_validator.rb +77 -0
- data/lib/better_model/validatable.rb +270 -0
- data/lib/better_model/version.rb +1 -1
- data/lib/better_model/version_record.rb +63 -0
- data/lib/better_model.rb +15 -2
- data/lib/generators/better_model/stateable/install_generator.rb +37 -0
- data/lib/generators/better_model/stateable/stateable_generator.rb +50 -0
- data/lib/generators/better_model/stateable/templates/README +38 -0
- data/lib/generators/better_model/stateable/templates/install_migration.rb.tt +20 -0
- data/lib/generators/better_model/stateable/templates/migration.rb.tt +9 -0
- data/lib/generators/better_model/traceable/templates/create_table_migration.rb.tt +28 -0
- data/lib/generators/better_model/traceable/traceable_generator.rb +77 -0
- metadata +22 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec236d26e4c1e0a5a62e9932606a1baa28de1bcadf8b447f1da545fb35d029b6
|
|
4
|
+
data.tar.gz: 24e4aa004924ab09c6104cad770229041b8c8d24556ce5f2f2d190bf15071e90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77d291e54bcbbb179eabd842290530d0f42e03636981e93bfdeb6575d5d0e02960d359072c0b0db0144f35cdbea647d463bf7320037c557552f6fce8365a393e
|
|
7
|
+
data.tar.gz: bfe3bf61206cb343dce4d84b2ff643e719c09b93a623de77e127a00aadbce646d25bfcc2cd66ca3bd725b6af224a82eb7cff2e059f79f45c8e026d5d878c95d6
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# BetterModel
|
|
1
|
+
# BetterModel ๐
|
|
2
2
|
|
|
3
|
-
BetterModel is a Rails engine gem (Rails 8.1+) that provides powerful extensions for ActiveRecord models, including declarative status management, permissions, archiving, sorting, filtering, and unified search capabilities.
|
|
3
|
+
BetterModel is a Rails engine gem (Rails 8.1+) that provides powerful extensions for ActiveRecord models, including declarative status management, permissions, state machines, validations, archiving, change tracking, sorting, filtering, and unified search capabilities.
|
|
4
4
|
|
|
5
|
-
## Installation
|
|
5
|
+
## ๐ฆ Installation
|
|
6
6
|
|
|
7
7
|
Add this line to your application's Gemfile:
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ Or install it yourself as:
|
|
|
20
20
|
$ gem install better_model
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
## Quick Start
|
|
23
|
+
## โก Quick Start
|
|
24
24
|
|
|
25
25
|
Simply include `BetterModel` in your model to get all features:
|
|
26
26
|
|
|
@@ -52,7 +52,52 @@ class Article < ApplicationRecord
|
|
|
52
52
|
skip_archived_by_default true # Hide archived records by default
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
# 6.
|
|
55
|
+
# 6. VALIDATABLE - Declarative validation system (opt-in)
|
|
56
|
+
validatable do
|
|
57
|
+
# Basic validations
|
|
58
|
+
validate :title, :content, presence: true
|
|
59
|
+
|
|
60
|
+
# Conditional validations
|
|
61
|
+
validate_if :is_published? do
|
|
62
|
+
validate :published_at, presence: true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Cross-field validations
|
|
66
|
+
validate_order :starts_at, :before, :ends_at
|
|
67
|
+
|
|
68
|
+
# Business rules
|
|
69
|
+
validate_business_rule :valid_category
|
|
70
|
+
|
|
71
|
+
# Validation groups (multi-step forms)
|
|
72
|
+
validation_group :step1, [:title, :content]
|
|
73
|
+
validation_group :step2, [:published_at]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# 7. STATEABLE - Declarative state machine (opt-in)
|
|
77
|
+
stateable do
|
|
78
|
+
# Define states
|
|
79
|
+
state :draft, initial: true
|
|
80
|
+
state :published
|
|
81
|
+
state :archived
|
|
82
|
+
|
|
83
|
+
# Define transitions with guards and callbacks
|
|
84
|
+
transition :publish, from: :draft, to: :published do
|
|
85
|
+
guard { valid? }
|
|
86
|
+
guard if: :is_ready_for_publishing? # Statusable integration
|
|
87
|
+
before { set_published_at }
|
|
88
|
+
after { notify_subscribers }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
transition :archive, from: [:draft, :published], to: :archived
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 8. TRACEABLE - Audit trail with time-travel (opt-in)
|
|
95
|
+
traceable do
|
|
96
|
+
track :title, :content, :status, :published_at
|
|
97
|
+
versions_table :article_versions # Optional: custom table
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 9. SEARCHABLE - Configure unified search interface
|
|
56
101
|
searchable do
|
|
57
102
|
per_page 25
|
|
58
103
|
max_per_page 100
|
|
@@ -62,41 +107,66 @@ class Article < ApplicationRecord
|
|
|
62
107
|
end
|
|
63
108
|
```
|
|
64
109
|
|
|
65
|
-
Now you can use all the features
|
|
110
|
+
๐ก **Now you can use all the features:**
|
|
66
111
|
|
|
67
112
|
```ruby
|
|
68
|
-
# Check statuses
|
|
113
|
+
# โ
Check statuses
|
|
69
114
|
article.is?(:draft) # => true/false
|
|
70
115
|
article.is_published? # => true/false
|
|
71
116
|
article.statuses # => { draft: true, published: false, ... }
|
|
72
117
|
|
|
73
|
-
# Check permissions
|
|
118
|
+
# ๐ Check permissions
|
|
74
119
|
article.permit?(:edit) # => true/false
|
|
75
120
|
article.permit_delete? # => true/false
|
|
76
121
|
article.permissions # => { edit: true, delete: true, ... }
|
|
77
122
|
|
|
78
|
-
# Sort
|
|
123
|
+
# โฌ๏ธ Sort
|
|
79
124
|
Article.sort_title_asc
|
|
80
125
|
Article.sort_view_count_desc
|
|
81
126
|
Article.sort_published_at_desc
|
|
82
127
|
|
|
83
|
-
# Filter with predicates
|
|
128
|
+
# ๐ Filter with predicates
|
|
84
129
|
Article.status_eq("published")
|
|
85
130
|
Article.title_cont("Rails")
|
|
86
131
|
Article.view_count_gteq(100)
|
|
87
132
|
Article.published_at_present
|
|
88
133
|
|
|
89
|
-
# Archive records
|
|
134
|
+
# ๐๏ธ Archive records
|
|
90
135
|
article.archive!(by: current_user, reason: "Outdated")
|
|
91
136
|
article.archived? # => true
|
|
92
137
|
article.restore!
|
|
93
138
|
|
|
94
|
-
# Query archived records
|
|
139
|
+
# ๐ Query archived records
|
|
95
140
|
Article.archived
|
|
96
141
|
Article.not_archived
|
|
97
142
|
Article.archived_recently(7.days)
|
|
98
143
|
|
|
99
|
-
#
|
|
144
|
+
# โ
Validate with groups (multi-step forms)
|
|
145
|
+
article.valid?(:step1) # Validate only step1 fields
|
|
146
|
+
article.valid?(:step2) # Validate only step2 fields
|
|
147
|
+
article.errors_for_group(:step1) # Get errors for step1 only
|
|
148
|
+
|
|
149
|
+
# ๐ State machine transitions
|
|
150
|
+
article.state # => "draft"
|
|
151
|
+
article.draft? # => true
|
|
152
|
+
article.can_publish? # => true (checks guards)
|
|
153
|
+
article.publish! # Executes transition with guards & callbacks
|
|
154
|
+
article.published? # => true
|
|
155
|
+
article.state_transitions # History of all transitions
|
|
156
|
+
article.transition_history # Formatted history array
|
|
157
|
+
|
|
158
|
+
# โฐ Time travel & rollback (Traceable)
|
|
159
|
+
article.audit_trail # Full change history
|
|
160
|
+
article.as_of(3.days.ago) # Reconstruct past state
|
|
161
|
+
article.rollback_to(version) # Restore to previous version
|
|
162
|
+
article.changes_for(:status) # Changes for specific field
|
|
163
|
+
|
|
164
|
+
# ๐ Query changes
|
|
165
|
+
Article.changed_by(user.id)
|
|
166
|
+
Article.changed_between(1.week.ago, Time.current)
|
|
167
|
+
Article.status_changed_from("draft").to("published")
|
|
168
|
+
|
|
169
|
+
# ๐ Unified search with filters, sorting, and pagination
|
|
100
170
|
Article.search(
|
|
101
171
|
{ status_eq: "published", view_count_gteq: 50 },
|
|
102
172
|
orders: [:sort_published_at_desc],
|
|
@@ -104,7 +174,7 @@ Article.search(
|
|
|
104
174
|
)
|
|
105
175
|
```
|
|
106
176
|
|
|
107
|
-
### Including Individual Concerns (Advanced)
|
|
177
|
+
### ๐ฏ Including Individual Concerns (Advanced)
|
|
108
178
|
|
|
109
179
|
If you only need specific features, you can include individual concerns:
|
|
110
180
|
|
|
@@ -113,21 +183,42 @@ class Article < ApplicationRecord
|
|
|
113
183
|
include BetterModel::Statusable # Only status management
|
|
114
184
|
include BetterModel::Permissible # Only permissions
|
|
115
185
|
include BetterModel::Archivable # Only archiving
|
|
186
|
+
include BetterModel::Traceable # Only audit trail & time-travel
|
|
116
187
|
include BetterModel::Sortable # Only sorting
|
|
117
188
|
include BetterModel::Predicable # Only filtering
|
|
189
|
+
include BetterModel::Validatable # Only validations
|
|
190
|
+
include BetterModel::Stateable # Only state machine
|
|
118
191
|
include BetterModel::Searchable # Only search (requires Predicable & Sortable)
|
|
119
192
|
|
|
120
193
|
# Define your features...
|
|
121
194
|
end
|
|
122
195
|
```
|
|
123
196
|
|
|
124
|
-
##
|
|
197
|
+
## ๐ Features Overview
|
|
198
|
+
|
|
199
|
+
BetterModel provides nine powerful concerns that work seamlessly together:
|
|
200
|
+
|
|
201
|
+
### Core Features
|
|
202
|
+
|
|
203
|
+
- **โจ Statusable** - Declarative status management with lambda-based conditions
|
|
204
|
+
- **๐ Permissible** - State-based permission system
|
|
205
|
+
- **๐๏ธ Archivable** - Soft delete with tracking (by user, reason)
|
|
206
|
+
- **โฐ Traceable** ๐ - Complete audit trail with time-travel and rollback
|
|
207
|
+
- **โฌ๏ธ Sortable** - Type-aware sorting scopes
|
|
208
|
+
- **๐ Predicable** - Advanced filtering with rich predicate system
|
|
209
|
+
- **๐ Searchable** - Unified search interface (Predicable + Sortable)
|
|
210
|
+
- **โ
Validatable** - Declarative validation DSL with conditional rules
|
|
211
|
+
- **๐ Stateable** ๐ - Declarative state machines with guards & callbacks
|
|
212
|
+
|
|
213
|
+
[See all features in detail โ](#-features)
|
|
214
|
+
|
|
215
|
+
## โ๏ธ Requirements
|
|
125
216
|
|
|
126
217
|
- **Ruby:** 3.0 or higher
|
|
127
218
|
- **Rails:** 8.1 or higher
|
|
128
219
|
- **ActiveRecord:** Included with Rails
|
|
129
220
|
|
|
130
|
-
## Database Compatibility
|
|
221
|
+
## ๐พ Database Compatibility
|
|
131
222
|
|
|
132
223
|
BetterModel works with all databases supported by ActiveRecord:
|
|
133
224
|
|
|
@@ -143,20 +234,20 @@ BetterModel works with all databases supported by ActiveRecord:
|
|
|
143
234
|
- Array predicates: `overlaps`, `contains`, `contained_by`
|
|
144
235
|
- JSONB predicates: `has_key`, `has_any_key`, `has_all_keys`, `jsonb_contains`
|
|
145
236
|
|
|
146
|
-
## Features
|
|
237
|
+
## ๐ Features
|
|
147
238
|
|
|
148
|
-
BetterModel provides
|
|
239
|
+
BetterModel provides eight powerful concerns that work together seamlessly:
|
|
149
240
|
|
|
150
241
|
### ๐ Statusable - Declarative Status Management
|
|
151
242
|
|
|
152
243
|
Define derived statuses dynamically based on model attributes - no database columns needed!
|
|
153
244
|
|
|
154
|
-
|
|
155
|
-
- Declarative DSL with clear, readable conditions
|
|
156
|
-
- Statuses calculated in real-time from model attributes
|
|
157
|
-
- Reference other statuses in conditions
|
|
158
|
-
- Automatic method generation (`is_draft?`, `is_published?`)
|
|
159
|
-
- Thread-safe with immutable registry
|
|
245
|
+
**๐ฏ Key Benefits:**
|
|
246
|
+
- โจ Declarative DSL with clear, readable conditions
|
|
247
|
+
- โก Statuses calculated in real-time from model attributes
|
|
248
|
+
- ๐ Reference other statuses in conditions
|
|
249
|
+
- ๐ค Automatic method generation (`is_draft?`, `is_published?`)
|
|
250
|
+
- ๐ Thread-safe with immutable registry
|
|
160
251
|
|
|
161
252
|
**[๐ Full Documentation โ](docs/statusable.md)**
|
|
162
253
|
|
|
@@ -166,12 +257,12 @@ Define derived statuses dynamically based on model attributes - no database colu
|
|
|
166
257
|
|
|
167
258
|
Define permissions dynamically based on model state and statuses - perfect for authorization logic!
|
|
168
259
|
|
|
169
|
-
|
|
170
|
-
- Declarative DSL following Statusable pattern
|
|
171
|
-
- Permissions calculated from model state
|
|
172
|
-
- Reference statuses in permission logic
|
|
173
|
-
- Automatic method generation (`permit_edit?`, `permit_delete?`)
|
|
174
|
-
- Thread-safe with immutable registry
|
|
260
|
+
**๐ฏ Key Benefits:**
|
|
261
|
+
- โจ Declarative DSL following Statusable pattern
|
|
262
|
+
- โก Permissions calculated from model state
|
|
263
|
+
- ๐ Reference statuses in permission logic
|
|
264
|
+
- ๐ค Automatic method generation (`permit_edit?`, `permit_delete?`)
|
|
265
|
+
- ๐ Thread-safe with immutable registry
|
|
175
266
|
|
|
176
267
|
**[๐ Full Documentation โ](docs/permissible.md)**
|
|
177
268
|
|
|
@@ -181,30 +272,87 @@ Define permissions dynamically based on model state and statuses - perfect for a
|
|
|
181
272
|
|
|
182
273
|
Soft-delete records with archive tracking, audit trails, and restoration capabilities.
|
|
183
274
|
|
|
184
|
-
|
|
185
|
-
- Opt-in activation: only enabled when explicitly configured
|
|
186
|
-
- Archive and restore methods with optional tracking
|
|
187
|
-
- Status methods: `archived?` and `active?`
|
|
188
|
-
- Semantic scopes: `archived`, `not_archived`, `archived_only`
|
|
189
|
-
- Helper predicates: `archived_today`, `archived_this_week`, `archived_recently`
|
|
190
|
-
- Optional default scope to hide archived records
|
|
191
|
-
- Migration generator with flexible options
|
|
192
|
-
- Thread-safe with immutable configuration
|
|
275
|
+
**๐ฏ Key Benefits:**
|
|
276
|
+
- ๐๏ธ Opt-in activation: only enabled when explicitly configured
|
|
277
|
+
- ๐ Archive and restore methods with optional tracking
|
|
278
|
+
- โ
Status methods: `archived?` and `active?`
|
|
279
|
+
- ๐ Semantic scopes: `archived`, `not_archived`, `archived_only`
|
|
280
|
+
- ๐ ๏ธ Helper predicates: `archived_today`, `archived_this_week`, `archived_recently`
|
|
281
|
+
- ๐ป Optional default scope to hide archived records
|
|
282
|
+
- ๐ Migration generator with flexible options
|
|
283
|
+
- ๐ Thread-safe with immutable configuration
|
|
193
284
|
|
|
194
285
|
**[๐ Full Documentation โ](docs/archivable.md)**
|
|
195
286
|
|
|
196
287
|
---
|
|
197
288
|
|
|
289
|
+
### โ
Validatable - Declarative Validation System
|
|
290
|
+
|
|
291
|
+
Define validations declaratively with support for conditional rules, cross-field validation, business rules, and validation groups.
|
|
292
|
+
|
|
293
|
+
**๐ฏ Key Benefits:**
|
|
294
|
+
- ๐๏ธ Opt-in activation: only enabled when explicitly configured
|
|
295
|
+
- โจ Declarative DSL for all validation types
|
|
296
|
+
- ๐ Conditional validations: `validate_if` / `validate_unless`
|
|
297
|
+
- ๐ Cross-field validations: `validate_order` for date/number comparisons
|
|
298
|
+
- ๐ผ Business rules: delegate complex logic to custom methods
|
|
299
|
+
- ๐ Validation groups: partial validation for multi-step forms
|
|
300
|
+
- ๐ Thread-safe with immutable configuration
|
|
301
|
+
|
|
302
|
+
**[๐ Full Documentation โ](docs/validatable.md)**
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### ๐ Stateable - Declarative State Machine
|
|
307
|
+
|
|
308
|
+
Define state machines declaratively with transitions, guards, validations, and callbacks for robust workflow management.
|
|
309
|
+
|
|
310
|
+
**๐ฏ Key Benefits:**
|
|
311
|
+
- ๐๏ธ Opt-in activation: only enabled when explicitly configured
|
|
312
|
+
- โจ Declarative DSL for states and transitions
|
|
313
|
+
- ๐ก๏ธ Guards: preconditions with lambda, methods, or Statusable predicates
|
|
314
|
+
- โ
Validations: custom validation logic per transition
|
|
315
|
+
- ๐ Callbacks: before/after/around hooks for each transition
|
|
316
|
+
- ๐ State history tracking with customizable table names
|
|
317
|
+
- ๐ค Dynamic methods: `pending?`, `confirm!`, `can_confirm?`
|
|
318
|
+
- ๐ Integration with Statusable for complex guard logic
|
|
319
|
+
- ๐ Thread-safe with immutable configuration
|
|
320
|
+
|
|
321
|
+
**[๐ Full Documentation โ](docs/stateable.md)**
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
### โฐ Traceable - Audit Trail with Time-Travel
|
|
326
|
+
|
|
327
|
+
Track all changes to your records with complete audit trail, time-travel capabilities, and rollback support.
|
|
328
|
+
|
|
329
|
+
**๐ฏ Key Benefits:**
|
|
330
|
+
- ๐๏ธ Opt-in activation: only enabled when explicitly configured
|
|
331
|
+
- ๐ Automatic change tracking on create, update, and destroy
|
|
332
|
+
- ๐ค User attribution: track who made each change
|
|
333
|
+
- ๐ฌ Change reasons: optional context for changes
|
|
334
|
+
- โฐ Time-travel: reconstruct object state at any point in history
|
|
335
|
+
- โฉ๏ธ Rollback support: restore records to previous versions
|
|
336
|
+
- ๐ Rich query API: find changes by user, time, or field transitions
|
|
337
|
+
- ๐ Flexible table naming: per-model, shared, or custom tables
|
|
338
|
+
- ๐ Polymorphic association for efficient storage
|
|
339
|
+
- ๐พ Database adapter safety: PostgreSQL, MySQL, SQLite support
|
|
340
|
+
- ๐ Thread-safe dynamic class creation
|
|
341
|
+
|
|
342
|
+
**[๐ Full Documentation โ](docs/traceable.md)**
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
198
346
|
### โฌ๏ธ Sortable - Type-Aware Sorting Scopes
|
|
199
347
|
|
|
200
348
|
Generate intelligent sorting scopes automatically with database-specific optimizations and NULL handling.
|
|
201
349
|
|
|
202
|
-
|
|
203
|
-
- Type-aware scope generation (string, numeric, datetime, boolean)
|
|
204
|
-
- Case-insensitive sorting for strings
|
|
205
|
-
- Database-specific NULLS FIRST/LAST support
|
|
206
|
-
- Sort by multiple fields with chaining
|
|
207
|
-
- Optimized queries with proper indexing support
|
|
350
|
+
**๐ฏ Key Benefits:**
|
|
351
|
+
- ๐ฏ Type-aware scope generation (string, numeric, datetime, boolean)
|
|
352
|
+
- ๐ค Case-insensitive sorting for strings
|
|
353
|
+
- ๐พ Database-specific NULLS FIRST/LAST support
|
|
354
|
+
- ๐ Sort by multiple fields with chaining
|
|
355
|
+
- โก Optimized queries with proper indexing support
|
|
208
356
|
|
|
209
357
|
**[๐ Full Documentation โ](docs/sortable.md)**
|
|
210
358
|
|
|
@@ -214,13 +362,13 @@ Generate intelligent sorting scopes automatically with database-specific optimiz
|
|
|
214
362
|
|
|
215
363
|
Generate comprehensive predicate scopes for filtering and searching with support for all data types.
|
|
216
364
|
|
|
217
|
-
|
|
218
|
-
- Complete coverage: string, numeric, datetime, boolean, null predicates
|
|
219
|
-
- Type-safe predicates based on column type
|
|
220
|
-
- Case-insensitive string matching
|
|
221
|
-
- Range queries (between) for numerics and dates
|
|
222
|
-
- PostgreSQL array and JSONB support
|
|
223
|
-
- Chainable with standard ActiveRecord queries
|
|
365
|
+
**๐ฏ Key Benefits:**
|
|
366
|
+
- โ
Complete coverage: string, numeric, datetime, boolean, null predicates
|
|
367
|
+
- ๐ Type-safe predicates based on column type
|
|
368
|
+
- ๐ค Case-insensitive string matching
|
|
369
|
+
- ๐ Range queries (between) for numerics and dates
|
|
370
|
+
- ๐ PostgreSQL array and JSONB support
|
|
371
|
+
- ๐ Chainable with standard ActiveRecord queries
|
|
224
372
|
|
|
225
373
|
**[๐ Full Documentation โ](docs/predicable.md)**
|
|
226
374
|
|
|
@@ -230,57 +378,230 @@ Generate comprehensive predicate scopes for filtering and searching with support
|
|
|
230
378
|
|
|
231
379
|
Orchestrate Predicable and Sortable into a powerful, secure search interface with pagination and security.
|
|
232
380
|
|
|
233
|
-
|
|
234
|
-
- Unified API: single `search()` method for all operations
|
|
235
|
-
- OR conditions for complex logic
|
|
236
|
-
- Built-in pagination with DoS protection (max_per_page)
|
|
237
|
-
- Security enforcement with required predicates
|
|
238
|
-
- Default ordering configuration
|
|
239
|
-
- Strong parameters integration
|
|
240
|
-
- Type-safe validation of all parameters
|
|
381
|
+
**๐ฏ Key Benefits:**
|
|
382
|
+
- ๐ฏ Unified API: single `search()` method for all operations
|
|
383
|
+
- ๐ OR conditions for complex logic
|
|
384
|
+
- ๐ Built-in pagination with DoS protection (max_per_page)
|
|
385
|
+
- ๐ Security enforcement with required predicates
|
|
386
|
+
- โ๏ธ Default ordering configuration
|
|
387
|
+
- ๐ช Strong parameters integration
|
|
388
|
+
- โ
Type-safe validation of all parameters
|
|
241
389
|
|
|
242
390
|
**[๐ Full Documentation โ](docs/searchable.md)**
|
|
243
391
|
|
|
244
392
|
---
|
|
245
393
|
|
|
246
|
-
|
|
394
|
+
### ๐ Traceable - Audit Trail & Change Tracking
|
|
395
|
+
|
|
396
|
+
Track all changes to your records with comprehensive audit trail functionality, time-travel queries, and rollback capabilities.
|
|
397
|
+
|
|
398
|
+
**๐ฏ Key Benefits:**
|
|
399
|
+
- ๐๏ธ Opt-in activation: only enabled when explicitly configured
|
|
400
|
+
- ๐ค Automatic change tracking on create/update/destroy
|
|
401
|
+
- โฐ Time-travel: reconstruct record state at any point in time
|
|
402
|
+
- โฉ๏ธ Rollback: restore to previous versions
|
|
403
|
+
- ๐ Audit trail with who/why tracking
|
|
404
|
+
- ๐ Query changes by user, date range, or field transitions
|
|
405
|
+
- ๐๏ธ Flexible table naming: per-model tables (default), shared table, or custom names
|
|
406
|
+
|
|
407
|
+
**[๐ Full Documentation โ](docs/traceable.md)**
|
|
408
|
+
|
|
409
|
+
#### ๐ Quick Setup
|
|
410
|
+
|
|
411
|
+
**1๏ธโฃ Step 1: Create the versions table**
|
|
412
|
+
|
|
413
|
+
By default, each model gets its own versions table (`{model}_versions`):
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# Creates migration for article_versions table
|
|
417
|
+
rails g better_model:traceable Article --create-table
|
|
418
|
+
rails db:migrate
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Or use a custom table name:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
# Creates migration for custom table name
|
|
425
|
+
rails g better_model:traceable Article --create-table --table-name=audit_log
|
|
426
|
+
rails db:migrate
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**2๏ธโฃ Step 2: Enable in your model**
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
class Article < ApplicationRecord
|
|
433
|
+
include BetterModel
|
|
434
|
+
|
|
435
|
+
# Activate traceable (opt-in)
|
|
436
|
+
traceable do
|
|
437
|
+
track :status, :title, :published_at # Fields to track
|
|
438
|
+
# versions_table 'audit_log' # Optional: custom table (default: article_versions)
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**๐ก Usage:**
|
|
444
|
+
|
|
445
|
+
```ruby
|
|
446
|
+
# ๐ค Automatic tracking on changes
|
|
447
|
+
article.update!(status: "published", updated_by_id: user.id, updated_reason: "Approved")
|
|
448
|
+
|
|
449
|
+
# ๐ Query version history
|
|
450
|
+
article.versions # All versions (ordered desc)
|
|
451
|
+
article.changes_for(:status) # Changes for specific field
|
|
452
|
+
article.audit_trail # Full formatted history
|
|
453
|
+
|
|
454
|
+
# โฐ Time-travel: reconstruct state at specific time
|
|
455
|
+
past_article = article.as_of(3.days.ago)
|
|
456
|
+
past_article.status # => "draft" (what it was 3 days ago)
|
|
457
|
+
|
|
458
|
+
# โฉ๏ธ Rollback to previous version
|
|
459
|
+
version = article.versions.where(event: "updated").first
|
|
460
|
+
article.rollback_to(version, updated_by_id: user.id, updated_reason: "Mistake")
|
|
461
|
+
|
|
462
|
+
# ๐ Class-level queries
|
|
463
|
+
Article.changed_by(user.id) # Records changed by user
|
|
464
|
+
Article.changed_between(1.week.ago, Time.current) # Changes in period
|
|
465
|
+
Article.status_changed_from("draft").to("published") # Specific transitions (PostgreSQL)
|
|
466
|
+
|
|
467
|
+
# ๐ฆ Integration with as_json
|
|
468
|
+
article.as_json(include_audit_trail: true) # Include full history in JSON
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**๐พ Database Schema:**
|
|
472
|
+
|
|
473
|
+
By default, each model gets its own versions table (e.g., `article_versions` for Article model).
|
|
474
|
+
You can also use a shared table across multiple models or a custom table name.
|
|
475
|
+
|
|
476
|
+
| Column | Type | Description |
|
|
477
|
+
|--------|------|-------------|
|
|
478
|
+
| `item_type` | string | Polymorphic model name |
|
|
479
|
+
| `item_id` | integer | Polymorphic record ID |
|
|
480
|
+
| `event` | string | Event type: created/updated/destroyed |
|
|
481
|
+
| `object_changes` | json | Before/after values for tracked fields |
|
|
482
|
+
| `updated_by_id` | integer | Optional: user who made the change |
|
|
483
|
+
| `updated_reason` | string | Optional: reason for the change |
|
|
484
|
+
| `created_at` | datetime | When the change occurred |
|
|
485
|
+
|
|
486
|
+
**๐๏ธ Table Naming Options:**
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
# 1๏ธโฃ Option 1: Per-model table (default)
|
|
490
|
+
class Article < ApplicationRecord
|
|
491
|
+
traceable do
|
|
492
|
+
track :status
|
|
493
|
+
# Uses article_versions table automatically
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# 2๏ธโฃ Option 2: Custom table name
|
|
498
|
+
class Article < ApplicationRecord
|
|
499
|
+
traceable do
|
|
500
|
+
track :status
|
|
501
|
+
versions_table 'audit_log' # Uses audit_log table
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# 3๏ธโฃ Option 3: Shared table across models
|
|
506
|
+
class Article < ApplicationRecord
|
|
507
|
+
traceable do
|
|
508
|
+
track :status
|
|
509
|
+
versions_table 'versions' # Shared table
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
class User < ApplicationRecord
|
|
514
|
+
traceable do
|
|
515
|
+
track :email
|
|
516
|
+
versions_table 'versions' # Same shared table
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
**๐ Optional Tracking:**
|
|
522
|
+
|
|
523
|
+
To track who made changes and why, simply set attributes before saving:
|
|
524
|
+
|
|
525
|
+
```ruby
|
|
526
|
+
article.updated_by_id = current_user.id
|
|
527
|
+
article.updated_reason = "Fixed typo"
|
|
528
|
+
article.update!(title: "Corrected Title")
|
|
529
|
+
|
|
530
|
+
# The version will automatically include updated_by_id and updated_reason
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## ๐ Version & Changelog
|
|
247
536
|
|
|
248
537
|
**Current Version:** 1.0.0
|
|
249
538
|
|
|
250
539
|
See [CHANGELOG.md](CHANGELOG.md) for version history and release notes.
|
|
251
540
|
|
|
252
|
-
## Support & Community
|
|
541
|
+
## ๐ฌ Support & Community
|
|
542
|
+
|
|
543
|
+
- ๐ **Issues & Bugs:** [GitHub Issues](https://github.com/alessiobussolari/better_model/issues)
|
|
544
|
+
- ๐ป **Source Code:** [GitHub Repository](https://github.com/alessiobussolari/better_model)
|
|
545
|
+
- ๐ **Documentation:** This README and detailed docs in `docs/` directory
|
|
546
|
+
|
|
547
|
+
## ๐ Complete Documentation
|
|
548
|
+
|
|
549
|
+
### ๐ Feature Guides
|
|
550
|
+
|
|
551
|
+
Detailed documentation for each BetterModel concern:
|
|
552
|
+
|
|
553
|
+
- [**Statusable**](docs/statusable.md) - Status management with derived conditions
|
|
554
|
+
- [**Permissible**](docs/permissible.md) - Permission system based on state
|
|
555
|
+
- [**Archivable**](docs/archivable.md) - Soft delete with comprehensive tracking
|
|
556
|
+
- [**Traceable**](docs/traceable.md) ๐ - Audit trail, time-travel, and rollback
|
|
557
|
+
- [**Sortable**](docs/sortable.md) - Type-aware sorting system
|
|
558
|
+
- [**Predicable**](docs/predicable.md) - Advanced filtering and predicates
|
|
559
|
+
- [**Searchable**](docs/searchable.md) - Unified search interface
|
|
560
|
+
- [**Validatable**](docs/validatable.md) - Declarative validation system
|
|
561
|
+
- [**Stateable**](docs/stateable.md) ๐ - State machine with transitions
|
|
253
562
|
|
|
254
|
-
|
|
255
|
-
- **Source Code:** [GitHub Repository](https://github.com/alessiobussolari/better_model)
|
|
256
|
-
- **Documentation:** This README and detailed docs in `docs/` directory
|
|
563
|
+
### ๐ Advanced Guides
|
|
257
564
|
|
|
258
|
-
|
|
565
|
+
Learn how to master BetterModel in production:
|
|
566
|
+
|
|
567
|
+
- [**Integration Guide**](docs/integration_guide.md) ๐ - Combining multiple concerns effectively
|
|
568
|
+
- [**Performance Guide**](docs/performance_guide.md) ๐ - Optimization strategies and indexing
|
|
569
|
+
- [**Migration Guide**](docs/migration_guide.md) ๐ - Adding BetterModel to existing apps
|
|
570
|
+
|
|
571
|
+
### ๐ก Quick Links
|
|
572
|
+
|
|
573
|
+
- [Installation](#-installation)
|
|
574
|
+
- [Quick Start](#-quick-start)
|
|
575
|
+
- [Features Overview](#-features-overview)
|
|
576
|
+
- [Requirements](#%EF%B8%8F-requirements)
|
|
577
|
+
- [Contributing](#-contributing)
|
|
578
|
+
|
|
579
|
+
## ๐ค Contributing
|
|
259
580
|
|
|
260
581
|
We welcome contributions! Here's how you can help:
|
|
261
582
|
|
|
262
|
-
### Reporting Bugs
|
|
583
|
+
### ๐ Reporting Bugs
|
|
263
584
|
|
|
264
|
-
1. Check if the issue already exists in [GitHub Issues](https://github.com/alessiobussolari/better_model/issues)
|
|
265
|
-
2. Create a new issue with:
|
|
585
|
+
1. โ
Check if the issue already exists in [GitHub Issues](https://github.com/alessiobussolari/better_model/issues)
|
|
586
|
+
2. ๐ Create a new issue with:
|
|
266
587
|
- Clear description of the problem
|
|
267
588
|
- Steps to reproduce
|
|
268
589
|
- Expected vs actual behavior
|
|
269
590
|
- Ruby/Rails versions
|
|
270
591
|
- Database adapter
|
|
271
592
|
|
|
272
|
-
### Submitting Pull Requests
|
|
593
|
+
### ๐ Submitting Pull Requests
|
|
273
594
|
|
|
274
|
-
1. Fork the repository
|
|
275
|
-
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
276
|
-
3. Make your changes with tests
|
|
277
|
-
4. Run the test suite (`bundle exec rake test`)
|
|
278
|
-
5. Ensure RuboCop passes (`bundle exec rubocop`)
|
|
279
|
-
6. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
280
|
-
7. Push to the branch (`git push origin feature/amazing-feature`)
|
|
281
|
-
8. Open a Pull Request
|
|
595
|
+
1. ๐ด Fork the repository
|
|
596
|
+
2. ๐ฟ Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
597
|
+
3. โ๏ธ Make your changes with tests
|
|
598
|
+
4. ๐งช Run the test suite (`bundle exec rake test`)
|
|
599
|
+
5. ๐
Ensure RuboCop passes (`bundle exec rubocop`)
|
|
600
|
+
6. ๐พ Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
601
|
+
7. ๐ค Push to the branch (`git push origin feature/amazing-feature`)
|
|
602
|
+
8. ๐ Open a Pull Request
|
|
282
603
|
|
|
283
|
-
### Development Setup
|
|
604
|
+
### ๐ง Development Setup
|
|
284
605
|
|
|
285
606
|
```bash
|
|
286
607
|
# Clone your fork
|
|
@@ -300,13 +621,34 @@ bundle exec rake test # Coverage report in coverage/index.html
|
|
|
300
621
|
bundle exec rubocop
|
|
301
622
|
```
|
|
302
623
|
|
|
303
|
-
###
|
|
624
|
+
### ๐ Test Coverage Notes
|
|
625
|
+
|
|
626
|
+
The test suite runs on **SQLite** for performance and portability. Current coverage: **91.45%** (1272 / 1391 lines).
|
|
627
|
+
|
|
628
|
+
**Database-Specific Features Not Covered:**
|
|
629
|
+
- **Predicable**: PostgreSQL array predicates (`_overlaps`, `_contains`, `_contained_by`) and JSONB predicates (`_has_key`, `_has_any_key`, `_has_all_keys`, `_jsonb_contains`) - lines 278-376 in `lib/better_model/predicable.rb`
|
|
630
|
+
- **Traceable**: PostgreSQL JSONB queries and MySQL JSON_EXTRACT queries for field-specific change tracking - lines 454-489 in `lib/better_model/traceable.rb`
|
|
631
|
+
- **Sortable**: MySQL NULLS emulation with CASE statements - lines 198-203 in `lib/better_model/sortable.rb`
|
|
632
|
+
|
|
633
|
+
These features are fully implemented with proper SQL sanitization but require manual testing on PostgreSQL/MySQL:
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
# Test on PostgreSQL
|
|
637
|
+
RAILS_ENV=test DATABASE_URL=postgresql://user:pass@localhost/better_model_test rails console
|
|
638
|
+
|
|
639
|
+
# Test on MySQL
|
|
640
|
+
RAILS_ENV=test DATABASE_URL=mysql2://user:pass@localhost/better_model_test rails console
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
All code has inline comments marking database-specific sections for maintainability.
|
|
644
|
+
|
|
645
|
+
### ๐ Code Guidelines
|
|
304
646
|
|
|
305
|
-
- Follow the existing code style (enforced by RuboCop Omakase)
|
|
306
|
-
- Write tests for new features
|
|
307
|
-
- Update documentation (README) for user-facing changes
|
|
308
|
-
- Keep pull requests focused (one feature/fix per PR)
|
|
647
|
+
- โจ Follow the existing code style (enforced by RuboCop Omakase)
|
|
648
|
+
- ๐งช Write tests for new features
|
|
649
|
+
- ๐ Update documentation (README) for user-facing changes
|
|
650
|
+
- ๐ฏ Keep pull requests focused (one feature/fix per PR)
|
|
309
651
|
|
|
310
|
-
## License
|
|
652
|
+
## ๐ License
|
|
311
653
|
|
|
312
654
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|