token_attr 0.1.0 → 0.2.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 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