RepoPilotOpen in app →

rubysherpas/paranoia

acts_as_paranoid for Rails 5, 6 and 7

Mixed

Slowing — last commit 6mo ago

worst of 4 axes
Use as dependencyConcerns

non-standard license (Other)

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 6mo ago
  • 36+ active contributors
  • Distributed ownership (top contributor 18% of recent commits)
Show 5 more →
  • Other licensed
  • CI configured
  • Tests present
  • Slowing — last commit 6mo ago
  • Non-standard license (Other) — review terms
What would change the summary?
  • Use as dependency ConcernsMixed if: clarify license terms

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Forkable" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Forkable
[![RepoPilot: Forkable](https://repopilot.app/api/badge/rubysherpas/paranoia?axis=fork)](https://repopilot.app/r/rubysherpas/paranoia)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/rubysherpas/paranoia on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: rubysherpas/paranoia

Generated by RepoPilot · 2026-05-10 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/rubysherpas/paranoia shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

WAIT — Slowing — last commit 6mo ago

  • Last commit 6mo ago
  • 36+ active contributors
  • Distributed ownership (top contributor 18% of recent commits)
  • Other licensed
  • CI configured
  • Tests present
  • ⚠ Slowing — last commit 6mo ago
  • ⚠ Non-standard license (Other) — review terms

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live rubysherpas/paranoia repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/rubysherpas/paranoia.

What it runs against: a local clone of rubysherpas/paranoia — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in rubysherpas/paranoia | Confirms the artifact applies here, not a fork | | 2 | License is still Other | Catches relicense before you depend on it | | 3 | Default branch core exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 214 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>rubysherpas/paranoia</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of rubysherpas/paranoia. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/rubysherpas/paranoia.git
#   cd paranoia
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of rubysherpas/paranoia and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "rubysherpas/paranoia(\\.git)?\\b" \\
  && ok "origin remote is rubysherpas/paranoia" \\
  || miss "origin remote is not rubysherpas/paranoia (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Other)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Other\"" package.json 2>/dev/null) \\
  && ok "license is Other" \\
  || miss "license drift — was Other at generation time"

# 3. Default branch
git rev-parse --verify core >/dev/null 2>&1 \\
  && ok "default branch core exists" \\
  || miss "default branch core no longer exists"

# 4. Critical files exist
test -f "lib/paranoia.rb" \\
  && ok "lib/paranoia.rb" \\
  || miss "missing critical file: lib/paranoia.rb"
test -f "lib/paranoia/active_record_5_2.rb" \\
  && ok "lib/paranoia/active_record_5_2.rb" \\
  || miss "missing critical file: lib/paranoia/active_record_5_2.rb"
test -f "paranoia.gemspec" \\
  && ok "paranoia.gemspec" \\
  || miss "missing critical file: paranoia.gemspec"
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"
test -f "test/paranoia_test.rb" \\
  && ok "test/paranoia_test.rb" \\
  || miss "missing critical file: test/paranoia_test.rb"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 214 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~184d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/rubysherpas/paranoia"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Paranoia is a soft-delete gem for Rails 5, 6, and 7 that overrides destroy to set a deleted_at timestamp instead of permanently removing records from the database. It automatically scopes all queries to exclude soft-deleted records and provides really_destroy! for permanent deletion. It handles cascading soft-deletes through has_many associations with dependent: :destroy. Minimal single-gem structure: core logic in lib/paranoia.rb, Rails version-specific patches in lib/paranoia/active_record_5_2.rb, RSpec integration in lib/paranoia/rspec.rb, and comprehensive tests in test/paranoia_test.rb. No complex subdirectories—the entire gem is <2KB of actual library code.

👥Who it's for

Rails developers maintaining legacy applications or building systems where audit trails and data recovery are critical, who need to hide records without permanent deletion and want minimal code changes (just add acts_as_paranoid to their models).

🌱Maturity & risk

Production-ready but in maintenance mode. The gem has been around for years and works with Rails 5–7, but the README explicitly warns that paranoia has surprising behavior and recommends the discard gem for new projects. CI is active (GitHub Actions in .github/workflows/build.yml), and bug fixes are accepted, but no new features are being added.

Low technical risk but significant deprecation risk. The maintainers themselves discourage new projects from using it due to unexpected side effects (overriding delete and destroy globally). Single maintainer structure suggests potential long-term support uncertainty. The explicit notice in the README pointing to discard as a superior alternative means new adopters may face pressure to migrate.

Active areas of work

The repo is in maintenance mode accepting bug fixes and Rails version compatibility updates. The README note about discard being recommended suggests recent repositioning toward legacy support rather than active feature development. Last visible workflow is build.yml which likely runs on pushes/PRs.

🚀Get running

git clone https://github.com/rubysherpas/paranoia.git
cd paranoia
bundle install
bundle exec rake test

Daily commands: Run tests with bundle exec rake test. No dev server—this is a library gem. Verify Rails compatibility by checking which Rails version constraint is active in paranoia.gemspec.

🗺️Map of the codebase

  • lib/paranoia.rb — Main entry point that implements the soft-delete mechanism via acts_as_paranoid mixin; all features originate here.
  • lib/paranoia/active_record_5_2.rb — Core ActiveRecord integration for Rails 5.2+ containing scoping logic and override of delete/destroy methods.
  • paranoia.gemspec — Defines gem dependencies and compatibility constraints; critical for understanding which Rails versions are supported.
  • README.md — Documents the soft-delete behavior, migration setup, and explicit warnings about surprising behavior changes.
  • test/paranoia_test.rb — Comprehensive test suite validating soft-delete scoping, restore behavior, and edge cases.
  • lib/paranoia/version.rb — Version management for the gem; needed when publishing and understanding release cadence.

🧩Components & responsibilities

  • Paranoia Mixin (lib/paranoia.rb) (Ruby, ActiveRecord) — Provides acts_as_paranoid DSL method and scoping logic for soft-delete models
    • Failure mode: If not included in model, destroy() will hard-delete; if scopes are not applied, soft-deleted records leak into queries
  • ActiveRecord Integration (lib/paranoia/active_record_5_2.rb) (Ruby, ActiveRecord) — Extends ActiveRecord::Base lifecycle and query interface to enforce deleted_at scoping
    • Failure mode: Monkey-patch conflicts with other gems; version mismatches cause NoMethodError or incorrect scoping
  • RSpec Helpers (lib/paranoia/rspec.rb) (Ruby, RSpec) — Provides custom matchers and assertion methods for testing soft-delete behavior
    • Failure mode: Missing matchers force manual SQL introspection in tests; reduces test readability

🔀Data flow

  • Rails AppActiveRecord Model — Application calls destroy() on model instance
  • ActiveRecord ModelParanoia Mixin — Model checks if acts_as_paranoid is active and delegates to soft-delete logic
  • Paranoia MixinDatabase — Issues UPDATE query setting deleted_at timestamp instead of DELETE
  • DatabaseRails App — Returns updated record; app queries automatically scoped to exclude deleted_at IS NOT NULL rows

🛠️How to make changes

Add paranoia to a new ActiveRecord model

  1. Create a migration to add deleted_at timestamp column to the target table (db/migrate/[timestamp]_add_deleted_at_to_table.rb)
  2. Open the model file and call acts_as_paranoid at class level (app/models/your_model.rb)
  3. Verify soft-delete behavior with unit tests using test/paranoia_test.rb as reference (test/models/your_model_test.rb)

Add RSpec matchers for soft-delete testing

  1. Include paranoia RSpec helpers in your spec setup (spec/spec_helper.rb)
  2. Reference lib/paranoia/rspec.rb to use provided matchers in model specs (spec/models/your_model_spec.rb)

Support a new Rails version

  1. Update paranoia.gemspec to include the new Rails version constraint in activerecord dependency (paranoia.gemspec)
  2. Add the Rails version to the CI matrix in the build workflow (.github/workflows/build.yml)
  3. Create a new Rails version-specific integration file if needed (following lib/paranoia/active_record_5_2.rb pattern) (lib/paranoia/active_record_X_Y.rb)

🔧Why these technologies

  • Ruby — Rails-native language; allows seamless ActiveRecord integration via monkey-patching
  • ActiveRecord — ORM target; paranoia extends its query interface and lifecycle hooks (destroy method)
  • Gem distribution — Packaged as Ruby gem for easy inclusion in Rails projects; transparent integration without code generation

⚖️Trade-offs already made

  • Soft-delete via deleted_at column instead of actual record removal

    • Why: Allows data recovery and audit trails without code changes
    • Consequence: Database size grows over time; queries must be scoped to exclude soft-deleted records; risk of data leaks if scoping is forgotten
  • Overrides ActiveRecord's destroy and delete methods

    • Why: Transparent to application code; no method name changes required
    • Consequence: Surprising behavior for developers unfamiliar with paranoia; can lead to unintended data retention and confusion
  • Bug fixes only; no new features accepted

    • Why: Acknowledges design issues; encourages migration to alternatives like discard gem
    • Consequence: Limited evolution; users seeking advanced soft-delete features must switch gems

🚫Non-goals (don't propose these)

  • Real-time soft-delete synchronization across distributed systems
  • Automatic data purging or retention policies
  • Encryption or anonymization of soft-deleted records
  • Soft-delete for non-ActiveRecord ORMs or raw SQL tables
  • New feature development beyond Rails 5/6/7 compatibility

📊Code metrics

  • Avg cyclomatic complexity: ~3 — Lightweight gem with minimal abstractions; primary logic is straightforward scope application and method override. No complex state machines or async operations.
  • Largest file: test/paranoia_test.rb (400 lines)
  • Estimated quality issues: ~2 — Monkey-patching and implicit scoping create maintenance and compatibility risks; lack of explicit data purge policy limits production readiness

⚠️Anti-patterns to avoid

  • Global monkey-patching of ActiveRecord::Base (High)lib/paranoia.rb, lib/paranoia/active_record_5_2.rb: Modifies core Rails classes directly, increasing risk of conflicts with other gems and version incompatibilities
  • Implicit scoping that can be forgotten (High)lib/paranoia/active_record_5_2.rb: Default scope applied silently; developers may forget to use .with_deleted or .only_deleted for edge cases, leading to data leaks
  • Overriding destroy semantics (Medium)lib/paranoia.rb, lib/paranoia/active_record_5_2.rb: destroy() no longer removes records, creating cognitive dissonance for Rails developers; not intuitive without reading docs

🔥Performance hotspots

  • lib/paranoia/active_record_5_2.rb (Database query performance) — deleted_at column queries on large tables without index can degrade performance; no automatic indexing recommendation in migration docs
  • Database storage (Storage and scaling) — Soft-deleted records accumulate indefinitely; no built-in purging mechanism; table bloat over time

🪤Traps & gotchas

  1. The gem overrides ActiveRecord's delete and destroy globally—this affects ALL models in your app once the gem is loaded, not just those with acts_as_paranoid. 2. Soft-deleted records are still in the database, so uniqueness validations may fail if you re-create a soft-deleted record. 3. The really_destroy! method will cascade-delete dependent associations permanently, bypassing soft-delete—easy to accidentally nuke data. 4. Default scopes can interact unexpectedly with .unscoped calls and other gems' query logic.

🏗️Architecture

💡Concepts to learn

  • Soft Delete — The entire premise of paranoia—soft-delete is a pattern where deletion is logical (flag/timestamp) not physical (database removal), enabling recovery and audit trails
  • Default Scope — Paranoia uses Rails' default_scope to automatically filter deleted_at IS NOT NULL across all queries, but this can cause surprising behavior with unscoped and N+1 query issues
  • Monkey Patching / Module Inclusion — Paranoia injects behavior into ActiveRecord::Base by redefining destroy and delete methods—understanding how this hook system works is critical to avoiding conflicts with other gems
  • Cascading Deletes — Paranoia must handle Rails' dependent: :destroy associations while deciding whether to soft-delete or hard-delete child records—this logic is non-obvious and error-prone
  • ActiveRecord Callbacks — Paranoia leverages before/after delete callbacks to implement soft-delete logic; understanding callback order is essential to debugging when soft-delete doesn't trigger
  • Timestamp-based Logical Deletion — Paranoia uses deleted_at NULL vs NOT NULL as a binary soft-delete marker—different from event sourcing or flag-based deletion, this pattern is simple but limits audit detail
  • Query Scope Pollution — The global default_scope means queries can unexpectedly exclude soft-deleted records; understanding .with_deleted, .only_deleted, and .unscoped is critical for correct behavior
  • jhawthorn/discard — Direct successor and recommended alternative to paranoia—provides soft-delete without overriding destroy, endorsed in paranoia's own README
  • ActsAsParanoid/acts_as_paranoid — Original predecessor gem for Rails 2/3 that paranoia reimplemented from scratch for Rails 5+ compatibility
  • paper_trail-association_tracking/paper_trail — Complementary audit trail gem often used alongside soft-deletes to track who deleted what and when
  • rails/rails — Core Rails framework—paranoia is tightly coupled to ActiveRecord's lifecycle hooks and scoping behavior
  • ruby/ruby — Language runtime—paranoia requires modern Ruby and uses metaprogramming patterns like extend and module_eval for hook installation

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add Rails 7+ specific soft-delete tests in test/paranoia_test.rb

The repo claims to support Rails 5, 6, and 7, but test/paranoia_test.rb likely lacks comprehensive coverage for Rails 7+ behavior changes (e.g., Zeitwerk autoloading, new ActiveRecord APIs, composite primary keys). This ensures the gem maintains compatibility as Rails evolves and catches regressions early.

  • [ ] Review test/paranoia_test.rb for existing Rails version-conditional tests
  • [ ] Add test cases for Rails 7+ specific features (e.g., strict_loading, composite primary keys if applicable)
  • [ ] Verify .github/workflows/build.yml runs tests against Rails 7.x and ensure any failures are addressed
  • [ ] Run full test suite locally to confirm new tests pass

Add comprehensive behavior documentation for lib/paranoia/active_record_5_2.rb in README.md

The README is truncated mid-sentence and doesn't explain which ActiveRecord versions use which implementation files. Developers need to understand why lib/paranoia/active_record_5_2.rb exists, what version-specific behaviors it handles, and how the gem adapts across Rails versions. This prevents confusion and reduces support burden.

  • [ ] Examine lib/paranoia/active_record_5_2.rb to identify version-specific monkey patches and behaviors
  • [ ] Check if additional version-specific files exist (e.g., for Rails 6/7) in lib/paranoia/
  • [ ] Add a 'Version Compatibility' section to README.md explaining which files apply to which Rails versions
  • [ ] Document any breaking changes or behavioral differences between Rails versions

Expand lib/paranoia/rspec.rb with RSpec matcher tests in test/paranoia_test.rb

The gem provides an RSpec integration file (lib/paranoia/rspec.rb) but there's no evidence of test coverage for the custom matchers it likely provides. Contributors using RSpec need confidence these matchers work correctly with soft-deleted records across different scenarios.

  • [ ] Review lib/paranoia/rspec.rb to identify all custom matchers and helpers
  • [ ] Add RSpec tests in test/paranoia_test.rb (or create test/paranoia_rspec_test.rb if preferred) covering matchers like 'be_paranoid', 'be_soft_deleted', etc.
  • [ ] Test edge cases: paranoid models with custom scopes, associations with soft-deleted records, and filtering behavior
  • [ ] Update .github/workflows/build.yml if needed to ensure RSpec tests run in CI

🌿Good first issues

  • Add tests for Rails 7 compatibility edge cases in test/paranoia_test.rb—the gem claims Rails 7 support but may be missing test coverage for newer Rails features like has_one :through or composite primary keys
  • Document the interaction between paranoia and Rails enums in the README—no mention exists, but soft-deleted enum records could cause unexpected behavior with scoping
  • Create a migration generator (rails generate paranoia:migration ModelName) to reduce boilerplate—currently users must manually create 'AddDeletedAtTo...' migrations

Top contributors

Click to expand

📝Recent commits

Click to expand
  • a950fe4 — Support Rails 8.0 and 8.1, drop Ruby 2.7, 3.0 and Rails 6.x (#581) (Copilot)
  • 12c851c — chore: fix rails tests on rails versions earlier than 7.1 (#576) (reedstonefood)
  • fe46c57 — Release v3.0.1 (mathieujobin)
  • d15a01e — fix typo in newly added readme (#567) (mohammednasser-32)
  • 7b96793 — feat: Trigger an after_commit callback when restoring a record (#559) (yoheimuta)
  • ba6dde7 — Handle #delete_all (#566) (mohammednasser-32)
  • f441c37 — Bump to 3.0.0 (radar)
  • 3faf7d3 — Document support for Rails edge + 7.2 (radar)
  • 4d7b87d — See what happens on Rails edge (#564) (radar)
  • 9790ee1 — Add support for Rails 7.2 (#563) (andrewhampton)

🔒Security observations

The paranoia gem presents moderate security concerns primarily due to its intentional design pattern of overriding standard ActiveRecord deletion semantics. While the library itself does not contain obvious injection vulnerabilities or hardcoded secrets, the core functionality poses risks if developers misunderstand soft-delete behavior or fail to properly scope queries. The project's own README discourages use in new projects. No Docker/infrastructure issues detected. Dependency analysis was limited due to missing Gemfile content. The main security posture depends heavily on proper implementation patterns used by consumers of this gem.

  • Medium · Overrides ActiveRecord Security Mechanisms — lib/paranoia.rb and lib/paranoia/active_record_5_2.rb. Paranoia overrides ActiveRecord's delete and destroy methods to implement soft deletes. This can introduce unexpected behavior where developers assume hard deletes occur, potentially bypassing security validations, cascade operations, or audit logs that depend on actual deletion. The README explicitly warns about this 'surprising behaviour.' Fix: Add comprehensive documentation about the soft-delete behavior. Implement explicit really_destroy! or permanently_delete methods for cases requiring hard deletes. Consider using the recommended discard gem for new projects which has clearer semantics.
  • Medium · Incomplete Visibility Control in Associations — lib/paranoia.rb. Soft-deleted records may still be accessible through ActiveRecord associations or scopes if not properly configured. If developers forget to scope queries to exclude deleted_at IS NOT NULL records, deleted data could be unintentionally exposed. Fix: Ensure default scopes are applied consistently across all model methods. Provide clear examples in documentation showing how to properly scope queries. Consider adding validation helpers to detect unscoped queries.
  • Low · Missing CHANGELOG and Dependency Details — Gemfile, CHANGELOG.md. The actual dependency file content (Gemfile) was not provided in the analysis context, making it impossible to verify if vulnerable gem versions are being used. The CHANGELOG.md exists but content wasn't analyzed. Fix: Regularly run bundle audit to check for known vulnerabilities in dependencies. Keep Rails and all gems updated to latest secure versions. Review and document all dependencies.
  • Low · Test Coverage for Security Scenarios — test/paranoia_test.rb. The test file (test/paranoia_test.rb) was not analyzed in detail. Security-critical behavior like association handling with soft deletes and permission checks may lack comprehensive test coverage. Fix: Add explicit tests for: soft-deleted records not appearing in associations, proper cascading behavior, permission checks on soft-deleted data, and restoration of soft-deleted records. Include negative test cases.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Mixed signals · rubysherpas/paranoia — RepoPilot