0.0
The project is in a healthy, maintained state
BetterModel is a Rails engine gem (Rails 8.1+) that provides powerful extensions for ActiveRecord models including declarative status management and more.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 8.1.0, < 9.0
 Project Readme

BetterModel ๐Ÿš€

CI Gem Version Ruby Rails License: MIT Coverage

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.

๐Ÿ“ฆ Installation

Add this line to your application's Gemfile:

gem "better_model"

And then execute:

$ bundle install

Or install it yourself as:

$ gem install better_model

โšก Quick Start

Simply include BetterModel in your model to get all features:

class Article < ApplicationRecord
  include BetterModel  # Includes all BetterModel concerns

  # 1. STATUSABLE - Define statuses with lambdas
  is :draft, -> { status == "draft" }
  is :published, -> { status == "published" && published_at.present? }
  is :expired, -> { expires_at.present? && expires_at <= Time.current }
  is :popular, -> { view_count >= 100 }
  is :active, -> { is?(:published) && !is?(:expired) }

  # 2. PERMISSIBLE - Define permissions based on statuses
  permit :edit, -> { is?(:draft) || (is?(:published) && !is?(:expired)) }
  permit :delete, -> { is?(:draft) }
  permit :publish, -> { is?(:draft) }
  permit :unpublish, -> { is?(:published) }

  # 3. SORTABLE - Define sortable fields
  sort :title, :view_count, :published_at, :created_at

  # 4. PREDICABLE - Define searchable/filterable fields
  predicates :title, :status, :view_count, :published_at, :created_at, :featured

  # 5. ARCHIVABLE - Soft delete with tracking (opt-in)
  archivable do
    skip_archived_by_default true  # Hide archived records by default
  end

  # 6. VALIDATABLE - Declarative validation system (opt-in)
  register_complex_validation :published_requirements do
    return unless status == "published"
    errors.add(:published_at, "must be present for published articles") if published_at.blank?
  end

  validatable do
    # Basic validations
    check :title, :content, presence: true

    # Conditional validations using Rails options
    check :published_at, presence: true, if: -> { status == "published" }

    # Complex validations for business rules
    check_complex :published_requirements

    # Validation groups (multi-step forms)
    validation_group :step1, [:title, :content]
    validation_group :step2, [:published_at]
  end

  # 7. STATEABLE - Declarative state machine (opt-in)
  stateable do
    # Define states
    state :draft, initial: true
    state :published
    state :archived

    # Define transitions with guards and callbacks
    transition :publish, from: :draft, to: :published do
      check { valid? }
      check { title.present? && content.present? }
      before_transition { self.published_at = Time.current }
      after_transition { Rails.logger.info "Article #{id} published" }
    end

    transition :archive, from: [:draft, :published], to: :archived
  end

  # 8. TRACEABLE - Audit trail with time-travel (opt-in)
  traceable do
    track :title, :content, :status, :published_at
    track :password_hash, sensitive: :full    # Complete redaction
    track :credit_card, sensitive: :partial   # Pattern-based masking
    track :api_token, sensitive: :hash        # SHA256 hashing
    versions_table :article_versions  # Optional: custom table
  end

  # 9. SEARCHABLE - Configure unified search interface
  searchable do
    per_page 25
    max_per_page 100
    default_order [:sort_published_at_desc]
    security :status_required, [:status_eq]
  end

  # 10. TAGGABLE - Tag management with statistics (opt-in)
  taggable do
    tag_field :tags
    normalize true         # Automatic lowercase
    strip true             # Remove whitespace
    min_length 2           # Minimum tag length
    max_length 30          # Maximum tag length
    delimiter ","          # CSV delimiter
    validates_tags minimum: 1, maximum: 10
  end
end

๐Ÿ’ก Now you can use all the features:

# โœ… Check statuses
article.is?(:draft)          # => true/false
article.is_published?        # => true/false
article.statuses             # => { draft: true, published: false, ... }

# ๐Ÿ” Check permissions
article.permit?(:edit)       # => true/false
article.permit_delete?       # => true/false
article.permissions          # => { edit: true, delete: true, ... }

# โฌ†๏ธ Sort
Article.sort_title_asc
Article.sort_view_count_desc
Article.sort_published_at_desc

