based_uuid 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ module BasedUUID
2
+ VERSION = "0.5.0".freeze
3
+ 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: []