better_model 1.3.0 โ 2.1.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 +304 -167
- data/lib/better_model/archivable.rb +2 -2
- data/lib/better_model/predicable.rb +43 -62
- data/lib/better_model/searchable.rb +28 -1
- data/lib/better_model/stateable/configurator.rb +46 -46
- data/lib/better_model/stateable/errors.rb +8 -5
- data/lib/better_model/stateable/guard.rb +17 -17
- data/lib/better_model/stateable/transition.rb +14 -14
- data/lib/better_model/stateable.rb +11 -11
- data/lib/better_model/validatable/configurator.rb +12 -12
- data/lib/better_model/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3237879bac91f8200057cba591a1a2f09ccc761f55fd2ded1f1acb5a156c65f2
|
|
4
|
+
data.tar.gz: 6ab53b6128104cac44fdb5ad8ce80ca662a78072724a29c946c9380ad66701f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf5e0a75c42e6f8c81f76756dd4f292779d1df7e258741bc19877665530a50363d7271c31fcace870d4dc61ac75e7dbd6bf01db3c28857fc29da8ab8e87cbed5
|
|
7
|
+
data.tar.gz: 61992d1b052d377e1db14d312ea3eb024998d2f0e36dcb031fd9e900a3bd35b0add348bfbfb6dc687c41b37ce9df85952924cbb5fb7942abd484af107ce87664
|
data/README.md
CHANGED
|
@@ -55,11 +55,11 @@ class Article < ApplicationRecord
|
|
|
55
55
|
# 6. VALIDATABLE - Declarative validation system (opt-in)
|
|
56
56
|
validatable do
|
|
57
57
|
# Basic validations
|
|
58
|
-
|
|
58
|
+
check :title, :content, presence: true
|
|
59
59
|
|
|
60
60
|
# Conditional validations
|
|
61
61
|
validate_if :is_published? do
|
|
62
|
-
|
|
62
|
+
check :published_at, presence: true
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Cross-field validations
|
|
@@ -82,10 +82,10 @@ class Article < ApplicationRecord
|
|
|
82
82
|
|
|
83
83
|
# Define transitions with guards and callbacks
|
|
84
84
|
transition :publish, from: :draft, to: :published do
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
check { valid? }
|
|
86
|
+
check { title.present? && content.present? }
|
|
87
|
+
before_transition { self.published_at = Time.current }
|
|
88
|
+
after_transition { Rails.logger.info "Article #{id} published" }
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
transition :archive, from: [:draft, :published], to: :archived
|
|
@@ -94,6 +94,9 @@ class Article < ApplicationRecord
|
|
|
94
94
|
# 8. TRACEABLE - Audit trail with time-travel (opt-in)
|
|
95
95
|
traceable do
|
|
96
96
|
track :title, :content, :status, :published_at
|
|
97
|
+
track :password_hash, sensitive: :full # Complete redaction
|
|
98
|
+
track :credit_card, sensitive: :partial # Pattern-based masking
|
|
99
|
+
track :api_token, sensitive: :hash # SHA256 hashing
|
|
97
100
|
versions_table :article_versions # Optional: custom table
|
|
98
101
|
end
|
|
99
102
|
|
|
@@ -222,24 +225,137 @@ class Article < ApplicationRecord
|
|
|
222
225
|
end
|
|
223
226
|
```
|
|
224
227
|
|
|
228
|
+
## ๐ ๏ธ Generators
|
|
229
|
+
|
|
230
|
+
Better Model provides Rails generators to help you quickly set up migrations for features that require database tables or columns.
|
|
231
|
+
|
|
232
|
+
### Traceable Generator
|
|
233
|
+
|
|
234
|
+
Create migrations for audit trail version tables:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# Basic usage - shows setup instructions
|
|
238
|
+
rails g better_model:traceable Article
|
|
239
|
+
|
|
240
|
+
# Create migration for versions table
|
|
241
|
+
rails g better_model:traceable Article --create-table
|
|
242
|
+
|
|
243
|
+
# Custom table name
|
|
244
|
+
rails g better_model:traceable Article --create-table --table-name=audit_log
|
|
245
|
+
|
|
246
|
+
# Run migrations
|
|
247
|
+
rails db:migrate
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Generated migration includes:**
|
|
251
|
+
- Polymorphic association (`item_type`, `item_id`)
|
|
252
|
+
- Event tracking (`created`, `updated`, `destroyed`)
|
|
253
|
+
- Change tracking (`object_changes` as JSON)
|
|
254
|
+
- User attribution (`updated_by_id`)
|
|
255
|
+
- Change reason (`updated_reason`)
|
|
256
|
+
- Optimized indexes
|
|
257
|
+
|
|
258
|
+
### Archivable Generator
|
|
259
|
+
|
|
260
|
+
Add soft-delete columns to existing models:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Basic usage - shows setup instructions
|
|
264
|
+
rails g better_model:archivable Article
|
|
265
|
+
|
|
266
|
+
# Add archivable columns to articles table
|
|
267
|
+
rails g better_model:archivable Article --create-columns
|
|
268
|
+
|
|
269
|
+
# Run migrations
|
|
270
|
+
rails db:migrate
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Generated migration adds:**
|
|
274
|
+
- `archived_at` (datetime) - when archived
|
|
275
|
+
- `archived_by_id` (integer) - who archived it
|
|
276
|
+
- `archive_reason` (string) - why archived
|
|
277
|
+
- Index on `archived_at`
|
|
278
|
+
|
|
279
|
+
### Stateable Generator
|
|
280
|
+
|
|
281
|
+
Create state machine with state column and transitions tracking:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# Basic usage - shows setup instructions
|
|
285
|
+
rails g better_model:stateable Article
|
|
286
|
+
|
|
287
|
+
# Create both state column and transitions table
|
|
288
|
+
rails g better_model:stateable Article --create-tables
|
|
289
|
+
|
|
290
|
+
# Custom initial state (default: draft)
|
|
291
|
+
rails g better_model:stateable Article --create-tables --initial-state=pending
|
|
292
|
+
|
|
293
|
+
# Custom transitions table name
|
|
294
|
+
rails g better_model:stateable Article --create-tables --table-name=article_state_history
|
|
295
|
+
|
|
296
|
+
# Run migrations
|
|
297
|
+
rails db:migrate
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Generated migrations include:**
|
|
301
|
+
1. **State column migration:**
|
|
302
|
+
- `state` (string) with default value and index
|
|
303
|
+
|
|
304
|
+
2. **Transitions table migration:**
|
|
305
|
+
- Polymorphic association (`transitionable_type`, `transitionable_id`)
|
|
306
|
+
- Event name and state tracking
|
|
307
|
+
- Optional metadata (JSON)
|
|
308
|
+
- Optimized indexes
|
|
309
|
+
|
|
310
|
+
### Generator Options
|
|
311
|
+
|
|
312
|
+
All generators support these common options:
|
|
313
|
+
|
|
314
|
+
- `--pretend` - Dry run, show what would be generated
|
|
315
|
+
- `--skip-model` - Only generate migrations, don't show model setup instructions
|
|
316
|
+
- `--force` - Overwrite existing files
|
|
317
|
+
|
|
318
|
+
**Example workflow:**
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# 1. Generate migrations (dry-run first to preview)
|
|
322
|
+
rails g better_model:traceable Article --create-table --pretend
|
|
323
|
+
rails g better_model:archivable Article --create-columns --pretend
|
|
324
|
+
rails g better_model:stateable Article --create-tables --pretend
|
|
325
|
+
|
|
326
|
+
# 2. Generate for real
|
|
327
|
+
rails g better_model:traceable Article --create-table
|
|
328
|
+
rails g better_model:archivable Article --create-columns
|
|
329
|
+
rails g better_model:stateable Article --create-tables
|
|
330
|
+
|
|
331
|
+
# 3. Run migrations
|
|
332
|
+
rails db:migrate
|
|
333
|
+
|
|
334
|
+
# 4. Enable in your model (generators show you the code)
|
|
335
|
+
# See model setup instructions after running each generator
|
|
336
|
+
```
|
|
337
|
+
|
|
225
338
|
## ๐ Features Overview
|
|
226
339
|
|
|
227
340
|
BetterModel provides ten powerful concerns that work seamlessly together:
|
|
228
341
|
|
|
229
|
-
### Core Features
|
|
342
|
+
### Core Features (Always Available)
|
|
230
343
|
|
|
231
344
|
- **โจ Statusable** - Declarative status management with lambda-based conditions
|
|
232
345
|
- **๐ Permissible** - State-based permission system
|
|
233
|
-
- **๐๏ธ Archivable** - Soft delete with tracking (by user, reason)
|
|
234
|
-
- **โฐ Traceable** - Complete audit trail with time-travel and rollback
|
|
235
346
|
- **โฌ๏ธ Sortable** - Type-aware sorting scopes
|
|
236
347
|
- **๐ Predicable** - Advanced filtering with rich predicate system
|
|
348
|
+
|
|
349
|
+
### Opt-in Features (Require Activation)
|
|
350
|
+
|
|
237
351
|
- **๐ Searchable** - Unified search interface (Predicable + Sortable)
|
|
352
|
+
- **๐๏ธ Archivable** - Soft delete with tracking (by user, reason)
|
|
238
353
|
- **โ
Validatable** - Declarative validation DSL with conditional rules
|
|
239
354
|
- **๐ Stateable** - Declarative state machines with guards & callbacks
|
|
355
|
+
- **โฐ Traceable** - Complete audit trail with time-travel and rollback
|
|
240
356
|
- **๐ท๏ธ Taggable** ๐ - Tag management with normalization, validation, and statistics
|
|
241
357
|
|
|
242
|
-
[See all features in detail โ](
|
|
358
|
+
[See all features in detail โ](#feature-details)
|
|
243
359
|
|
|
244
360
|
## โ๏ธ Requirements
|
|
245
361
|
|
|
@@ -263,9 +379,22 @@ BetterModel works with all databases supported by ActiveRecord:
|
|
|
263
379
|
- Array predicates: `overlaps`, `contains`, `contained_by`
|
|
264
380
|
- JSONB predicates: `has_key`, `has_any_key`, `has_all_keys`, `jsonb_contains`
|
|
265
381
|
|
|
266
|
-
##
|
|
382
|
+
## ๐๏ธ Database Requirements
|
|
383
|
+
|
|
384
|
+
Some opt-in features require database columns. Use the provided generators to add them:
|
|
385
|
+
|
|
386
|
+
| Feature | Database Requirement | Generator Command |
|
|
387
|
+
|---------|---------------------|-------------------|
|
|
388
|
+
| **Archivable** | `archived_at` column | `rails g better_model:archivable Model` |
|
|
389
|
+
| **Stateable** | `state`, `transitions` columns | `rails g better_model:stateable Model` |
|
|
390
|
+
| **Traceable** | `version_records` table | `rails g better_model:traceable Model` |
|
|
391
|
+
| **Taggable** | `tags` JSONB/text column | `rails g better_model:taggable Model` |
|
|
267
392
|
|
|
268
|
-
|
|
393
|
+
**Core features** (Statusable, Permissible, Predicable, Sortable, Searchable, Validatable) require no database changes.
|
|
394
|
+
|
|
395
|
+
## ๐ Feature Details
|
|
396
|
+
|
|
397
|
+
BetterModel provides ten powerful concerns that work together seamlessly:
|
|
269
398
|
|
|
270
399
|
### ๐ Statusable - Declarative Status Management
|
|
271
400
|
|
|
@@ -358,10 +487,11 @@ Track all changes to your records with complete audit trail, time-travel capabil
|
|
|
358
487
|
**๐ฏ Key Benefits:**
|
|
359
488
|
- ๐๏ธ Opt-in activation: only enabled when explicitly configured
|
|
360
489
|
- ๐ Automatic change tracking on create, update, and destroy
|
|
490
|
+
- ๐ Sensitive data protection: 3-level redaction system (full, partial, hash)
|
|
361
491
|
- ๐ค User attribution: track who made each change
|
|
362
492
|
- ๐ฌ Change reasons: optional context for changes
|
|
363
493
|
- โฐ Time-travel: reconstruct object state at any point in history
|
|
364
|
-
- โฉ๏ธ Rollback support: restore records to previous versions
|
|
494
|
+
- โฉ๏ธ Rollback support: restore records to previous versions (with sensitive field protection)
|
|
365
495
|
- ๐ Rich query API: find changes by user, time, or field transitions
|
|
366
496
|
- ๐ Flexible table naming: per-model, shared, or custom tables
|
|
367
497
|
- ๐ Polymorphic association for efficient storage
|
|
@@ -398,9 +528,75 @@ Generate comprehensive predicate scopes for filtering and searching with support
|
|
|
398
528
|
- ๐ Range queries (between) for numerics and dates
|
|
399
529
|
- ๐ PostgreSQL array and JSONB support
|
|
400
530
|
- ๐ Chainable with standard ActiveRecord queries
|
|
531
|
+
- ๐งฉ Custom complex predicates for business logic
|
|
401
532
|
|
|
402
533
|
**[๐ Full Documentation โ](docs/predicable.md)**
|
|
403
534
|
|
|
535
|
+
#### ๐งฉ Complex Predicates
|
|
536
|
+
|
|
537
|
+
For queries that go beyond single-field filtering, you can register **complex predicates** - custom scopes that combine multiple conditions, work with associations, or encapsulate business logic.
|
|
538
|
+
|
|
539
|
+
**Basic Example:**
|
|
540
|
+
|
|
541
|
+
```ruby
|
|
542
|
+
class Article < ApplicationRecord
|
|
543
|
+
include BetterModel
|
|
544
|
+
|
|
545
|
+
predicates :title, :view_count, :published_at
|
|
546
|
+
|
|
547
|
+
# Define a complex predicate with parameters
|
|
548
|
+
register_complex_predicate :trending do |days = 7, min_views = 100|
|
|
549
|
+
where("published_at >= ? AND view_count >= ?", days.days.ago, min_views)
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Define a complex predicate for association queries
|
|
553
|
+
register_complex_predicate :popular_author do |min_articles = 10|
|
|
554
|
+
joins(:author)
|
|
555
|
+
.group("articles.author_id")
|
|
556
|
+
.having("COUNT(articles.id) >= ?", min_articles)
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Usage:**
|
|
562
|
+
|
|
563
|
+
```ruby
|
|
564
|
+
# Use with default parameters
|
|
565
|
+
Article.trending
|
|
566
|
+
# => Articles from last 7 days with 100+ views
|
|
567
|
+
|
|
568
|
+
# Use with custom parameters
|
|
569
|
+
Article.trending(14, 200)
|
|
570
|
+
# => Articles from last 14 days with 200+ views
|
|
571
|
+
|
|
572
|
+
# Chain with standard predicates
|
|
573
|
+
Article.trending(7, 100)
|
|
574
|
+
.title_cont("Ruby")
|
|
575
|
+
.status_eq("published")
|
|
576
|
+
.sort_view_count_desc
|
|
577
|
+
|
|
578
|
+
# Association-based queries
|
|
579
|
+
Article.popular_author(5)
|
|
580
|
+
.published_at_within(30.days)
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**When to Use Complex Predicates:**
|
|
584
|
+
|
|
585
|
+
| Standard Predicates โ
| Complex Predicates ๐งฉ |
|
|
586
|
+
|------------------------|------------------------|
|
|
587
|
+
| Single field filtering | Multi-field conditions |
|
|
588
|
+
| `title_eq("Ruby")` | `trending(days, views)` |
|
|
589
|
+
| `view_count_gt(100)` | Association queries |
|
|
590
|
+
| `published_at_within(7.days)` | Business logic encapsulation |
|
|
591
|
+
| Simple comparisons | Custom SQL expressions |
|
|
592
|
+
|
|
593
|
+
**Check if Defined:**
|
|
594
|
+
|
|
595
|
+
```ruby
|
|
596
|
+
Article.complex_predicate?(:trending) # => true
|
|
597
|
+
Article.complex_predicates_registry # => { trending: #<Proc> }
|
|
598
|
+
```
|
|
599
|
+
|
|
404
600
|
---
|
|
405
601
|
|
|
406
602
|
### ๐ Searchable - Unified Search Interface
|
|
@@ -415,9 +611,54 @@ Orchestrate Predicable and Sortable into a powerful, secure search interface wit
|
|
|
415
611
|
- โ๏ธ Default ordering configuration
|
|
416
612
|
- ๐ช Strong parameters integration
|
|
417
613
|
- โ
Type-safe validation of all parameters
|
|
614
|
+
- ๐ Eager loading support with `includes:`, `preload:`, `eager_load:`
|
|
418
615
|
|
|
419
616
|
**[๐ Full Documentation โ](docs/searchable.md)**
|
|
420
617
|
|
|
618
|
+
#### ๐ Eager Loading Associations
|
|
619
|
+
|
|
620
|
+
Optimize N+1 queries with built-in eager loading support:
|
|
621
|
+
|
|
622
|
+
```ruby
|
|
623
|
+
# Single association (always use array syntax)
|
|
624
|
+
Article.search(
|
|
625
|
+
{ status_eq: "published" },
|
|
626
|
+
includes: [:author]
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
# Multiple associations
|
|
630
|
+
Article.search(
|
|
631
|
+
{ status_eq: "published" },
|
|
632
|
+
includes: [:author, :comments],
|
|
633
|
+
preload: [:tags]
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
# Nested associations
|
|
637
|
+
Article.search(
|
|
638
|
+
{ status_eq: "published" },
|
|
639
|
+
includes: [{ author: :profile }]
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Complex mix of associations
|
|
643
|
+
Article.search(
|
|
644
|
+
{ status_eq: "published" },
|
|
645
|
+
includes: [:tags, { author: :profile }, { comments: :user }]
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
# Combined with pagination and ordering
|
|
649
|
+
Article.search(
|
|
650
|
+
{ status_eq: "published" },
|
|
651
|
+
pagination: { page: 1, per_page: 25 },
|
|
652
|
+
orders: [:sort_view_count_desc],
|
|
653
|
+
includes: [:author, :comments]
|
|
654
|
+
)
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
**Strategies:**
|
|
658
|
+
- `includes:` - Smart loading (LEFT OUTER JOIN or separate queries)
|
|
659
|
+
- `preload:` - Separate queries (avoids JOIN ambiguity)
|
|
660
|
+
- `eager_load:` - Force LEFT OUTER JOIN (use with caution with default_order)
|
|
661
|
+
|
|
421
662
|
---
|
|
422
663
|
|
|
423
664
|
### ๐ท๏ธ Taggable - Tag Management with Statistics
|
|
@@ -438,147 +679,6 @@ Manage tags with automatic normalization, validation, and comprehensive statisti
|
|
|
438
679
|
|
|
439
680
|
---
|
|
440
681
|
|
|
441
|
-
### ๐ Traceable - Audit Trail & Change Tracking
|
|
442
|
-
|
|
443
|
-
Track all changes to your records with comprehensive audit trail functionality, time-travel queries, and rollback capabilities.
|
|
444
|
-
|
|
445
|
-
**๐ฏ Key Benefits:**
|
|
446
|
-
- ๐๏ธ Opt-in activation: only enabled when explicitly configured
|
|
447
|
-
- ๐ค Automatic change tracking on create/update/destroy
|
|
448
|
-
- โฐ Time-travel: reconstruct record state at any point in time
|
|
449
|
-
- โฉ๏ธ Rollback: restore to previous versions
|
|
450
|
-
- ๐ Audit trail with who/why tracking
|
|
451
|
-
- ๐ Query changes by user, date range, or field transitions
|
|
452
|
-
- ๐๏ธ Flexible table naming: per-model tables (default), shared table, or custom names
|
|
453
|
-
|
|
454
|
-
**[๐ Full Documentation โ](docs/traceable.md)**
|
|
455
|
-
|
|
456
|
-
#### ๐ Quick Setup
|
|
457
|
-
|
|
458
|
-
**1๏ธโฃ Step 1: Create the versions table**
|
|
459
|
-
|
|
460
|
-
By default, each model gets its own versions table (`{model}_versions`):
|
|
461
|
-
|
|
462
|
-
```bash
|
|
463
|
-
# Creates migration for article_versions table
|
|
464
|
-
rails g better_model:traceable Article --create-table
|
|
465
|
-
rails db:migrate
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
Or use a custom table name:
|
|
469
|
-
|
|
470
|
-
```bash
|
|
471
|
-
# Creates migration for custom table name
|
|
472
|
-
rails g better_model:traceable Article --create-table --table-name=audit_log
|
|
473
|
-
rails db:migrate
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
**2๏ธโฃ Step 2: Enable in your model**
|
|
477
|
-
|
|
478
|
-
```ruby
|
|
479
|
-
class Article < ApplicationRecord
|
|
480
|
-
include BetterModel
|
|
481
|
-
|
|
482
|
-
# Activate traceable (opt-in)
|
|
483
|
-
traceable do
|
|
484
|
-
track :status, :title, :published_at # Fields to track
|
|
485
|
-
# versions_table 'audit_log' # Optional: custom table (default: article_versions)
|
|
486
|
-
end
|
|
487
|
-
end
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
**๐ก Usage:**
|
|
491
|
-
|
|
492
|
-
```ruby
|
|
493
|
-
# ๐ค Automatic tracking on changes
|
|
494
|
-
article.update!(status: "published", updated_by_id: user.id, updated_reason: "Approved")
|
|
495
|
-
|
|
496
|
-
# ๐ Query version history
|
|
497
|
-
article.versions # All versions (ordered desc)
|
|
498
|
-
article.changes_for(:status) # Changes for specific field
|
|
499
|
-
article.audit_trail # Full formatted history
|
|
500
|
-
|
|
501
|
-
# โฐ Time-travel: reconstruct state at specific time
|
|
502
|
-
past_article = article.as_of(3.days.ago)
|
|
503
|
-
past_article.status # => "draft" (what it was 3 days ago)
|
|
504
|
-
|
|
505
|
-
# โฉ๏ธ Rollback to previous version
|
|
506
|
-
version = article.versions.where(event: "updated").first
|
|
507
|
-
article.rollback_to(version, updated_by_id: user.id, updated_reason: "Mistake")
|
|
508
|
-
|
|
509
|
-
# ๐ Class-level queries
|
|
510
|
-
Article.changed_by(user.id) # Records changed by user
|
|
511
|
-
Article.changed_between(1.week.ago, Time.current) # Changes in period
|
|
512
|
-
Article.status_changed_from("draft").to("published") # Specific transitions (PostgreSQL)
|
|
513
|
-
|
|
514
|
-
# ๐ฆ Integration with as_json
|
|
515
|
-
article.as_json(include_audit_trail: true) # Include full history in JSON
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
**๐พ Database Schema:**
|
|
519
|
-
|
|
520
|
-
By default, each model gets its own versions table (e.g., `article_versions` for Article model).
|
|
521
|
-
You can also use a shared table across multiple models or a custom table name.
|
|
522
|
-
|
|
523
|
-
| Column | Type | Description |
|
|
524
|
-
|--------|------|-------------|
|
|
525
|
-
| `item_type` | string | Polymorphic model name |
|
|
526
|
-
| `item_id` | integer | Polymorphic record ID |
|
|
527
|
-
| `event` | string | Event type: created/updated/destroyed |
|
|
528
|
-
| `object_changes` | json | Before/after values for tracked fields |
|
|
529
|
-
| `updated_by_id` | integer | Optional: user who made the change |
|
|
530
|
-
| `updated_reason` | string | Optional: reason for the change |
|
|
531
|
-
| `created_at` | datetime | When the change occurred |
|
|
532
|
-
|
|
533
|
-
**๐๏ธ Table Naming Options:**
|
|
534
|
-
|
|
535
|
-
```ruby
|
|
536
|
-
# 1๏ธโฃ Option 1: Per-model table (default)
|
|
537
|
-
class Article < ApplicationRecord
|
|
538
|
-
traceable do
|
|
539
|
-
track :status
|
|
540
|
-
# Uses article_versions table automatically
|
|
541
|
-
end
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
# 2๏ธโฃ Option 2: Custom table name
|
|
545
|
-
class Article < ApplicationRecord
|
|
546
|
-
traceable do
|
|
547
|
-
track :status
|
|
548
|
-
versions_table 'audit_log' # Uses audit_log table
|
|
549
|
-
end
|
|
550
|
-
end
|
|
551
|
-
|
|
552
|
-
# 3๏ธโฃ Option 3: Shared table across models
|
|
553
|
-
class Article < ApplicationRecord
|
|
554
|
-
traceable do
|
|
555
|
-
track :status
|
|
556
|
-
versions_table 'versions' # Shared table
|
|
557
|
-
end
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
class User < ApplicationRecord
|
|
561
|
-
traceable do
|
|
562
|
-
track :email
|
|
563
|
-
versions_table 'versions' # Same shared table
|
|
564
|
-
end
|
|
565
|
-
end
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
**๐ Optional Tracking:**
|
|
569
|
-
|
|
570
|
-
To track who made changes and why, simply set attributes before saving:
|
|
571
|
-
|
|
572
|
-
```ruby
|
|
573
|
-
article.updated_by_id = current_user.id
|
|
574
|
-
article.updated_reason = "Fixed typo"
|
|
575
|
-
article.update!(title: "Corrected Title")
|
|
576
|
-
|
|
577
|
-
# The version will automatically include updated_by_id and updated_reason
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
---
|
|
581
|
-
|
|
582
682
|
## ๐ Changelog
|
|
583
683
|
|
|
584
684
|
See [CHANGELOG.md](CHANGELOG.md) for version history and release notes.
|
|
@@ -604,22 +704,15 @@ Detailed documentation for each BetterModel concern:
|
|
|
604
704
|
- [**Searchable**](docs/searchable.md) - Unified search interface
|
|
605
705
|
- [**Validatable**](docs/validatable.md) - Declarative validation system
|
|
606
706
|
- [**Stateable**](docs/stateable.md) ๐ - State machine with transitions
|
|
607
|
-
|
|
608
|
-
### ๐ Advanced Guides
|
|
609
|
-
|
|
610
|
-
Learn how to master BetterModel in production:
|
|
611
|
-
|
|
612
|
-
- [**Integration Guide**](docs/integration_guide.md) ๐ - Combining multiple concerns effectively
|
|
613
|
-
- [**Performance Guide**](docs/performance_guide.md) ๐ - Optimization strategies and indexing
|
|
614
|
-
- [**Migration Guide**](docs/migration_guide.md) ๐ - Adding BetterModel to existing apps
|
|
707
|
+
- [**Taggable**](docs/taggable.md) ๐ - Flexible tag management with normalization
|
|
615
708
|
|
|
616
709
|
### ๐ก Quick Links
|
|
617
710
|
|
|
618
|
-
- [Installation](
|
|
619
|
-
- [Quick Start](
|
|
620
|
-
- [Features Overview](
|
|
621
|
-
- [Requirements](
|
|
622
|
-
- [Contributing](
|
|
711
|
+
- [Installation](#installation)
|
|
712
|
+
- [Quick Start](#quick-start)
|
|
713
|
+
- [Features Overview](#features-overview)
|
|
714
|
+
- [Requirements](#requirements)
|
|
715
|
+
- [Contributing](#contributing)
|
|
623
716
|
|
|
624
717
|
## ๐ค Contributing
|
|
625
718
|
|
|
@@ -648,6 +741,35 @@ We welcome contributions! Here's how you can help:
|
|
|
648
741
|
|
|
649
742
|
### ๐ง Development Setup
|
|
650
743
|
|
|
744
|
+
#### ๐ณ Using Docker (Recommended)
|
|
745
|
+
|
|
746
|
+
The easiest way to get started is with Docker, which provides a consistent development environment:
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
# Clone your fork
|
|
750
|
+
git clone https://github.com/YOUR_USERNAME/better_model.git
|
|
751
|
+
cd better_model
|
|
752
|
+
|
|
753
|
+
# One-time setup: build image and install dependencies
|
|
754
|
+
bin/docker-setup
|
|
755
|
+
|
|
756
|
+
# Run tests
|
|
757
|
+
bin/docker-test
|
|
758
|
+
|
|
759
|
+
# Run RuboCop
|
|
760
|
+
bin/docker-rubocop
|
|
761
|
+
|
|
762
|
+
# Open interactive shell for debugging
|
|
763
|
+
docker compose run --rm app sh
|
|
764
|
+
|
|
765
|
+
# Run any command in the container
|
|
766
|
+
docker compose run --rm app bundle exec [command]
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
#### ๐ป Local Setup (Without Docker)
|
|
770
|
+
|
|
771
|
+
If you prefer to use your local Ruby installation:
|
|
772
|
+
|
|
651
773
|
```bash
|
|
652
774
|
# Clone your fork
|
|
653
775
|
git clone https://github.com/YOUR_USERNAME/better_model.git
|
|
@@ -666,6 +788,11 @@ bundle exec rake test # Coverage report in coverage/index.html
|
|
|
666
788
|
bundle exec rubocop
|
|
667
789
|
```
|
|
668
790
|
|
|
791
|
+
**Requirements:**
|
|
792
|
+
- Ruby 3.0+ (Ruby 3.3 recommended)
|
|
793
|
+
- Rails 8.1+
|
|
794
|
+
- SQLite3
|
|
795
|
+
|
|
669
796
|
### ๐ Test Coverage Notes
|
|
670
797
|
|
|
671
798
|
The test suite runs on **SQLite** for performance and portability. Current coverage: **92.57%** (1507 / 1628 lines).
|
|
@@ -698,3 +825,13 @@ All code has inline comments marking database-specific sections for maintainabil
|
|
|
698
825
|
## ๐ License
|
|
699
826
|
|
|
700
827
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
831
|
+
<div align="center">
|
|
832
|
+
|
|
833
|
+
**Made with โค๏ธ by [Alessio Bussolari](https://github.com/alessiobussolari)**
|
|
834
|
+
|
|
835
|
+
[Report Bug](https://github.com/alessiobussolari/better_model/issues) ยท [Request Feature](https://github.com/alessiobussolari/better_model/issues) ยท [Documentation](https://github.com/alessiobussolari/better_model)
|
|
836
|
+
|
|
837
|
+
</div>
|
|
@@ -104,8 +104,8 @@ module BetterModel
|
|
|
104
104
|
sort :archived_at unless sortable_field?(:archived_at)
|
|
105
105
|
|
|
106
106
|
# Definisci gli scope alias (approccio ibrido)
|
|
107
|
-
scope :archived, -> { archived_at_present }
|
|
108
|
-
scope :not_archived, -> { archived_at_null }
|
|
107
|
+
scope :archived, -> { archived_at_present(true) }
|
|
108
|
+
scope :not_archived, -> { archived_at_null(true) }
|
|
109
109
|
|
|
110
110
|
# Configura se passato un blocco
|
|
111
111
|
if block_given?
|