# ๐Ÿ” Filter with predicates
Article.status_eq("published")
Article.title_cont("Rails")
Article.view_count_gteq(100)
Article.published_at_present

# ๐Ÿ—„๏ธ Archive records
article.archive!(by: current_user, reason: "Outdated")
article.archived?  # => true
article.restore!

# ๐Ÿ“‚ Query archived records
Article.archived
Article.not_archived
Article.archived_recently(7.days)

# โœ… Validate with groups (multi-step forms)
article.valid?(:step1)  # Validate only step1 fields
article.valid?(:step2)  # Validate only step2 fields
article.errors_for_group(:step1)  # Get errors for step1 only

# ๐Ÿ”„ State machine transitions
article.state            # => "draft"
article.draft?           # => true
article.can_publish?     # => true (checks guards)
article.publish!         # Executes transition with guards & callbacks
article.published?       # => true
article.state_transitions  # History of all transitions
article.transition_history # Formatted history array

# โฐ Time travel & rollback (Traceable)
article.audit_trail              # Full change history
article.as_of(3.days.ago)        # Reconstruct past state
article.rollback_to(version)     # Restore to previous version
article.changes_for(:status)     # Changes for specific field

# ๐Ÿ” Query changes
Article.changed_by(user.id)
Article.changed_between(1.week.ago, Time.current)
Article.status_changed_from("draft").to("published")

# ๐Ÿ”Ž Unified search with filters, sorting, and pagination
Article.search(
  { status_eq: "published", view_count_gteq: 50 },
  orders: [:sort_published_at_desc],
  pagination: { page: 1, per_page: 25 }
)

# ๐Ÿท๏ธ Manage tags
article.tag_with("ruby", "rails", "tutorial")
article.untag("tutorial")
article.tagged_with?("ruby")  # => true
article.tag_list = "ruby, rails, api"

# ๐Ÿ“Š Query with tags (via Predicable)
Article.tags_contains("ruby")
Article.tags_overlaps(["ruby", "python"])
Article.tags_contains_all(["ruby", "rails"])

# ๐Ÿ“ˆ Tag statistics
Article.tag_counts                    # => {"ruby" => 45, "rails" => 38}
Article.popular_tags(limit: 10)       # => [["ruby", 45], ["rails", 38]]
Article.related_tags("ruby", limit: 5)  # => ["rails", "gem", "tutorial"]

๐ŸŽฏ Including Individual Concerns (Advanced)

If you only need specific features, you can include individual concerns:

class Article < ApplicationRecord
  include BetterModel::Statusable    # Only status management
  include BetterModel::Permissible   # Only permissions
  include BetterModel::Archivable    # Only archiving
  include BetterModel::Traceable     # Only audit trail & time-travel
  include BetterModel::Sortable      # Only sorting
  include BetterModel::Predicable    # Only filtering
  include BetterModel::Validatable   # Only validations
  include BetterModel::Stateable     # Only state machine
  include BetterModel::Searchable    # Only search (requires Predicable & Sortable)
  include BetterModel::Taggable      # Only tag management

  # Define your features...
end

๐Ÿ› ๏ธ Generators

Better Model provides Rails generators to help you quickly set up migrations for features that require database tables or columns.

Traceable Generator

Create migrations for audit trail version tables:

# Basic usage - shows setup instructions
rails g better_model:traceable Article

# Create migration for versions table
rails g better_model:traceable Article --create-table

# Custom table name
rails g better_model:traceable Article --create-table --table-name=audit_log

# Run migrations
rails db:migrate

Generated migration includes:

  • Polymorphic association (item_type, item_id)
  • Event tracking (created, updated, destroyed)
  • Change tracking (object_changes as JSON)
  • User attribution (updated_by_id)
  • Change reason (updated_reason)
  • Optimized indexes

Archivable Generator

Add soft-delete columns to existing models:

# Basic usage - shows setup instructions
rails g better_model:archivable Article

# Add archivable columns to articles table
rails g better_model:archivable Article --create-columns

# Run migrations
rails db:migrate

Generated migration adds:

  • archived_at (datetime) - when archived
  • archived_by_id (integer) - who archived it
  • archive_reason (string) - why archived
  • Index on archived_at

