based_uuid 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +25 -0
- data/MIT-LICENSE +19 -0
- data/README.md +92 -0
- data/Rakefile +16 -0
- data/lib/based_uuid/base32_uuid.rb +48 -0
- data/lib/based_uuid/has_based_uuid.rb +57 -0
- data/lib/based_uuid/version.rb +3 -0
- data/lib/based_uuid.rb +40 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ea972425316d18c771fce81d203b5a5b9131542e5477ccc3ffa3efea68b3cf00
|
4
|
+
data.tar.gz: adef7fd3cd72c47a47db279c726cf85100f8e05da7e138513d3fa96d35901bca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5821c706b57958282dd0abb1e516cd2338b5df1dfce9f9032127f881c72bcf42c7eaed0fb2ffb420cfd2fba56f270f6ead84c0d07c13b6b11dbdf3c18e7d0f24
|
7
|
+
data.tar.gz: 5319f5038db91a4ab6286a126f5aeb003c0e0c204bc479cac76e2356a80997110b5b699567e50d8f57f66e34b225eaa2140644f868e7a4bae70fe83635f694d0
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.2
|
3
|
+
|
4
|
+
Style/StringLiterals:
|
5
|
+
Enabled: true
|
6
|
+
EnforcedStyle: double_quotes
|
7
|
+
|
8
|
+
Style/StringLiteralsInInterpolation:
|
9
|
+
Enabled: true
|
10
|
+
EnforcedStyle: double_quotes
|
11
|
+
|
12
|
+
Style/FrozenStringLiteralComment:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Style/Documentation:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/NumericPredicate:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Layout/LineLength:
|
22
|
+
Max: 120
|
23
|
+
|
24
|
+
Naming/PredicateName:
|
25
|
+
Enabled: false
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) Piotr Chmolowski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
14
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
15
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
16
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
17
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
18
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
19
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# BasedUUID: double-clickable, URL-friendly UUIDs for your Rails models
|
2
|
+
|
3
|
+
[![Build Status](https://github.com/pch/based_uuid/workflows/Tests/badge.svg)](https://github.com/pch/based_uuid/actions) [![Gem Version](https://badge.fury.io/rb/based_uuid.svg)](https://badge.fury.io/rb/based_uuid)
|
4
|
+
|
5
|
+
|
6
|
+
Generate “double-clickable”, URL-friendly UUIDs with (optional) prefixes:
|
7
|
+
|
8
|
+
```
|
9
|
+
user_4yoiald3wfbvpeu2sbsk7cssdi
|
10
|
+
acc_ejwqg7b3gvaphiylb25xq545tm
|
11
|
+
```
|
12
|
+
|
13
|
+
This gem works by encoding UUIDs into 26-character lowercase strings using [Crockford’s base32](https://www.crockford.com/base32.html) encoding. The optional prefix helps you identify the model it represents.
|
14
|
+
|
15
|
+
BasedUUID doesn’t affect how your ActiveRecord primary key UUIDs are stored in the database. Prefix and base32 encoding are only used for presentation.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your `Gemfile`:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem "based_uuid"
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
BasedUUID assumes that you have a [UUID primary key](https://guides.rubyonrails.org/v5.0/active_record_postgresql.html#uuid) in your ActiveRecord model.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class BlogPost < ApplicationRecord
|
31
|
+
has_based_uuid prefix: :bpo
|
32
|
+
end
|
33
|
+
|
34
|
+
post = BlogPost.last
|
35
|
+
post.based_uuid #=> bpo_3ah4veflijgy7de6bflk7k4ld4
|
36
|
+
post.based_uuid(prefix: false) #=> 3ah4veflijgy7de6bflk7k4ld4
|
37
|
+
```
|
38
|
+
|
39
|
+
### Lookup
|
40
|
+
|
41
|
+
BasedUUID includes a `find_by_based_uuid` model method to look up records:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
BlogPost.find_by_based_uuid("bpo_3ah4veflijgy7de6bflk7k4ld4")
|
45
|
+
|
46
|
+
# or without the prefix:
|
47
|
+
BlogPost.find_by_based_uuid("3ah4veflijgy7de6bflk7k4ld4")
|
48
|
+
|
49
|
+
# there’s also the bang version:
|
50
|
+
BlogPost.find_by_based_uuid!("3ah4veflijgy7de6bflk7k4ld4")
|
51
|
+
```
|
52
|
+
|
53
|
+
### Generic lookup
|
54
|
+
|
55
|
+
The gem provides a generic lookup method to help you find the correct model for the UUID, based on prefix:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
BasedUUID.find("bpo_3ah4veflijgy7de6bflk7k4ld4")
|
59
|
+
#=> #<BlogPost>
|
60
|
+
BasedUUID.find("acc_ejwqg7b3gvaphiylb25xq545tm")
|
61
|
+
#=> #<Account>
|
62
|
+
```
|
63
|
+
|
64
|
+
**⚠️ NOTE:** Rails lazy-loads models in the development environment, so this method won’t know about your models until you’ve referenced them at least once. If you’re using this method in a Rails console, you’ll need to run `BlogPost` (or any other model) before you can use it.
|
65
|
+
|
66
|
+
### BasedUUID as default URL identifiers
|
67
|
+
|
68
|
+
BasedUUID aims to be unintrusive and it doesn’t affect how Rails URLs are generated, so if you want to use it as default URL param, add this to your model:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
def to_param
|
72
|
+
based_uuid
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
* * *
|
77
|
+
|
78
|
+
## Development
|
79
|
+
|
80
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
81
|
+
|
82
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/pch/based_uuid.
|
87
|
+
|
88
|
+
### Credits
|
89
|
+
|
90
|
+
This gem is heavily inspired by [Stripe IDs](https://stripe.com/docs/api) and the [prefixed_ids](https://github.com/excid3/prefixed_ids/tree/master) gem by Chris Oliver.
|
91
|
+
|
92
|
+
Parts of the base32 encoding code are borrowed from the [ulid gem](https://github.com/rafaelsales/ulid) by Rafael Sales.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rubocop/rake_task"
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module BasedUUID
|
2
|
+
module Base32UUID
|
3
|
+
CROCKFORDS_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz".freeze
|
4
|
+
CHARACTER_MAP = CROCKFORDS_ALPHABET.bytes.freeze
|
5
|
+
|
6
|
+
ENCODED_LENGTH = 26
|
7
|
+
BITS_PER_CHAR = 5
|
8
|
+
ZERO = "0".ord
|
9
|
+
MASK = 0x1f
|
10
|
+
UUID_LENGTH = 32
|
11
|
+
|
12
|
+
UUID_VALIDATION_REGEXP = /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/
|
13
|
+
UUID_REGEXP = /\A(.{8})(.{4})(.{4})(.{4})(.{12})\z/
|
14
|
+
BASE32_VALIDATION_REGEXP = /\A[#{CROCKFORDS_ALPHABET}]{26}\z/
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def encode(uuid)
|
18
|
+
raise ArgumentError, "Invalid UUID" if uuid !~ UUID_VALIDATION_REGEXP
|
19
|
+
|
20
|
+
input = uuid.delete("-").to_i(16)
|
21
|
+
encoded = Array.new(ENCODED_LENGTH, ZERO)
|
22
|
+
i = ENCODED_LENGTH - 1
|
23
|
+
|
24
|
+
while input > 0
|
25
|
+
encoded[i] = CHARACTER_MAP[input & MASK]
|
26
|
+
input >>= BITS_PER_CHAR
|
27
|
+
i -= 1
|
28
|
+
end
|
29
|
+
|
30
|
+
encoded.pack("c*")
|
31
|
+
end
|
32
|
+
|
33
|
+
def decode(str)
|
34
|
+
raise ArgumentError, "Invalid base32 string" if str !~ BASE32_VALIDATION_REGEXP
|
35
|
+
|
36
|
+
decoded = 0
|
37
|
+
|
38
|
+
str.each_byte do |byte|
|
39
|
+
value = CHARACTER_MAP.index(byte)
|
40
|
+
decoded = (decoded << BITS_PER_CHAR) | value
|
41
|
+
end
|
42
|
+
|
43
|
+
uuid = decoded.to_s(16)
|
44
|
+
uuid.rjust(UUID_LENGTH, "0").scan(UUID_REGEXP).join("-")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "active_support/lazy_load_hooks"
|
2
|
+
require "active_support/concern"
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
require "active_record"
|
6
|
+
|
7
|
+
module BasedUUID
|
8
|
+
module HasBasedUUID
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :_based_uuid_prefix
|
13
|
+
end
|
14
|
+
|
15
|
+
class_methods do
|
16
|
+
def has_based_uuid(prefix: nil)
|
17
|
+
include ModelExtensions
|
18
|
+
|
19
|
+
self._based_uuid_prefix = prefix
|
20
|
+
BasedUUID.register_model_prefix(prefix, self) if prefix
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ModelExtensions
|
26
|
+
extend ActiveSupport::Concern
|
27
|
+
|
28
|
+
class_methods do
|
29
|
+
def find_by_based_uuid(token)
|
30
|
+
prefix, uuid_base32 = BasedUUID.split(token)
|
31
|
+
raise ArgumentError, "Invalid prefix" if prefix && prefix.to_sym != _based_uuid_prefix
|
32
|
+
|
33
|
+
find_by(primary_key => Base32UUID.decode(uuid_base32))
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_by_based_uuid!(token)
|
37
|
+
find_by_based_uuid(token) or raise ActiveRecord::RecordNotFound
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def based_uuid(prefix: true)
|
42
|
+
raise ArgumentError, "UUID is empty" if _primary_key_value.blank?
|
43
|
+
|
44
|
+
BasedUUID.based_uuid(uuid: _primary_key_value, prefix: prefix ? self.class._based_uuid_prefix : nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def _primary_key_value
|
50
|
+
self[self.class.primary_key]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
ActiveSupport.on_load :active_record do
|
56
|
+
include BasedUUID::HasBasedUUID
|
57
|
+
end
|
data/lib/based_uuid.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "active_support/core_ext/module/attribute_accessors"
|
2
|
+
|
3
|
+
require_relative "based_uuid/version"
|
4
|
+
require_relative "based_uuid/base32_uuid"
|
5
|
+
require_relative "based_uuid/has_based_uuid"
|
6
|
+
|
7
|
+
module BasedUUID
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
mattr_accessor :delimiter, default: "_"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def find(token)
|
14
|
+
prefix, = split(token)
|
15
|
+
registered_models.fetch(prefix.to_sym).find_by_based_uuid(token)
|
16
|
+
rescue KeyError
|
17
|
+
raise Error, "Unable to find model for `#{prefix}`. Registered prefixes: #{registered_models.keys.join(", ")}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def register_model_prefix(prefix, model)
|
21
|
+
registered_models[prefix] = model
|
22
|
+
end
|
23
|
+
|
24
|
+
def registered_models
|
25
|
+
@registered_models ||= {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def split(token)
|
29
|
+
prefix, _, uuid_base32 = token.to_s.rpartition(delimiter)
|
30
|
+
[prefix.presence, uuid_base32]
|
31
|
+
end
|
32
|
+
|
33
|
+
def based_uuid(uuid:, prefix:)
|
34
|
+
uuid_base32 = Base32UUID.encode(uuid)
|
35
|
+
return uuid_base32 unless prefix
|
36
|
+
|
37
|
+
"#{prefix}#{delimiter}#{uuid_base32}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: based_uuid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Piotr Chmolowski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-12-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- piotr@chmolowski.pl
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".rubocop.yml"
|
49
|
+
- MIT-LICENSE
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- lib/based_uuid.rb
|
53
|
+
- lib/based_uuid/base32_uuid.rb
|
54
|
+
- lib/based_uuid/has_based_uuid.rb
|
55
|
+
- lib/based_uuid/version.rb
|
56
|
+
homepage: https://github.com/pchm/based_uuid
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.2.0
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubygems_version: 3.4.10
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: URL-friendly, Base32-encoded UUIDs for Rails models
|
79
|
+
test_files: []
|