token_attr 0.0.1
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 +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +22 -0
- data/README.md +21 -0
- data/Rakefile +4 -0
- data/lib/token_attr/version.rb +3 -0
- data/lib/token_attr.rb +78 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/token_attr_spec.rb +122 -0
- data/token_attr.gemspec +28 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 27e5d3e895c988371564f5bf875ddc5ca127e780
|
4
|
+
data.tar.gz: 79b85868b6e66a37d2641a9afeff44e15cdcd103
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2285b96a0f45dbe7cc4457ca51f5d6b8180752506a4ca833b87bed229fdeb53c26a0892348d2ed2badd1c8d426a0de00429dd68e3cdbc4231f2c90a30f808d03
|
7
|
+
data.tar.gz: 497a279733f62dc93abea258ceb1934bc02f116d85d47d056be9dc0c85147c5d80b6f8af54f74e06d7a61ba22c8021dab733bb9922dbbb13579b1b207182b078
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
token_attr (0.0.1)
|
5
|
+
activerecord (>= 4.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.1.0)
|
11
|
+
activesupport (= 4.1.0)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activerecord (4.1.0)
|
14
|
+
activemodel (= 4.1.0)
|
15
|
+
activesupport (= 4.1.0)
|
16
|
+
arel (~> 5.0.0)
|
17
|
+
activesupport (4.1.0)
|
18
|
+
i18n (~> 0.6, >= 0.6.9)
|
19
|
+
json (~> 1.7, >= 1.7.7)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
thread_safe (~> 0.1)
|
22
|
+
tzinfo (~> 1.1)
|
23
|
+
arel (5.0.0)
|
24
|
+
builder (3.2.2)
|
25
|
+
coderay (1.1.0)
|
26
|
+
diff-lcs (1.2.5)
|
27
|
+
i18n (0.6.9)
|
28
|
+
json (1.8.1)
|
29
|
+
method_source (0.8.2)
|
30
|
+
minitest (5.3.2)
|
31
|
+
pry (0.9.12.6)
|
32
|
+
coderay (~> 1.0)
|
33
|
+
method_source (~> 0.8)
|
34
|
+
slop (~> 3.4)
|
35
|
+
rake (10.1.1)
|
36
|
+
rspec (2.14.1)
|
37
|
+
rspec-core (~> 2.14.0)
|
38
|
+
rspec-expectations (~> 2.14.0)
|
39
|
+
rspec-mocks (~> 2.14.0)
|
40
|
+
rspec-core (2.14.8)
|
41
|
+
rspec-expectations (2.14.5)
|
42
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
43
|
+
rspec-mocks (2.14.6)
|
44
|
+
slop (3.5.0)
|
45
|
+
sqlite3 (1.3.9)
|
46
|
+
thread_safe (0.3.3)
|
47
|
+
tzinfo (1.1.0)
|
48
|
+
thread_safe (~> 0.1)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
bundler (~> 1.5)
|
55
|
+
pry
|
56
|
+
rake
|
57
|
+
rspec
|
58
|
+
sqlite3
|
59
|
+
token_attr!
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Michel Billard
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# token_attr
|
2
|
+
|
3
|
+
[Description]
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add `token_attr` to your Gemfile:
|
8
|
+
|
9
|
+
gem 'token_attr'
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
[TODO]
|
14
|
+
|
15
|
+
## Contributing
|
16
|
+
|
17
|
+
1. Fork it ( http://github.com/mbillard/token_attr/fork )
|
18
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
19
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
20
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
21
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/token_attr.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'token_attr/version'
|
2
|
+
require 'active_record'
|
3
|
+
require 'active_support/concern'
|
4
|
+
|
5
|
+
module TokenAttr
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
DEFAULT_TOKEN_LENGTH = 8.freeze
|
9
|
+
DEFAULT_SLUG_ALPHABET = [('a'..'z'),('A'..'Z'),(0..9)].map(&:to_a).flatten.freeze
|
10
|
+
|
11
|
+
class TooManyAttemptsError < StandardError
|
12
|
+
attr_reader :attribute, :token
|
13
|
+
|
14
|
+
def initialize(attr_name, token, message = nil)
|
15
|
+
@attribute = attr_name
|
16
|
+
@token = token
|
17
|
+
message ||= "Can't generate unique token for \"#{attr_name}\". Last attempt with \"#{token}\"."
|
18
|
+
super(message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
included do
|
23
|
+
before_validation :generate_tokens
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def token_attr(attr_name, options = {})
|
28
|
+
token_attributes << attr_name
|
29
|
+
|
30
|
+
define_method "should_generate_new_#{attr_name}_token?" do
|
31
|
+
send(attr_name).blank?
|
32
|
+
end
|
33
|
+
|
34
|
+
define_method "generate_new_#{attr_name}_token" do
|
35
|
+
token_length = options.fetch(:length, DEFAULT_TOKEN_LENGTH)
|
36
|
+
|
37
|
+
if alphabet = options[:alphabet]
|
38
|
+
alphabet = DEFAULT_SLUG_ALPHABET if alphabet == :slug
|
39
|
+
alphabet_array = alphabet.split('')
|
40
|
+
(0...token_length).map{ alphabet_array.sample }.join
|
41
|
+
else
|
42
|
+
hex_length = (token_length / 2.0).ceil # 2 characters per length
|
43
|
+
SecureRandom.hex(hex_length).slice(0...token_length)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def token_attributes
|
49
|
+
@token_attributes ||= []
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_tokens
|
54
|
+
self.class.token_attributes.each do |attr_name|
|
55
|
+
if send("should_generate_new_#{attr_name}_token?")
|
56
|
+
new_token = nil
|
57
|
+
try_count = 0
|
58
|
+
begin
|
59
|
+
raise TooManyAttemptsError.new(attr_name, new_token) if try_count == 5
|
60
|
+
new_token = send("generate_new_#{attr_name}_token")
|
61
|
+
try_count += 1
|
62
|
+
end until token_is_unique?(attr_name, new_token)
|
63
|
+
|
64
|
+
send "#{attr_name}=", new_token
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def token_is_unique?(attr_name, token)
|
70
|
+
scope = self.class.where(attr_name => token)
|
71
|
+
scope = scope.where(id != self.id) if self.persisted?
|
72
|
+
!scope.exists?
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# Uncomment to auto-extend ActiveRecord, probably not a good idea
|
78
|
+
# ActiveRecord::Base.send(:extend, TokenAttr)
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'token_attr'
|
3
|
+
|
4
|
+
# Require this file using `require 'spec_helper'` to ensure that it is only
|
5
|
+
# loaded once.
|
6
|
+
#
|
7
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.before :each do
|
10
|
+
Model.delete_all
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3',
|
15
|
+
database: ':memory:')
|
16
|
+
|
17
|
+
ActiveRecord::Schema.define do
|
18
|
+
self.verbose = false
|
19
|
+
|
20
|
+
create_table :models, force: true do |t|
|
21
|
+
t.string :token
|
22
|
+
t.string :private_token
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TokenAttr do
|
4
|
+
|
5
|
+
class Model < ActiveRecord::Base
|
6
|
+
include TokenAttr
|
7
|
+
token_attr :token
|
8
|
+
end
|
9
|
+
|
10
|
+
class ModelWithLength < ActiveRecord::Base
|
11
|
+
self.table_name = 'models'
|
12
|
+
include TokenAttr
|
13
|
+
token_attr :token, length: 13
|
14
|
+
end
|
15
|
+
|
16
|
+
class ModelWithAlphabet < ActiveRecord::Base
|
17
|
+
self.table_name = 'models'
|
18
|
+
include TokenAttr
|
19
|
+
token_attr :token, alphabet: 'abc123'
|
20
|
+
end
|
21
|
+
|
22
|
+
class ModelWithSlugAlphabet < ActiveRecord::Base
|
23
|
+
self.table_name = 'models'
|
24
|
+
include TokenAttr
|
25
|
+
token_attr :token, alphabet: :slug
|
26
|
+
end
|
27
|
+
|
28
|
+
class ModelWithMultipleTokens < ActiveRecord::Base
|
29
|
+
self.table_name = 'models'
|
30
|
+
include TokenAttr
|
31
|
+
token_attr :token
|
32
|
+
token_attr :private_token
|
33
|
+
end
|
34
|
+
|
35
|
+
describe ".token_attr" do
|
36
|
+
let(:model) { Model.new }
|
37
|
+
|
38
|
+
context "when token is blank" do
|
39
|
+
before { model.token = '' }
|
40
|
+
|
41
|
+
it "generates a token on validation" do
|
42
|
+
model.valid?
|
43
|
+
model.token.should_not be_blank
|
44
|
+
end
|
45
|
+
|
46
|
+
it "generates a token of the default length" do
|
47
|
+
stub_const('TokenAttr::DEFAULT_TOKEN_LENGTH', 4)
|
48
|
+
model.valid?
|
49
|
+
model.token.length.should == 4
|
50
|
+
end
|
51
|
+
|
52
|
+
it "generates a unique token" do
|
53
|
+
SecureRandom.should_receive(:hex).twice.with(4).and_return('12345678')
|
54
|
+
SecureRandom.should_receive(:hex).once.with(4).and_return('abcdefgh')
|
55
|
+
existing_model = Model.create
|
56
|
+
model.valid?
|
57
|
+
model.token.should == 'abcdefgh'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises an exception if it can't find a unique token" do
|
61
|
+
SecureRandom.should_receive(:hex).exactly(5 + 1).times.with(4).and_return('12345678')
|
62
|
+
existing_model = Model.create
|
63
|
+
expect{ model.valid? }.to raise_error(TokenAttr::TooManyAttemptsError)
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when length is specified" do
|
67
|
+
let(:model) { ModelWithLength.new }
|
68
|
+
|
69
|
+
it "generates a token of the specified length" do
|
70
|
+
model.valid?
|
71
|
+
model.token.length.should == 13
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when an alphabet is specified" do
|
76
|
+
let(:model) { ModelWithAlphabet.new }
|
77
|
+
|
78
|
+
it "generates a token with characters from that alphabet" do
|
79
|
+
model.valid?
|
80
|
+
model.token.split('').all? do |c|
|
81
|
+
'abc123'.include?(c)
|
82
|
+
end.should be_true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "generates a token of the default length even if lower than the alphabet" do
|
86
|
+
stub_const('TokenAttr::DEFAULT_TOKEN_LENGTH', 12)
|
87
|
+
model.valid?
|
88
|
+
model.token.length.should == 12
|
89
|
+
end
|
90
|
+
|
91
|
+
it "generates a token with the default slug alphabet if the alphabet is :slug" do
|
92
|
+
model = ModelWithSlugAlphabet.new
|
93
|
+
stub_const('TokenAttr::DEFAULT_SLUG_ALPHABET', 'token')
|
94
|
+
model.valid?
|
95
|
+
model.token.split('').all? do |c|
|
96
|
+
'token'.include?(c)
|
97
|
+
end.should be_true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "when the model has multiple tokens" do
|
102
|
+
let(:model) { ModelWithMultipleTokens.new }
|
103
|
+
|
104
|
+
it "generates each token" do
|
105
|
+
model.valid?
|
106
|
+
model.token.should_not be_blank
|
107
|
+
model.private_token.should_not be_blank
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when token is not blank" do
|
113
|
+
before { model.token = 'not blank' }
|
114
|
+
|
115
|
+
it "does not generate a token on validation" do
|
116
|
+
model.valid?
|
117
|
+
model.token.should == 'not blank'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
data/token_attr.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'token_attr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "token_attr"
|
8
|
+
spec.version = TokenAttr::VERSION
|
9
|
+
spec.authors = ["Michel Billard"]
|
10
|
+
spec.email = ["michel@mbillard.com"]
|
11
|
+
spec.summary = "Unique random token generator for ActiveRecord"
|
12
|
+
# spec.description = %q{TODO: Write a longer description. Optional.}
|
13
|
+
spec.homepage = "http://github.com/mbillard/token_attr"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'activerecord', '>= 4.0.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
24
|
+
spec.add_development_dependency 'pry'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'rspec'
|
27
|
+
spec.add_development_dependency 'sqlite3'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: token_attr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michel Billard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-26 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.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- michel@mbillard.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- .rspec
|
106
|
+
- Gemfile
|
107
|
+
- Gemfile.lock
|
108
|
+
- LICENSE.txt
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- lib/token_attr.rb
|
112
|
+
- lib/token_attr/version.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
- spec/token_attr_spec.rb
|
115
|
+
- token_attr.gemspec
|
116
|
+
homepage: http://github.com/mbillard/token_attr
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.2.2
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: Unique random token generator for ActiveRecord
|
140
|
+
test_files:
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
- spec/token_attr_spec.rb
|