Stateable Generator

Create state machine with state column and transitions tracking:

# Basic usage - shows setup instructions
rails g better_model:stateable Article

# Create both state column and transitions table
rails g better_model:stateable Article --create-tables

# Custom initial state (default: draft)
rails g better_model:stateable Article --create-tables --initial-state=pending

# Custom transitions table name
rails g better_model:stateable Article --create-tables --table-name=article_state_history

# Run migrations
rails db:migrate

Generated migrations include:

  1. State column migration:

    • state (string) with default value and index
  2. Transitions table migration:

    • Polymorphic association (transitionable_type, transitionable_id)
    • Event name and state tracking
    • Optional metadata (JSON)
    • Optimized indexes

Generator Options

All generators support these common options:

  • --pretend - Dry run, show what would be generated
  • --skip-model - Only generate migrations, don't show model setup instructions
  • --force - Overwrite existing files

Example workflow:

# 1. Generate migrations (dry-run first to preview)
rails g better_model:traceable Article --create-table --pretend
rails g better_model:archivable Article --create-columns --pretend
rails g better_model:stateable Article --create-tables --pretend

# 2. Generate for real
rails g better_model:traceable Article --create-table
rails g better_model:archivable Article --create-columns
rails g better_model:stateable Article --create-tables

# 3. Run migrations
rails db:migrate

# 4. Enable in your model (generators show you the code)
# See model setup instructions after running each generator

Repository Generator

Create repository classes that implement the Repository Pattern:

# Basic usage - creates model repository and ApplicationRepository
rails g better_model:repository Article

# Custom path
rails g better_model:repository Article --path app/services/repositories

# Skip ApplicationRepository creation
rails g better_model:repository Article --skip-base

# With namespace
rails g better_model:repository Article --namespace Admin

Generated repository includes:

  • Repository class inheriting from ApplicationRepository (or BetterModel::Repositable::BaseRepository with --skip-base)
  • Modern Ruby 3 endless method syntax: def model_class = Article
  • Commented examples of custom query methods
  • Integration with BetterModel's Searchable, Predicable, and Sortable features
  • Auto-generated documentation of available predicates and sort scopes (if model uses BetterModel)

ApplicationRepository (generated once):

  • Base class for all repositories in your application
  • Inherits from BetterModel::Repositable::BaseRepository
  • Can be customized with application-wide repository behaviors

Example usage:

# app/repositories/article_repository.rb
class ArticleRepository < ApplicationRepository
  def model_class = Article

  def published
    search({ status_eq: "published" })
  end

  def recent(days: 7)
    search({ created_at_gteq: days.days.ago }, order_scope: { field: :created_at, direction: :desc })
  end

  def popular(min_views: 100)
    search({ view_count_gteq: min_views }, orders: [:sort_view_count_desc])
  end

  def search_text(query)
    search({
      or: [
        { title_i_cont: query },
        { content_i_cont: query }
      ]
    })
  end
end

# In your controllers/services
repo = ArticleRepository.new
articles = repo.published
popular = repo.popular(min_views: 200)
results = repo.search({ status_eq: "published" }, page: 1, per_page: 20)
article = repo.search({ id_eq: 1 }, limit: 1)

BaseRepository features:

  • search(predicates, page:, per_page:, includes:, joins:, order:, order_scope:, limit:) - Main search method
  • find(id), find_by(...) - Standard ActiveRecord finders
  • create(attrs), create!(attrs) - Create records
  • build(attrs) - Build new instances
  • update(id, attrs) - Update records
  • delete(id) - Delete records
  • where(...), all, count, exists? - Basic ActiveRecord methods

Search method parameters:

  • predicates: Hash of BetterModel predicates (e.g., { status_eq: "published", view_count_gt: 100 })
  • page/per_page: Pagination (default: page 1, 20 per page)
  • includes/joins: Eager loading and associations
  • order: SQL order clause
  • order_scope: BetterModel sort scope (e.g., { field: :published_at, direction: :desc })
  • limit: Result limit (Integer for limit, :default for pagination, nil for all results)

๐Ÿ“‹ Features Overview

BetterModel provides ten powerful concerns that work seamlessly together:

