pg_random_id 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|