mongoid_token 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|