pg_random_id 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +88 -24
- data/lib/pg_random_id.rb +1 -4
- data/lib/pg_random_id/migrations.rb +43 -0
- data/lib/pg_random_id/version.rb +1 -1
- data/pg_random_id.gemspec +2 -2
- data/spec/helpers/active_record_helper.rb +10 -0
- data/spec/{migrations/active_record_spec.rb → helpers/migration_spec_helper.rb} +12 -18
- data/spec/helpers/sequel_helper.rb +24 -0
- data/spec/migrations_spec.rb +15 -0
- data/spec/spec_helper.rb +4 -0
- metadata +27 -7
- data/lib/pg_random_id/migrations/active_record.rb +0 -35
data/README.md
CHANGED
@@ -2,9 +2,19 @@
|
|
2
2
|
|
3
3
|
Allow usage of pseudo-random IDs in Postgresql databases.
|
4
4
|
Changes sequential surrogate ids (1, 2, 3...) into a pseudo-random
|
5
|
-
sequence of unique 30-bits nonnegative integer values (eg. 760280231, 110168588, 1029278017...)
|
5
|
+
sequence of unique 30-bits nonnegative integer values (eg. 760280231, 110168588, 1029278017...)
|
6
|
+
or 6-character human-friendly-ish strings (eg. kn5xx1, qy2kp8, e5f67z...).
|
6
7
|
|
7
|
-
|
8
|
+
Since surrogate IDs are often used in REST-ful URLs, this makes the addresses less revealing and harder to guess
|
9
|
+
(while preserving the straightforward mapping from URLs to database IDs):
|
10
|
+
- http://example.com/products/1 → http://example.com/products/134178313
|
11
|
+
- http://example.com/products/2 → http://example.com/products/121521131
|
12
|
+
- http://example.com/widgets/1 → http://example.com/widgets/2agc30
|
13
|
+
- http://example.com/widgets/2 → http://example.com/widgets/4zkabg
|
14
|
+
|
15
|
+
|
16
|
+
Although the code is 100% database-side, it has been packaged into Ruby functions plugging
|
17
|
+
into ActiveRecord and Sequel migrations for ease of use.
|
8
18
|
|
9
19
|
## Installation
|
10
20
|
|
@@ -20,49 +30,103 @@ Or install it yourself as:
|
|
20
30
|
|
21
31
|
$ gem install pg_random_id
|
22
32
|
|
23
|
-
##
|
33
|
+
## Synopsis
|
24
34
|
|
25
|
-
|
35
|
+
### ActiveRecord
|
26
36
|
|
27
|
-
|
37
|
+
```ruby
|
38
|
+
class InstallRandomId < ActiveRecord::Migration
|
39
|
+
def up
|
40
|
+
# install the necessary SQL functions in the DB
|
28
41
|
create_random_id_functions
|
29
|
-
|
42
|
+
end
|
43
|
+
end
|
30
44
|
|
31
|
-
|
45
|
+
class CreateProducts < ActiveRecord::Migration
|
46
|
+
def up
|
47
|
+
create_table :products do |t|
|
48
|
+
t.string :name
|
49
|
+
end
|
50
|
+
|
51
|
+
# make the table use random ids
|
52
|
+
random_id :products
|
53
|
+
end
|
54
|
+
end
|
32
55
|
|
33
|
-
|
34
|
-
class RandomizeIdsOnFoo < ActiveRecord::Migration
|
56
|
+
class RandomizeIdsOnWidgets < ActiveRecord::Migration
|
35
57
|
def up
|
36
|
-
|
37
|
-
|
58
|
+
# make ids on a previously created table
|
59
|
+
# 'widgets' random (using string ids)
|
60
|
+
random_str_id :widgets, :widget_id # you can specify id column name
|
38
61
|
end
|
39
62
|
end
|
40
63
|
```
|
41
64
|
|
42
|
-
|
43
|
-
similarly, the id column name defaults to :id.
|
44
|
-
This means that you can create a vanilla AR table with random ids
|
45
|
-
with the following simple migration:
|
65
|
+
### Sequel
|
46
66
|
|
47
67
|
```ruby
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
68
|
+
Sequel.migration do
|
69
|
+
up do
|
70
|
+
# install the necessary SQL functions in the DB
|
71
|
+
create_random_id_functions
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
Sequel.migration do
|
76
|
+
up do
|
77
|
+
create_table :products do
|
78
|
+
primary_key :id
|
79
|
+
String :name
|
52
80
|
end
|
81
|
+
|
82
|
+
# make the table use random ids
|
53
83
|
random_id :products
|
54
84
|
end
|
55
85
|
end
|
86
|
+
|
87
|
+
Sequel.migration do
|
88
|
+
up do
|
89
|
+
# make ids on a previously created table
|
90
|
+
# 'widgets' random (using string ids)
|
91
|
+
random_str_id :widgets, :widget_id # you can specify id column name
|
92
|
+
end
|
93
|
+
end
|
56
94
|
```
|
57
95
|
|
96
|
+
## Considerations
|
97
|
+
|
58
98
|
No model modification is necessary, just use the table as usual and it will simply work.
|
59
|
-
|
99
|
+
Each table will use its own unique sequence, chosen at random at migration time.
|
100
|
+
|
101
|
+
If you use `random_str_id` make sure to use a string type in
|
102
|
+
foreign key columns:
|
103
|
+
```ruby
|
104
|
+
class CreateContraptions < ActiveRecord::Migration
|
105
|
+
def up
|
106
|
+
create_table :contraptions do |t|
|
107
|
+
t.string :widget_id, limit: 6
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
```ruby
|
113
|
+
Sequel.migration do
|
114
|
+
up do
|
115
|
+
create_table :contraptions do
|
116
|
+
String :widget_id, size: 6
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
## Notes
|
60
123
|
|
61
|
-
|
124
|
+
The `random_id` function changes the default value of the ID column to a scrambled next sequence value.
|
125
|
+
The scrambling function is a simple Feistel network, with a variable parameter which is used to choose the sequence.
|
62
126
|
|
63
|
-
|
64
|
-
|
65
|
-
|
127
|
+
With `random_str_id` function, the column type is changed to character(6)
|
128
|
+
and the sequence is additionally base32-encoded
|
129
|
+
with [Crockford encoding](http://www.crockford.com/wrmg/base32.html).
|
66
130
|
|
67
131
|
## Contributing
|
68
132
|
|
data/lib/pg_random_id.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'pg_random_id/sql'
|
2
|
+
|
3
|
+
module PgRandomId
|
4
|
+
module Migrations
|
5
|
+
# Create in the database the functions
|
6
|
+
# necessary for this gem to work.
|
7
|
+
def create_random_id_functions
|
8
|
+
execute PgRandomId::Sql::install
|
9
|
+
end
|
10
|
+
|
11
|
+
# Apply a random id to a table.
|
12
|
+
# If you don't give a key, a random one will be generated.
|
13
|
+
# The ids will be based on sequence "#{table}_#{column}_seq".
|
14
|
+
# You need to make sure the table is empty; migrating existing records is not implemented.
|
15
|
+
def random_id table, column = :id, key = nil
|
16
|
+
execute PgRandomId::Sql::apply(table, column, key)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Apply a random string id to a table.
|
20
|
+
# Also changes the type of the id column to char(6).
|
21
|
+
# If you don't give a key, a random one will be generated.
|
22
|
+
# The ids will be based on sequence "#{table}_#{column}_seq",
|
23
|
+
# scrambled and base32-encoded.
|
24
|
+
# You need to make sure the table is empty; migrating existing records is not implemented.
|
25
|
+
def random_str_id table, column = :id, key = nil
|
26
|
+
execute PgRandomId::Sql::apply_str(table, column, key)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Install the migration functions for ActiveRecord
|
30
|
+
def self.install_active_record
|
31
|
+
require 'active_record/migration'
|
32
|
+
ActiveRecord::Migration.send :include, self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Install the migration functions for Sequel
|
36
|
+
def self.install_sequel
|
37
|
+
Sequel::Database.send :include, self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
PgRandomId::Migrations::install_active_record if defined? ActiveRecord
|
43
|
+
PgRandomId::Migrations::install_sequel if defined? Sequel
|
data/lib/pg_random_id/version.rb
CHANGED
data/pg_random_id.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = PgRandomId::VERSION
|
9
9
|
gem.authors = ["Rafał Rzepecki"]
|
10
10
|
gem.email = ["divided.mind@gmail.com"]
|
11
|
-
gem.description = %q{Easily use randomized
|
11
|
+
gem.description = %q{Easily use randomized keys instead of sequential values for your record surrogate ids.}
|
12
12
|
gem.summary = %q{Pseudo-random record ids in Postgres}
|
13
13
|
gem.homepage = "https://github.com/dividedmind/pg_random_id"
|
14
14
|
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
%w(activerecord rspec).each do |g|
|
20
|
+
%w(activerecord rspec sequel).each do |g|
|
21
21
|
gem.add_development_dependency g
|
22
22
|
end
|
23
23
|
end
|
@@ -1,9 +1,15 @@
|
|
1
1
|
shared_context 'active_record' do
|
2
|
+
require 'active_record'
|
3
|
+
|
2
4
|
before do
|
3
5
|
dburl = ENV['TEST_DATABASE_URL'] || 'postgres:///pg_random_id_test'
|
4
6
|
ActiveRecord::Base.establish_connection dburl
|
5
7
|
ActiveRecord::Base.connection.execute 'BEGIN;'
|
6
8
|
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
ActiveRecord::Migration.verbose = false
|
12
|
+
end
|
7
13
|
|
8
14
|
after do
|
9
15
|
ActiveRecord::Base.connection.execute 'ROLLBACK;'
|
@@ -16,4 +22,8 @@ shared_context 'active_record' do
|
|
16
22
|
def execute code
|
17
23
|
ActiveRecord::Base.connection.select_one(code)
|
18
24
|
end
|
25
|
+
|
26
|
+
def create_table *a
|
27
|
+
migration.create_table *a
|
28
|
+
end
|
19
29
|
end
|
@@ -1,12 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'active_record'
|
4
|
-
require 'pg_random_id/migrations/active_record'
|
5
|
-
|
6
|
-
describe PgRandomId::Migrations::ActiveRecord do
|
7
|
-
include_context 'active_record'
|
1
|
+
shared_context 'test_migration' do
|
8
2
|
describe '#create_random_id_functions' do
|
9
|
-
it "installs the
|
3
|
+
it "installs the functions" do
|
10
4
|
migration.create_random_id_functions
|
11
5
|
|
12
6
|
execute("SELECT 1 FROM pg_proc WHERE proname = 'crockford'").should be
|
@@ -17,37 +11,37 @@ describe PgRandomId::Migrations::ActiveRecord do
|
|
17
11
|
describe '#random_id' do
|
18
12
|
it "changes the default value" do
|
19
13
|
migration.create_random_id_functions
|
20
|
-
|
14
|
+
create_table :foo
|
21
15
|
migration.random_id :foo
|
22
16
|
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%pri_scramble%'").should be
|
23
17
|
end
|
24
18
|
|
25
19
|
it "creates a few values without error" do
|
26
20
|
migration.create_random_id_functions
|
27
|
-
|
21
|
+
create_table :foo
|
28
22
|
migration.random_id :foo
|
29
23
|
10.times do
|
30
24
|
expect {
|
31
|
-
|
25
|
+
execute('INSERT INTO foo VALUES(default)')
|
32
26
|
}.to_not raise_error
|
33
27
|
end
|
34
|
-
execute("SELECT COUNT(*) FROM foo")[
|
28
|
+
execute("SELECT COUNT(*) FROM foo").first[1].to_i.should == 10
|
35
29
|
end
|
36
30
|
end
|
37
31
|
|
38
32
|
describe '#random_str_id' do
|
39
33
|
it "changes the default value" do
|
40
34
|
migration.create_random_id_functions
|
41
|
-
|
35
|
+
create_table :foo
|
42
36
|
migration.random_str_id :foo
|
43
37
|
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%pri_scramble%'").should be
|
44
38
|
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%crockford%'").should be
|
45
|
-
execute("INSERT INTO foo VALUES (DEFAULT) RETURNING id;")[
|
39
|
+
execute("INSERT INTO foo VALUES (DEFAULT) RETURNING id;").first[1].should_not == '1'
|
46
40
|
end
|
47
41
|
|
48
42
|
it "changes the type" do
|
49
43
|
migration.create_random_id_functions
|
50
|
-
|
44
|
+
create_table :foo
|
51
45
|
migration.random_str_id :foo
|
52
46
|
execute("""
|
53
47
|
SELECT typname FROM pg_attribute, pg_type
|
@@ -60,14 +54,14 @@ describe PgRandomId::Migrations::ActiveRecord do
|
|
60
54
|
|
61
55
|
it "creates a few values without error" do
|
62
56
|
migration.create_random_id_functions
|
63
|
-
|
57
|
+
create_table :foo
|
64
58
|
migration.random_str_id :foo
|
65
59
|
10.times do
|
66
60
|
expect {
|
67
|
-
|
61
|
+
execute('INSERT INTO foo VALUES(default)')
|
68
62
|
}.to_not raise_error
|
69
63
|
end
|
70
|
-
execute("SELECT COUNT(*) FROM foo")[
|
64
|
+
execute("SELECT COUNT(*) FROM foo").first[1].to_i.should == 10
|
71
65
|
end
|
72
66
|
end
|
73
67
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
shared_context 'sequel' do
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
let(:dburl) { ENV['TEST_DATABASE_URL'] || 'postgres:///pg_random_id_test' }
|
5
|
+
let(:db) { Sequel::connect dburl }
|
6
|
+
|
7
|
+
around do |example|
|
8
|
+
db.transaction rollback: :always do
|
9
|
+
example.run
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:migration) { db }
|
14
|
+
|
15
|
+
def execute code
|
16
|
+
db[code].first
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_table *a
|
20
|
+
db.create_table *a do
|
21
|
+
primary_key :id
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PgRandomId::Migrations do
|
4
|
+
context "with ActiveRecord" do
|
5
|
+
include_context 'active_record'
|
6
|
+
PgRandomId::Migrations.install_active_record
|
7
|
+
include_context 'test_migration'
|
8
|
+
end
|
9
|
+
|
10
|
+
context "with Sequel" do
|
11
|
+
include_context 'sequel'
|
12
|
+
PgRandomId::Migrations.install_sequel
|
13
|
+
include_context 'test_migration'
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_random_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -43,8 +43,24 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
-
|
47
|
-
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sequel
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Easily use randomized keys instead of sequential values for your record
|
63
|
+
surrogate ids.
|
48
64
|
email:
|
49
65
|
- divided.mind@gmail.com
|
50
66
|
executables: []
|
@@ -57,7 +73,7 @@ files:
|
|
57
73
|
- README.md
|
58
74
|
- Rakefile
|
59
75
|
- lib/pg_random_id.rb
|
60
|
-
- lib/pg_random_id/migrations
|
76
|
+
- lib/pg_random_id/migrations.rb
|
61
77
|
- lib/pg_random_id/sql.rb
|
62
78
|
- lib/pg_random_id/sql/crockford-pure.sql
|
63
79
|
- lib/pg_random_id/sql/crockford.sql
|
@@ -65,7 +81,9 @@ files:
|
|
65
81
|
- lib/pg_random_id/version.rb
|
66
82
|
- pg_random_id.gemspec
|
67
83
|
- spec/helpers/active_record_helper.rb
|
68
|
-
- spec/
|
84
|
+
- spec/helpers/migration_spec_helper.rb
|
85
|
+
- spec/helpers/sequel_helper.rb
|
86
|
+
- spec/migrations_spec.rb
|
69
87
|
- spec/spec_helper.rb
|
70
88
|
- spec/sql/crockford_spec.rb
|
71
89
|
- spec/sql/scramble_spec.rb
|
@@ -95,7 +113,9 @@ specification_version: 3
|
|
95
113
|
summary: Pseudo-random record ids in Postgres
|
96
114
|
test_files:
|
97
115
|
- spec/helpers/active_record_helper.rb
|
98
|
-
- spec/
|
116
|
+
- spec/helpers/migration_spec_helper.rb
|
117
|
+
- spec/helpers/sequel_helper.rb
|
118
|
+
- spec/migrations_spec.rb
|
99
119
|
- spec/spec_helper.rb
|
100
120
|
- spec/sql/crockford_spec.rb
|
101
121
|
- spec/sql/scramble_spec.rb
|
@@ -1,35 +0,0 @@
|
|
1
|
-
require 'active_record'
|
2
|
-
require 'active_record/migration'
|
3
|
-
require 'pg_random_id/sql'
|
4
|
-
|
5
|
-
module PgRandomId
|
6
|
-
module Migrations
|
7
|
-
module ActiveRecord
|
8
|
-
# Create in the database the function (pri_scramble(bigint, bigint))
|
9
|
-
# necessary for this gem to work.
|
10
|
-
def create_random_id_functions
|
11
|
-
execute PgRandomId::Sql::install
|
12
|
-
end
|
13
|
-
|
14
|
-
# Apply a random id to a table.
|
15
|
-
# If you don't give a key, a random one will be generated.
|
16
|
-
# The ids will be based on sequence "#{table}_#{column}_seq".
|
17
|
-
# You need to make sure the table is empty; migrating existing records is not implemented.
|
18
|
-
def random_id table, column = :id, key = nil
|
19
|
-
execute PgRandomId::Sql::apply(table, column, key)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Apply a random string id to a table.
|
23
|
-
# Also changes the type of the id column to char(6).
|
24
|
-
# If you don't give a key, a random one will be generated.
|
25
|
-
# The ids will be based on sequence "#{table}_#{column}_seq",
|
26
|
-
# scrambled and base32-encoded.
|
27
|
-
# You need to make sure the table is empty; migrating existing records is not implemented.
|
28
|
-
def random_str_id table, column = :id, key = nil
|
29
|
-
execute PgRandomId::Sql::apply_str(table, column, key)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
ActiveRecord::Migration.send :include, PgRandomId::Migrations::ActiveRecord
|