File: README.md

package info (click to toggle)
ruby-sequenced 3.1.1-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 3,880 kB
  • sloc: ruby: 696; makefile: 3
file content (201 lines) | stat: -rw-r--r-- 5,894 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# Sequenced

[![Build Status](https://travis-ci.org/djreimer/sequenced.png)](https://travis-ci.org/djreimer/sequenced)
[![Code Climate](https://codeclimate.com/github/djreimer/sequenced.png)](https://codeclimate.com/github/djreimer/sequenced)
[![Gem Version](https://badge.fury.io/rb/sequenced.png)](http://badge.fury.io/rb/sequenced)

Sequenced is a simple gem that generates scoped sequential IDs for
ActiveRecord models. This gem provides an `acts_as_sequenced` macro that
automatically assigns a unique, sequential ID to each record. The sequential ID is
not a replacement for the database primary key, but rather adds another way to
retrieve the object without exposing the primary key.

## Purpose

It's generally a bad practice to expose your primary keys to the world
in your URLs. However, it is often appropriate to number objects in sequence
(in the context of a parent object).

For example, given a Question model that has many Answers, it makes sense
to number answers sequentially for each individual question. You can achieve
this with Sequenced in one line of code:

```ruby
class Question < ActiveRecord::Base
  has_many :answers
end

class Answer < ActiveRecord::Base
  belongs_to :question
  acts_as_sequenced scope: :question_id
end
```

## Installation

Add the gem to your Gemfile:

    gem 'sequenced'

Install the gem with bundler:

    bundle install

## Usage

To add a sequential ID to a model, first add an integer column called
`sequential_id` to the model (or you many name the column anything you
like and override the default). For example:

    rails generate migration add_sequential_id_to_answers sequential_id:integer
    rake db:migrate

Then, call the `acts_as_sequenced` macro in your model class:

```ruby
class Answer < ActiveRecord::Base
  belongs_to :question
  acts_as_sequenced scope: :question_id
end
```

The `scope` option can be any attribute, but will typically be the foreign
key of an associated parent object. You can even scope by multiple columns
for polymorphic relationships:

```ruby
class Answer < ActiveRecord::Base
  belongs_to :questionable, :polymorphic => true
  acts_as_sequenced scope: [:questionable_id, :questionable_type]
end
```

Multiple sequences can be defined by using the macro multiple times:

```ruby
class Answer < ActiveRecord::Base
  belongs_to :account
  belongs_to :question

  acts_as_sequenced column: :question_answer_number, scope: :question_id
  acts_as_sequenced column: :account_answer_number, scope: :account_id
end
```

## Schema and data integrity

**This gem is only concurrent-safe for PostgreSQL databases**. For other database systems, unexpected behavior may occur if you attempt to create records concurrently.

You 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.

It 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.

Here is an example migration for a model that has a `sequential_id` scoped to a `burrow_id`:

```ruby
# app/db/migrations/20151120190645_create_badgers.rb
class CreateBadgers < ActiveRecord::Migration
  def change
    create_table :badgers do |t|
      t.integer :sequential_id, null: false
      t.integer :burrow_id
    end

    add_index :badgers, [:sequential_id, :burrow_id], unique: true
  end
end
```

## Configuration

### Overriding the default sequential ID column

By default, Sequenced uses the `sequential_id` column and assumes it already
exists. If you wish to store the sequential ID in different integer column,
simply specify the column name with the `column` option:

```ruby
acts_as_sequenced scope: :question_id, column: :my_sequential_id
```

### Starting the sequence at a specific number

By default, Sequenced begins sequences with 1. To start at a different
integer, simply set the `start_at` option:

```ruby
acts_as_sequenced start_at: 1000
```

You may also pass a lambda to the `start_at` option:

```ruby
acts_as_sequenced start_at: lambda { |r| r.computed_start_value }
```

### Indexing the sequential ID column

For optimal performance, it's a good idea to index the sequential ID column
on sequenced models.

### Skipping sequential ID generation

If you'd like to skip generating a sequential ID under certain conditions,
you may pass a lambda to the `skip` option:

```ruby
acts_as_sequenced skip: lambda { |r| r.score == 0 }
```

## Example

Suppose you have a question model that has many answers. This example
demonstrates how to use Sequenced to enable access to the nested answer
resource via its sequential ID.

```ruby
# app/models/question.rb
class Question < ActiveRecord::Base
  has_many :answers
end

# app/models/answer.rb
class Answer < ActiveRecord::Base
  belongs_to :question
  acts_as_sequenced scope: :question_id

  # Automatically use the sequential ID in URLs
  def to_param
    self.sequential_id
  end
end

# config/routes.rb
resources :questions do
  resources :answers
end

# app/controllers/answers_controller.rb
class AnswersController < ApplicationController
  def show
    @question = Question.find(params[:question_id])
    @answer = @question.answers.find_by(sequential_id: params[:id])
  end
end
```

Now, answers are accessible via their sequential IDs:

    http://example.com/questions/5/answers/1  # Good

instead of by their primary keys:

    http://example.com/questions/5/answer/32454  # Bad

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Added some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request