mongoid-ids 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,396 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Ids do
4
+ let(:document_class) do
5
+ Object.send(:remove_const, :Document) if Object.const_defined?(:Document)
6
+ class Document
7
+ include Mongoid::Document
8
+ include Mongoid::Ids
9
+ end
10
+ Class.new(Document)
11
+ end
12
+
13
+ let(:document) do
14
+ document_class.create
15
+ end
16
+
17
+ describe '#token' do
18
+ describe 'default "_id"' do
19
+
20
+ before(:each) { document_class.send(:token) }
21
+
22
+ it 'should be created' do
23
+ expect(document).to have_field(:_id)
24
+ end
25
+
26
+ it 'should be valid' do
27
+ expect(document).to be_valid
28
+ end
29
+
30
+ it 'should be persisted' do
31
+ document.save
32
+ # why the anonymous #document_class doesn't work?
33
+ expect(Document.count).to eq(1)
34
+ end
35
+
36
+ it 'should not create any secondary index' do
37
+ expect(document.index_specifications).to be_empty
38
+ end
39
+ end
40
+
41
+ describe 'field "token"' do
42
+ before(:each) { document_class.send(:token, :token) }
43
+ it 'should be created' do
44
+ expect(document).to have_field(:token)
45
+ end
46
+
47
+ it 'should be indexed' do
48
+ index = document.index_specifications.first
49
+ expect(index.fields).to eq([:token])
50
+ expect(index.options).to have_key(:unique)
51
+ expect(index.options).to have_key(:sparse)
52
+ end
53
+ end
54
+
55
+ describe 'options' do
56
+ it 'should accept custom field names' do
57
+ document_class.send(:token, :smells_as_sweet)
58
+ expect(document).to have_field(:smells_as_sweet)
59
+ end
60
+
61
+ it 'should accept custom lengths' do
62
+ document_class.send(:token, :length => 13)
63
+ expect(document.id.length).to eq 13
64
+ end
65
+
66
+ it 'should accept custom lengths on custom fields' do
67
+ document_class.send(:token, :sweet, :length => 13)
68
+ expect(document.sweet.length).to eq 13
69
+ end
70
+
71
+ it 'should not create any custom finder' do
72
+ class UntaintedDocument
73
+ include Mongoid::Document
74
+ include Mongoid::Ids
75
+ end
76
+ dc = Class.new(UntaintedDocument)
77
+
78
+ dc.send(:token, :skip_finders => true)
79
+ expect(dc.public_methods).to_not include(:find_with_id)
80
+ end
81
+
82
+ it 'should not create custom finders with default id' do
83
+ class UntaintedDocument
84
+ include Mongoid::Document
85
+ include Mongoid::Ids
86
+ end
87
+ dc = Class.new(UntaintedDocument)
88
+
89
+ dc.send(:token)
90
+ expect(dc.public_methods).to_not include(:find_with_id)
91
+ end
92
+
93
+ it 'should disable custom finders with optional field' do
94
+ class UntaintedDocument
95
+ include Mongoid::Document
96
+ include Mongoid::Ids
97
+ end
98
+ dc = Class.new(UntaintedDocument)
99
+
100
+ dc.send(:token, :token, skip_finders: true)
101
+ expect(dc.public_methods).to_not include(:find_with_token)
102
+ end
103
+
104
+ it 'should not change `to_param`' do
105
+ document_class.send(:token, override_to_param: false)
106
+ expect(document.to_param).to eq document.id
107
+ end
108
+
109
+ it 'should disable `to_param` overrides' do
110
+ document_class.send(:token, :token, override_to_param: false)
111
+ expect(document.to_param).to_not eq document.token
112
+ end
113
+
114
+ it 'should return id when token does not exist when calling `to_param`' do
115
+ document_class.send(:token, :token, override_to_param: true)
116
+ document.unset :token
117
+ expect(document.to_param).to eq document.id.to_s
118
+ end
119
+
120
+ describe 'contains' do
121
+ context 'with :alphanumeric' do
122
+ it 'should contain only letters and numbers' do
123
+ document_class.send(:token, contains: :alphanumeric, length: 64)
124
+ expect(document.id).to match(/[A-Za-z0-9]{64}/)
125
+ end
126
+
127
+ it 'should contain only letters and numbers on custom field' do
128
+ document_class.send(:token, :token, :contains => :alphanumeric, :length => 64)
129
+ expect(document.token).to match(/[A-Za-z0-9]{64}/)
130
+ end
131
+ end
132
+
133
+ context 'with :alpha' do
134
+ it 'should contain only letters' do
135
+ document_class.send(:token, contains: :alpha, length: 64)
136
+ expect(document.id).to match(/[A-Za-z]{64}/)
137
+ end
138
+
139
+ it 'should contain only letters on custom fields' do
140
+ document_class.send(:token, :token, contains: :alpha, length: 64)
141
+ expect(document.token).to match(/[A-Za-z]{64}/)
142
+ end
143
+ end
144
+
145
+ context 'with :alpha_upper' do
146
+ it 'should contain only uppercase letters' do
147
+ document_class.send(:token, contains: :alpha_upper, length: 64)
148
+ expect(document.id).to match(/[A-Z]{64}/)
149
+ end
150
+
151
+ it 'should contain only uppercase letters on custom fields' do
152
+ document_class.send(:token, :token, contains: :alpha_upper, length: 64)
153
+ expect(document.token).to match(/[A-Z]{64}/)
154
+ end
155
+ end
156
+
157
+ context "with :alpha_lower" do
158
+ it "should contain only lowercase letters" do
159
+ document_class.send(:token, :token, contains: :alpha_lower, length: 64)
160
+ expect(document.token).to match(/[a-z]{64}/)
161
+ end
162
+ end
163
+
164
+ context 'with :numeric' do
165
+ it 'should only contain numbers' do
166
+ document_class.send(:token, :token, :contains => :numeric, :length => 64)
167
+ expect(document.token).to match(/[0-9]{1,64}/)
168
+ end
169
+ end
170
+
171
+ context 'with :fixed_numeric' do
172
+ it 'should contain only numbers and be a fixed-length' do
173
+ document_class.send(:token, :token, :contains => :fixed_numeric, :length => 64)
174
+ expect(document.token).to match(/[0-9]{64}/)
175
+ end
176
+ end
177
+
178
+ context 'with :fixed_numeric_no_leading_zeros' do
179
+ it 'should contain only numbers, be a fixed length, and have no leading zeros' do
180
+ document_class.send(:token, :token, :contains => :fixed_numeric_no_leading_zeros, :length => 64)
181
+ expect(document.token).to match(/[1-9]{1}[0-9]{63}/)
182
+ end
183
+ end
184
+ end
185
+
186
+ describe 'pattern' do
187
+ it 'should conform' do
188
+ document_class.send(:token, :token, :pattern => "%d%d%d%d%C%C%C%C")
189
+ expect(document.token).to match(/[0-9]{4}[A-Z]{4}/)
190
+ end
191
+ context 'when there\'s a static prefix' do
192
+ it 'should start with the prefix' do
193
+ document_class.send(:token, :token, :pattern => "PREFIX-%d%d%d%d")
194
+ expect(document.token).to match(/PREFIX\-[0-9]{4}/)
195
+ end
196
+ end
197
+ context 'when there\'s an infix' do
198
+ it 'should contain the infix' do
199
+ document_class.send(:token, :token, :pattern => "%d%d%d%d-INFIX-%d%d%d%d")
200
+ expect(document.token).to match(/[0-9]{4}\-INFIX\-[0-9]{4}/)
201
+ end
202
+ end
203
+ context 'when there\'s a suffix' do
204
+ it 'should end with the suffix' do
205
+ document_class.send(:token, :token, :pattern => "%d%d%d%d-SUFFIX")
206
+ expect(document.token).to match(/[0-9]{4}\-SUFFIX/)
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ it 'should allow for multiple tokens of different names' do
213
+ document_class.send(:token, :token, :contains => :alpha_upper)
214
+ document_class.send(:token, :sharing_id, :contains => :alpha_lower)
215
+ expect(document.token).to match(/[A-Z]{4}/)
216
+ expect(document.sharing_id).to match(/[a-z]{4}/)
217
+ end
218
+ end
219
+
220
+ describe 'callbacks' do
221
+ context 'when the document is a new record' do
222
+ let(:document){ document_class.new }
223
+ it 'should create the token after being saved' do
224
+ document_class.send(:token, :token)
225
+ expect(document.token).to be_nil
226
+ document.save
227
+ expect(document.token).to_not be_nil
228
+ end
229
+ end
230
+
231
+ context 'when the document is not a new record' do
232
+ it 'should not change the token after being saved' do
233
+ document_class.send(:token, :token)
234
+ token_before = document.token
235
+ document.save
236
+ expect(document.token).to eq token_before
237
+ end
238
+ context 'and the token is nil' do
239
+ it 'should create a new token after being saved' do
240
+ document_class.send(:token, :token)
241
+ token_before = document.token
242
+ document.token = nil
243
+ document.save
244
+ expect(document.token).to_not be_nil
245
+ expect(document.token).to_not eq token_before
246
+ end
247
+ end
248
+ context 'when the document is initialized with a token' do
249
+ it 'should not change the token after being saved' do
250
+ document_class.send(:token, :token)
251
+ token = 'test token'
252
+ expect(document_class.create!(token: token).token).to eq token
253
+ end
254
+ end
255
+ end
256
+ context 'when the document is cloned' do
257
+ it 'should set the token to nil' do
258
+ document.class.send(:token, :token, length: 64, contains: :alpha_upper)
259
+ d2 = document.clone
260
+ expect(d2.token).to be_nil
261
+ end
262
+
263
+ it 'should generate a new token with the same options as the source document' do
264
+ document.class.send(:token, :token, length: 64, contains: :alpha_upper)
265
+ d2 = document.clone
266
+ d2.save
267
+ expect(d2.token).to_not eq document.token
268
+ expect(d2.token).to match(/[A-Z]{64}/)
269
+ end
270
+ end
271
+ end
272
+
273
+ describe 'finders' do
274
+ it 'should not create a custom find method for id' do
275
+ document_class.send(:token)
276
+ expect(document.class.public_methods).to_not include(:find_by_id)
277
+ end
278
+
279
+ it 'should create a custom find method' do
280
+ document_class.send(:token, field_name: :other_token)
281
+ expect(document.class.public_methods).to include(:find_by_other_token)
282
+ end
283
+ end
284
+
285
+ describe '.to_param' do
286
+ it 'should return the token' do
287
+ document_class.send(:token, :token)
288
+ expect(document.to_param).to eq document.token
289
+ end
290
+ end
291
+
292
+ describe 'collision resolution on id field' do
293
+ before(:each) do
294
+ document_class.send(:token)
295
+ document_class.create_indexes
296
+ end
297
+
298
+ context 'when creating a new record' do
299
+ it 'should raise an exception when collisions can\'t be resolved on save' do
300
+ allow_any_instance_of(document_class).to receive(:generate_token).and_return('1234')
301
+ # expect_any_instance_of(document_class)
302
+ # .to receive(:generate_token).exactly(3).times.and_return('1234')
303
+ # expect(d2).to receive(:generate_token).and_return('1234')
304
+ d2 = document.clone
305
+ expect { d2.save }.to raise_exception(Mongoid::Ids::CollisionRetriesExceeded)
306
+ # binding.pry
307
+ end
308
+
309
+ it 'should raise an exception when collisions can\'t be resolved on create!' do
310
+ allow_any_instance_of(document_class).to receive(:generate_token).and_return('1234')
311
+ document.save
312
+ expect { document_class.create! }.to raise_exception(Mongoid::Ids::CollisionRetriesExceeded)
313
+ end
314
+ end
315
+
316
+ it 'should not raise a custom error if another error is thrown during saving' do
317
+ I18n.enforce_available_locales = false # Supress warnings in this example
318
+ document_class.send(:field, :name)
319
+ document_class.send(:validates_presence_of, :name)
320
+ allow_any_instance_of(document_class).to receive(:generate_token).and_return('1234')
321
+ allow(document_class).to receive(:model_name).and_return(ActiveModel::Name.new(document_class, nil, "temp"))
322
+ expect { document_class.create! }.to raise_exception(Mongoid::Errors::Validations)
323
+ end
324
+
325
+ context 'with other unique indexes present' do
326
+ before(:each) do
327
+ document_class.send(:field, :name)
328
+ document_class.send(:index, { name: 1 }, { unique: true })
329
+ document_class.create_indexes
330
+ end
331
+
332
+ context 'when violating the other index' do
333
+ it 'should raise an operation failure' do
334
+ duplicate_name = 'Got Duped.'
335
+ document_class.create!(name: duplicate_name)
336
+ expect{ document_class.create!(name: duplicate_name) }
337
+ .to raise_exception(Moped::Errors::OperationFailure)
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ describe 'collision resolution on custom field' do
344
+ before(:each) do
345
+ document_class.send(:token, :token)
346
+ document_class.create_indexes
347
+ end
348
+
349
+ context 'when creating a new record' do
350
+ it 'should raise when collisions can\'t be resolved on save' do
351
+ document.token = '1234'
352
+ document.save
353
+ d2 = document.clone
354
+ expect(d2)
355
+ .to receive(:generate_token).exactly(3).times.and_return('1234')
356
+ expect { d2.save }
357
+ .to raise_exception(Mongoid::Ids::CollisionRetriesExceeded)
358
+ end
359
+
360
+ it 'should raise when collisions can\'t be resolved on create!' do
361
+ document.token = '1234'
362
+ document.save
363
+ allow_any_instance_of(document_class).to receive(:generate_token).and_return('1234')
364
+ expect { document_class.create! }
365
+ .to raise_exception(Mongoid::Ids::CollisionRetriesExceeded)
366
+ end
367
+ end
368
+
369
+ it 'should not raise a custom error if another error is thrown during saving' do
370
+ I18n.enforce_available_locales = false # Supress warnings in this example
371
+ document_class.send(:field, :name)
372
+ document_class.send(:validates_presence_of, :name)
373
+ allow_any_instance_of(document_class).to receive(:generate_token).and_return('1234')
374
+ allow(document_class).to receive(:model_name).and_return(ActiveModel::Name.new(document_class, nil, 'temp'))
375
+ expect{ document_class.create! }
376
+ .to raise_exception(Mongoid::Errors::Validations)
377
+ end
378
+
379
+ context 'with other unique indexes present' do
380
+ before(:each) do
381
+ document_class.send(:field, :name)
382
+ document_class.send(:index, { name: 1 }, { unique: true })
383
+ document_class.create_indexes
384
+ end
385
+
386
+ context 'when violating the other index' do
387
+ it 'should raise an operation failure' do
388
+ duplicate_name = 'Got Duped.'
389
+ document_class.create!(name: duplicate_name)
390
+ expect{ document_class.create!(name: duplicate_name) }
391
+ .to raise_exception(Moped::Errors::OperationFailure)
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,35 @@
1
+ # require 'codeclimate-test-reporter'
2
+ # CodeClimate::TestReporter.start
3
+
4
+ $: << File.expand_path("../../lib", __FILE__)
5
+
6
+ # require 'pry'
7
+ # require 'database_cleaner'
8
+ require 'mongoid'
9
+ require 'mongoid-rspec'
10
+
11
+ require 'mongoid/ids'
12
+
13
+ ENV['MONGOID_ENV'] = "test"
14
+
15
+ Mongoid.configure do |config|
16
+ config.sessions = {
17
+ default: {
18
+ database: 'mongoid_ids_test',
19
+ hosts: [ "localhost:#{ENV['BOXEN_MONGODB_PORT'] || 27017}" ],
20
+ options: {}
21
+ }
22
+ }
23
+ end
24
+
25
+ RSpec.configure do |config|
26
+ config.include Mongoid::Matchers
27
+ # config.before(:suite) do
28
+ # # DatabaseCleaner.strategy = :truncation
29
+ # end
30
+
31
+ config.before(:each) do
32
+ # DatabaseCleaner.clean
33
+ Mongoid.purge!
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-ids
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Nicholas Bruning
8
+ - Marcos Piccinini
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-01-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 4.0.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 4.0.0
28
+ description: Mongoid token is a gem for creating random, unique tokens for mongoid
29
+ documents. Highly configurable and great for making URLs a little more compact.
30
+ email:
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".autotest"
36
+ - ".gitignore"
37
+ - ".rspec"
38
+ - ".travis.yml"
39
+ - Gemfile
40
+ - Guardfile
41
+ - MIT-LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - benchmarks/benchmark.rb
45
+ - lib/mongoid/ids.rb
46
+ - lib/mongoid/ids/collision_resolver.rb
47
+ - lib/mongoid/ids/collisions.rb
48
+ - lib/mongoid/ids/exceptions.rb
49
+ - lib/mongoid/ids/finders.rb
50
+ - lib/mongoid/ids/generator.rb
51
+ - lib/mongoid/ids/options.rb
52
+ - lib/mongoid/ids/version.rb
53
+ - mongoid-ids.gemspec
54
+ - spec/mongoid/ids/collisions_spec.rb
55
+ - spec/mongoid/ids/exceptions_spec.rb
56
+ - spec/mongoid/ids/finders_spec.rb
57
+ - spec/mongoid/ids/generator_spec.rb
58
+ - spec/mongoid/ids/options_spec.rb
59
+ - spec/mongoid/token_spec.rb
60
+ - spec/spec_helper.rb
61
+ homepage: http://github.com/nofxx/mongoid-ids
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project: mongoid-ids
81
+ rubygems_version: 2.4.5
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: A little random, unique token generator for Mongoid documents.
85
+ test_files:
86
+ - spec/mongoid/ids/collisions_spec.rb
87
+ - spec/mongoid/ids/exceptions_spec.rb
88
+ - spec/mongoid/ids/finders_spec.rb
89
+ - spec/mongoid/ids/generator_spec.rb
90
+ - spec/mongoid/ids/options_spec.rb
91
+ - spec/mongoid/token_spec.rb
92
+ - spec/spec_helper.rb
93
+ has_rdoc: