better_model 2.0.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 +179 -196
- data/lib/better_model/archivable.rb +0 -1
- data/lib/better_model/predicable.rb +39 -79
- data/lib/better_model/searchable.rb +31 -4
- 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
- data/lib/better_model.rb +0 -4
- metadata +3 -9
- data/lib/better_model/schedulable/occurrence_calculator.rb +0 -1034
- data/lib/better_model/schedulable/schedule_builder.rb +0 -269
- data/lib/better_model/schedulable.rb +0 -356
- data/lib/generators/better_model/taggable/taggable_generator.rb +0 -129
- data/lib/generators/better_model/taggable/templates/README.tt +0 -62
- data/lib/generators/better_model/taggable/templates/migration.rb.tt +0 -21
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
|
|
@@ -307,34 +307,6 @@ rails db:migrate
|
|
|
307
307
|
- Optional metadata (JSON)
|
|
308
308
|
- Optimized indexes
|
|
309
309
|
|
|
310
|
-
### Taggable Generator
|
|
311
|
-
|
|
312
|
-
Add tags column with database-specific optimizations:
|
|
313
|
-
|
|
314
|
-
```bash
|
|
315
|
-
# Basic usage - adds tags column
|
|
316
|
-
rails g better_model:taggable Article
|
|
317
|
-
|
|
318
|
-
# Custom column name
|
|
319
|
-
rails g better_model:taggable Article --column-name=categories
|
|
320
|
-
|
|
321
|
-
# Skip GIN index (PostgreSQL only)
|
|
322
|
-
rails g better_model:taggable Article --skip-index
|
|
323
|
-
|
|
324
|
-
# Run migrations
|
|
325
|
-
rails db:migrate
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
**Generated migration includes:**
|
|
329
|
-
- **PostgreSQL**: Native array column (`array: true, default: []`) with GIN index for optimal performance
|
|
330
|
-
- **SQLite/MySQL**: Text column with serialization instructions
|
|
331
|
-
- Database-specific detection and optimization
|
|
332
|
-
- Automatic index creation for PostgreSQL (GIN index)
|
|
333
|
-
|
|
334
|
-
**Database-specific behavior:**
|
|
335
|
-
- PostgreSQL: Ready to use immediately with native array support
|
|
336
|
-
- SQLite/MySQL: Requires adding `serialize :tags, coder: JSON, type: Array` to model
|
|
337
|
-
|
|
338
310
|
### Generator Options
|
|
339
311
|
|
|
340
312
|
All generators support these common options:
|
|
@@ -350,13 +322,11 @@ All generators support these common options:
|
|
|
350
322
|
rails g better_model:traceable Article --create-table --pretend
|
|
351
323
|
rails g better_model:archivable Article --create-columns --pretend
|
|
352
324
|
rails g better_model:stateable Article --create-tables --pretend
|
|
353
|
-
rails g better_model:taggable Article --pretend
|
|
354
325
|
|
|
355
326
|
# 2. Generate for real
|
|
356
327
|
rails g better_model:traceable Article --create-table
|
|
357
328
|
rails g better_model:archivable Article --create-columns
|
|
358
329
|
rails g better_model:stateable Article --create-tables
|
|
359
|
-
rails g better_model:taggable Article
|
|
360
330
|
|
|
361
331
|
# 3. Run migrations
|
|
362
332
|
rails db:migrate
|
|
@@ -369,20 +339,23 @@ rails db:migrate
|
|
|
369
339
|
|
|
370
340
|
BetterModel provides ten powerful concerns that work seamlessly together:
|
|
371
341
|
|
|
372
|
-
### Core Features
|
|
342
|
+
### Core Features (Always Available)
|
|
373
343
|
|
|
374
344
|
- **✨ Statusable** - Declarative status management with lambda-based conditions
|
|
375
345
|
- **🔐 Permissible** - State-based permission system
|
|
376
|
-
- **🗄️ Archivable** - Soft delete with tracking (by user, reason)
|
|
377
|
-
- **⏰ Traceable** - Complete audit trail with time-travel and rollback
|
|
378
346
|
- **⬆️ Sortable** - Type-aware sorting scopes
|
|
379
347
|
- **🔍 Predicable** - Advanced filtering with rich predicate system
|
|
348
|
+
|
|
349
|
+
### Opt-in Features (Require Activation)
|
|
350
|
+
|
|
380
351
|
- **🔎 Searchable** - Unified search interface (Predicable + Sortable)
|
|
352
|
+
- **🗄️ Archivable** - Soft delete with tracking (by user, reason)
|
|
381
353
|
- **✅ Validatable** - Declarative validation DSL with conditional rules
|
|
382
354
|
- **🔄 Stateable** - Declarative state machines with guards & callbacks
|
|
355
|
+
- **⏰ Traceable** - Complete audit trail with time-travel and rollback
|
|
383
356
|
- **🏷️ Taggable** 🆕 - Tag management with normalization, validation, and statistics
|
|
384
357
|
|
|
385
|
-
[See all features in detail →](
|
|
358
|
+
[See all features in detail →](#feature-details)
|
|
386
359
|
|
|
387
360
|
## ⚙️ Requirements
|
|
388
361
|
|
|
@@ -406,9 +379,22 @@ BetterModel works with all databases supported by ActiveRecord:
|
|
|
406
379
|
- Array predicates: `overlaps`, `contains`, `contained_by`
|
|
407
380
|
- JSONB predicates: `has_key`, `has_any_key`, `has_all_keys`, `jsonb_contains`
|
|
408
381
|
|
|
409
|
-
##
|
|
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` |
|
|
410
392
|
|
|
411
|
-
|
|
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:
|
|
412
398
|
|
|
413
399
|
### 📋 Statusable - Declarative Status Management
|
|
414
400
|
|
|
@@ -542,9 +528,75 @@ Generate comprehensive predicate scopes for filtering and searching with support
|
|
|
542
528
|
- 📊 Range queries (between) for numerics and dates
|
|
543
529
|
- 🐘 PostgreSQL array and JSONB support
|
|
544
530
|
- 🔗 Chainable with standard ActiveRecord queries
|
|
531
|
+
- 🧩 Custom complex predicates for business logic
|
|
545
532
|
|
|
546
533
|
**[📖 Full Documentation →](docs/predicable.md)**
|
|
547
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
|
+
|
|
548
600
|
---
|
|
549
601
|
|
|
550
602
|
### 🔎 Searchable - Unified Search Interface
|
|
@@ -559,9 +611,54 @@ Orchestrate Predicable and Sortable into a powerful, secure search interface wit
|
|
|
559
611
|
- ⚙️ Default ordering configuration
|
|
560
612
|
- 💪 Strong parameters integration
|
|
561
613
|
- ✅ Type-safe validation of all parameters
|
|
614
|
+
- 🚀 Eager loading support with `includes:`, `preload:`, `eager_load:`
|
|
562
615
|
|
|
563
616
|
**[📖 Full Documentation →](docs/searchable.md)**
|
|
564
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
|
+
|
|
565
662
|
---
|
|
566
663
|
|
|
567
664
|
### 🏷️ Taggable - Tag Management with Statistics
|
|
@@ -582,147 +679,6 @@ Manage tags with automatic normalization, validation, and comprehensive statisti
|
|
|
582
679
|
|
|
583
680
|
---
|
|
584
681
|
|
|
585
|
-
### 📜 Traceable - Audit Trail & Change Tracking
|
|
586
|
-
|
|
587
|
-
Track all changes to your records with comprehensive audit trail functionality, time-travel queries, and rollback capabilities.
|
|
588
|
-
|
|
589
|
-
**🎯 Key Benefits:**
|
|
590
|
-
- 🎛️ Opt-in activation: only enabled when explicitly configured
|
|
591
|
-
- 🤖 Automatic change tracking on create/update/destroy
|
|
592
|
-
- ⏰ Time-travel: reconstruct record state at any point in time
|
|
593
|
-
- ↩️ Rollback: restore to previous versions
|
|
594
|
-
- 📝 Audit trail with who/why tracking
|
|
595
|
-
- 🔍 Query changes by user, date range, or field transitions
|
|
596
|
-
- 🗂️ Flexible table naming: per-model tables (default), shared table, or custom names
|
|
597
|
-
|
|
598
|
-
**[📖 Full Documentation →](docs/traceable.md)**
|
|
599
|
-
|
|
600
|
-
#### 🚀 Quick Setup
|
|
601
|
-
|
|
602
|
-
**1️⃣ Step 1: Create the versions table**
|
|
603
|
-
|
|
604
|
-
By default, each model gets its own versions table (`{model}_versions`):
|
|
605
|
-
|
|
606
|
-
```bash
|
|
607
|
-
# Creates migration for article_versions table
|
|
608
|
-
rails g better_model:traceable Article --create-table
|
|
609
|
-
rails db:migrate
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
Or use a custom table name:
|
|
613
|
-
|
|
614
|
-
```bash
|
|
615
|
-
# Creates migration for custom table name
|
|
616
|
-
rails g better_model:traceable Article --create-table --table-name=audit_log
|
|
617
|
-
rails db:migrate
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
**2️⃣ Step 2: Enable in your model**
|
|
621
|
-
|
|
622
|
-
```ruby
|
|
623
|
-
class Article < ApplicationRecord
|
|
624
|
-
include BetterModel
|
|
625
|
-
|
|
626
|
-
# Activate traceable (opt-in)
|
|
627
|
-
traceable do
|
|
628
|
-
track :status, :title, :published_at # Fields to track
|
|
629
|
-
# versions_table 'audit_log' # Optional: custom table (default: article_versions)
|
|
630
|
-
end
|
|
631
|
-
end
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
**💡 Usage:**
|
|
635
|
-
|
|
636
|
-
```ruby
|
|
637
|
-
# 🤖 Automatic tracking on changes
|
|
638
|
-
article.update!(status: "published", updated_by_id: user.id, updated_reason: "Approved")
|
|
639
|
-
|
|
640
|
-
# 🔍 Query version history
|
|
641
|
-
article.versions # All versions (ordered desc)
|
|
642
|
-
article.changes_for(:status) # Changes for specific field
|
|
643
|
-
article.audit_trail # Full formatted history
|
|
644
|
-
|
|
645
|
-
# ⏰ Time-travel: reconstruct state at specific time
|
|
646
|
-
past_article = article.as_of(3.days.ago)
|
|
647
|
-
past_article.status # => "draft" (what it was 3 days ago)
|
|
648
|
-
|
|
649
|
-
# ↩️ Rollback to previous version
|
|
650
|
-
version = article.versions.where(event: "updated").first
|
|
651
|
-
article.rollback_to(version, updated_by_id: user.id, updated_reason: "Mistake")
|
|
652
|
-
|
|
653
|
-
# 📊 Class-level queries
|
|
654
|
-
Article.changed_by(user.id) # Records changed by user
|
|
655
|
-
Article.changed_between(1.week.ago, Time.current) # Changes in period
|
|
656
|
-
Article.status_changed_from("draft").to("published") # Specific transitions (PostgreSQL)
|
|
657
|
-
|
|
658
|
-
# 📦 Integration with as_json
|
|
659
|
-
article.as_json(include_audit_trail: true) # Include full history in JSON
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
**💾 Database Schema:**
|
|
663
|
-
|
|
664
|
-
By default, each model gets its own versions table (e.g., `article_versions` for Article model).
|
|
665
|
-
You can also use a shared table across multiple models or a custom table name.
|
|
666
|
-
|
|
667
|
-
| Column | Type | Description |
|
|
668
|
-
|--------|------|-------------|
|
|
669
|
-
| `item_type` | string | Polymorphic model name |
|
|
670
|
-
| `item_id` | integer | Polymorphic record ID |
|
|
671
|
-
| `event` | string | Event type: created/updated/destroyed |
|
|
672
|
-
| `object_changes` | json | Before/after values for tracked fields |
|
|
673
|
-
| `updated_by_id` | integer | Optional: user who made the change |
|
|
674
|
-
| `updated_reason` | string | Optional: reason for the change |
|
|
675
|
-
| `created_at` | datetime | When the change occurred |
|
|
676
|
-
|
|
677
|
-
**🗂️ Table Naming Options:**
|
|
678
|
-
|
|
679
|
-
```ruby
|
|
680
|
-
# 1️⃣ Option 1: Per-model table (default)
|
|
681
|
-
class Article < ApplicationRecord
|
|
682
|
-
traceable do
|
|
683
|
-
track :status
|
|
684
|
-
# Uses article_versions table automatically
|
|
685
|
-
end
|
|
686
|
-
end
|
|
687
|
-
|
|
688
|
-
# 2️⃣ Option 2: Custom table name
|
|
689
|
-
class Article < ApplicationRecord
|
|
690
|
-
traceable do
|
|
691
|
-
track :status
|
|
692
|
-
versions_table 'audit_log' # Uses audit_log table
|
|
693
|
-
end
|
|
694
|
-
end
|
|
695
|
-
|
|
696
|
-
# 3️⃣ Option 3: Shared table across models
|
|
697
|
-
class Article < ApplicationRecord
|
|
698
|
-
traceable do
|
|
699
|
-
track :status
|
|
700
|
-
versions_table 'versions' # Shared table
|
|
701
|
-
end
|
|
702
|
-
end
|
|
703
|
-
|
|
704
|
-
class User < ApplicationRecord
|
|
705
|
-
traceable do
|
|
706
|
-
track :email
|
|
707
|
-
versions_table 'versions' # Same shared table
|
|
708
|
-
end
|
|
709
|
-
end
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
**📝 Optional Tracking:**
|
|
713
|
-
|
|
714
|
-
To track who made changes and why, simply set attributes before saving:
|
|
715
|
-
|
|
716
|
-
```ruby
|
|
717
|
-
article.updated_by_id = current_user.id
|
|
718
|
-
article.updated_reason = "Fixed typo"
|
|
719
|
-
article.update!(title: "Corrected Title")
|
|
720
|
-
|
|
721
|
-
# The version will automatically include updated_by_id and updated_reason
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
---
|
|
725
|
-
|
|
726
682
|
## 📌 Changelog
|
|
727
683
|
|
|
728
684
|
See [CHANGELOG.md](CHANGELOG.md) for version history and release notes.
|
|
@@ -748,22 +704,15 @@ Detailed documentation for each BetterModel concern:
|
|
|
748
704
|
- [**Searchable**](docs/searchable.md) - Unified search interface
|
|
749
705
|
- [**Validatable**](docs/validatable.md) - Declarative validation system
|
|
750
706
|
- [**Stateable**](docs/stateable.md) 🆕 - State machine with transitions
|
|
751
|
-
|
|
752
|
-
### 🎓 Advanced Guides
|
|
753
|
-
|
|
754
|
-
Learn how to master BetterModel in production:
|
|
755
|
-
|
|
756
|
-
- [**Integration Guide**](docs/integration_guide.md) 🆕 - Combining multiple concerns effectively
|
|
757
|
-
- [**Performance Guide**](docs/performance_guide.md) 🆕 - Optimization strategies and indexing
|
|
758
|
-
- [**Migration Guide**](docs/migration_guide.md) 🆕 - Adding BetterModel to existing apps
|
|
707
|
+
- [**Taggable**](docs/taggable.md) 🆕 - Flexible tag management with normalization
|
|
759
708
|
|
|
760
709
|
### 💡 Quick Links
|
|
761
710
|
|
|
762
|
-
- [Installation](
|
|
763
|
-
- [Quick Start](
|
|
764
|
-
- [Features Overview](
|
|
765
|
-
- [Requirements](
|
|
766
|
-
- [Contributing](
|
|
711
|
+
- [Installation](#installation)
|
|
712
|
+
- [Quick Start](#quick-start)
|
|
713
|
+
- [Features Overview](#features-overview)
|
|
714
|
+
- [Requirements](#requirements)
|
|
715
|
+
- [Contributing](#contributing)
|
|
767
716
|
|
|
768
717
|
## 🤝 Contributing
|
|
769
718
|
|
|
@@ -792,6 +741,35 @@ We welcome contributions! Here's how you can help:
|
|
|
792
741
|
|
|
793
742
|
### 🔧 Development Setup
|
|
794
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
|
+
|
|
795
773
|
```bash
|
|
796
774
|
# Clone your fork
|
|
797
775
|
git clone https://github.com/YOUR_USERNAME/better_model.git
|
|
@@ -810,6 +788,11 @@ bundle exec rake test # Coverage report in coverage/index.html
|
|
|
810
788
|
bundle exec rubocop
|
|
811
789
|
```
|
|
812
790
|
|
|
791
|
+
**Requirements:**
|
|
792
|
+
- Ruby 3.0+ (Ruby 3.3 recommended)
|
|
793
|
+
- Rails 8.1+
|
|
794
|
+
- SQLite3
|
|
795
|
+
|
|
813
796
|
### 📊 Test Coverage Notes
|
|
814
797
|
|
|
815
798
|
The test suite runs on **SQLite** for performance and portability. Current coverage: **92.57%** (1507 / 1628 lines).
|
|
@@ -104,7 +104,6 @@ module BetterModel
|
|
|
104
104
|
sort :archived_at unless sortable_field?(:archived_at)
|
|
105
105
|
|
|
106
106
|
# Definisci gli scope alias (approccio ibrido)
|
|
107
|
-
# v2.0.0: predicates require parameters
|
|
108
107
|
scope :archived, -> { archived_at_present(true) }
|
|
109
108
|
scope :not_archived, -> { archived_at_null(true) }
|
|
110
109
|
|