token_attr 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f2b3cdab36a03426a385de026e0b16b271e94b79
4
- data.tar.gz: 39bbbec8a942559030535a44f3b0ead803550177
3
+ metadata.gz: 8883c5c7026e366c148643bd1e4bd13dc57feba4
4
+ data.tar.gz: 7f661a16d09ab1e9368680efff5b940e20c51f05
5
5
  SHA512:
6
- metadata.gz: d6f07c48755e3b73c7f07741fcce0a17f0a58fd113d2c87a6edcf873c31b9f5041f629409e60ee2a4bcd2bef53737e194421ad3e71137df6ce6d46c5075e6706
7
- data.tar.gz: 6d7668305f5a4868b1ac2a1b460a24b9b8f5bf520367bea6ce560aca5033bcd641aa23cac3954c2e5e03f6342d6ff4c47e8e72d21c6d3abcbc1b11683d2793a6
6
+ metadata.gz: a01317959dd57120d37231389020af8e9ea669596788aad13b50c60b1f170f3cf3155ba20e640bbdd2e1076657bb08e8c218b592c4c19c33f98fada74accb73f
7
+ data.tar.gz: 404e200b4bfe0c10ddeee8cf1b9970cc39c0ee572815e7c3c5ba11c3737ffc47f9f82521faefb043120d21dbbcf4ebdd4ac415cbd1c8e56a4e57023317c4a970
data/README.md CHANGED
@@ -6,13 +6,13 @@ Unique random token generator for ActiveRecord.
6
6
 
7
7
  Add `token_attr` to your Gemfile:
8
8
 
9
- gem 'token_attr', '~> 0.1.0'
9
+ gem 'token_attr', '~> 0.2.0'
10
10
 
11
11
  ## Usage
12
12
 
13
13
  ```
14
14
  class User < ActiveRecord::Base
15
- include TokenAttr
15
+ include TokenAttr::Concern
16
16
  token_attr :token
17
17
  end
18
18
 
@@ -21,7 +21,7 @@ user.valid?
21
21
  user.token # => "b8bd30ff"
22
22
  ```
23
23
 
24
- The token is generated in a `before_validation` callback.
24
+ The token is generated in a `before_validation` callback only if the it's `nil`.
25
25
 
26
26
  ### Options
27
27
 
@@ -48,10 +48,10 @@ Accepted values:
48
48
  - a string - a string of your choice of the characters you want to use
49
49
 
50
50
  ```
51
- token_attr :token, alphabet: :numeric # => "82051173"
52
- token_attr :token, alphabet: :alphabetic # => "xqnInSJa"
51
+ token_attr :token, alphabet: :numeric # => "82051173"
52
+ token_attr :token, alphabet: :alphabetic # => "xqnInSJa"
53
53
  token_attr :token, alphabet: :alphanumeric # => "61nD0lUo"
54
- token_attr :token, alphabet: "token" # => "ktnekoet"
54
+ token_attr :token, alphabet: "token" # => "ktnekoet"
55
55
  ```
56
56
 
57
57
  ## Contributing