Core Features (Always Available)

  • โœจ Statusable - Declarative status management with lambda-based conditions
  • ๐Ÿ” Permissible - State-based permission system
  • โฌ†๏ธ Sortable - Type-aware sorting scopes
  • ๐Ÿ” Predicable - Advanced filtering with rich predicate system

Opt-in Features (Require Activation)

  • ๐Ÿ”Ž Searchable - Unified search interface (Predicable + Sortable)
  • ๐Ÿ—„๏ธ Archivable - Soft delete with tracking (by user, reason)
  • โœ… Validatable - Declarative validation DSL with conditional rules
  • ๐Ÿ”„ Stateable - Declarative state machines with guards & callbacks
  • โฐ Traceable - Complete audit trail with time-travel and rollback
  • ๐Ÿท๏ธ Taggable - Tag management with normalization, validation, and statistics

See all features in detail โ†’

โš™๏ธ Requirements

  • Ruby: 3.0 or higher
  • Rails: 8.1 or higher
  • ActiveRecord: Included with Rails

๐Ÿ’พ Database Compatibility

BetterModel works with all databases supported by ActiveRecord:

Database Status Notes
PostgreSQL โœ… Full support Recommended. Includes array and JSONB predicates
MySQL/MariaDB โœ… Full support NULLS emulation for sorting
SQLite โœ… Full support Great for development and testing
SQL Server โœ… Full support Standard features work
Oracle โœ… Full support Standard features work

PostgreSQL-Specific Features:

  • Array predicates: overlaps, contains, contained_by
  • JSONB predicates: has_key, has_any_key, has_all_keys, jsonb_contains

๐Ÿ—„๏ธ Database Requirements

Some opt-in features require database columns. Use the provided generators to add them:

Feature Database Requirement Generator Command
Archivable archived_at column rails g better_model:archivable Model
Stateable state, transitions columns rails g better_model:stateable Model
Traceable version_records table rails g better_model:traceable Model
Taggable tags JSONB/text column rails g better_model:taggable Model

Core features (Statusable, Permissible, Predicable, Sortable, Searchable, Validatable) require no database changes.

๐Ÿ“š Feature Details

BetterModel provides ten powerful concerns that work together seamlessly:

๐Ÿ“‹ Statusable - Declarative Status Management

Define derived statuses dynamically based on model attributes - no database columns needed!

๐ŸŽฏ Key Benefits:

  • โœจ Declarative DSL with clear, readable conditions
  • โšก Statuses calculated in real-time from model attributes
  • ๐Ÿ”— Reference other statuses in conditions
  • ๐Ÿค– Automatic method generation (is_draft?, is_published?)
  • ๐Ÿ”’ Thread-safe with immutable registry

๐Ÿ“– Full Documentation โ†’


๐Ÿ” Permissible - Declarative Permission Management

Define permissions dynamically based on model state and statuses - perfect for authorization logic!

๐ŸŽฏ Key Benefits:

  • โœจ Declarative DSL following Statusable pattern
  • โšก Permissions calculated from model state
  • ๐Ÿ”— Reference statuses in permission logic
  • ๐Ÿค– Automatic method generation (permit_edit?, permit_delete?)
  • ๐Ÿ”’ Thread-safe with immutable registry

๐Ÿ“– Full Documentation โ†’


๐Ÿ—„๏ธ Archivable - Soft Delete with Archive Management

Soft-delete records with archive tracking, audit trails, and restoration capabilities.

๐ŸŽฏ Key Benefits:

  • ๐ŸŽ›๏ธ Opt-in activation: only enabled when explicitly configured
  • ๐Ÿ”„ Archive and restore methods with optional tracking
  • โœ… Status methods: archived? and active?
  • ๐Ÿ” Semantic scopes: archived, not_archived, archived_only
  • ๐Ÿ› ๏ธ Helper predicates: archived_today, archived_this_week, archived_recently
  • ๐Ÿ‘ป Optional default scope to hide archived records
  • ๐Ÿš€ Migration generator with flexible options
  • ๐Ÿ”’ Thread-safe with immutable configuration

๐Ÿ“– Full Documentation โ†’


โœ… Validatable - Declarative Validation System

