acts_as_having_string_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 +113 -0
- data/Rakefile +34 -0
- data/lib/acts_as_having_string_id/railtie.rb +7 -0
- data/lib/acts_as_having_string_id/string_id.rb +24 -0
- data/lib/acts_as_having_string_id/tea.rb +117 -0
- data/lib/acts_as_having_string_id/version.rb +3 -0
- data/lib/acts_as_having_string_id.rb +28 -0
- data/lib/tasks/acts_as_having_string_id_tasks.rake +4 -0
- metadata +101 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 3ebaa9731f8d0e4405bfc9a05a7a9b9ff0dd9e31
|
|
4
|
+
data.tar.gz: ca0d9fce70599e3b82bdaf3c7321a669669e4dfd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e625c3f0785cda0b1b1a913992fef3ead5751546fb8590bfdea81ad8247ce133b9f4f414339629b72a3019f2a1feca900533ee54768eb01c479274708cc12678
|
|
7
|
+
data.tar.gz: 20a95e36d732d027d009cc11cbbc66c35588d58d7ca7fc4ea874339c1a3d61f75512705b4182baf17045a457f8733010101e72dce9ed20cc9fc4dcf9ba17ee2c
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2016 Magnus Hult
|
|
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,113 @@
|
|
|
1
|
+
# ActsAsHavingStringId
|
|
2
|
+
A Rails plugin for exposing non-sequential (Youtube-like) string IDs instead of the sequential integer IDs provided by Rails.
|
|
3
|
+
|
|
4
|
+
Before, your API may look like
|
|
5
|
+
|
|
6
|
+
GET /users/123
|
|
7
|
+
{
|
|
8
|
+
"id": 123,
|
|
9
|
+
"name": "Alice O'User"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
After
|
|
13
|
+
|
|
14
|
+
GET /users/9w63Hubh4oL
|
|
15
|
+
{
|
|
16
|
+
"id": "9w63Hubh4oL",
|
|
17
|
+
"name": "Alice O'User"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Exposing sequential integer IDs has several drawbacks:
|
|
21
|
+
|
|
22
|
+
* Javascript has a 53-bit limit for integers (see https://dev.twitter.com/overview/api/twitter-ids-json-and-snowflake), which is a problem if you have large IDs
|
|
23
|
+
* Perhaps you don't want objects to be easily enumerable, even if they're public (if you know about http://example.com/documents/104, it's way too easy to find document 105)
|
|
24
|
+
* Sequential IDs make it easy to know how much usage your product gets (if my newly created user is http://example.com/users/1337, your product probably has 1,337 users)
|
|
25
|
+
|
|
26
|
+
Rails makes heavy use of sequential integer IDs internally, but there's no need of exposing them. `ActsAsHavingStringId` provides an alternative string representation of your IDs. This representation is
|
|
27
|
+
|
|
28
|
+
base62(tea(id, md5(ModelClass.name + Rails.application.secrets.string_id_key)))
|
|
29
|
+
|
|
30
|
+
The representation looks something like "E0znqip4mRA".
|
|
31
|
+
|
|
32
|
+
`tea` above is the "New variant" of the [Tiny Encryption Algorithm](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm). You should probably not consider your id to be forever secret, but it should be pretty hard to figure out from the string representation.
|
|
33
|
+
|
|
34
|
+
Your controllers will continue to work without modification, but will start to accept string IDs. So if http://example.com/orders/104 worked before, something like http://example.com/orders/E0znqip4mRA should magically work.
|
|
35
|
+
|
|
36
|
+
You do however need to take care never to expose the `id` member of your models. Instead, use `id_string`.
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
First, set up your `secrets.yml`:
|
|
40
|
+
|
|
41
|
+
development:
|
|
42
|
+
string_id_key: notverysecret
|
|
43
|
+
|
|
44
|
+
test:
|
|
45
|
+
string_id_key: notverysecreteither
|
|
46
|
+
|
|
47
|
+
production:
|
|
48
|
+
string_id_key: <%= ENV["STRING_ID_KEY"] %>
|
|
49
|
+
|
|
50
|
+
Then, call the method in your model class:
|
|
51
|
+
|
|
52
|
+
class MyModel < ApplicationRecord
|
|
53
|
+
acts_as_having_string_id
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
The string representation is now available as `id_string` on your model object. As an example:
|
|
57
|
+
|
|
58
|
+
> m = MyModel.create!
|
|
59
|
+
> m.id
|
|
60
|
+
=> 1
|
|
61
|
+
> m.id_string
|
|
62
|
+
=> "7EajpSfdWIf"
|
|
63
|
+
|
|
64
|
+
All ActiveRecord functions will also accept the string representation as input:
|
|
65
|
+
|
|
66
|
+
> MyModel.find("7EajpSfdWIf")
|
|
67
|
+
=> #<MyModel id: 1, created_at: "2016-08-31 13:27:02", updated_at: "2016-08-31 13:27:02">
|
|
68
|
+
> MyModel.where(id: "7EajpSfdWIf")
|
|
69
|
+
=> #<ActiveRecord::Relation [#<MyModel id: 1, created_at: "2016-08-31 13:27:02", updated_at: "2016-08-31 13:27:02">]>
|
|
70
|
+
|
|
71
|
+
Then, for exposing your string ID, use the `id_string` method. For example, if you're using [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers):
|
|
72
|
+
|
|
73
|
+
class UserSerializer < ActiveModel::Serializer
|
|
74
|
+
attributes :id, :name
|
|
75
|
+
|
|
76
|
+
def id
|
|
77
|
+
object.id_string
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
And that's just about it!
|
|
82
|
+
|
|
83
|
+
## TODO
|
|
84
|
+
* Publish on rubygems
|
|
85
|
+
* Since the `MyModel.find("7EajpSfdWIf")` functionality depends on the argument now being a string, `MyModel.find("5")` will no longer mean `MyModel.find(5)`, but rather `MyModel.find(4387534)` or something. Is that a problem?
|
|
86
|
+
* It's a potential security problem that we don't force strings from controllers (integer id coming from JSON postdata will make it find by original id)
|
|
87
|
+
* Although TEA handles (and outputs) 64-bit ids, we currently limit the input to 32-bit
|
|
88
|
+
|
|
89
|
+
## Installation
|
|
90
|
+
Add this line to your application's Gemfile:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
gem 'acts_as_having_string_id'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
And then execute:
|
|
97
|
+
```bash
|
|
98
|
+
$ bundle
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or install it yourself as:
|
|
102
|
+
```bash
|
|
103
|
+
$ gem install acts_as_having_string_id
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Contributing
|
|
107
|
+
To contribute, fork the repo, edit the code and create a pull request with tests. :)
|
|
108
|
+
|
|
109
|
+
## Acknowledgements
|
|
110
|
+
The Tiny Encryption Algorithm was created by David Wheeler and Roger Needham of the Cambridge Computer Laboratory. This library's implementation is based on [this code](https://github.com/pmarreck/ruby-snippets/blob/master/TEA.rb) by Jeremy Hinegardner.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'rdoc/task'
|
|
8
|
+
|
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
11
|
+
rdoc.title = 'ActsAsHavingStringId'
|
|
12
|
+
rdoc.options << '--line-numbers'
|
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
require 'bundler/gem_tasks'
|
|
23
|
+
|
|
24
|
+
require 'rake/testtask'
|
|
25
|
+
|
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
|
27
|
+
t.libs << 'lib'
|
|
28
|
+
t.libs << 'test'
|
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
|
30
|
+
t.verbose = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
task default: :test
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ActsAsHavingStringId
|
|
2
|
+
class StringId < ActiveRecord::Type::Value
|
|
3
|
+
def initialize(tea)
|
|
4
|
+
@tea = tea
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def serialize(value)
|
|
8
|
+
if value.is_a? String
|
|
9
|
+
i = @tea.decrypt(value.base62_decode)
|
|
10
|
+
if i >= 2**31
|
|
11
|
+
# Since Postgres SERIAL is a signed 32-bit integer, we can
|
|
12
|
+
# only represent integers up until (2**32)-1. If we're
|
|
13
|
+
# serializing a larger id, we want a not found rather than
|
|
14
|
+
# a postgres datatype out of bounds error. WHERE id = -1
|
|
15
|
+
# will definitely not be found.
|
|
16
|
+
return -1
|
|
17
|
+
end
|
|
18
|
+
return i
|
|
19
|
+
else
|
|
20
|
+
value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#---------------------------------------------------------------------
|
|
2
|
+
# This is a pure ruby implementation of the Tiny Encryption Algorithm
|
|
3
|
+
# (TEA) by David Wheeler and Roger Needham of the Cambridge Computer
|
|
4
|
+
# Laboratory.
|
|
5
|
+
#
|
|
6
|
+
# For more information:
|
|
7
|
+
#
|
|
8
|
+
# http://www.simonshepherd.supanet.com/tea.htm
|
|
9
|
+
#
|
|
10
|
+
# This is an implementation of the 'New Variant' of the cipher.
|
|
11
|
+
#
|
|
12
|
+
# ====================================================================
|
|
13
|
+
# Copyright (c) 2005, 2006 Jeremy Hinegardner <jeremy@hinegardner.org>
|
|
14
|
+
#
|
|
15
|
+
# This implementation of the TEA New Variant is released under
|
|
16
|
+
# the MIT License:
|
|
17
|
+
#
|
|
18
|
+
# http://www.opensource.org/licenses/mit-license.html
|
|
19
|
+
#
|
|
20
|
+
# ====================================================================
|
|
21
|
+
#
|
|
22
|
+
# Altered for Gem suitability and to support encryption of 64-bit
|
|
23
|
+
# integers by Magnus Hult
|
|
24
|
+
#
|
|
25
|
+
#---------------------------------------------------------------------
|
|
26
|
+
# Ruby 1.8 compatibility
|
|
27
|
+
if RUBY_VERSION.include?('1.8')
|
|
28
|
+
class Fixnum; def ord; return self; end; end
|
|
29
|
+
end
|
|
30
|
+
require 'digest/md5'
|
|
31
|
+
module ActsAsHavingStringId
|
|
32
|
+
class TEA
|
|
33
|
+
DELTA = 0x9e3779b9
|
|
34
|
+
ITERATIONS = 32
|
|
35
|
+
|
|
36
|
+
def initialize(pass_phrase)
|
|
37
|
+
@key = passphrase_to_key(pass_phrase)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def encrypt(num)
|
|
41
|
+
nums = to_32bit_ints(num)
|
|
42
|
+
enc = encrypt_chunk(nums[0], nums[1], @key)
|
|
43
|
+
from_32bit_ints(enc[0], enc[1])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def decrypt(num)
|
|
47
|
+
nums = to_32bit_ints(num)
|
|
48
|
+
dec = decrypt_chunk(nums[0], nums[1], @key)
|
|
49
|
+
from_32bit_ints(dec[0], dec[1])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
############
|
|
53
|
+
private
|
|
54
|
+
############
|
|
55
|
+
|
|
56
|
+
def to_32bit_ints(num)
|
|
57
|
+
# From a 64-bit integer, return an array of two 32-bit integers
|
|
58
|
+
# high bits first
|
|
59
|
+
[(num & 0xFFFFFFFF00000000) >> 32, num & 0x00000000FFFFFFFF]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def from_32bit_ints(num1, num2)
|
|
63
|
+
# From two 32-bit integers, high bits first, return
|
|
64
|
+
# a 64-bit integer
|
|
65
|
+
(num1 << 32) | num2
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#-------------------------------------------------------------
|
|
69
|
+
# convert the given passphrase to and MD5 sum and get the 128
|
|
70
|
+
# bit key as 4 x 32 bit ints
|
|
71
|
+
#-------------------------------------------------------------
|
|
72
|
+
def passphrase_to_key(pass_phrase)
|
|
73
|
+
Digest::MD5.digest(pass_phrase).unpack('L*')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
#-------------------------------------------------------------
|
|
78
|
+
# encrypt 2 of the integers ( 8 characters ) of the input into
|
|
79
|
+
# the cipher text output
|
|
80
|
+
#-------------------------------------------------------------
|
|
81
|
+
def encrypt_chunk(num1,num2,key)
|
|
82
|
+
y,z,sum = num1,num2,0
|
|
83
|
+
|
|
84
|
+
ITERATIONS.times do |i|
|
|
85
|
+
y += ( z << 4 ^ z >> 5) + z ^ sum + key[sum & 3]
|
|
86
|
+
y = y & 0xFFFFFFFF;
|
|
87
|
+
|
|
88
|
+
sum += DELTA
|
|
89
|
+
z += ( y << 4 ^ y >> 5) + y ^ sum + key[sum >> 11 & 3]
|
|
90
|
+
z = z & 0xFFFFFFFF;
|
|
91
|
+
|
|
92
|
+
# ruby can keep on getting bigger because of Bignum so
|
|
93
|
+
# you have to and with 0xFFFFFFFF to get the Fixnum
|
|
94
|
+
# bytes
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
return [y,z]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
#-------------------------------------------------------------
|
|
102
|
+
# decrypt 2 of the integer cipher texts into the plaintext
|
|
103
|
+
#-------------------------------------------------------------
|
|
104
|
+
def decrypt_chunk(num1,num2,key)
|
|
105
|
+
y,z = num1,num2
|
|
106
|
+
sum = DELTA << 5
|
|
107
|
+
ITERATIONS.times do |i|
|
|
108
|
+
z -= ( y << 4 ^ y >> 5) + y ^ sum + key[sum >> 11 & 3]
|
|
109
|
+
z = z & 0xFFFFFFFF
|
|
110
|
+
sum -= DELTA
|
|
111
|
+
y -= ( z << 4 ^ z >> 5) + z ^ sum + key[sum & 3]
|
|
112
|
+
y = y & 0xFFFFFFFF
|
|
113
|
+
end
|
|
114
|
+
return [y,z]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'base62'
|
|
2
|
+
require 'acts_as_having_string_id/tea'
|
|
3
|
+
require 'acts_as_having_string_id/string_id'
|
|
4
|
+
require 'acts_as_having_string_id/railtie' if defined?(Rails)
|
|
5
|
+
|
|
6
|
+
module ActsAsHavingStringId
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def acts_as_having_string_id(options = {})
|
|
11
|
+
class_eval do
|
|
12
|
+
attribute :id, ActsAsHavingStringId::StringId.new(_tea)
|
|
13
|
+
end
|
|
14
|
+
include ActsAsHavingStringId::LocalInstanceMethods
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def _tea
|
|
18
|
+
pass_phrase = self.class.name + Rails.application.secrets.string_id_key
|
|
19
|
+
@_tea ||= ActsAsHavingStringId::TEA.new(pass_phrase)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module LocalInstanceMethods
|
|
24
|
+
def id_string
|
|
25
|
+
self.class._tea.encrypt(id).base62_encode
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: acts_as_having_string_id
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Magnus Hult
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-09-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 5.0.0
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 5.0.0.1
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - "~>"
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 5.0.0
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 5.0.0.1
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: base62
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 1.0.0
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 1.0.0
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: sqlite3
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
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
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
description: Makes a model accept and expose a seemingly random string id
|
|
62
|
+
email:
|
|
63
|
+
- magnus@magnushult.se
|
|
64
|
+
executables: []
|
|
65
|
+
extensions: []
|
|
66
|
+
extra_rdoc_files: []
|
|
67
|
+
files:
|
|
68
|
+
- MIT-LICENSE
|
|
69
|
+
- README.md
|
|
70
|
+
- Rakefile
|
|
71
|
+
- lib/acts_as_having_string_id.rb
|
|
72
|
+
- lib/acts_as_having_string_id/railtie.rb
|
|
73
|
+
- lib/acts_as_having_string_id/string_id.rb
|
|
74
|
+
- lib/acts_as_having_string_id/tea.rb
|
|
75
|
+
- lib/acts_as_having_string_id/version.rb
|
|
76
|
+
- lib/tasks/acts_as_having_string_id_tasks.rake
|
|
77
|
+
homepage: http://github.com/hult/acts_as_having_string_id
|
|
78
|
+
licenses:
|
|
79
|
+
- MIT
|
|
80
|
+
metadata: {}
|
|
81
|
+
post_install_message:
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
requirements: []
|
|
96
|
+
rubyforge_project:
|
|
97
|
+
rubygems_version: 2.5.1
|
|
98
|
+
signing_key:
|
|
99
|
+
specification_version: 4
|
|
100
|
+
summary: Makes a model accept and expose a seemingly random string id
|
|
101
|
+
test_files: []
|