RepoPilotOpen in app →

norman/friendly_id

FriendlyId is the “Swiss Army bulldozer” of slugging and permalink plugins for ActiveRecord. It allows you to create pretty URL’s and work with human-friendly strings as if they were numeric ids for ActiveRecord models.

Healthy

Healthy across the board

Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

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 2d ago
  • 39+ active contributors
  • Distributed ownership (top contributor 41% of recent commits)
Show 3 more →
  • MIT licensed
  • CI configured
  • Tests present

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 "Healthy" badge

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

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/norman/friendly_id)](https://repopilot.app/r/norman/friendly_id)

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/norman/friendly_id on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: norman/friendly_id

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/norman/friendly_id 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

GO — Healthy across the board

  • Last commit 2d ago
  • 39+ active contributors
  • Distributed ownership (top contributor 41% of recent commits)
  • MIT licensed
  • CI configured
  • Tests present

<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 norman/friendly_id repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/norman/friendly_id.

What it runs against: a local clone of norman/friendly_id — 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 norman/friendly_id | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | Last commit ≤ 32 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>norman/friendly_id</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of norman/friendly_id. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/norman/friendly_id.git
#   cd friendly_id
#
# 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 norman/friendly_id and re-run."
  exit 2
fi

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

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

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

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 32 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~2d)"
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/norman/friendly_id"
  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

FriendlyId is an ActiveRecord plugin that generates human-readable URL slugs for Rails models, allowing you to use pretty URLs like /users/joe-schmoe instead of /users/42. It provides slug generation, versioning/history tracking, i18n support, scoped slugs, and reserved word filtering—all integrated into the ActiveRecord query interface via .friendly.find(). Monolithic gem structure: lib/friendly_id/base.rb contains the core module that extends ActiveRecord; feature modules live as separate files (slugged.rb, history.rb, scoped.rb, sequentially_slugged.rb); lib/friendly_id/finders.rb hooks into ActiveRecord queries; lib/generators/friendly_id_generator.rb provides Rails generators for migrations; test suite mirrors the lib structure under test/.

👥Who it's for

Rails developers building customer-facing web applications who need SEO-friendly URLs and want to work with human-readable identifiers in controllers without manual slug management. Also for gem maintainers and contributors supporting multiple Rails versions (6.0–8.0).

🌱Maturity & risk

Mature and production-ready. The project has a full CI/CD pipeline (GitHub Actions for test and release workflows), comprehensive test suite in the test/ directory, support across 7 Rails versions (gemfiles/), detailed documentation (guide.rb, UPGRADING.md), and an MIT license. Last activity indicates active maintenance.

Low risk for most uses. The codebase is stable and dependency-light (primarily ActiveRecord). Main risks: single maintainer (norman/), potential for breaking changes across major Rails versions requiring version bumps, and the slug table requirement means it's not a zero-configuration solution. The plugin modifies ActiveRecord finder behavior, so incompatibilities could arise with other scoping gems.

Active areas of work

Active maintenance with CI/CD workflows testing against Rails 6.0–8.0. The repository includes Dependabot configuration for dependency updates and a release workflow, suggesting regular patch and feature releases. No major architectural changes are evident in the file list, indicating focus on stability and Rails compatibility.

🚀Get running

git clone https://github.com/norman/friendly_id.git
cd friendly_id
bundle install
bundle exec rake test

Daily commands: This is a gem library, not an executable app. Run tests with bundle exec rake test. The gem is tested against multiple Rails versions using gemfiles/Gemfile.rails-*.rb. For local development: bundle install && bundle exec rake runs the default Rakefile tasks.

🗺️Map of the codebase

  • lib/friendly_id/base.rb: Core module that sets up friendly_id on models; entry point for understanding how the DSL (friendly_id :name, use: :slugged) works
  • lib/friendly_id/finders.rb: Implements the query interface (FriendlyId::Finders, FriendlyId::FinderMethods) that hooks into ActiveRecord's .friendly.find() chain
  • lib/friendly_id/slugged.rb: Core slugging module—implements automatic slug generation, validation, and the main friendly_id :attribute feature
  • lib/friendly_id/slug.rb: Slug model and ActiveRecord association that stores generated slugs; critical for understanding persistence layer
  • lib/friendly_id/configuration.rb: Configuration DSL handler; defines how options like :use, :allow_nil, :scope are processed
  • lib/friendly_id/slug_generator.rb: Text-to-slug conversion logic; where parameterization and sanitization rules live
  • lib/friendly_id/history.rb: Optional module implementing slug versioning; shows how to extend core functionality with mixins
  • test/core_test.rb: Comprehensive test suite covering basic friendly_id behavior; good reference for testing patterns

🛠️How to make changes

For new slug features: modify lib/friendly_id/slug_generator.rb and lib/friendly_id/candidates.rb (slug candidate generation logic). For finder improvements: edit lib/friendly_id/finders.rb and lib/friendly_id/finder_methods.rb. For new modules: create a file like lib/friendly_id/my_feature.rb following the pattern of scoped.rb. Add tests: mirror the file structure under test/ (e.g., test/my_feature_test.rb). Database schema changes: implement using lib/friendly_id/migration.rb patterns.

