mongoid-ids 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.autotest +0 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.travis.yml +18 -0
- data/Gemfile +10 -0
- data/Guardfile +22 -0
- data/MIT-LICENSE.txt +22 -0
- data/README.md +225 -0
- data/Rakefile +2 -0
- data/benchmarks/benchmark.rb +48 -0
- data/lib/mongoid/ids/collision_resolver.rb +37 -0
- data/lib/mongoid/ids/collisions.rb +33 -0
- data/lib/mongoid/ids/exceptions.rb +16 -0
- data/lib/mongoid/ids/finders.rb +15 -0
- data/lib/mongoid/ids/generator.rb +76 -0
- data/lib/mongoid/ids/options.rb +78 -0
- data/lib/mongoid/ids/version.rb +5 -0
- data/lib/mongoid/ids.rb +83 -0
- data/mongoid-ids.gemspec +22 -0
- data/spec/mongoid/ids/collisions_spec.rb +102 -0
- data/spec/mongoid/ids/exceptions_spec.rb +4 -0
- data/spec/mongoid/ids/finders_spec.rb +41 -0
- data/spec/mongoid/ids/generator_spec.rb +49 -0
- data/spec/mongoid/ids/options_spec.rb +74 -0
- data/spec/mongoid/token_spec.rb +396 -0
- data/spec/spec_helper.rb +35 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f98fcca5a53dd0256f8e74ab38719ce9bd2e001b
|
4
|
+
data.tar.gz: 47b8832b2ff1d6a217aac882b275770ff9c7fb24
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 37852de0e84195e089881f026e322679977bfc3a576d5bfd57dcd14e97c6983110875ee5896a7b318509d5a983d6e07a22f4f08ae7edfc99808c0950cbc18579
|
7
|
+
data.tar.gz: cc4fa090eabec9cd29b999e04406d680e73537e2bec545d28b0f115665c2f40a0f782ed5aa016a1def94872e04953ceac65c5ca3e6bedcd780b55ecda59eb952
|
data/.autotest
ADDED
File without changes
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
2
|
+
# rspec may be run, below are examples of the most common uses.
|
3
|
+
# * bundler: 'bundle exec rspec'
|
4
|
+
# * bundler binstubs: 'bin/rspec'
|
5
|
+
# * spring: 'bin/rsspec' (This will use spring if running and you have
|
6
|
+
# installed the spring binstubs per the docs)
|
7
|
+
# * zeus: 'zeus rspec' (requires the server to be started separetly)
|
8
|
+
# * 'just' rspec: 'rspec'
|
9
|
+
|
10
|
+
# guard :rubocop do
|
11
|
+
guard :rubocop, all_on_start: false, keep_failed: false, cli: ['-D'] do
|
12
|
+
watch(%r{.+\.rb$})
|
13
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
14
|
+
end
|
15
|
+
|
16
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
17
|
+
watch(%r{^spec/.+_spec\.rb$})
|
18
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
19
|
+
watch(/^generators\/(.+)\.rb$/) { |_m| 'spec/schemaless/worker_spec' }
|
20
|
+
|
21
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
22
|
+
end
|
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Nicholas Bruning
|
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,225 @@
|
|
1
|
+
# Mongoid::Ids - Short snappy ids/tokens for Mongoid documents
|
2
|
+
|
3
|
+
[![Build Status](https://secure.travis-ci.org/nofxx/mongoid-ids.png)](http://travis-ci.org/nofxx/mongoid-ids)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/nofxx/mongoid-ids.png)](https://codeclimate.com/github/nofxx/mongoid-ids)
|
5
|
+
|
6
|
+
This library is a quick and simple way to generate unique, random tokens
|
7
|
+
for your mongoid documents, in the cases where you can't, or don't want
|
8
|
+
to use slugs, or the default MongoDB ObjectIDs.
|
9
|
+
|
10
|
+
Mongoid::Ids can help turn this:
|
11
|
+
|
12
|
+
http://bestappever.com/video/4dcfbb3c6a4f1d4c4a000012
|
13
|
+
|
14
|
+
Into something more like this:
|
15
|
+
|
16
|
+
http://bestappever.com/video/8tmQ9p
|
17
|
+
|
18
|
+
|
19
|
+
## Getting started
|
20
|
+
|
21
|
+
In your gemfile, add:
|
22
|
+
|
23
|
+
gem 'mongoid-ids', '~> 2.0.0'
|
24
|
+
|
25
|
+
Then update your bundle
|
26
|
+
|
27
|
+
$ bundle update
|
28
|
+
|
29
|
+
In your Mongoid documents, just add `include Mongoid::Ids` and the
|
30
|
+
`token` method will take care of all the setup, like so:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class Person
|
34
|
+
include Mongoid::Document
|
35
|
+
include Mongoid::Ids
|
36
|
+
|
37
|
+
field :name
|
38
|
+
|
39
|
+
token
|
40
|
+
end
|
41
|
+
|
42
|
+
```
|
43
|
+
|
44
|
+
And that's it! There's lots of configuration options too - which are all
|
45
|
+
listed [below](#configuration). By default, the `token` method will
|
46
|
+
create tokens 4 characters long, containing random alphanumeric characters.
|
47
|
+
|
48
|
+
__Note:__ Mongoid::Ids leverages Mongoid's 'safe mode' by
|
49
|
+
automatically creating a unique index on your documents using the token
|
50
|
+
field. In order to take advantage of this feature (and ensure that your
|
51
|
+
documents always have unique tokens) remember to create your indexes.
|
52
|
+
|
53
|
+
|
54
|
+
## Finders
|
55
|
+
|
56
|
+
By default, the gem will override the existing `find` method in Mongoid,
|
57
|
+
in order to search for documents based on their token first (although
|
58
|
+
the default behaviour of ObjectIDs is also there). You can disable these
|
59
|
+
with the [`skip_finders` configuration option](#skip-finders-skip_finders).
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
Video.find("x3v98")
|
63
|
+
Account.find("ACC-123456")
|
64
|
+
```
|
65
|
+
|
66
|
+
|
67
|
+
## Configuration
|
68
|
+
|
69
|
+
### Idss
|
70
|
+
|
71
|
+
As of `Mongoid::Ids` 2.0.0, you can now choose between two different
|
72
|
+
systems for managing how your tokens look.
|
73
|
+
|
74
|
+
For simple setup, you can use
|
75
|
+
combination of the [`length`](#length-length) and [`contains`](#contains-contains), which modify the length and
|
76
|
+
types of characters to use.
|
77
|
+
|
78
|
+
For when you need to generate more complex tokens, you can use the
|
79
|
+
[`pattern`](#patterns-pattern) option, which allows for very low-level control of the precise
|
80
|
+
structure of your tokens, as well as allowing for static strings, like
|
81
|
+
prefixes, infixes or suffixes.
|
82
|
+
|
83
|
+
#### Length (`:length`)
|
84
|
+
|
85
|
+
This one is easy, it's just an integer.
|
86
|
+
|
87
|
+
__Example:__
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
token :length => 6 # Tokens are now of length 6
|
91
|
+
token :length => 12 # Whow, whow, whow. Slow down egghead.
|
92
|
+
```
|
93
|
+
|
94
|
+
You get the idea.
|
95
|
+
|
96
|
+
The only caveat here is that if used in combination with the
|
97
|
+
`:contains => :numeric` option, tokens may vary in length _up to_ the
|
98
|
+
specified length.
|
99
|
+
|
100
|
+
#### Contains (`:contains`)
|
101
|
+
|
102
|
+
Contains has 7 different options:
|
103
|
+
|
104
|
+
* `:alphanumeric` - contains uppercase & lowercase characters, as well
|
105
|
+
as numbers
|
106
|
+
* `:alpha` - contains only uppercase & lowercase characters
|
107
|
+
* `:alpha_upper` - contains only uppercase letters
|
108
|
+
* `:alpha_lower` - contains only lowercase letters
|
109
|
+
* `:numeric` - integer, length may be shorter than `:length`
|
110
|
+
* `:fixed_numeric` - integer, but will always be of length `:length`
|
111
|
+
* `:fixed_numeric_no_leading_zeros` - same as `:fixed_numeric`, but will
|
112
|
+
never start with zeros
|
113
|
+
|
114
|
+
__Examples:__
|
115
|
+
```ruby
|
116
|
+
token :contains => :alpha_upper, :length => 8
|
117
|
+
token :contains => :fixed_numeric
|
118
|
+
```
|
119
|
+
|
120
|
+
#### Patterns (`:pattern`)
|
121
|
+
|
122
|
+
New in 2.0.0, patterns allow you fine-grained control over how your
|
123
|
+
tokens look. It's great for generating random data that has a
|
124
|
+
requirements to also have some basic structure. If you use the
|
125
|
+
`:pattern` option, it will override both the `:length` and `:contains`
|
126
|
+
options.
|
127
|
+
|
128
|
+
This was designed to operate in a similar way to something like `strftime`,
|
129
|
+
if the syntax offends you - please open an issue, I'd love to get some
|
130
|
+
feedback here and better refine how these are generated.
|
131
|
+
|
132
|
+
Any characters in the string are treated as static, except those that are
|
133
|
+
proceeded by a `%`. Those special characters represent a single, randomly
|
134
|
+
generated character, and are as follows:
|
135
|
+
|
136
|
+
* `%s` - any uppercase, lowercase, or numeric character
|
137
|
+
* `%w` - any uppercase, or lowercase character
|
138
|
+
* `%c` - any lowercase character
|
139
|
+
* `%C` - any uppercase character
|
140
|
+
* `%d` - any digit
|
141
|
+
* `%D` - any non-zero digit
|
142
|
+
|
143
|
+
__Example:__
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
token :pattern => "PRE-%C%C-%d%d%d%d" # Generates something like: 'PRE-ND-3485'
|
147
|
+
```
|
148
|
+
|
149
|
+
You can also add a repetition modifier, which can help improve readability on
|
150
|
+
more complex patterns. You simply add any integer after the letter.
|
151
|
+
|
152
|
+
__Examples:__
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
token :pattern => "APP-%d6" # Generates something like; "APP-638924"
|
156
|
+
```
|
157
|
+
|
158
|
+
### Field Name
|
159
|
+
|
160
|
+
This allows you to change the field name used by `Mongoid::Ids`
|
161
|
+
This is particularly handy to use multiple tokens one a single document.
|
162
|
+
|
163
|
+
__Examples:__
|
164
|
+
```ruby
|
165
|
+
token :length => 6
|
166
|
+
token :sharing_token, :length => 12
|
167
|
+
token :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
|
+
```ruby
|
178
|
+
token :skip_finders => true
|
179
|
+
```
|
180
|
+
|
181
|
+
|
182
|
+
### Override to_param (`:override_to_param`)
|
183
|
+
|
184
|
+
By default, `Mongoid::Ids` 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
|
+
```ruby
|
190
|
+
token :override_to_param => false
|
191
|
+
```
|
192
|
+
|
193
|
+
|
194
|
+
### Retry Count (`:retry_count`)
|
195
|
+
|
196
|
+
In the event of a token collision, this gem will attempt to try three
|
197
|
+
more times before raising a `Mongoid::Ids::CollisionRetriesExceeded`
|
198
|
+
error. If you're wanting it to try harder, or less hard, then this
|
199
|
+
option is for you.
|
200
|
+
|
201
|
+
__Examples:__
|
202
|
+
```ruby
|
203
|
+
token :retry_count => 9
|
204
|
+
token :retry_count => 0
|
205
|
+
```
|
206
|
+
|
207
|
+
# Notes
|
208
|
+
|
209
|
+
If you find a problem, please [submit an issue](http://github.com/nofxx/mongoid-ids/issues) (and a failing test, if
|
210
|
+
you can). Pull requests and feature requests are always welcome and
|
211
|
+
greatly appreciated.
|
212
|
+
|
213
|
+
Thanks to everyone that has contributed to this gem over the past year.
|
214
|
+
Many, many thanks - you guys rawk.
|
215
|
+
|
216
|
+
|
217
|
+
## Contributors:
|
218
|
+
|
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).
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
$: << File.expand_path("../../lib", __FILE__)
|
2
|
+
|
3
|
+
require 'database_cleaner'
|
4
|
+
require 'mongoid'
|
5
|
+
require 'mongoid/ids'
|
6
|
+
require 'benchmark'
|
7
|
+
|
8
|
+
Mongoid.configure do |config|
|
9
|
+
config.connect_to("mongoid_ids_benchmark2")
|
10
|
+
end
|
11
|
+
|
12
|
+
DatabaseCleaner.strategy = :truncation
|
13
|
+
|
14
|
+
# start benchmarks
|
15
|
+
|
16
|
+
TOKEN_LENGTH = 5
|
17
|
+
|
18
|
+
class Link
|
19
|
+
include Mongoid::Document
|
20
|
+
include Mongoid::Ids
|
21
|
+
field :url
|
22
|
+
token :length => TOKEN_LENGTH, :contains => :alphanumeric
|
23
|
+
end
|
24
|
+
|
25
|
+
class NoIdsLink
|
26
|
+
include Mongoid::Document
|
27
|
+
field :url
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_link(token = true)
|
31
|
+
if token
|
32
|
+
Link.create(:url => "http://involved.com.au")
|
33
|
+
else
|
34
|
+
NoIdsLink.create(:url => "http://involved.com.au")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Link.delete_all
|
39
|
+
Link.create_indexes
|
40
|
+
NoIdsLink.delete_all
|
41
|
+
num_records = [1, 50, 100, 1000, 2000, 3000, 5000, 10000, 30000, 50000]
|
42
|
+
puts "-- Alphanumeric token of length #{TOKEN_LENGTH} (#{62**TOKEN_LENGTH} possible tokens)"
|
43
|
+
Benchmark.bm do |b|
|
44
|
+
num_records.each do |qty|
|
45
|
+
b.report("#{qty.to_s.rjust(5, " ")} records "){ qty.times{ create_link(false) } }
|
46
|
+
b.report("#{qty.to_s.rjust(5, " ")} records tok"){ qty.times{ create_link } }
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mongoid/ids/collisions'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Ids
|
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::Ids::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.send(: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,33 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Ids
|
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
|
+
else
|
16
|
+
raise e
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def raise_collision_retries_exceeded_error(field_name, retry_count)
|
22
|
+
Rails.logger.warn "[Mongoid::Ids] Warning: Maximum token generation retries (#{retry_count}) exceeded on `#{field_name}'." if defined?(Rails)
|
23
|
+
raise Mongoid::Ids::CollisionRetriesExceeded.new(self, retry_count)
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_duplicate_token_error?(err, document, field_name)
|
27
|
+
[11000, 11001].include?(err.details['code']) &&
|
28
|
+
err.details['err'] =~ /dup key/ &&
|
29
|
+
err.details['err'] =~ /"#{document.send(field_name)}"/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Ids
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
class CollisionRetriesExceeded < Error
|
6
|
+
def initialize(resource = "unknown resource", attempts = "unspecified")
|
7
|
+
@resource = resource
|
8
|
+
@attempts = attempts
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"Failed to generate unique token for #{@resource} after #{@attempts} attempts."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Ids
|
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| BSON::ObjectId.legal?(arg)} ? send(:"find_without_#{field_name}",*args) : klass.send(:"find_by_#{field_name.to_s}", args.first)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,76 @@
|
|
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 Ids
|
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.rand_string_from_chars(chars, length = 1)
|
44
|
+
Array.new(length).map{ chars.sample }.join
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.down_character(length = 1)
|
48
|
+
self.rand_string_from_chars ('a'..'z').to_a, length
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.up_character(length = 1)
|
52
|
+
self.rand_string_from_chars ('A'..'Z').to_a, length
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.integer(length = 1)
|
56
|
+
(rand(10**length - 10**(length-1)) + 10**(length-1)).to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.digits(length = 1)
|
60
|
+
rand(10**length).to_s.rjust(length, "0")
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.alpha(length = 1)
|
64
|
+
self.rand_string_from_chars (('A'..'Z').to_a + ('a'..'z').to_a), length
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.alphanumeric(length = 1)
|
68
|
+
(1..length).collect { (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }.join
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.punctuation(length = 1)
|
72
|
+
self.rand_string_from_chars ['.','-','_','=','+','$'], length
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Ids
|
3
|
+
class Options
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = merge_defaults validate_options(options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def length
|
9
|
+
@options[:length]
|
10
|
+
end
|
11
|
+
|
12
|
+
def retry_count
|
13
|
+
@options[:retry_count]
|
14
|
+
end
|
15
|
+
|
16
|
+
def contains
|
17
|
+
@options[:contains]
|
18
|
+
end
|
19
|
+
|
20
|
+
def field_name
|
21
|
+
@options[:field_name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def field_name=(param)
|
25
|
+
return if param.nil? || param.empty?
|
26
|
+
@options[:field_name] = param
|
27
|
+
end
|
28
|
+
|
29
|
+
def skip_finders?
|
30
|
+
@options[:skip_finders]
|
31
|
+
end
|
32
|
+
|
33
|
+
def override_to_param?
|
34
|
+
@options[:override_to_param]
|
35
|
+
end
|
36
|
+
|
37
|
+
def pattern
|
38
|
+
@options[:pattern] ||= \
|
39
|
+
case @options[:contains].to_sym
|
40
|
+
when :alphanumeric
|
41
|
+
"%s#{@options[:length]}"
|
42
|
+
when :alpha
|
43
|
+
"%w#{@options[:length]}"
|
44
|
+
when :alpha_upper
|
45
|
+
"%C#{@options[:length]}"
|
46
|
+
when :alpha_lower
|
47
|
+
"%c#{@options[:length]}"
|
48
|
+
when :numeric
|
49
|
+
"%d1,#{@options[:length]}"
|
50
|
+
when :fixed_numeric
|
51
|
+
"%d#{@options[:length]}"
|
52
|
+
when :fixed_numeric_no_leading_zeros
|
53
|
+
"%D#{@options[:length]}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def validate_options(options)
|
59
|
+
if options.has_key?(:retry)
|
60
|
+
STDERR.puts "Mongoid::Ids Deprecation Warning: option `retry` has been renamed to `retry_count`. `:retry` will be removed in v2.1"
|
61
|
+
options[:retry_count] = options[:retry]
|
62
|
+
end
|
63
|
+
options
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge_defaults(options)
|
67
|
+
{
|
68
|
+
:length => 4,
|
69
|
+
:retry_count => 3,
|
70
|
+
:contains => :alphanumeric,
|
71
|
+
:field_name => :_id,
|
72
|
+
:skip_finders => false,
|
73
|
+
:override_to_param => true,
|
74
|
+
}.merge(options)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|