Define validations declaratively with support for conditional rules, cross-field validation, business rules, and validation groups.

๐ŸŽฏ Key Benefits:

  • ๐ŸŽ›๏ธ Opt-in activation: only enabled when explicitly configured
  • โœจ Declarative DSL with check method for basic validations
  • ๐Ÿ”— Complex validations: reusable validation blocks for cross-field and business logic
  • ๐Ÿ“‹ Validation groups: partial validation for multi-step forms
  • ๐Ÿ”€ Conditional validations using Rails if: / unless: options
  • ๐Ÿ”’ Thread-safe with immutable configuration

๐Ÿ“– Full Documentation โ†’


๐Ÿ”„ Stateable - Declarative State Machine

Define state machines declaratively with transitions, guards, validations, and callbacks for robust workflow management.

๐ŸŽฏ Key Benefits:

  • ๐ŸŽ›๏ธ Opt-in activation: only enabled when explicitly configured
  • โœจ Declarative DSL for states and transitions
  • ๐Ÿ›ก๏ธ Guards: preconditions with lambda, methods, or Statusable predicates
  • โœ… Validations: custom validation logic per transition
  • ๐Ÿ”— Callbacks: before/after/around hooks for each transition
  • ๐Ÿ“œ State history tracking with customizable table names
  • ๐Ÿค– Dynamic methods: pending?, confirm!, can_confirm?
  • ๐Ÿ”— Integration with Statusable for complex guard logic
  • ๐Ÿ”’ Thread-safe with immutable configuration

๐Ÿ“– Full Documentation โ†’


โฐ Traceable - Audit Trail with Time-Travel

Track all changes to your records with complete audit trail, time-travel capabilities, and rollback support.

๐ŸŽฏ Key Benefits:

  • ๐ŸŽ›๏ธ Opt-in activation: only enabled when explicitly configured
  • ๐Ÿ“ Automatic change tracking on create, update, and destroy
  • ๐Ÿ” Sensitive data protection: 3-level redaction system (full, partial, hash)
  • ๐Ÿ‘ค User attribution: track who made each change
  • ๐Ÿ’ฌ Change reasons: optional context for changes
  • โฐ Time-travel: reconstruct object state at any point in history
  • โ†ฉ๏ธ Rollback support: restore records to previous versions (with sensitive field protection)
  • ๐Ÿ” Rich query API: find changes by user, time, or field transitions
  • ๐Ÿ“Š Flexible table naming: per-model, shared, or custom tables
  • ๐Ÿ”— Polymorphic association for efficient storage
  • ๐Ÿ’พ Database adapter safety: PostgreSQL, MySQL, SQLite support
  • ๐Ÿ”’ Thread-safe dynamic class creation

๐Ÿ“– Full Documentation โ†’


โฌ†๏ธ Sortable - Type-Aware Sorting Scopes

Generate intelligent sorting scopes automatically with database-specific optimizations and NULL handling.

๐ŸŽฏ Key Benefits:

  • ๐ŸŽฏ Type-aware scope generation (string, numeric, datetime, boolean)
  • ๐Ÿ”ค Case-insensitive sorting for strings
  • ๐Ÿ’พ Database-specific NULLS FIRST/LAST support
  • ๐Ÿ”— Sort by multiple fields with chaining
  • โšก Optimized queries with proper indexing support

๐Ÿ“– Full Documentation โ†’


๐Ÿ” Predicable - Advanced Query Scopes

Generate comprehensive predicate scopes for filtering and searching with support for all data types.

๐ŸŽฏ Key Benefits:

  • โœ… Complete coverage: string, numeric, datetime, boolean, null predicates
  • ๐Ÿ”’ Type-safe predicates based on column type
  • ๐Ÿ”ค Case-insensitive string matching
  • ๐Ÿ“Š Range queries (between) for numerics and dates
  • ๐Ÿ˜ PostgreSQL array and JSONB support
  • ๐Ÿ”— Chainable with standard ActiveRecord queries
  • ๐Ÿงฉ Custom complex predicates for business logic

๐Ÿ“– Full Documentation โ†’

๐Ÿงฉ Complex Predicates

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.

Basic Example:

