{"id":19367584,"url":"https://github.com/rameerez/slugifiable","last_synced_at":"2026-02-19T23:03:08.837Z","repository":{"id":260319026,"uuid":"859630631","full_name":"rameerez/slugifiable","owner":"rameerez","description":"🐌 Ruby gem to generate SEO-optimized URL slugs for your records","archived":false,"fork":false,"pushed_at":"2025-08-08T04:27:04.000Z","size":32976,"stargazers_count":29,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-30T07:34:59.139Z","etag":null,"topics":["activerecord","gem","rails","ruby","ruby-on-rails","search-engine","search-engine-optimization","seo","seo-friendly","seo-optimization","slug","slug-generator"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rameerez.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-09-19T02:19:47.000Z","updated_at":"2025-08-20T00:40:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"a329ce6f-ab42-4eb6-81b3-3a0b21c15b24","html_url":"https://github.com/rameerez/slugifiable","commit_stats":{"total_commits":18,"total_committers":1,"mean_commits":18.0,"dds":0.0,"last_synced_commit":"c8b945f91fd1ef9f945a7b6743c5c2049bb432c5"},"previous_names":["rameerez/slugifiable"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/rameerez/slugifiable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fslugifiable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fslugifiable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fslugifiable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fslugifiable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rameerez","download_url":"https://codeload.github.com/rameerez/slugifiable/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fslugifiable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002108,"owners_count":26083307,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-09T02:00:07.460Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","gem","rails","ruby","ruby-on-rails","search-engine","search-engine-optimization","seo","seo-friendly","seo-optimization","slug","slug-generator"],"created_at":"2024-11-10T08:03:31.739Z","updated_at":"2026-02-19T23:03:08.832Z","avatar_url":"https://github.com/rameerez.png","language":"Ruby","readme":"# 🐌 `slugifiable` - Generate SEO-optimized URL slugs\n\n[![Gem Version](https://badge.fury.io/rb/slugifiable.svg)](https://badge.fury.io/rb/slugifiable) [![Build Status](https://github.com/rameerez/slugifiable/workflows/Tests/badge.svg)](https://github.com/rameerez/slugifiable/actions)\n\n\u003e [!TIP]\n\u003e **🚀 Ship your next Rails app 10x faster!** I've built **[RailsFast](https://railsfast.com/?ref=slugifiable)**, a production-ready Rails boilerplate template that comes with everything you need to launch a software business in days, not weeks. Go [check it out](https://railsfast.com/?ref=slugifiable)!\n\nRuby gem to automatically generate unique slugs for your Rails' model records, so you can expose SEO-friendly URLs.\n\nExample:\n```\nhttps://myapp.com/products/big-red-backpack-321678\n```\n\nWhere `big-red-backpack-321678` is the slug.\n\n`slugifiable` can generate:\n- Slugs like `\"big-red-backpack\"` or `\"big-red-backpack-321678\"`: unique, string-based slugs based on any attribute, such as `product.name`\n- Slugs like `\"d4735e3a265\"`: unique **hex string slugs**\n- Slugs like `321678`: unique **number-only slugs**\n\n## Why\n\nWhen building Rails apps, we usually need to expose _something_ in the URL to identify a record, like:\n```\nhttps://myapp.com/products/123\n```\n\nBut exposing IDs (like `123`) is not usually good practice. It's not SEO-friendly, it can give away how many records you have in the database, it could also be an attack vector, and it just feels off.\n\nIt would be much better to have a random-like string or number instead, while still remaining unique and identifiable:\n```\nhttps://myapp.com/products/d4735e3a265\n```\n\nOr better yet, use other instance attribute (like `product.name`) to build the slug:\n```\nhttps://myapp.com/products/big-red-backpack\n```\n\n`slugifiable` takes care of building all these kinds of slugs for you.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n```ruby\ngem 'slugifiable'\n```\n\nThen run `bundle install`.\n\nAfter installing the gem, add `include Slugifiable::Model` to any ActiveRecord model, like this:\n```ruby\nclass Product \u003c ApplicationRecord\n  include Slugifiable::Model # Adding this provides all the required slug-related methods to your model\nend\n```\n\nThat's it!\n\nThen you can, for example, get the slug for a product like this:\n```ruby\nProduct.first.slug\n=\u003e \"4e07408562b\"\n```\n\nYou can also define how to generate slugs:\n```ruby\nclass Product \u003c ApplicationRecord\n  include Slugifiable::Model\n  generate_slug_based_on :name\nend\n```\n\nAnd this will generate slugs based on your `Product` instance `name`, like:\n```ruby\nProduct.first.slug\n=\u003e \"big-red-backpack\"\n```\n\nIf your model has a `slug` attribute in the database, `slugifiable` will automatically generate a slug for that model upon instance creation, and save it to the DB.\n\n### Nullable vs NOT NULL Slug Columns\n\n**Nullable columns (default, simpler):**\n```ruby\n# migration\nadd_column :products, :slug, :string\nadd_index :products, :slug, unique: true\n\n# model\nclass Product \u003c ApplicationRecord\n  include Slugifiable::Model\n  generate_slug_based_on :name\nend\n```\n\n**NOT NULL columns (requires `before_validation` setup):**\n```ruby\n# migration\nadd_column :products, :slug, :string, null: false\nadd_index :products, :slug, unique: true\n\n# model\nclass Product \u003c ApplicationRecord\n  include Slugifiable::Model\n  generate_slug_based_on :name\n\n  before_validation :ensure_slug_present, on: :create\n\n  private\n\n  def ensure_slug_present\n    self.slug = compute_slug if slug.blank?\n  end\nend\n```\n\n\u003e [!NOTE]\n\u003e When using NOT NULL slug columns, `slugifiable` handles race conditions automatically. If two processes try to create records with the same slug simultaneously, the second one will retry with a new random suffix.\n\n\u003e [!CAUTION]\n\u003e For NOT NULL slug columns, retries re-run the `around_create` callback chain. If a slug collision happens, your `before_create` callbacks will run again. Keep `before_create` callbacks idempotent (or move side effects to `after_create`/background jobs).\n\n\u003e [!NOTE]\n\u003e Retry handling is DB-constraint-driven (`RecordNotUnique`), not validation-driven. A validation-layer race that raises `RecordInvalid` will bubble up.\n\n\u003e [!NOTE]\n\u003e Slug collision detection matches errors containing `slug`/`_on_slug`. If your index uses a custom name that does not include those patterns, retries will not trigger and the exception will bubble up.\n\nIf you're generating slugs based off the model `id`, you can also set a desired length:\n```ruby\nclass Product \u003c ApplicationRecord\n  include Slugifiable::Model\n  generate_slug_based_on :id, length: 6\nend\n```\n\nWhich would return something like:\n```ruby\nProduct.first.slug\n=\u003e \"6b86b2\"\n```\n\nMore details in the \"How to use\" section.\n\n## How to use\n\nSlugs should never change, so it's recommended you save your slugs to the database.\n\nTherefore, all models that include `Slugifiable::Model` should have a `slug` attribute that persists the slug in the database. If your model doesn't have a `slug` attribute yet, just run:\n```\nrails g migration addSlugTo\u003cMODEL_NAME\u003e slug:text\n```\n\nwhere `\u003cMODEL_NAME\u003e` is your model name in plural, and then run:\n```\nrails db:migrate\n```\n\nAnd your model should now have a `slug` attribute in the database.\n\nWhen a model has a `slug` attribute, `slugifiable` automatically generates a slug for that model upon instance creation, and saves it to the DB.\n\n`slugifiable` can also work without persisting slugs to the databse, though: you can always run `.slug`, and that will give you a valid, unique slug for your record.\n\n### Define how slugs are generated\n\nBy default, when you include `Slugifiable::Model`, slugs will be generated as a random-looking string based off the record `id` (SHA hash)\n\n`slugifiable` supports both `id` and `uuid`.\n\nThe default setting is:\n```ruby\ngenerate_slug_based_on id: :hex_string\n```\n\nWhich returns slugs like: `d4735e3a265`\n\nIf you don't like hex strings, you can get number-only slugs with:\n```ruby\ngenerate_slug_based_on id: :number\n```\n\nWhich will return slugs like: `321678` – nonconsecutive, nonincremental, not a total count.\n\nWhen you're generating obfuscated slugs (based on `id`), you can specify a desired slug length:\n```ruby\ngenerate_slug_based_on id: :number, length: 3\n```\n\nThe length should be a positive number between 1 and 64.\n\nIf instead of obfuscated slugs you want human-readable slugs, you can specify an attribute to base your slugs off of. For example:\n```ruby\ngenerate_slug_based_on :name\n```\n\nWill look for a `name` attribute in your instance, and use its value to generate the slug. So if you have a product like:\n```ruby\nProduct.first.name\n=\u003e \"Big Red Backpack\"\n```\n\nthen the slug will be computed as:\n```ruby\nProduct.first.slug\n=\u003e \"big-red-backpack\"\n```\n\nYou can also use instance methods to generate more complex slugs. This is useful when you need to combine multiple attributes:\n```ruby\nclass Event \u003c ApplicationRecord\n  include Slugifiable::Model\n  belongs_to :location\n  \n  generate_slug_based_on :title_with_location\n\n  # The method can return any string - slugifiable will handle the parameterization\n  def title_with_location\n    if location.present?\n      \"#{title} #{location.city} #{location.region}\"  # Returns raw string, slugifiable parameterizes it\n    else\n      title\n    end\n  end\nend\n```\n\nThis will generate slugs like:\n```ruby\nEvent.first.slug\n=\u003e \"my-awesome-event-new-york\"  # Automatically parameterized\n```\n\nThere may be collisions if two records share the same name – but slugs should be unique! To resolve this, when this happens, `slugifiable` will append a unique string at the end to make the slug unique:\n```ruby\nProduct.first.slug\n=\u003e \"big-red-backpack-321678\"\n```\n\n## Testing\n\nThe gem includes a comprehensive test suite that covers:\n\n- Default slug generation behavior\n- Attribute-based slug generation\n- Method-based slug generation (including private/protected methods)\n- Collision resolution and uniqueness handling\n- Special cases and edge conditions\n\nTo run the tests:\n\n```bash\nbundle install\nbundle exec rake test\n```\n\nThe test suite uses SQLite3 in-memory database and requires no additional setup.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/rameerez/slugifiable. Our code of conduct is: just be nice and make your mom proud of what you do and post online.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frameerez%2Fslugifiable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frameerez%2Fslugifiable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frameerez%2Fslugifiable/lists"}