has_tokenable 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/.gitignore +1 -0
- data/Gemfile +2 -0
- data/LICENSE +9 -0
- data/README.md +129 -0
- data/Rakefile +12 -0
- data/has_tokenable.gemspec +33 -0
- data/lib/has_tokenable/concern.rb +77 -0
- data/lib/has_tokenable/finder_methods.rb +44 -0
- data/lib/has_tokenable/table_definition.rb +15 -0
- data/lib/has_tokenable/version.rb +3 -0
- data/lib/has_tokenable.rb +31 -0
- metadata +113 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9031526dff08471220b84d58ed37738fa3f70760c9ab1769ef450449e395e7bf
|
|
4
|
+
data.tar.gz: d00ec6c32f4aec5e802801340d51e193600d4b29a806242bef23761a553b74ab
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e2c151f14038955e82d34faed45541ab7496c7a58203b3f72ffdb9af2626c48645811ce231da110363e10e65a1cbaabae5c10c792792f52cf5a3d798e34bbc7e
|
|
7
|
+
data.tar.gz: cf06d4088ba210c159e58cf5bf05be2f8527cc73b31f183e9b40a1d8cb39f83521d45d706a510c8d50c6947f8cb8b39ff30e51f2a0241d2d95c05c9538a3013f
|
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2019 Carlos Montalvo.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
8
|
+
|
|
9
|
+
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Identify your active records with random tokens when you don't want your users to see a sequential ID.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
------------------------------------------------------------------------------
|
|
5
|
+
Installation
|
|
6
|
+
------------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
Add has_tokenable to your Gemfile like so:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'has_tokenable', '~> 0.1.0'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Now run `bundle install` and you're good to go!
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
------------------------------------------------------------------------------
|
|
18
|
+
Usage
|
|
19
|
+
------------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
First, add a token to your model's table with a migration:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# Upgrade and existing table
|
|
25
|
+
class AddTokenToItems < ActiveRecord::Migration
|
|
26
|
+
add_column :items, :token, :string
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Add to a new table
|
|
30
|
+
class CreateItems < ActiveRecord::Migration
|
|
31
|
+
def change
|
|
32
|
+
create_table :items do |t|
|
|
33
|
+
t.token
|
|
34
|
+
t.string :name
|
|
35
|
+
|
|
36
|
+
t.timestamps
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Now make sure your model knows to use it's token by calling `has_tokenable`
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
class Item < ActiveRecord::Base
|
|
47
|
+
has_tokenable
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
That's basically it! Your Items will now know to use their token as their identifier.
|
|
52
|
+
|
|
53
|
+
Try it out in your `rails console`
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
@item = Item.create(name: "Tokenz!")
|
|
57
|
+
#<Item id: 1, token: "B5OvJvy6B2_DZg", name: "Tokenz!", created_at: "2020-01-26 20:17:13", updated_at: "2020-01-26 20:17:13">
|
|
58
|
+
@item.to_param
|
|
59
|
+
# B5OvJvy6B2_DZg
|
|
60
|
+
@item == Item.find("B5OvJvy6B2_DZg")
|
|
61
|
+
# true
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
------------------------------------------------------------------------------
|
|
66
|
+
Options
|
|
67
|
+
------------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
You can customize has_tokenable by setting a few options. Here's the defaults:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
{
|
|
73
|
+
prefix: nil, # if nil use first letter of class name
|
|
74
|
+
length: 10,
|
|
75
|
+
param_name: 'token',
|
|
76
|
+
method_random: 'urlsafe_base64' #method random urlsafe_base64 hex alphanumeric random_number uuid
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
Options can be set globally by overwriting the `HasTokenable.default_token_options`
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# config/initializers/has_tokenable.rb
|
|
85
|
+
|
|
86
|
+
# for one option
|
|
87
|
+
HasTokenable.default_token_options[:prefix] = "OMG"
|
|
88
|
+
|
|
89
|
+
# for multiple options
|
|
90
|
+
HasTokenable.default_token_options.merge!(
|
|
91
|
+
method_random: 'alphanumeric',
|
|
92
|
+
length: 8
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Options can also be set on a per-class level:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
class List < ActiveRecord::Base
|
|
100
|
+
has_tokenable prefix: "LI", length: 10
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class Item < ActiveRecord::Base
|
|
104
|
+
has_tokenable prefix: "ITM"
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
------------------------------------------------------------------------------
|
|
110
|
+
Demo
|
|
111
|
+
------------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
Try out the demo to get a real clear idea of what has_tokenable does.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
git clone git://github.com/CarlangasGO/has_tokenable.git
|
|
117
|
+
cd has_tokenable
|
|
118
|
+
bundle install
|
|
119
|
+
rails s
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Now open your browser to [http://localhost:3000](http://localhost:3000)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
------------------------------------------------------------------------------
|
|
126
|
+
License
|
|
127
|
+
------------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
Copyright (c) 2019 - 2020, released under the New BSD License All rights reserved.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "has_tokenable/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
|
|
7
|
+
s.name = %q{has_tokenable}
|
|
8
|
+
s.version = HasTokenable::VERSION
|
|
9
|
+
s.platform = Gem::Platform::RUBY
|
|
10
|
+
s.authors = ["Carlos Montalvo"]
|
|
11
|
+
s.email = ["carlosmontalvo@zetanova.com"]
|
|
12
|
+
s.homepage = "https://github.com/CarlangasGO/has_tokenable"
|
|
13
|
+
s.summary = %q{Identifies your active records with a random token.}
|
|
14
|
+
s.description = %q{Identifies your active records with a random token. For more information, please see the documentation.}
|
|
15
|
+
s.license = "BSD-2-Clause"
|
|
16
|
+
s.metadata = { "source_code_uri" => "https://github.com/CarlangasGO/has_tokenable" }
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
s.files = `git ls-files`.split("\n")
|
|
20
|
+
|
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
22
|
+
s.require_paths = ["lib"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
s.require_paths = ["lib"]
|
|
26
|
+
|
|
27
|
+
s.add_dependency('activerecord', '~> 4')
|
|
28
|
+
s.add_dependency('activesupport', '~> 4')
|
|
29
|
+
|
|
30
|
+
s.add_development_dependency('rails', '~> 4')
|
|
31
|
+
s.add_development_dependency('sqlite3', '~> 1.3')
|
|
32
|
+
|
|
33
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module HasTokenable
|
|
2
|
+
module Concern
|
|
3
|
+
|
|
4
|
+
METHODS = %w(urlsafe_base64 hex alphanumeric random_number uuid).freeze
|
|
5
|
+
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.send(:extend, ClassMethods)
|
|
8
|
+
base.send(:include, InstanceMethods)
|
|
9
|
+
base.class_eval do
|
|
10
|
+
validates :token, presence: true, uniqueness: true
|
|
11
|
+
before_validation :generate_token, on: :create, if: proc{|record| record.token.nil? }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
|
|
17
|
+
def method_random=(value)
|
|
18
|
+
values = value.is_a?(Array) ? value : [value]
|
|
19
|
+
values.map! { |v| v.to_s }
|
|
20
|
+
|
|
21
|
+
raise ArgumentError.new('Method is not supported') if (values - METHODS).size > 0
|
|
22
|
+
values
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Default options as well as an overwrite point so you can assign different defaults to different models
|
|
26
|
+
def default_token_options
|
|
27
|
+
@default_token_options ||= begin
|
|
28
|
+
options = HasTokenable.default_token_options
|
|
29
|
+
options[:prefix] ||= self.name[0, 1]
|
|
30
|
+
options
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_method(options)
|
|
35
|
+
args = [ options[:method_random], options[:length].to_i ]
|
|
36
|
+
|
|
37
|
+
args.slice!(-1) if options[:method_random] == 'uuid'
|
|
38
|
+
|
|
39
|
+
SecureRandom.send(*args)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Generates a unique token based on the options
|
|
43
|
+
def generate_unique_token
|
|
44
|
+
record, options = true, @has_tokenable_options
|
|
45
|
+
conditions = {}
|
|
46
|
+
|
|
47
|
+
token = loop do
|
|
48
|
+
random_token = self.generate_method(options)
|
|
49
|
+
|
|
50
|
+
conditions[options[:param_name].to_sym] = random_token
|
|
51
|
+
|
|
52
|
+
break random_token unless self.exists?(conditions)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
token
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end # ClassMethods
|
|
59
|
+
|
|
60
|
+
module InstanceMethods
|
|
61
|
+
|
|
62
|
+
# Returns the resource's token
|
|
63
|
+
def to_param
|
|
64
|
+
self.send(self.class.has_tokenable_options[:param_name])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def generate_token
|
|
70
|
+
self.token = self.class.generate_unique_token
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end # InstanceMethods
|
|
74
|
+
|
|
75
|
+
end # Concern
|
|
76
|
+
|
|
77
|
+
end # HasTokenable
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module HasTokenable
|
|
2
|
+
module FinderMethods
|
|
3
|
+
|
|
4
|
+
# Find by token ensuring case sensitivity
|
|
5
|
+
def find_by_case_sensitive_token(token)
|
|
6
|
+
return if token.nil?
|
|
7
|
+
where("#{token_with_table_name} = ?", token).first
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Find by token
|
|
11
|
+
def find_by_token(token)
|
|
12
|
+
return if token.nil?
|
|
13
|
+
send(:find_by_case_sensitive_token , token)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Find by token and raise error if no record is found
|
|
17
|
+
def find_by_token!(token)
|
|
18
|
+
record = find_by_token(token)
|
|
19
|
+
raise ActiveRecord::RecordNotFound, "Could not find #{self.name} with token #{token.inspect}" if record.nil?
|
|
20
|
+
record
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Find by token if the first param looks like a token, otherwise use super
|
|
24
|
+
def find(*args)
|
|
25
|
+
if args[0].is_a?(String) && args[0].length == has_tokenable_options[:length]
|
|
26
|
+
record = find_by_token(args[0])
|
|
27
|
+
end
|
|
28
|
+
record || super(*args)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find!(*args)
|
|
32
|
+
record = find(*args)
|
|
33
|
+
raise ActiveRecord::RecordNotFound if record.nil?
|
|
34
|
+
record
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def token_with_table_name
|
|
40
|
+
"#{table_name}.#{has_tokenable_options[:param_name]}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'active_record/connection_adapters/abstract/schema_definitions'
|
|
2
|
+
|
|
3
|
+
module HasTokenable
|
|
4
|
+
module TableDefinition
|
|
5
|
+
|
|
6
|
+
def token(*args)
|
|
7
|
+
options = { length: HasTokenable.default_token_options[:length] }.merge(args.extract_options!)
|
|
8
|
+
puts "opeiont: #{options}"
|
|
9
|
+
column(:token, :string, options.merge(nil: false))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, HasTokenable::TableDefinition)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'has_tokenable/concern'
|
|
2
|
+
require 'has_tokenable/finder_methods'
|
|
3
|
+
require 'has_tokenable/table_definition'
|
|
4
|
+
|
|
5
|
+
module HasTokenable
|
|
6
|
+
|
|
7
|
+
module ActiveRecordTie
|
|
8
|
+
|
|
9
|
+
def has_tokenable(options={})
|
|
10
|
+
self.send(:include, HasTokenable::Concern)
|
|
11
|
+
@has_tokenable_options ||= HasTokenable.default_token_options.merge(options)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def has_tokenable_options
|
|
15
|
+
@has_tokenable_options ||= HasTokenable.default_token_options
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.default_token_options
|
|
20
|
+
@default_token_options ||= {
|
|
21
|
+
:prefix => nil, # if nil use first letter of class name
|
|
22
|
+
:length => 10,
|
|
23
|
+
:param_name => 'token',
|
|
24
|
+
:method_random => 'urlsafe_base64'
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
ActiveRecord::Base.send(:extend, HasTokenable::ActiveRecordTie)
|
|
31
|
+
ActiveRecord::Base.send(:extend, HasTokenable::FinderMethods)
|
metadata
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: has_tokenable
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Carlos Montalvo
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-04-30 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: '4'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '4'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rails
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '4'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '4'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sqlite3
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.3'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.3'
|
|
69
|
+
description: Identifies your active records with a random token. For more information,
|
|
70
|
+
please see the documentation.
|
|
71
|
+
email:
|
|
72
|
+
- carlosmontalvo@zetanova.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- ".gitignore"
|
|
78
|
+
- Gemfile
|
|
79
|
+
- LICENSE
|
|
80
|
+
- README.md
|
|
81
|
+
- Rakefile
|
|
82
|
+
- has_tokenable.gemspec
|
|
83
|
+
- lib/has_tokenable.rb
|
|
84
|
+
- lib/has_tokenable/concern.rb
|
|
85
|
+
- lib/has_tokenable/finder_methods.rb
|
|
86
|
+
- lib/has_tokenable/table_definition.rb
|
|
87
|
+
- lib/has_tokenable/version.rb
|
|
88
|
+
homepage: https://github.com/CarlangasGO/has_tokenable
|
|
89
|
+
licenses:
|
|
90
|
+
- BSD-2-Clause
|
|
91
|
+
metadata:
|
|
92
|
+
source_code_uri: https://github.com/CarlangasGO/has_tokenable
|
|
93
|
+
post_install_message:
|
|
94
|
+
rdoc_options: []
|
|
95
|
+
require_paths:
|
|
96
|
+
- lib
|
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '0'
|
|
107
|
+
requirements: []
|
|
108
|
+
rubyforge_project:
|
|
109
|
+
rubygems_version: 2.7.6.2
|
|
110
|
+
signing_key:
|
|
111
|
+
specification_version: 4
|
|
112
|
+
summary: Identifies your active records with a random token.
|
|
113
|
+
test_files: []
|