class Article < ApplicationRecord
  include BetterModel

  predicates :title, :view_count, :published_at

  # Define a complex predicate with parameters
  register_complex_predicate :trending do |days = 7, min_views = 100|
    where("published_at >= ? AND view_count >= ?", days.days.ago, min_views)
  end

  # Define a complex predicate for association queries
  register_complex_predicate :popular_author do |min_articles = 10|
    joins(:author)
      .group("articles.author_id")
      .having("COUNT(articles.id) >= ?", min_articles)
  end
end

Usage:

# Use with default parameters
Article.trending
# => Articles from last 7 days with 100+ views

# Use with custom parameters
Article.trending(14, 200)
# => Articles from last 14 days with 200+ views

# Chain with standard predicates
Article.trending(7, 100)
       .title_cont("Ruby")
       .status_eq("published")
       .sort_view_count_desc

# Association-based queries
Article.popular_author(5)
       .published_at_within(30.days)

When to Use Complex Predicates:

Standard Predicates โœ… Complex Predicates ๐Ÿงฉ
Single field filtering Multi-field conditions
title_eq("Ruby") trending(days, views)
view_count_gt(100) Association queries
published_at_within(7.days) Business logic encapsulation
Simple comparisons Custom SQL expressions

Check if Defined:

Article.complex_predicate?(:trending)  # => true
Article.complex_predicates_registry    # => { trending: #<Proc> }

๐Ÿ”Ž Searchable - Unified Search Interface

Orchestrate Predicable and Sortable into a powerful, secure search interface with pagination and security.

๐ŸŽฏ Key Benefits:

  • ๐ŸŽฏ Unified API: single search() method for all operations
  • ๐Ÿ”€ OR conditions for complex logic
  • ๐Ÿ“„ Built-in pagination with DoS protection (max_per_page)
  • ๐Ÿ”’ Security enforcement with required predicates
  • โš™๏ธ Default ordering configuration
  • ๐Ÿ’ช Strong parameters integration
  • โœ… Type-safe validation of all parameters
  • ๐Ÿš€ Eager loading support with includes:, preload:, eager_load:

๐Ÿ“– Full Documentation โ†’

๐Ÿ”— Eager Loading Associations

Optimize N+1 queries with built-in eager loading support:

# Single association (always use array syntax)
Article.search(
  { status_eq: "published" },
  includes: [:author]
)

# Multiple associations
Article.search(
  { status_eq: "published" },
  includes: [:author, :comments],
  preload: [:tags]
)

# Nested associations
Article.search(
  { status_eq: "published" },
  includes: [{ author: :profile }]
)

# Complex mix of associations
Article.search(
  { status_eq: "published" },
  includes: [:tags, { author: :profile }, { comments: :user }]
)

# Combined with pagination and ordering
Article.search(
  { status_eq: "published" },
  pagination: { page: 1, per_page: 25 },
  orders: [:sort_view_count_desc],
  includes: [:author, :comments]
)

Strategies:

  • includes: - Smart loading (LEFT OUTER JOIN or separate queries)
  • preload: - Separate queries (avoids JOIN ambiguity)
  • eager_load: - Force LEFT OUTER JOIN (use with caution with default_order)

๐Ÿท๏ธ Taggable - Tag Management with Statistics

Manage tags with automatic normalization, validation, and comprehensive statistics - integrated with Predicable for powerful searches.

๐ŸŽฏ Key Benefits:

  • ๐ŸŽ›๏ธ Opt-in activation: only enabled when explicitly configured
  • ๐Ÿค– Automatic normalization (lowercase, strip, length limits)
  • โœ… Validation (min/max count, whitelist, blacklist)
  • ๐Ÿ“Š Statistics (tag counts, popularity, co-occurrence)
  • ๐Ÿ” Automatic Predicable integration for searches
  • ๐Ÿ“ CSV import/export with tag_list
  • ๐Ÿ˜ PostgreSQL arrays or serialized JSON for SQLite
  • ๐ŸŽฏ Thread-safe configuration

๐Ÿ“– Full Documentation โ†’ | ๐Ÿ“š Examples โ†’


๐Ÿ“Œ Changelog

See CHANGELOG.md for version history and release notes.

