mongoid_token 1.1.1 → 2.0.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.
- data/.autotest +0 -1
- data/README.md +147 -77
- data/lib/mongoid/token/collision_resolver.rb +37 -0
- data/lib/mongoid/token/collisions.rb +31 -0
- data/lib/mongoid/token/finders.rb +19 -0
- data/lib/mongoid/token/generator.rb +72 -0
- data/lib/mongoid/token/options.rb +68 -0
- data/lib/mongoid/token.rb +63 -0
- data/lib/mongoid_token.rb +1 -116
- data/lib/version.rb +1 -1
- data/spec/mongoid/token/collisions_spec.rb +92 -0
- data/spec/mongoid/token/exceptions_spec.rb +4 -0
- data/spec/mongoid/token/finders_spec.rb +41 -0
- data/spec/mongoid/token/generator_spec.rb +49 -0
- data/spec/mongoid/token/options_spec.rb +70 -0
- data/spec/mongoid/token_spec.rb +183 -200
- data/spec/spec_helper.rb +5 -1
- metadata +18 -2
data/.autotest
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
require 'autotest/growl'
|
data/README.md
CHANGED
@@ -4,30 +4,22 @@
|
|
4
4
|
|
5
5
|
This library is a quick and simple way to generate unique, random tokens
|
6
6
|
for your mongoid documents, in the cases where you can't, or don't want
|
7
|
-
to use slugs, or the default MongoDB
|
7
|
+
to use slugs, or the default MongoDB ObjectIDs.
|
8
8
|
|
9
9
|
Mongoid::Token can help turn this:
|
10
10
|
|
11
|
-
http://
|
11
|
+
http://bestappever.com/video/4dcfbb3c6a4f1d4c4a000012
|
12
12
|
|
13
13
|
Into something more like this:
|
14
14
|
|
15
|
-
http://
|
16
|
-
|
17
|
-
|
18
|
-
## Mongoid 3.x Support
|
19
|
-
|
20
|
-
As of version 1.1.0, Mongoid::Token now supports Mongoid 3.x.
|
21
|
-
|
22
|
-
> If you still require __Mongoid 2.x__ support, please install
|
23
|
-
> Mongoid::Token 1.0.0.
|
15
|
+
http://bestappever.com/video/8tmQ9p
|
24
16
|
|
25
17
|
|
26
18
|
## Getting started
|
27
19
|
|
28
20
|
In your gemfile, add:
|
29
21
|
|
30
|
-
gem 'mongoid_token', '~>
|
22
|
+
gem 'mongoid_token', '~> 2.0.0'
|
31
23
|
|
32
24
|
Then update your bundle
|
33
25
|
|
@@ -39,102 +31,178 @@ In your Mongoid documents, just add `include Mongoid::Token` and the
|
|
39
31
|
````
|
40
32
|
class Person
|
41
33
|
include Mongoid::Document
|
42
|
-
include Mongoid::Timestamps
|
43
34
|
include Mongoid::Token
|
44
35
|
|
45
|
-
field :
|
46
|
-
field :last_name
|
36
|
+
field :name
|
47
37
|
|
48
|
-
token
|
38
|
+
token
|
49
39
|
end
|
50
40
|
|
51
41
|
````
|
52
42
|
|
53
|
-
|
54
|
-
|
43
|
+
And that's it! There's lots of configuration options too - which are all
|
44
|
+
listed [below](#configuration). By default, the `token` method will
|
45
|
+
create tokens 4 characters long, containing random alphanumeric characters.
|
55
46
|
|
56
47
|
__Note:__ Mongoid::Token leverages Mongoid's 'safe mode' by
|
57
48
|
automatically creating a unique index on your documents using the token
|
58
49
|
field. In order to take advantage of this feature (and ensure that your
|
59
50
|
documents always have unique tokens) remember to create your indexes.
|
60
51
|
|
61
|
-
See 'Token collision/duplicate prevention' below for more details.
|
62
52
|
|
53
|
+
## Finders
|
63
54
|
|
64
|
-
|
55
|
+
By default, the gem will override the existing `find` method in Mongoid,
|
56
|
+
in order to search for documents based on their token first (although
|
57
|
+
the default behaviour of ObjectIDs is also there). You can disable these
|
58
|
+
with the [`skip_finders` configuration option](#skip-finders-skip_finders).
|
65
59
|
|
66
|
-
|
67
|
-
|
68
|
-
|
60
|
+
````
|
61
|
+
Video.find("x3v98")
|
62
|
+
Account.find("ACC-123456")
|
63
|
+
````
|
69
64
|
|
70
|
-
The options for `contains` are as follows:
|
71
65
|
|
72
|
-
|
73
|
-
* `:alpha` - letters (upper and lowercase) only
|
74
|
-
* `:alpha_lower` - letters (lowercase) only
|
75
|
-
* `:alpha_upper` - letters (uppercase) only
|
76
|
-
* `:numeric` - numbers only, anything from 1 character long, up to and
|
77
|
-
`length`
|
78
|
-
* `:fixed_numeric` - numbers only, but with the number of characters always the same as `length`
|
79
|
-
* `:fixed_numeric_no_leading_zeros` - as above, but will never start with
|
80
|
-
zeros
|
66
|
+
## Configuration
|
81
67
|
|
82
|
-
|
83
|
-
`:field_name` option:
|
68
|
+
### Tokens
|
84
69
|
|
85
|
-
|
70
|
+
As of `Mongoid::Token` 2.0.0, you can now choose between two different
|
71
|
+
systems for managing how your tokens look.
|
86
72
|
|
87
|
-
|
73
|
+
For simple setup, you can use
|
74
|
+
combination of the [`length`](#length-length) and [`contains`](#contains-contains), which modify the length and
|
75
|
+
types of characters to use.
|
88
76
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
* `token :length => 4, :contains => :numeric` could generate anything
|
94
|
-
from `0` upto `9999` - but in a random order
|
95
|
-
* `token :length => 4, :contains => :fixed_numeric` will generate
|
96
|
-
anything from `0000` to `9999` in a random order
|
97
|
-
* `token :length => 4, :contains => :fixed_numeric_no_leading_zeros` will
|
98
|
-
generate anything from `1000` to `9999` in a random order
|
77
|
+
For when you need to generate more complex tokens, you can use the
|
78
|
+
[`pattern`](#pattern-pattern) option, which allows for very low-level control of the precise
|
79
|
+
structure of your tokens, as well as allowing for static strings, like
|
80
|
+
prefixes, infixes or suffixes.
|
99
81
|
|
82
|
+
#### Length (`:length`)
|
100
83
|
|
101
|
-
|
84
|
+
This one is easy, it's just an integer.
|
102
85
|
|
103
|
-
|
104
|
-
called `find_by_token`, e.g:
|
86
|
+
__Example:__
|
105
87
|
|
106
|
-
|
88
|
+
````
|
89
|
+
token :length => 6 # Tokens are now of length 6
|
90
|
+
token :length => 12 # Whow, whow, whow. Slow down egghead.
|
91
|
+
````
|
107
92
|
|
93
|
+
You get the idea.
|
108
94
|
|
109
|
-
|
95
|
+
The only caveat here is that if used in combination with the
|
96
|
+
`:contains => :numeric` option, tokens may vary in length _up to_ the
|
97
|
+
specified length.
|
110
98
|
|
111
|
-
|
112
|
-
tokens to - all you need to do is save each of your models and they will
|
113
|
-
be assigned a token, if it's missing.
|
99
|
+
#### Contains (`:contains`)
|
114
100
|
|
101
|
+
Contains has 7 different options:
|
115
102
|
|
116
|
-
|
103
|
+
* `:alphanumeric` - contains uppercase & lowercase characters, as well
|
104
|
+
as numbers
|
105
|
+
* `:alpha` - contains only uppercase & lowercase characters
|
106
|
+
* `:alpha_upper` - contains only uppercase letters
|
107
|
+
* `:alpha_lower` - contains only lowercase letters
|
108
|
+
* `:numeric` - integer, length may be shorter than `:length`
|
109
|
+
* `:fixed_numeric` - integer, but will always be of length `:length`
|
110
|
+
* `:fixed_numeric_no_leading_zeros` - same as `:fixed_numeric`, but will
|
111
|
+
never start with zeros
|
117
112
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
113
|
+
__Examples:__
|
114
|
+
````
|
115
|
+
token :contains => :alpha_upper, :length => 8
|
116
|
+
token :contains => :fixed_numeric
|
117
|
+
````
|
118
|
+
|
119
|
+
#### Patterns (`:pattern`)
|
120
|
+
|
121
|
+
New in 2.0.0, patterns allow you find-grained control over how your
|
122
|
+
tokens look. It's great for generating random data that has a
|
123
|
+
requirements to also have some basic structure. If you use the
|
124
|
+
`:pattern` option, it will override both the `:length` and `:contains`
|
125
|
+
options.
|
126
|
+
|
127
|
+
This was designed to operate in a similar way to something like `strftime`,
|
128
|
+
if the syntax offends you - please open an issue, I'd love to get some
|
129
|
+
feedback here and better refine how these are generated.
|
130
|
+
|
131
|
+
Any characters in the string are treated as static, except those that are
|
132
|
+
proceeded by a `%`. Those special characters represent a single, randomly
|
133
|
+
generated character, and are as follows:
|
134
|
+
|
135
|
+
* `%s` - any uppercase, lowercase, or numeric character
|
136
|
+
* `%w` - any uppercase, or lowercase character
|
137
|
+
* `%c` - any lowercase character
|
138
|
+
* `%C` - any uppercase character
|
139
|
+
* `%d` - any digit
|
140
|
+
* `%D` - any non-zero digit
|
141
|
+
|
142
|
+
__Example:__
|
143
|
+
|
144
|
+
````
|
145
|
+
token :pattern => "PRE-%C%C-%d%d%d%d" # Generates something like: 'PRE-ND-3485'
|
146
|
+
````
|
122
147
|
|
123
|
-
You can
|
148
|
+
You can also add a repetition modifier, which can help improve readability on
|
149
|
+
more complex patterns. You simply add any integer after the letter.
|
124
150
|
|
125
|
-
|
126
|
-
before eventually giving up and raising a
|
127
|
-
`Mongoid::Token::CollisionRetriesExceeded` exception. To take advantage
|
128
|
-
of this, one must set `persist_in_safe_mode = true` in your Mongoid
|
129
|
-
configuration.
|
151
|
+
__Examples:__
|
130
152
|
|
131
|
-
|
132
|
-
|
153
|
+
````
|
154
|
+
token :sku => "APP-%d6" # Generates something like; "APP-638924"
|
155
|
+
````
|
156
|
+
|
157
|
+
### Field Name (`:field_name`)
|
158
|
+
|
159
|
+
This allows you to change the field name used by `Mongoid::Token`
|
160
|
+
(default is `:token`). This is particularly handy when you're wanting to
|
161
|
+
use multiple tokens one a single document.
|
162
|
+
|
163
|
+
__Examples:__
|
164
|
+
````
|
165
|
+
token :length => 6
|
166
|
+
token :field_name => :sharing_token, :length => 12
|
167
|
+
token :field_name => :yet_another
|
168
|
+
````
|
169
|
+
|
170
|
+
|
171
|
+
### Skip Finders (`:skip_finders`)
|
172
|
+
|
173
|
+
This will prevent the gem from creating the customised finders and
|
174
|
+
overrides for the default `find` behaviour used by Mongoid.
|
175
|
+
|
176
|
+
__Example:__
|
177
|
+
````
|
178
|
+
token :skip_finders => true
|
179
|
+
````
|
180
|
+
|
181
|
+
|
182
|
+
### Override to_param (`:override_to_param`)
|
183
|
+
|
184
|
+
By default, `Mongoid::Token` will override to_param, to make it an easy
|
185
|
+
drop-in replacement for the default ObjectIDs. If needed, you can turn
|
186
|
+
this behaviour off:
|
187
|
+
|
188
|
+
__Example:__
|
189
|
+
````
|
190
|
+
token :override_to_param => false
|
191
|
+
````
|
192
|
+
|
193
|
+
|
194
|
+
### Retry Count (`:retry_count`)
|
133
195
|
|
134
|
-
|
135
|
-
times before
|
136
|
-
|
196
|
+
In the event of a token collision, this gem will attempt to try three
|
197
|
+
more times before raising a `Mongoid::Token::CollisionRetriesExceeded`
|
198
|
+
error. If you're wanting it to try harder, or less hard, then this
|
199
|
+
option is for you.
|
137
200
|
|
201
|
+
__Examples:__
|
202
|
+
````
|
203
|
+
token :retry_count => 9
|
204
|
+
token :retry_count => 0
|
205
|
+
````
|
138
206
|
|
139
207
|
# Notes
|
140
208
|
|
@@ -145,11 +213,13 @@ greatly appreciated.
|
|
145
213
|
Thanks to everyone that has contributed to this gem over the past year.
|
146
214
|
Many, many thanks - you guys rawk.
|
147
215
|
|
216
|
+
|
148
217
|
## Contributors:
|
149
218
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
219
|
+
Thanks to everyone who has provided support for this gem over the years.
|
220
|
+
In particular: [olliem](https://github.com/olliem),
|
221
|
+
[msolli](https://github.com/msolli),
|
222
|
+
[siong1987](https://github.com/siong1987),
|
223
|
+
[stephan778](https://github.com/stephan778),
|
224
|
+
[eagleas](https://github.com/eagleas), and
|
225
|
+
[jamesotron](https://github.com/jamesotron).
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mongoid/token/collisions'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Token
|
5
|
+
class CollisionResolver
|
6
|
+
attr_accessor :create_new_token
|
7
|
+
attr_reader :klass
|
8
|
+
attr_reader :field_name
|
9
|
+
attr_reader :retry_count
|
10
|
+
|
11
|
+
def initialize(klass, field_name, retry_count)
|
12
|
+
@create_new_token = Proc.new {|doc|}
|
13
|
+
@klass = klass
|
14
|
+
@field_name = field_name
|
15
|
+
@retry_count = retry_count
|
16
|
+
klass.send(:include, Mongoid::Token::Collisions)
|
17
|
+
alias_method_with_collision_resolution(:insert)
|
18
|
+
alias_method_with_collision_resolution(:upsert)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_new_token_for(document)
|
22
|
+
@create_new_token.call(document)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def alias_method_with_collision_resolution(method)
|
27
|
+
handler = self
|
28
|
+
klass.define_method(:"#{method.to_s}_with_#{handler.field_name}_safety") do |method_options = {}|
|
29
|
+
self.resolve_token_collisions handler do
|
30
|
+
with(:safe => true).send(:"#{method.to_s}_without_#{handler.field_name}_safety", method_options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
klass.alias_method_chain method.to_sym, :"#{handler.field_name}_safety"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Token
|
3
|
+
module Collisions
|
4
|
+
def resolve_token_collisions(resolver)
|
5
|
+
retries = resolver.retry_count
|
6
|
+
begin
|
7
|
+
yield
|
8
|
+
rescue Moped::Errors::OperationFailure => e
|
9
|
+
if is_duplicate_token_error?(e, self, resolver.field_name)
|
10
|
+
if (retries -= 1) >= 0
|
11
|
+
resolver.create_new_token_for(self)
|
12
|
+
retry
|
13
|
+
end
|
14
|
+
raise_collision_retries_exceeded_error resolver.field_name, resolver.retry_count
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def raise_collision_retries_exceeded_error(field_name, retry_count)
|
20
|
+
Rails.logger.warn "[Mongoid::Token] Warning: Maximum token generation retries (#{retry_count}) exceeded on `#{field_name}'." if defined?(Rails)
|
21
|
+
raise Mongoid::Token::CollisionRetriesExceeded.new(self, retry_count)
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_duplicate_token_error?(err, document, field_name)
|
25
|
+
[11000, 11001].include?(err.details['code']) &&
|
26
|
+
err.details['err'] =~ /dup key/ &&
|
27
|
+
err.details['err'] =~ /"#{document.send(field_name)}"/
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Token
|
3
|
+
module Finders
|
4
|
+
def self.define_custom_token_finder_for(klass, field_name = :token)
|
5
|
+
klass.define_singleton_method(:"find_by_#{field_name.to_s}") do |token|
|
6
|
+
self.find_by(field_name.to_sym => token)
|
7
|
+
end
|
8
|
+
|
9
|
+
klass.define_singleton_method :"find_with_#{field_name}" do |*args| # this is going to be painful if tokens happen to look like legal object ids
|
10
|
+
args.all?{|arg| Moped::BSON::ObjectId.legal?(arg)} ? send(:"find_without_#{field_name}",*args) : klass.send(:"find_by_#{field_name.to_s}", args.first)
|
11
|
+
end
|
12
|
+
|
13
|
+
# this craziness taken from, and then compacted into a string class_eval
|
14
|
+
# http://geoffgarside.co.uk/2007/02/19/activesupport-alias-method-chain-modules-and-class-methods/
|
15
|
+
klass.class_eval("class << self; alias_method_chain :find, :#{field_name} if self.method_defined?(:find); end")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# proposed pattern options
|
2
|
+
# %c - lowercase character
|
3
|
+
# %C - uppercase character
|
4
|
+
# %d - digit
|
5
|
+
# %D - non-zero digit / no-leading zero digit if longer than 1
|
6
|
+
# %s - alphanumeric character
|
7
|
+
# %w - upper and lower alpha character
|
8
|
+
# %p - URL-safe punctuation
|
9
|
+
#
|
10
|
+
# Any pattern can be followed by a number, representing how many of that type to generate
|
11
|
+
|
12
|
+
module Mongoid
|
13
|
+
module Token
|
14
|
+
module Generator
|
15
|
+
REPLACE_PATTERN = /%((?<character>[cCdDpsw]{1})(?<length>\d+(,\d+)?)?)/
|
16
|
+
|
17
|
+
def self.generate(pattern)
|
18
|
+
pattern.gsub REPLACE_PATTERN do |match|
|
19
|
+
match_data = $~
|
20
|
+
type = match_data[:character]
|
21
|
+
length = [match_data[:length].to_i, 1].max
|
22
|
+
|
23
|
+
case type
|
24
|
+
when 'c'
|
25
|
+
down_character(length)
|
26
|
+
when 'C'
|
27
|
+
up_character(length)
|
28
|
+
when 'd'
|
29
|
+
digits(length)
|
30
|
+
when 'D'
|
31
|
+
integer(length)
|
32
|
+
when 's'
|
33
|
+
alphanumeric(length)
|
34
|
+
when 'w'
|
35
|
+
alpha(length)
|
36
|
+
when 'p'
|
37
|
+
"-"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def self.down_character(length = 1)
|
44
|
+
Array.new(length).map{['a'..'z'].map{|r|r.to_a}.flatten[rand(26)]}.join
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.up_character(length = 1)
|
48
|
+
Array.new(length).map{['A'..'Z'].map{|r|r.to_a}.flatten[rand(26)]}.join
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.integer(length = 1)
|
52
|
+
(rand(10**length - 10**(length-1)) + 10**(length-1)).to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.digits(length = 1)
|
56
|
+
rand(10**length).to_s.rjust(length, "0")
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.alpha(length = 1)
|
60
|
+
Array.new(length).map{['A'..'Z','a'..'z'].map{|r|r.to_a}.flatten[rand(52)]}.join
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.alphanumeric(length = 1)
|
64
|
+
(1..length).collect { (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }.join
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.punctuation(length = 1)
|
68
|
+
Array.new(length).map{['.','-','_','=','+','$'].map{|r|r.to_a}.flatten[rand(52)]}.join
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Mongoid::Token::Options
|
2
|
+
def initialize(options = {})
|
3
|
+
@options = merge_defaults validate_options(options)
|
4
|
+
end
|
5
|
+
|
6
|
+
def length
|
7
|
+
@options[:length]
|
8
|
+
end
|
9
|
+
|
10
|
+
def retry_count
|
11
|
+
@options[:retry_count]
|
12
|
+
end
|
13
|
+
|
14
|
+
def contains
|
15
|
+
@options[:contains]
|
16
|
+
end
|
17
|
+
|
18
|
+
def field_name
|
19
|
+
@options[:field_name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def skip_finders?
|
23
|
+
@options[:skip_finders]
|
24
|
+
end
|
25
|
+
|
26
|
+
def override_to_param?
|
27
|
+
@options[:override_to_param]
|
28
|
+
end
|
29
|
+
|
30
|
+
def pattern
|
31
|
+
@options[:pattern] ||= case @options[:contains].to_sym
|
32
|
+
when :alphanumeric
|
33
|
+
"%s#{@options[:length]}"
|
34
|
+
when :alpha
|
35
|
+
"%w#{@options[:length]}"
|
36
|
+
when :alpha_upper
|
37
|
+
"%C#{@options[:length]}"
|
38
|
+
when :alpha_lower
|
39
|
+
"%c#{@options[:length]}"
|
40
|
+
when :numeric
|
41
|
+
"%d1,#{@options[:length]}"
|
42
|
+
when :fixed_numeric
|
43
|
+
"%d#{@options[:length]}"
|
44
|
+
when :fixed_numeric_no_leading_zeros
|
45
|
+
"%D#{@options[:length]}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def validate_options(options)
|
51
|
+
if options.has_key?(:retry)
|
52
|
+
STDERR.puts "Mongoid::Token Deprecation Warning: option `retry` has been renamed to `retry_count`. `:retry` will be removed in v2.1"
|
53
|
+
options[:retry_count] = options[:retry]
|
54
|
+
end
|
55
|
+
options
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge_defaults(options)
|
59
|
+
{
|
60
|
+
:length => 4,
|
61
|
+
:retry_count => 3,
|
62
|
+
:contains => :alphanumeric,
|
63
|
+
:field_name => :token,
|
64
|
+
:skip_finders => false,
|
65
|
+
:override_to_param => true,
|
66
|
+
}.merge(options)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'mongoid/token/exceptions'
|
2
|
+
require 'mongoid/token/options'
|
3
|
+
require 'mongoid/token/generator'
|
4
|
+
require 'mongoid/token/finders'
|
5
|
+
require 'mongoid/token/collision_resolver'
|
6
|
+
|
7
|
+
module Mongoid
|
8
|
+
module Token
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def initialize_copy(source)
|
13
|
+
super
|
14
|
+
self.token = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def token(*args)
|
18
|
+
options = Mongoid::Token::Options.new(args.extract_options!)
|
19
|
+
|
20
|
+
self.field options.field_name, :type => String, :default => nil
|
21
|
+
self.index({ options.field_name => 1 }, { :unique => true })
|
22
|
+
|
23
|
+
resolver = Mongoid::Token::CollisionResolver.new(self, options.field_name, options.retry_count)
|
24
|
+
resolver.create_new_token = Proc.new do |document|
|
25
|
+
document.send(:create_token, options.field_name, options.pattern)
|
26
|
+
end
|
27
|
+
|
28
|
+
if options.skip_finders? == false
|
29
|
+
Finders.define_custom_token_finder_for(self, options.field_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
set_callback(:create, :before) do |document|
|
33
|
+
document.create_token options.field_name, options.pattern
|
34
|
+
end
|
35
|
+
|
36
|
+
set_callback(:save, :before) do |document|
|
37
|
+
document.create_token_if_nil options.field_name, options.pattern
|
38
|
+
end
|
39
|
+
|
40
|
+
if options.override_to_param?
|
41
|
+
self.define_method :to_param do
|
42
|
+
self.send(options.field_name) || super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
def create_token(field_name, pattern)
|
50
|
+
self.send :"#{field_name.to_s}=", self.generate_token(pattern)
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_token_if_nil(field_name, pattern)
|
54
|
+
if self[field_name.to_sym].blank?
|
55
|
+
self.create_token field_name, pattern
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_token(pattern)
|
60
|
+
Mongoid::Token::Generator.generate pattern
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|