snowflake_id 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +143 -0
- data/Rakefile +15 -0
- data/lib/generators/snowflake_id/install/install_generator.rb +25 -0
- data/lib/generators/snowflake_id/install/templates/install_snowflake_id.rb.erb +13 -0
- data/lib/snowflake_id/column_methods.rb +21 -0
- data/lib/snowflake_id/database_tasks.rb +40 -0
- data/lib/snowflake_id/generator.rb +188 -0
- data/lib/snowflake_id/railtie.rb +19 -0
- data/lib/snowflake_id/version.rb +3 -0
- data/lib/snowflake_id.rb +10 -0
- data/lib/tasks/snowflake_id_tasks.rake +4 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 35ccd9038bfa98f7542f483829f7c3d810636e9d9bd7c1bf26e3233fe1b1d287
|
4
|
+
data.tar.gz: 8569b7d8c6d63f406ed02c294006bfa065e63d1bc27e0931354f39f880ca487d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0b86cbebcaef643798a8d29ab5aafbcfe168f4e8233e438f5478e24710e7363ade49e661ba94eacdb4cf449af2bf005b645a91de3c4b6d3f087c490f52b6703
|
7
|
+
data.tar.gz: fc337bd840a6b65ad48d597378508e0fe4f95ed42bf0bcff16da3fe7fba9440922d86b8fc608749c65857f028e1e04be69a20f1ab120e63dc4e2a3ab9a50a24e
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Luiz Eduardo Kowalski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# SnowflakeId
|
2
|
+
|
3
|
+
A Rails plugin that provides Snowflake-like IDs for your ActiveRecord models with minimal configuration.
|
4
|
+
|
5
|
+
Snowflake IDs are 64-bit integers that contain:
|
6
|
+
- **48 bits** for millisecond-level timestamp
|
7
|
+
- **16 bits** for sequence data (includes hashed table name + secret salt + sequence number)
|
8
|
+
|
9
|
+
This ensures globally unique, time-sortable IDs that don't reveal the total count of records in your database.
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
- **Transparent** - Just use `t.snowflake` and it works automatically
|
14
|
+
- **Automatic database setup** - Hooks into `db:migrate` and `db:prepare` tasks to ensure everything is set up.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem "snowflake_id"
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
```bash
|
26
|
+
rails generate snowflake_id:install
|
27
|
+
```
|
28
|
+
|
29
|
+
## Quick Start
|
30
|
+
|
31
|
+
**That's it!** Just use `t.snowflake` in your migrations and everything works automatically.
|
32
|
+
|
33
|
+
### For Snowflake ID as primary key:
|
34
|
+
```ruby
|
35
|
+
class CreateUsers < ActiveRecord::Migration[8.0]
|
36
|
+
def change
|
37
|
+
create_table :users, id: false do |t|
|
38
|
+
t.snowflake :id, primary_key: true # Snowflake primary key
|
39
|
+
t.string :name
|
40
|
+
t.timestamps
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
**Note**: When using `t.snowflake :id` directly, Rails will complain about redefining the primary key. Always use `create_table :table_name, id: false` when you want a snowflake primary key.
|
47
|
+
|
48
|
+
### For additional snowflake columns (non-primary key):
|
49
|
+
```ruby
|
50
|
+
class CreatePosts < ActiveRecord::Migration[8.0]
|
51
|
+
def change
|
52
|
+
create_table :posts do |t|
|
53
|
+
t.string :title
|
54
|
+
t.text :content
|
55
|
+
t.snowflake :uid # Additional snowflake column
|
56
|
+
t.timestamps
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
### Generator Support
|
63
|
+
|
64
|
+
You can also use Snowflake helper in Rails generators:
|
65
|
+
|
66
|
+
```bash
|
67
|
+
# Generate a model with a snowflake field
|
68
|
+
rails generate model Post title:string uid:snowflake
|
69
|
+
|
70
|
+
# This will create a migration like:
|
71
|
+
# create_table :posts do |t|
|
72
|
+
# t.string :title
|
73
|
+
# t.snowflake :uid
|
74
|
+
# t.timestamps
|
75
|
+
# end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Working with Snowflake IDs
|
79
|
+
|
80
|
+
There's nothing else to be done at this point. `t.snowflake` columns will automatically get unique IDs on record creation, and they are just a `:bigint` column in the database.
|
81
|
+
At this point, you can use them like any other integer ID.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
user = User.create!(name: "Alice")
|
85
|
+
user.id # => 115198501587747344
|
86
|
+
|
87
|
+
# Convert ID back to timestamp
|
88
|
+
SnowflakeId::Generator.to_time(user.id)
|
89
|
+
# => 2024-12-25 10:15:42 UTC
|
90
|
+
|
91
|
+
# Generate ID for specific timestamp
|
92
|
+
SnowflakeId::Generator.at(1.hour.ago)
|
93
|
+
# => 1766651542000012345
|
94
|
+
```
|
95
|
+
|
96
|
+
### Database Integration
|
97
|
+
|
98
|
+
The gem automatically hooks into these Rails tasks:
|
99
|
+
- `db:migrate`
|
100
|
+
- `db:schema:load`
|
101
|
+
- `db:structure:load`
|
102
|
+
- `db:seed`
|
103
|
+
|
104
|
+
## Migration from Standard IDs
|
105
|
+
|
106
|
+
If you have existing models with standard Rails IDs, you'll need to run a migration to convert them to Snowflake IDs.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
execute("ALTER TABLE table_name ALTER COLUMN id SET DEFAULT timestamp_id('table_name')")
|
110
|
+
```
|
111
|
+
|
112
|
+
⚠️ **Warning**: This is a complex operation that may require downtime and careful planning.
|
113
|
+
|
114
|
+
## Requirements
|
115
|
+
|
116
|
+
- **Database**: PostgreSQL (uses PostgreSQL-specific functions)
|
117
|
+
- **Rails**: 7.0+ (may work with earlier versions)
|
118
|
+
- **Ruby**: 3.0+
|
119
|
+
|
120
|
+
## How it Works
|
121
|
+
|
122
|
+
1. **Function Creation**: Creates a PostgreSQL `timestamp_id()` function
|
123
|
+
2. **Sequence Management**: Auto-creates sequences for each table (`table_name_id_seq`)
|
124
|
+
3. **ID Generation**: Uses timestamp + hashed sequence for uniqueness
|
125
|
+
4. **Rails Integration**: Hooks into model lifecycle and database tasks
|
126
|
+
|
127
|
+
|
128
|
+
## Contributing
|
129
|
+
|
130
|
+
1. Fork the repository
|
131
|
+
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
132
|
+
3. Add tests for your changes
|
133
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
134
|
+
5. Push to the branch (`git push origin feature/my-new-feature`)
|
135
|
+
6. Create a Pull Request
|
136
|
+
|
137
|
+
## License
|
138
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
139
|
+
|
140
|
+
|
141
|
+
## Acknowledgements
|
142
|
+
|
143
|
+
The implementation of Snowflake-like ids was initially done by [Mastodon](https://github.com/mastodon/mastodon/blob/06803422da3794538cd9cd5c7ccd61a0694ef921/lib/mastodon/snowflake.rb)
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
4
|
+
load "rails/tasks/engine.rake"
|
5
|
+
|
6
|
+
require "bundler/gem_tasks"
|
7
|
+
require "rake/testtask"
|
8
|
+
|
9
|
+
task default: %i[test]
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << "test"
|
13
|
+
t.libs << "lib"
|
14
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
module SnowflakeId::Generators
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
include ActiveRecord::Generators::Migration
|
9
|
+
|
10
|
+
TEMPLATES = File.join(File.dirname(__FILE__), "templates")
|
11
|
+
source_paths << TEMPLATES
|
12
|
+
|
13
|
+
desc "Install SnowflakeId by creating a migration to setup the timestamp_id function"
|
14
|
+
|
15
|
+
def create_migration_file
|
16
|
+
migration_template "install_snowflake_id.rb.erb", File.join(db_migrate_path, "install_snowflake_id.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def migration_version
|
22
|
+
"[#{ActiveRecord::VERSION::STRING.to_f}]"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class InstallSnowflakeId < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def up
|
5
|
+
# Create the timestamp_id PostgreSQL function
|
6
|
+
SnowflakeId::Generator.define_timestamp_id
|
7
|
+
end
|
8
|
+
|
9
|
+
def down
|
10
|
+
# Remove the timestamp_id function
|
11
|
+
execute "DROP FUNCTION IF EXISTS timestamp_id(text)"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnowflakeId
|
4
|
+
module ColumnMethods
|
5
|
+
def snowflake(name, **options)
|
6
|
+
if name == :id && !options[:primary_key]
|
7
|
+
raise ArgumentError, "Cannot use t.snowflake :id directly. Use `create_table` with `id: false` and then t.snowflake :id, primary_key: true"
|
8
|
+
end
|
9
|
+
|
10
|
+
table_name = @name
|
11
|
+
|
12
|
+
unless table_name
|
13
|
+
raise ArgumentError, "Could not determine table name for snowflake column. Make sure you're using it within a create_table block."
|
14
|
+
end
|
15
|
+
|
16
|
+
options[:default] = -> { "timestamp_id('#{table_name}'::text)" }
|
17
|
+
|
18
|
+
column(name, :bigint, **options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Hook into Rails database tasks to ensure snowflake sequences exist
|
4
|
+
def ensure_snowflake_sequences
|
5
|
+
return unless defined?(ActiveRecord::Base)
|
6
|
+
|
7
|
+
begin
|
8
|
+
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
9
|
+
Rails.logger.debug "SnowflakeId: Ensure sequences exist for `timestamp_id` columns"
|
10
|
+
SnowflakeId::Generator.ensure_id_sequences_exist
|
11
|
+
end
|
12
|
+
rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished
|
13
|
+
Rails.logger.warn "SnowflakeId: Could not ensure sequences: #{e.message}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Enhance existing Rails database tasks by adding our hook to them
|
18
|
+
if Rake::Task.task_defined?("db:migrate")
|
19
|
+
Rake::Task["db:migrate"].enhance do
|
20
|
+
ensure_snowflake_sequences
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if Rake::Task.task_defined?("db:schema:load")
|
25
|
+
Rake::Task["db:schema:load"].enhance do
|
26
|
+
ensure_snowflake_sequences
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if Rake::Task.task_defined?("db:structure:load")
|
31
|
+
Rake::Task["db:structure:load"].enhance do
|
32
|
+
ensure_snowflake_sequences
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
if Rake::Task.task_defined?("db:seed")
|
37
|
+
Rake::Task["db:seed"].enhance do
|
38
|
+
ensure_snowflake_sequences
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copied from https://github.com/mastodon/mastodon/blob/06803422da3794538cd9cd5c7ccd61a0694ef921/lib/mastodon/snowflake.rb
|
4
|
+
|
5
|
+
module SnowflakeId
|
6
|
+
module Generator
|
7
|
+
DEFAULT_REGEX = /timestamp_id\('(?<seq_prefix>\w+)'/
|
8
|
+
|
9
|
+
class Callbacks
|
10
|
+
def self.around_create(record)
|
11
|
+
now = Time.now.utc
|
12
|
+
|
13
|
+
if record.created_at.nil? || record.created_at >= now || record.created_at == record.updated_at || record.override_timestamps
|
14
|
+
yield
|
15
|
+
else
|
16
|
+
record.id = SnowflakeId::Generator.at(record.created_at)
|
17
|
+
tries = 0
|
18
|
+
|
19
|
+
begin
|
20
|
+
yield
|
21
|
+
rescue ActiveRecord::RecordNotUnique
|
22
|
+
raise if tries > 100
|
23
|
+
|
24
|
+
tries += 1
|
25
|
+
record.id += rand(100)
|
26
|
+
|
27
|
+
retry
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
# Our ID will be composed of the following:
|
35
|
+
# 6 bytes (48 bits) of millisecond-level timestamp
|
36
|
+
# 2 bytes (16 bits) of sequence data
|
37
|
+
#
|
38
|
+
# The 'sequence data' is intended to be unique within a
|
39
|
+
# given millisecond, yet obscure the 'serial number' of
|
40
|
+
# this row.
|
41
|
+
#
|
42
|
+
# To do this, we hash the following data:
|
43
|
+
# * Table name (if provided, skipped if not)
|
44
|
+
# * Secret salt (should not be guessable)
|
45
|
+
# * Timestamp (again, millisecond-level granularity)
|
46
|
+
#
|
47
|
+
# We then take the first two bytes of that value, and add
|
48
|
+
# the lowest two bytes of the table ID sequence number
|
49
|
+
# (`table_name`_id_seq). This means that even if we insert
|
50
|
+
# two rows at the same millisecond, they will have
|
51
|
+
# distinct 'sequence data' portions.
|
52
|
+
#
|
53
|
+
# If this happens, and an attacker can see both such IDs,
|
54
|
+
# they can determine which of the two entries was inserted
|
55
|
+
# first, but not the total number of entries in the table
|
56
|
+
# (even mod 2**16).
|
57
|
+
#
|
58
|
+
# The table name is included in the hash to ensure that
|
59
|
+
# different tables derive separate sequence bases so rows
|
60
|
+
# inserted in the same millisecond in different tables do
|
61
|
+
# not reveal the table ID sequence number for one another.
|
62
|
+
#
|
63
|
+
# The secret salt is included in the hash to ensure that
|
64
|
+
# external users cannot derive the sequence base given the
|
65
|
+
# timestamp and table name, which would allow them to
|
66
|
+
# compute the table ID sequence number.
|
67
|
+
def define_timestamp_id
|
68
|
+
return if already_defined?
|
69
|
+
|
70
|
+
connection.execute(sanitized_timestamp_id_sql)
|
71
|
+
end
|
72
|
+
|
73
|
+
def ensure_id_sequences_exist
|
74
|
+
# Find tables using timestamp IDs.
|
75
|
+
connection.tables.each do |table|
|
76
|
+
ensure_id_sequences_exist_for(table)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def ensure_id_sequences_exist_for(table_name)
|
81
|
+
# We're only concerned with "id" columns.
|
82
|
+
id_col = connection.columns(table_name).find { |col| col.name == "id" }
|
83
|
+
return unless id_col
|
84
|
+
|
85
|
+
# And only those that are using timestamp_id.
|
86
|
+
data = DEFAULT_REGEX.match(id_col.default_function)
|
87
|
+
return unless data
|
88
|
+
|
89
|
+
seq_name = "#{data[:seq_prefix]}_id_seq"
|
90
|
+
|
91
|
+
# If we were on Postgres 9.5+, we could do CREATE SEQUENCE IF
|
92
|
+
# NOT EXISTS, but we can't depend on that. Instead, catch the
|
93
|
+
# possible exception and ignore it.
|
94
|
+
# Note that seq_name isn't a column name, but it's a
|
95
|
+
# relation, like a column, and follows the same quoting rules
|
96
|
+
# in Postgres.
|
97
|
+
connection.execute(<<~SQL)
|
98
|
+
DO $$
|
99
|
+
BEGIN
|
100
|
+
CREATE SEQUENCE #{connection.quote_column_name(seq_name)};
|
101
|
+
EXCEPTION WHEN duplicate_table THEN
|
102
|
+
-- Do nothing, we have the sequence already.
|
103
|
+
END
|
104
|
+
$$ LANGUAGE plpgsql;
|
105
|
+
SQL
|
106
|
+
rescue StandardError => e
|
107
|
+
Rails.logger.warn "SnowflakeId: Could not ensure sequence for #{table_name}: #{e.message}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def at(timestamp, with_random: true)
|
111
|
+
id = timestamp.to_i * 1000
|
112
|
+
id += rand(1000) if with_random
|
113
|
+
id <<= 16
|
114
|
+
id += rand(2**16) if with_random
|
115
|
+
id
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_time(id)
|
119
|
+
Time.at((id >> 16) / 1000).utc
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def already_defined?
|
125
|
+
connection.execute(<<~SQL.squish).values.first.first
|
126
|
+
SELECT EXISTS(
|
127
|
+
SELECT * FROM pg_proc WHERE proname = 'timestamp_id'
|
128
|
+
);
|
129
|
+
SQL
|
130
|
+
end
|
131
|
+
|
132
|
+
def sanitized_timestamp_id_sql
|
133
|
+
ActiveRecord::Base.sanitize_sql_array(timestamp_id_sql_array)
|
134
|
+
end
|
135
|
+
|
136
|
+
def timestamp_id_sql_array
|
137
|
+
[ timestamp_id_sql_string, { random_string: SecureRandom.hex(16) } ]
|
138
|
+
end
|
139
|
+
|
140
|
+
def timestamp_id_sql_string
|
141
|
+
<<~SQL
|
142
|
+
CREATE OR REPLACE FUNCTION timestamp_id(table_name text)
|
143
|
+
RETURNS bigint AS
|
144
|
+
$$
|
145
|
+
DECLARE
|
146
|
+
time_part bigint;
|
147
|
+
sequence_base bigint;
|
148
|
+
tail bigint;
|
149
|
+
BEGIN
|
150
|
+
time_part := (
|
151
|
+
-- Get the time in milliseconds
|
152
|
+
((date_part('epoch', now()) * 1000))::bigint
|
153
|
+
-- And shift it over two bytes
|
154
|
+
<< 16);
|
155
|
+
|
156
|
+
sequence_base := (
|
157
|
+
'x' ||
|
158
|
+
-- Take the first two bytes (four hex characters)
|
159
|
+
substr(
|
160
|
+
-- Of the MD5 hash of the data we documented
|
161
|
+
md5(table_name || :random_string || time_part::text),
|
162
|
+
1, 4
|
163
|
+
)
|
164
|
+
-- And turn it into a bigint
|
165
|
+
)::bit(16)::bigint;
|
166
|
+
|
167
|
+
-- Finally, add our sequence number to our base, and chop
|
168
|
+
-- it to the last two bytes
|
169
|
+
tail := (
|
170
|
+
(sequence_base + nextval(table_name || '_id_seq'))
|
171
|
+
& 65535);
|
172
|
+
|
173
|
+
-- Return the time part and the sequence part. OR appears
|
174
|
+
-- faster here than addition, but they're equivalent:
|
175
|
+
-- time_part has no trailing two bytes, and tail is only
|
176
|
+
-- the last two bytes.
|
177
|
+
RETURN time_part | tail;
|
178
|
+
END
|
179
|
+
$$ LANGUAGE plpgsql VOLATILE;
|
180
|
+
SQL
|
181
|
+
end
|
182
|
+
|
183
|
+
def connection
|
184
|
+
ActiveRecord::Base.connection
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SnowflakeId
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
initializer "snowflake_id.register_field_type" do
|
4
|
+
ActiveSupport.on_load(:active_record) do
|
5
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.prepend(SnowflakeId::ColumnMethods)
|
6
|
+
|
7
|
+
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
8
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:snowflake] = { name: "bigint" }
|
9
|
+
else
|
10
|
+
raise "SnowflakeId: Unsupported database adapter. Only PostgreSQL is supported."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
rake_tasks do
|
16
|
+
load "snowflake_id/database_tasks.rb"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/snowflake_id.rb
ADDED
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: snowflake_id
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luiz Eduardo Kowalski
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rails
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '7.2'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '7.2'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: zeitwerk
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.7'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.7'
|
40
|
+
description: A Rails plugin that provides a simple way to generate unique Snowflake
|
41
|
+
IDs for your ActiveRecord models.
|
42
|
+
email:
|
43
|
+
- luizeduardokowalski@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- MIT-LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- lib/generators/snowflake_id/install/install_generator.rb
|
52
|
+
- lib/generators/snowflake_id/install/templates/install_snowflake_id.rb.erb
|
53
|
+
- lib/snowflake_id.rb
|
54
|
+
- lib/snowflake_id/column_methods.rb
|
55
|
+
- lib/snowflake_id/database_tasks.rb
|
56
|
+
- lib/snowflake_id/generator.rb
|
57
|
+
- lib/snowflake_id/railtie.rb
|
58
|
+
- lib/snowflake_id/version.rb
|
59
|
+
- lib/tasks/snowflake_id_tasks.rake
|
60
|
+
homepage: https://github.com/luizkowalski/snowflake_id/
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata:
|
64
|
+
homepage_uri: https://github.com/luizkowalski/snowflake_id/
|
65
|
+
source_code_uri: https://github.com/luizkowalski/snowflake_id
|
66
|
+
changelog_uri: https://github.com/luizkowalski/snowflake_id/CHAGNES.md
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.2'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubygems_version: 3.7.2
|
82
|
+
specification_version: 4
|
83
|
+
summary: Generate Snowflake IDs in Rails models.
|
84
|
+
test_files: []
|