data/lib/token_attr.rb CHANGED
@@ -1,88 +1,3 @@
1
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
- ALPHABETIC_ALPHABET = [('a'..'z'),('A'..'Z')].map(&:to_a).flatten.freeze
10
- NUMERIC_ALPHABET = [(0..9)].map(&:to_a).flatten.freeze
11
- ALPHANUMERIC_ALPHABET = [ALPHABETIC_ALPHABET, NUMERIC_ALPHABET].flatten.freeze
12
-
13
- class TooManyAttemptsError < StandardError
14
- attr_reader :attribute, :token
15
-
16
- def initialize(attr_name, token, message = nil)
17
- @attribute = attr_name
18
- @token = token
19
- message ||= "Can't generate unique token for \"#{attr_name}\". Last attempt with \"#{token}\"."
20
- super(message)
21
- end
22
- end
23
-
24
- included do
25
- before_validation :generate_tokens
26
- end
27
-
28
- module ClassMethods
29
- def token_attr(attr_name, options = {})
30
- token_attributes << attr_name
31
-
32
- define_method "should_generate_new_#{attr_name}_token?" do
33
- send(attr_name).blank?
34
- end
35
-
36
- define_method "generate_new_#{attr_name}_token" do
37
- token_length = options.fetch(:length, DEFAULT_TOKEN_LENGTH)
38
-
39
- if alphabet = options[:alphabet]
40
- alphabet_array = case alphabet
41
- when :alphanumeric
42
- ALPHANUMERIC_ALPHABET
43
- when :alphabetic
44
- ALPHABETIC_ALPHABET
45
- when :numeric
46
- NUMERIC_ALPHABET
47
- else
48
- alphabet.split('')
49
- end
50
- (0...token_length).map{ alphabet_array.sample }.join
51
- else
52
- hex_length = (token_length / 2.0).ceil # 2 characters per length
53
- SecureRandom.hex(hex_length).slice(0...token_length)
54
- end
55
- end
56
- end
57
-
58
- def token_attributes
59
- @token_attributes ||= []
60
- end
61
- end
62
-
63
- def generate_tokens
64
- self.class.token_attributes.each do |attr_name|
65
- if send("should_generate_new_#{attr_name}_token?")
66
- new_token = nil
67
- try_count = 0
68
- begin
69
- raise TooManyAttemptsError.new(attr_name, new_token) if try_count == 5
70
- new_token = send("generate_new_#{attr_name}_token")
71
- try_count += 1
72
- end until token_is_unique?(attr_name, new_token)
73
-
74
- send "#{attr_name}=", new_token
75
- end
76
- end
77
- end
78
-
79
- def token_is_unique?(attr_name, token)
80
- scope = self.class.where(attr_name => token)
81
- scope = scope.where(id != self.id) if self.persisted?
82
- !scope.exists?
83
- end
84
-
85
- end
86
-
87
- # Uncomment to auto-extend ActiveRecord, probably not a good idea
88
- # ActiveRecord::Base.send(:extend, TokenAttr)
2
+ require 'token_attr/concern'
3
+ require 'token_attr/errors'
@@ -0,0 +1,84 @@
1
+ require 'active_record'
2
+ require 'active_support/concern'
3
+
4
+ module TokenAttr
5
+ DEFAULT_TOKEN_LENGTH = 8.freeze
6
+ ALPHABETIC_ALPHABET = [('a'..'z'),('A'..'Z')].map(&:to_a).flatten.freeze
7
+ NUMERIC_ALPHABET = [(0..9)].map(&:to_a).flatten.freeze
8
+ ALPHANUMERIC_ALPHABET = [ALPHABETIC_ALPHABET, NUMERIC_ALPHABET].flatten.freeze
9
+
10
+ TokenDefinition = Struct.new(:attr_name, :scope_attr)
11
+
12
+ module Concern
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ before_validation :generate_tokens
17
+ end
18
+
19
+ module ClassMethods
20
+ def token_attr(attr_name, options = {})
21
+ token_definitions << TokenDefinition.new(attr_name, options[:scope])
22
+
23
+ define_method "should_generate_new_#{attr_name}?" do
24
+ send(attr_name).blank?
25
+ end
26
+
27
+ define_method "generate_new_#{attr_name}" do
28
+ token_length = options.fetch(:length, DEFAULT_TOKEN_LENGTH)
29
+
30
+ if alphabet = options[:alphabet]
31
+ alphabet_array = case alphabet
32
+ when :alphanumeric
33
+ ALPHANUMERIC_ALPHABET
34
+ when :alphabetic
35
+ ALPHABETIC_ALPHABET
36
+ when :numeric
37
+ NUMERIC_ALPHABET
38
+ else
39
+ alphabet.split('')
40
+ end
41
+ (0...token_length).map{ alphabet_array.sample }.join
42
+ else
43
+ hex_length = (token_length / 2.0).ceil # 2 characters per length
44
+ SecureRandom.hex(hex_length).slice(0...token_length)
45
+ end
46
+ end
47
+ end
48
+
49
+ def token_definitions
50
+ @token_definitions ||= []
51
+ end
52
+ end
53
+
54
+ def generate_tokens
55
+ self.class.token_definitions.each do |td|
56
+ if send("should_generate_new_#{td.attr_name}?")
57
+ new_token = nil
58
+ try_count = 0
59
+ begin
60
+ raise TooManyAttemptsError.new(td.attr_name, new_token) if try_count == 5
61
+ new_token = send("generate_new_#{td.attr_name}")
62
+ try_count += 1
63
+ end until token_is_unique?(td, new_token)
64
+
65
+ send "#{td.attr_name}=", new_token
66
+ end
67
+ end
68
+ end
69
+
70
+ def token_is_unique?(token_definition, token)
71
+ attr_name = token_definition.attr_name
72
+ scope_attr = token_definition.scope_attr
73
+
74
+ scope = self.class.where(attr_name => token)
75
+ scope = scope.where.not(id: self.id) if self.persisted?
76
+ scope = scope.where(scope_attr => read_attribute(scope_attr)) if scope_attr
77
+ !scope.exists?
78
+ end
79
+
80
+ end
81
+ end
82
+
83
+ # Uncomment to auto-extend ActiveRecord, probably not a good idea
84
+ # ActiveRecord::Base.send(:extend, TokenAttr)
@@ -0,0 +1,14 @@
1
+ module TokenAttr
2
+
3
+ class TooManyAttemptsError < StandardError
4
+ attr_reader :attribute, :token
5
+
6
+ def initialize(attr_name, token, message = nil)
7
+ @attribute = attr_name
8
+ @token = token
9
+ message ||= "Can't generate unique token for \"#{attr_name}\". Last attempt with \"#{token}\"."
10
+ super(message)
11
+ end
12
+ end
13
+
14
+ end
@@ -1,3 +1,3 @@
1
1
  module TokenAttr
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -20,6 +20,12 @@ ActiveRecord::Schema.define do
20
20
  create_table :models, force: true do |t|