๐Ÿ’ฌ Support & Community

  • ๐Ÿ› Issues & Bugs: GitHub Issues
  • ๐Ÿ’ป Source Code: GitHub Repository
  • ๐Ÿ“– Documentation: This README and detailed docs in docs/ directory

๐Ÿ“š Complete Documentation

๐Ÿ“– Feature Guides

Detailed documentation for each BetterModel concern:

๐Ÿ’ก Quick Links

  • Installation
  • Quick Start
  • Features Overview
  • Requirements
  • Contributing

๐Ÿค Contributing

We welcome contributions! Here's how you can help:

๐Ÿ› Reporting Bugs

  1. โœ… Check if the issue already exists in GitHub Issues
  2. ๐Ÿ“ Create a new issue with:
    • Clear description of the problem
    • Steps to reproduce
    • Expected vs actual behavior
    • Ruby/Rails versions
    • Database adapter

๐Ÿš€ Submitting Pull Requests

  1. ๐Ÿด Fork the repository
  2. ๐ŸŒฟ Create a feature branch (git checkout -b feature/amazing-feature)
  3. โœ๏ธ Make your changes with tests
  4. ๐Ÿงช Run the test suite (bundle exec rake test)
  5. ๐Ÿ’… Ensure RuboCop passes (bundle exec rubocop)
  6. ๐Ÿ’พ Commit your changes (git commit -m 'Add amazing feature')
  7. ๐Ÿ“ค Push to the branch (git push origin feature/amazing-feature)
  8. ๐ŸŽ‰ Open a Pull Request

๐Ÿ”ง Development Setup

๐Ÿณ Using Docker (Recommended)

The easiest way to get started is with Docker, which provides a consistent development environment:

# Clone your fork
git clone https://github.com/YOUR_USERNAME/better_model.git
cd better_model

# One-time setup: build image and install dependencies
bin/docker-setup

# Run tests
bin/docker-test

# Run RuboCop
bin/docker-rubocop

# Open interactive shell for debugging
docker compose run --rm app sh

# Run any command in the container
docker compose run --rm app bundle exec [command]

๐Ÿ’ป Local Setup (Without Docker)

If you prefer to use your local Ruby installation:

# Clone your fork
git clone https://github.com/YOUR_USERNAME/better_model.git
cd better_model

# Install dependencies
bundle install

# Run tests
bundle exec rake test

# Run SimpleCov for coverage
bundle exec rake test  # Coverage report in coverage/index.html

# Run RuboCop
bundle exec rubocop

Requirements:

  • Ruby 3.0+ (Ruby 3.3 recommended)
  • Rails 8.1+
  • SQLite3

๐Ÿ“Š Test Coverage Notes

The test suite runs on SQLite for performance and portability. Current coverage: 94.5% (1891 / 2001 lines).

Database-Specific Features Not Covered:

  • Predicable: PostgreSQL array predicates (_overlaps, _contains, _contained_by) and JSONB predicates (_has_key, _has_any_key, _has_all_keys, _jsonb_contains)
  • Traceable: PostgreSQL JSONB queries and MySQL JSON_EXTRACT queries for field-specific change tracking
  • Sortable: MySQL NULLS emulation with CASE statements
  • Taggable: PostgreSQL native array operations (covered by Predicable tests)

These features are fully implemented with proper SQL sanitization but require manual testing on PostgreSQL/MySQL:

# Test on PostgreSQL
RAILS_ENV=test DATABASE_URL=postgresql://user:pass@localhost/better_model_test rails console

# Test on MySQL
RAILS_ENV=test DATABASE_URL=mysql2://user:pass@localhost/better_model_test rails console

All code has inline comments marking database-specific sections for maintainability.

๐Ÿ“ Code Guidelines

  • โœจ Follow the existing code style (enforced by RuboCop Omakase)
  • ๐Ÿงช Write tests for new features
  • ๐Ÿ“ Update documentation (README) for user-facing changes
  • ๐ŸŽฏ Keep pull requests focused (one feature/fix per PR)

๐Ÿ“ License

The gem is available as open source under the terms of the MIT License.


Made with โค๏ธ by Alessio Bussolari

Report Bug ยท Request Feature ยท Documentation