🪤Traps & gotchas

  1. Slug table is required: Even without history enabled, FriendlyId expects a slug column and uses a separate friendly_id_slugs table for querying. The migration generator (rails generate friendly_id) must be run. 2. Finder behavior changes: .friendly.find() is not identical to .find()—it queries by slug first, then falls back to ID. Chaining with other scopes can have subtle precedence issues. 3. Rails version gemfiles matter: Test suite uses gemfiles/Gemfile.rails-*.rb; local bundle.lock may differ from CI. Use the correct gemfile for your Rails version during development. 4. Reserved words: The reserved.rb module filters certain slug values (e.g., 'new', 'edit'); custom routes with these names can conflict. 5. I18n parameterization: SimpleI18n uses I18n.locale at runtime; locale-specific slug generation depends on Rails i18n setup being correct.

💡Concepts to learn

  • Slug generation and parameterization — FriendlyId's core job is converting arbitrary text (e.g., product names, user names) into URL-safe strings. Understanding slug generation rules (lowercasing, accent removal, whitespace-to-dash conversion) is critical for customizing or debugging slug behavior.
  • Finder method chaining and scope — FriendlyId integrates into ActiveRecord's finder interface via scope chaining (.friendly.find()). Understanding how Ruby modules override and extend class methods is essential for modifying the finder behavior or debugging query precedence.
  • Module mixins and the Strategy pattern — FriendlyId uses mixins (Slugged, History, Scoped) to compose features. Each module adds behavior via included callbacks and module methods. Learning this pattern is essential for extending FriendlyId or adding new modules.
  • Callback hooks in ActiveRecord (before_save, before_validation) — FriendlyId uses ActiveRecord callbacks to auto-generate slugs before records are saved. Understanding callback order and when slugs are generated vs. updated is critical for debugging unexpected slug behavior.
  • Normalized form equivalence (NFD/NFC) — The slug generator must handle Unicode normalization to ensure accented characters (é, ñ, etc.) are consistently converted to ASCII-safe slugs. This is non-obvious in slug_generator.rb and affects i18n behavior.
  • Polymorphic association querying — The Slug model uses polymorphic associations (sluggable) to attach slugs to any model. Understanding how ActiveRecord handles type and id columns in polymorphic queries is important for debugging the finder logic.
  • Migration generation and schema design — FriendlyId requires specific schema columns (slug on the model, friendly_id_slugs table with polymorphic columns). Understanding how the generator (lib/generators/friendly_id_generator.rb) creates migrations and indexing strategy affects performance and compatibility.
  • mongoid/mongoid-slug — MongoDB equivalent of FriendlyId; solves the same slug problem for Mongoid instead of ActiveRecord
  • SamSaffron/minitest-benchmark — Benchmarking framework used by Rails projects; relevant for understanding the test/benchmarks/ structure and performance testing patterns
  • rails/rails — Core ActiveRecord framework that FriendlyId extends; understanding Rails 6.0–8.0 query interface is essential for contributing
  • ruby-i18n/i18n — Internationalization library that FriendlyId's SimpleI18n module depends on for locale-aware slug generation
  • rspec/rspec-rails — Testing framework commonly used alongside FriendlyId in Rails projects; shows testing patterns for model specs with slug behavior

🪄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 comprehensive test coverage for Rails 8.0 compatibility

The repo has a Gemfile.rails-8.0.rb indicating Rails 8.0 support, but there's no corresponding test suite verification in the test files. New contributors should add integration tests to ensure all FriendlyId features work correctly with Rails 8.0 (generators, scoped slugs, history tracking, sequentially_slugged, etc.) which is critical as Rails 8.0 introduces significant changes.

  • [ ] Create a new test file test/rails_8_compatibility_test.rb
  • [ ] Add tests for basic slug generation with Rails 8.0
  • [ ] Add tests for scoped slugs with Rails 8.0 (reference test/scoped_test.rb patterns)
  • [ ] Add tests for history functionality with Rails 8.0 (reference test/history_test.rb)
  • [ ] Ensure test/helper.rb properly loads Rails 8.0 dependencies
  • [ ] Run full test suite with Gemfile.rails-8.0.rb to verify no regressions

Add tests for numeric slug edge cases and security

The test/numeric_slug_test.rb file exists but is minimal. Given that FriendlyId allows finding records by numeric IDs alongside slugs, there's a critical security and functionality gap: tests for SQL injection prevention, type coercion edge cases, and collision handling between numeric and string slugs need expansion.

  • [ ] Expand test/numeric_slug_test.rb with edge case tests (zero, negative numbers, very large integers)
  • [ ] Add tests for SQL injection attempts via numeric slug parameters (reference test/finders_test.rb for finder patterns)
  • [ ] Add tests for collision handling when a slug is purely numeric and matches an actual ID
  • [ ] Add tests for finder methods with ambiguous inputs (numeric strings like '123' that could be ID or slug)
  • [ ] Test backward compatibility with models that have switched between numeric and non-numeric finders

