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/lib/mongoid_token.rb CHANGED
@@ -1,116 +1 @@
1
- require 'mongoid/token/exceptions'
2
-
3
- module Mongoid
4
- module Token
5
- extend ActiveSupport::Concern
6
-
7
- module ClassMethods
8
- def token(*args)
9
- options = args.extract_options!
10
- options[:length] ||= 4
11
- options[:retry] ||= 3
12
- options[:contains] ||= :alphanumeric
13
- options[:field_name] ||= :token
14
- #options[:key] ||= false
15
-
16
- self.field options[:field_name].to_sym, :type => String
17
- self.index({ options[:field_name].to_sym => 1 }, { :unique => true })
18
-
19
- #if options[:key]
20
- # self.key options[:field_name].to_sym
21
- #end
22
-
23
- set_callback(:create, :before) do |document|
24
- document.create_token(options[:length], options[:contains])
25
- end
26
-
27
- set_callback(:save, :before) do |document|
28
- document.create_token_if_nil(options[:length], options[:contains])
29
- end
30
-
31
- after_initialize do # set_callback did not work with after_initialize callback
32
- self.instance_variable_set :@max_collision_retries, options[:retry]
33
- self.instance_variable_set :@token_field_name, options[:field_name]
34
- self.instance_variable_set :@token_length, options[:length]
35
- self.instance_variable_set :@token_contains, options[:contains]
36
- end
37
-
38
- if options[:retry] > 0
39
- alias_method_chain :insert, :safety
40
- alias_method_chain :upsert, :safety
41
- end
42
-
43
- self.class_variable_set :@@token_field_name, options[:field_name]
44
- end
45
-
46
- def find_by_token(token)
47
- field_name = self.class_variable_get :@@token_field_name
48
- self.find_by(field_name.to_sym => token)
49
- end
50
- end
51
-
52
- def to_param
53
- self.send(@token_field_name.to_sym) || super
54
- end
55
-
56
- protected
57
-
58
- def resolve_token_collisions
59
- retries = @max_collision_retries
60
-
61
- begin
62
- yield
63
- rescue Moped::Errors::OperationFailure => e
64
- # This is horrible, but seems to be the only way to get the details of the exception?
65
- continue unless [11000, 11001].include?(e.details['code'])
66
- continue unless e.details['err'] =~ /dup key/ &&
67
- e.details['err'] =~ /"#{self.send(@token_field_name.to_sym)}"/
68
-
69
- if (retries -= 1) > 0
70
- self.create_token(@token_length, @token_contains)
71
- retry
72
- else
73
- Rails.logger.warn "[Mongoid::Token] Warning: Maximum to generation retries (#{@max_collision_retries}) exceeded." if defined?(Rails) && Rails.env == 'development'
74
- raise Mongoid::Token::CollisionRetriesExceeded.new(self, @max_collision_retries)
75
- end
76
- end
77
- end
78
-
79
- def insert_with_safety(options = {})
80
- resolve_token_collisions { with(:safe => true).insert_without_safety(options) }
81
- end
82
-
83
- def upsert_with_safety(options = {})
84
- resolve_token_collisions { with(:safe => true).upsert_without_safety(options) }
85
- end
86
-
87
- def create_token(length, characters)
88
- self.send(:"#{@token_field_name}=", self.generate_token(length, characters))
89
- end
90
-
91
- def create_token_if_nil(length, characters)
92
- if self[@token_field_name.to_sym].blank?
93
- self.create_token(length, characters)
94
- end
95
- end
96
-
97
- def generate_token(length, characters = :alphanumeric)
98
- case characters
99
- when :alphanumeric
100
- (1..length).collect { (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }.join
101
- when :numeric
102
- rand(10**length).to_s
103
- when :fixed_numeric
104
- rand(10**length).to_s.rjust(length,rand(10).to_s)
105
- when :fixed_numeric_no_leading_zeros
106
- (rand(10**length - 10**(length-1)) + 10**(length-1)).to_s
107
- when :alpha
108
- Array.new(length).map{['A'..'Z','a'..'z'].map{|r|r.to_a}.flatten[rand(52)]}.join
109
- when :alpha_lower
110
- Array.new(length).map{['a'..'z'].map{|r|r.to_a}.flatten[rand(26)]}.join
111
- when :alpha_upper
112
- Array.new(length).map{['A'..'Z'].map{|r|r.to_a}.flatten[rand(26)]}.join
113
- end
114
- end
115
- end
116
- end
1
+ require 'mongoid/token'
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module MongoidToken
2
- VERSION = "1.1.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -0,0 +1,92 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Mongoid::Token::Collisions do
4
+ let(:document) { Object.new }
5
+ describe "#resolve_token_collisions" do
6
+ context "when there is a duplicate token" do
7
+ let(:resolver) { double("Mongoid::Token::CollisionResolver") }
8
+
9
+ before(:each) do
10
+ resolver.stub(:field_name).and_return(:token)
11
+ resolver.stub(:create_new_token_for){|doc|}
12
+ document.class.send(:include, Mongoid::Token::Collisions)
13
+ document.stub(:is_duplicate_token_error?).and_return(true)
14
+ end
15
+
16
+ context "and there are zero retries" do
17
+ it "should raise an error after the first try" do
18
+ resolver.stub(:retry_count).and_return(0)
19
+ attempts = 0
20
+ expect{document.resolve_token_collisions(resolver) { attempts += 1; raise Moped::Errors::OperationFailure.new("","") }}.to raise_error Mongoid::Token::CollisionRetriesExceeded
21
+ expect(attempts).to eq 1
22
+ end
23
+ end
24
+
25
+ context "and retries is set to 1" do
26
+ it "should raise an error after retrying once" do
27
+ resolver.stub(:retry_count).and_return(1)
28
+ attempts = 0
29
+ expect{document.resolve_token_collisions(resolver) { attempts += 1; raise Moped::Errors::OperationFailure.new("","") }}.to raise_error Mongoid::Token::CollisionRetriesExceeded
30
+ expect(attempts).to eq 2
31
+ end
32
+ end
33
+
34
+ context "and retries is greater than 1" do
35
+ it "should raise an error after retrying" do
36
+ resolver.stub(:retry_count).and_return(3)
37
+ attempts = 0
38
+ expect{document.resolve_token_collisions(resolver) { attempts += 1; raise Moped::Errors::OperationFailure.new("","") }}.to raise_error Mongoid::Token::CollisionRetriesExceeded
39
+ expect(attempts).to eq 4
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "#raise_collision_retries_exceeded_error" do
46
+ before(:each) do
47
+ document.class.send(:include, Mongoid::Token::Collisions)
48
+ end
49
+
50
+ it "should warn the rails logger" do
51
+ message = nil
52
+
53
+ stub_const("Rails", Class.new)
54
+
55
+ logger = double("logger")
56
+ logger.stub("warn"){ |msg| message = msg }
57
+ Rails.stub("logger").and_return(logger)
58
+
59
+ begin
60
+ document.raise_collision_retries_exceeded_error(:token, 3)
61
+ rescue
62
+ end
63
+ expect(message).to_not be_nil
64
+ end
65
+
66
+ it "should raise an error" do
67
+ expect{ document.raise_collision_retries_exceeded_error(:token, 3) }.to raise_error(Mongoid::Token::CollisionRetriesExceeded)
68
+ end
69
+ end
70
+
71
+ describe "#is_duplicate_token_error?" do
72
+ before(:each) do
73
+ document.class.send(:include, Mongoid::Token::Collisions)
74
+ end
75
+ context "when there is a duplicate key error" do
76
+ it "should return true" do
77
+ document.stub("token").and_return("tokenvalue123")
78
+ err = double("Moped::Errors::OperationFailure")
79
+ err.stub("details").and_return do
80
+ {
81
+ "err" => "E11000 duplicate key error index: mongoid_token_test.links.$token_1 dup key: { : \"tokenvalue123\" }",
82
+ "code" => 11000,
83
+ "n" => 0,
84
+ "connectionId" => 130,
85
+ "ok" => 1.0
86
+ }
87
+ document.is_duplicate_token_error?(err, document, :token)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Mongoid::Token::CollisionRetriesExceeded do
4
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Mongoid::Token::Finders do
4
+ before :each do
5
+ end
6
+
7
+ it "define a finder based on a field_name" do
8
+ klass = Class.new
9
+ field = :another_token
10
+ Mongoid::Token::Finders.define_custom_token_finder_for(klass, field)
11
+ klass.singleton_methods.should include(:"find_by_#{field}")
12
+ end
13
+
14
+ it "override the `find` method of the document" do
15
+ klass = Class.new
16
+ klass.define_singleton_method(:find) {|*args| :original_find }
17
+ klass.define_singleton_method(:find_by) {|*args| :token_find }
18
+
19
+ Mongoid::Token::Finders.define_custom_token_finder_for(klass)
20
+
21
+ klass.find(Moped::BSON::ObjectId.new).should == :original_find
22
+ klass.find(Moped::BSON::ObjectId.new, Moped::BSON::ObjectId.new).should == :original_find
23
+ klass.find().should == :original_find
24
+ klass.find(Moped::BSON::ObjectId.new, "token").should == :token_find
25
+ klass.find("token").should == :token_find
26
+ end
27
+
28
+ it "retrieve a document using the dynamic finder" do
29
+ class Document; include Mongoid::Document; field :token; end
30
+ document = Document.create!(:token => "1234")
31
+ Mongoid::Token::Finders.define_custom_token_finder_for(Document)
32
+ Document.find_by_token("1234").should == document
33
+ end
34
+
35
+ it "retrieve a document using the `find` method" do
36
+ class Document; include Mongoid::Document; field :token; end
37
+ document = Document.create! :token => "1234"
38
+ Mongoid::Token::Finders.define_custom_token_finder_for(Document)
39
+ Document.find("1234").should == document
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Mongoid::Token::Generator do
4
+ describe "#generate" do
5
+ it "generates lowercase characters" do
6
+ 100.times{ Mongoid::Token::Generator.generate("%c").should =~ /[a-z]/ }
7
+ end
8
+
9
+ it "generates uppercase characters" do
10
+ 100.times{ Mongoid::Token::Generator.generate("%C").should =~ /[A-Z]/ }
11
+ end
12
+
13
+ it "generates digits" do
14
+ 100.times{ Mongoid::Token::Generator.generate("%d").should =~ /[0-9]/ }
15
+ end
16
+
17
+ it "generates non-zero digits" do
18
+ 100.times{ Mongoid::Token::Generator.generate("%D").should =~ /[1-9]/ }
19
+ end
20
+
21
+ it "generates alphanumeric characters" do
22
+ 100.times{ Mongoid::Token::Generator.generate("%s").should =~ /[A-Za-z0-9]/ }
23
+ end
24
+
25
+ it "generates upper and lowercase characters" do
26
+ 100.times{ Mongoid::Token::Generator.generate("%w").should =~ /[A-Za-z]/ }
27
+ end
28
+
29
+ it "generates URL-safe punctuation" do
30
+ 100.times{ Mongoid::Token::Generator.generate("%p").should =~ /[\.\-\_\=\+\$]/ }
31
+ end
32
+
33
+ it "generates patterns of a fixed length" do
34
+ 100.times{ Mongoid::Token::Generator.generate("%s8").should =~ /[A-Za-z0-9]{8}/ }
35
+ end
36
+
37
+ it "generates patterns of a variable length" do
38
+ 100.times{ Mongoid::Token::Generator.generate("%s1,5").should =~ /[A-Za-z0-9]{1,5}/ }
39
+ end
40
+
41
+ it "generates patterns with static prefixes/suffixes" do
42
+ 100.times { Mongoid::Token::Generator.generate("prefix-%s4-suffix").should =~ /prefix\-[A-Za-z0-9]{4}\-suffix/ }
43
+ end
44
+
45
+ it "generates more complex patterns" do
46
+ 100.times { Mongoid::Token::Generator.generate("pre-%d4-%C3-%d4").should =~ /pre\-[0-9]{4}\-[A-Z]{3}\-[0-9]{4}/ }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,70 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Mongoid::Token::Options do
4
+ before do
5
+ @options = Mongoid::Token::Options.new(
6
+ {
7
+ :length => 9999,
8
+ :retry_count => 8888,
9
+ :contains => :nonsense,
10
+ :field_name => :not_a_token
11
+ }
12
+ )
13
+ end
14
+
15
+ it "should have a length" do
16
+ @options.length.should == 9999
17
+ end
18
+
19
+ it "should default to a length of 4" do
20
+ Mongoid::Token::Options.new.length.should == 4
21
+ end
22
+
23
+ it "should have a retry count" do
24
+ @options.retry_count.should == 8888
25
+ end
26
+
27
+ it "should default to a retry count of 3" do
28
+ Mongoid::Token::Options.new.retry_count.should == 3
29
+ end
30
+
31
+ it "should have a list of characters to contain" do
32
+ @options.contains.should == :nonsense
33
+ end
34
+
35
+ it "should default to an alphanumeric set of characters to contain" do
36
+ Mongoid::Token::Options.new.contains.should == :alphanumeric
37
+ end
38
+
39
+ it "should have a field name" do
40
+ @options.field_name.should == :not_a_token
41
+ end
42
+
43
+ it "should default to a field name of 'token'" do
44
+ Mongoid::Token::Options.new.field_name.should == :token
45
+ end
46
+
47
+ it "should create a pattern" do
48
+ Mongoid::Token::Options.new.pattern.should == "%s4"
49
+ end
50
+
51
+ describe "override_to_param" do
52
+ it "should be an option" do
53
+ expect(Mongoid::Token::Options.new({:override_to_param => false}).override_to_param?).to eq false
54
+ end
55
+
56
+ it "should default to true" do
57
+ expect(Mongoid::Token::Options.new.override_to_param?).to eq true
58
+ end
59
+ end
60
+
61
+ describe "skip_finder" do
62
+ it "should be an option" do
63
+ expect(Mongoid::Token::Options.new({:skip_finders => true}).skip_finders?).to eq true
64
+ end
65
+
66
+ it "should default to false" do
67
+ expect(Mongoid::Token::Options.new.skip_finders?).to eq false
68
+ end
69
+ end
70
+ end