21
21
  t.string :token
22
22
  t.string :private_token
23
+ t.integer :scope_id
23
24
  end
24
25
 
25
26
  end
27
+
28
+ class BaseModel < ActiveRecord::Base
29
+ self.table_name = 'models'
30
+ include TokenAttr::Concern
31
+ end
@@ -2,48 +2,47 @@ require 'spec_helper'
2
2
 
3
3
  describe TokenAttr do
4
4
 
5
- class Model < ActiveRecord::Base
6
- include TokenAttr
5
+ class Model < BaseModel
7
6
  token_attr :token
8
7
  end
9
8
 
10
- class ModelWithLength < ActiveRecord::Base
11
- self.table_name = 'models'
12
- include TokenAttr
9
+ class ModelWithLength < BaseModel
13
10
  token_attr :token, length: 13
14
11
  end
15
12
 
16
- class ModelWithAlphabet < ActiveRecord::Base
17
- self.table_name = 'models'
18
- include TokenAttr
13
+ class ModelWithAlphabet < BaseModel
19
14
  token_attr :token, alphabet: 'abc123'
20
15
  end
21
16
 
22
- class ModelWithAlphanumericAlphabet < ActiveRecord::Base
23
- self.table_name = 'models'
24
- include TokenAttr
17
+ class ModelWithAlphanumericAlphabet < BaseModel
25
18
  token_attr :token, alphabet: :alphanumeric
26
19
  end
27
20
 
