{"id":13878424,"url":"https://github.com/derrickreimer/sequenced","last_synced_at":"2025-04-05T09:09:36.325Z","repository":{"id":2500460,"uuid":"3475088","full_name":"derrickreimer/sequenced","owner":"derrickreimer","description":"Generate scoped sequential IDs for ActiveRecord models","archived":false,"fork":false,"pushed_at":"2022-11-02T00:28:48.000Z","size":504,"stargazers_count":406,"open_issues_count":6,"forks_count":64,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-30T00:37:16.443Z","etag":null,"topics":["activerecord","ruby"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/sequenced","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/derrickreimer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-02-18T00:13:52.000Z","updated_at":"2024-10-28T18:25:04.000Z","dependencies_parsed_at":"2022-08-20T06:11:06.805Z","dependency_job_id":null,"html_url":"https://github.com/derrickreimer/sequenced","commit_stats":null,"previous_names":["djreimer/sequenced"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derrickreimer%2Fsequenced","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derrickreimer%2Fsequenced/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derrickreimer%2Fsequenced/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derrickreimer%2Fsequenced/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/derrickreimer","download_url":"https://codeload.github.com/derrickreimer/sequenced/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247312082,"owners_count":20918344,"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","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","ruby"],"created_at":"2024-08-06T08:01:49.205Z","updated_at":"2025-04-05T09:09:36.297Z","avatar_url":"https://github.com/derrickreimer.png","language":"Ruby","readme":"# Sequenced\n\n[![.github/workflows/ci.yml](https://github.com/derrickreimer/sequenced/actions/workflows/ci.yml/badge.svg)](https://github.com/derrickreimer/sequenced/actions/workflows/ci.yml)\n[![Code Climate](https://codeclimate.com/github/djreimer/sequenced.svg)](https://codeclimate.com/github/djreimer/sequenced)\n[![Gem Version](https://badge.fury.io/rb/sequenced.svg)](http://badge.fury.io/rb/sequenced)\n\nSequenced is a simple gem that generates scoped sequential IDs for\nActiveRecord models. This gem provides an `acts_as_sequenced` macro that\nautomatically assigns a unique, sequential ID to each record. The sequential ID is\nnot a replacement for the database primary key, but rather adds another way to\nretrieve the object without exposing the primary key.\n\n## Purpose\n\nIt's generally a bad practice to expose your primary keys to the world\nin your URLs. However, it is often appropriate to number objects in sequence\n(in the context of a parent object).\n\nFor example, given a Question model that has many Answers, it makes sense\nto number answers sequentially for each individual question. You can achieve\nthis with Sequenced in one line of code:\n\n```ruby\nclass Question \u003c ActiveRecord::Base\n  has_many :answers\nend\n\nclass Answer \u003c ActiveRecord::Base\n  belongs_to :question\n  acts_as_sequenced scope: :question_id\nend\n```\n\n## Installation\n\nAdd the gem to your Gemfile:\n\n    gem 'sequenced'\n\nInstall the gem with bundler:\n\n    bundle install\n\n## Usage\n\nTo add a sequential ID to a model, first add an integer column called\n`sequential_id` to the model (or you many name the column anything you\nlike and override the default). For example:\n\n    rails generate migration add_sequential_id_to_answers sequential_id:integer\n    rake db:migrate\n\nThen, call the `acts_as_sequenced` macro in your model class:\n\n```ruby\nclass Answer \u003c ActiveRecord::Base\n  belongs_to :question\n  acts_as_sequenced scope: :question_id\nend\n```\n\nThe `scope` option can be any attribute, but will typically be the foreign\nkey of an associated parent object. You can even scope by multiple columns\nfor polymorphic relationships:\n\n```ruby\nclass Answer \u003c ActiveRecord::Base\n  belongs_to :questionable, :polymorphic =\u003e true\n  acts_as_sequenced scope: [:questionable_id, :questionable_type]\nend\n```\n\nMultiple sequences can be defined by using the macro multiple times:\n\n```ruby\nclass Answer \u003c ActiveRecord::Base\n  belongs_to :account\n  belongs_to :question\n\n  acts_as_sequenced column: :question_answer_number, scope: :question_id\n  acts_as_sequenced column: :account_answer_number, scope: :account_id\nend\n```\n\n## Schema and data integrity\n\n**This gem is only concurrent-safe for PostgreSQL databases**. For other database systems, unexpected behavior may occur if you attempt to create records concurrently.\n\nYou can mitigate this somewhat by applying a unique index to your sequential ID column (or a multicolumn unique index on sequential ID and scope columns, if you are using scopes). This will ensure that you can never have duplicate sequential IDs within a scope, causing concurrent updates to instead raise a uniqueness error at the database-level.\n\nIt is also a good idea to apply a not-null constraint to your sequential ID column as well if you never intend to skip it.\n\nHere is an example migration for a model that has a `sequential_id` scoped to a `burrow_id`:\n\n```ruby\n# app/db/migrations/20151120190645_create_badgers.rb\nclass CreateBadgers \u003c ActiveRecord::Migration\n  def change\n    create_table :badgers do |t|\n      t.integer :sequential_id, null: false\n      t.integer :burrow_id\n    end\n\n    add_index :badgers, [:sequential_id, :burrow_id], unique: true\n  end\nend\n```\n\nIf you are adding a sequenced column to an existing table, you need to account for that in your migration.\n\nHere is an example migration that adds and sets the `sequential_id` column based on the current database records:\n```ruby\n# app/db/migrations/20151120190645_add_sequental_id_to_badgers.rb\nclass AddSequentalIdToBadgers \u003c ActiveRecord::Migration\n  add_column :badgers, :sequential_id, :integer\n\n  execute \u003c\u003c~SQL\n    UPDATE badgers\n    SET sequential_id = old_badgers.next_sequential_id\n    FROM (\n      SELECT id, ROW_NUMBER()\n      OVER(\n        PARTITION BY burrow_id\n        ORDER BY id\n      ) AS next_sequential_id\n      FROM badgers\n    ) old_badgers\n    WHERE badgers.id = old_badgers.id\n  SQL\n\n  change_column :badgers, :sequential_id, :integer, null: false\n  add_index :badgers, [:sequential_id, :burrow_id], unique: true\nend\n```\n\n## Configuration\n\n### Overriding the default sequential ID column\n\nBy default, Sequenced uses the `sequential_id` column and assumes it already\nexists. If you wish to store the sequential ID in different integer column,\nsimply specify the column name with the `column` option:\n\n```ruby\nacts_as_sequenced scope: :question_id, column: :my_sequential_id\n```\n\n### Starting the sequence at a specific number\n\nBy default, Sequenced begins sequences with 1. To start at a different\ninteger, simply set the `start_at` option:\n\n```ruby\nacts_as_sequenced start_at: 1000\n```\n\nYou may also pass a lambda to the `start_at` option:\n\n```ruby\nacts_as_sequenced start_at: lambda { |r| r.computed_start_value }\n```\n\n### Indexing the sequential ID column\n\nFor optimal performance, it's a good idea to index the sequential ID column\non sequenced models.\n\n### Skipping sequential ID generation\n\nIf you'd like to skip generating a sequential ID under certain conditions,\nyou may pass a lambda to the `skip` option:\n\n```ruby\nacts_as_sequenced skip: lambda { |r| r.score == 0 }\n```\n\n## Example\n\nSuppose you have a question model that has many answers. This example\ndemonstrates how to use Sequenced to enable access to the nested answer\nresource via its sequential ID.\n\n```ruby\n# app/models/question.rb\nclass Question \u003c ActiveRecord::Base\n  has_many :answers\nend\n\n# app/models/answer.rb\nclass Answer \u003c ActiveRecord::Base\n  belongs_to :question\n  acts_as_sequenced scope: :question_id\n\n  # Automatically use the sequential ID in URLs\n  def to_param\n    self.sequential_id.to_s\n  end\nend\n\n# config/routes.rb\nresources :questions do\n  resources :answers\nend\n\n# app/controllers/answers_controller.rb\nclass AnswersController \u003c ApplicationController\n  def show\n    @question = Question.find(params[:question_id])\n    @answer = @question.answers.find_by(sequential_id: params[:id])\n  end\nend\n```\n\nNow, answers are accessible via their sequential IDs:\n\n    http://example.com/questions/5/answers/1  # Good\n\ninstead of by their primary keys:\n\n    http://example.com/questions/5/answer/32454  # Bad\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Added some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderrickreimer%2Fsequenced","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fderrickreimer%2Fsequenced","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderrickreimer%2Fsequenced/lists"}