Add missing documentation and tests for SimpleI18n slug generation with multiple locales

The test/simple_i18n_test.rb exists but the UPGRADING.md and guide.rb don't comprehensively document locale-specific slug behavior, fallback chains, or how sequentially_slugged interacts with SimpleI18n. This is a complex feature with insufficient edge case coverage.

  • [ ] Add comprehensive tests in test/simple_i18n_test.rb for locale fallback chains and precedence
  • [ ] Add integration tests for SimpleI18n + sequentially_slugged interaction (test/sequentially_slugged_test.rb references this but doesn't test i18n)
  • [ ] Add tests for slug regeneration when switching between locales
  • [ ] Document locale-specific slug generation behavior in a new section of guide.rb with code examples
  • [ ] Add tests for reserved words handling across multiple locales (reference test/reserved_test.rb and lib/friendly_id/reserved.rb)

🌿Good first issues

  • Add test coverage for lib/friendly_id/object_utils.rb—the file exists but has no corresponding test file in test/. Write a test/object_utils_test.rb mirroring the module's methods (e.g., testing friendly_id attribute resolution with various object types).
  • Expand documentation in lib/friendly_id/sequentially_slugged.rb with inline code examples in the class docstring. The module handles slug conflicts by appending numbers, but the implementation logic is underdocumented compared to other modules like Slugged.
  • Add a GitHub Actions workflow test matrix for the bench.rb and test/benchmarks/ suite. Currently only test.yml runs; benchmarks could be tracked over time with a separate workflow that logs results, helping detect performance regressions.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • c66779c — Bump version to 5.7.0 (#1044) (parndt)
  • cf4b81c — Reduce gem size by excluding test files (#1042) (yuri-zubov)
  • f72defb — Use the 'v' prefix for version numbers. (#1041) (parndt)
  • 192a2bb — Add bundler/gem_tasks and update VERSION (#1040) (parndt)
  • af14c1c — Add Trusted Publishing workflow (#1039) (parndt)
  • 70dd565 — Bump actions/checkout from 5 to 6 (#1038) (dependabot[bot])
  • db7f702 — Add treat_numeric_as_conflict option to prevent ambiguous numeric slugs (#1037) (oehlschl)
  • cca90a6 — Bump actions/checkout from 4 to 5 (#1036) (dependabot[bot])
  • c288abb — Update CI matrix to support Rails 8.0 and 7.2, add Ruby 3.3 and 3.4 (#1033) (willnet)
  • 40b899a — Fix typo and prompt to install ~> 5.5.0 (#1023) (dchacke)

🔒Security observations

The FriendlyId gem is a well-maintained Rails plugin with a reasonably secure codebase. The primary concerns are potential SQL injection in query generation and XSS via unescaped slug output in views. These risks are largely dependent on proper Rails framework usage by developers. No hardcoded credentials, exposed secrets, or Docker configuration issues were identified. The gem lacks explicit input validation documentation and length limits, which could be improved. Overall security posture is good with moderate concerns around developer misuse rather than inherent vulnerabilities.

  • Medium · Potential SQL Injection in Slug Queries — lib/friendly_id/finder_methods.rb, lib/friendly_id/finders.rb, lib/friendly_id/scoped.rb. The friendly_id gem generates database queries based on user-provided slugs. If slug generation and sanitization is not properly implemented across all modules (finder_methods.rb, finders.rb, scoped.rb), there could be SQL injection vulnerabilities when slugs are interpolated into queries. Fix: Ensure all database queries use parameterized queries/ActiveRecord's query interface rather than string interpolation. Review slug_generator.rb and candidates.rb to verify proper sanitization of input before database queries.
  • Medium · Potential XSS via Unescaped Slug Output — lib/friendly_id/simple_i18n.rb, lib/friendly_id/slug_generator.rb. Slugs are user-generated content derived from model attributes. If slugs are displayed in views without proper escaping, they could contain malicious scripts, especially in i18n scenarios (simple_i18n.rb). Fix: Document that all slug output in views must be properly HTML-escaped using Rails helpers (e.g., h() or <%= %>). Consider adding security notes to the Guide about handling user-controlled slug content.
  • Low · Missing Input Validation Documentation — lib/friendly_id/configuration.rb, lib/friendly_id/slug_generator.rb, lib/friendly_id/reserved.rb. The codebase lacks explicit validation of slug input length and character restrictions. Excessively long or specially-crafted slugs could potentially cause DoS or bypass reserved word checks. Fix: Implement and document maximum slug length limits and allowed character sets. Review reserved word filtering logic to ensure robustness against bypass attempts.
  • Low · Potential Information Disclosure via History Table — lib/friendly_id/history.rb. The history module (lib/friendly_id/history.rb) stores all previous slugs in a database table. This could leak sensitive information if the model's slug is derived from confidential user data. Fix: Document privacy implications of slug history. Recommend applications sanitize sensitive data before using it in slugs, and consider implementing cleanup policies for old history records.

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.