28
- class ModelWithAlphabeticAlphabet < ActiveRecord::Base
29
- self.table_name = 'models'
30
- include TokenAttr
21
+ class ModelWithAlphabeticAlphabet < BaseModel
31
22
  token_attr :token, alphabet: :alphabetic
32
23
  end
33
24
 
34
- class ModelWithNumericAlphabet < ActiveRecord::Base
35
- self.table_name = 'models'
36
- include TokenAttr
25
+ class ModelWithNumericAlphabet < BaseModel
37
26
  token_attr :token, alphabet: :numeric
38
27
  end
39
28
 
40
- class ModelWithMultipleTokens < ActiveRecord::Base
41
- self.table_name = 'models'
42
- include TokenAttr
29
+ class ModelWithMultipleTokens < BaseModel
43
30
  token_attr :token
44
31
  token_attr :private_token
45
32
  end
46
33
 
34
+ class ModelWithOverride < BaseModel
35
+ token_attr :token
36
+
37
+ def should_generate_new_token?
38
+ token == '1234'
39
+ end
40
+ end
41
+
42
+ class ModelWithScope < BaseModel
43
+ token_attr :token, scope: :scope_id
44
+ end
45
+
47
46
  describe ".token_attr" do
48
47
  let(:model) { Model.new }
49
48
 
@@ -136,6 +135,47 @@ describe TokenAttr do
136
135
  end
137
136
  end
138
137
 
138
+ context "when the should_generate_new_[attr_name]? method is overridden" do
139
+ let(:model) { ModelWithOverride.new }
140
+
141
+ it "generates a token when the condition is satisfied" do
142
+ SecureRandom.should_receive(:hex).with(4).and_return('newtoken')
143
+ model.token = '1234'
144
+ model.valid?
145
+ model.token.should == 'newtoken'
146
+ end
147
+
148
+ it "does not generate a new token when the condition is not satisfied" do
149
+ model.valid?
150
+ model.token.should be_nil
151
+ end
152
+ end
153
+
154
+ context "with scope" do
155
+ let(:model) { ModelWithScope.new }
156
+
157
+ context "when the scope attributes are different" do
158
+ it "allows duplicate tokens" do
159
+ SecureRandom.should_receive(:hex).twice.with(4).and_return('12345678')
160
+ ModelWithScope.create(scope_id: 1)
161
+ model.scope_id = 2
162
+ model.valid?
163
+ model.token.should == '12345678'
164
+ end
165
+ end
166
+
167
+ context "when the scope attributes are the same" do
168
+ it "regenerates a duplicate token" do
169
+ SecureRandom.should_receive(:hex).twice.with(4).and_return('12345678')
170
+ SecureRandom.should_receive(:hex).once.with(4).and_return('asdfghjk')
171
+ ModelWithScope.create(scope_id: 1)
172
+ model.scope_id = 1
173
+ model.valid?
174
+ model.token.should == 'asdfghjk'
175
+ end
176
+ end
177
+ end
178
+
139
179
  context "when token is not blank" do
140
180
  before { model.token = 'not blank' }
141
181
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: token_attr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michel Billard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-26 00:00:00.000000000 Z
11
+ date: 2014-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -109,9 +109,11 @@ files:
109
109
  - README.md
110
110
  - Rakefile
111
111
  - lib/token_attr.rb
112
+ - lib/token_attr/concern.rb
113
+ - lib/token_attr/errors.rb
112
114
  - lib/token_attr/version.rb
113
115
  - spec/spec_helper.rb
114
- - spec/token_attr_spec.rb
116
+ - spec/token_attr/concern_spec.rb
115
117
  - token_attr.gemspec
116
118
  homepage: http://github.com/mbillard/token_attr
117
119
  licenses:
@@ -139,4 +141,4 @@ specification_version: 4
139
141
  summary: Unique random token generator for ActiveRecord
140
142
  test_files:
141
143
  - spec/spec_helper.rb
142
- - spec/token_attr_spec.rb
144
+ - spec/token_attr/concern_spec.rb