mongoid4-rspec 1.11.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +6 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +21 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +20 -0
  9. data/README.md +207 -0
  10. data/Rakefile +17 -0
  11. data/lib/matchers/accept_nested_attributes.rb +67 -0
  12. data/lib/matchers/allow_mass_assignment.rb +101 -0
  13. data/lib/matchers/associations.rb +316 -0
  14. data/lib/matchers/collections.rb +9 -0
  15. data/lib/matchers/document.rb +163 -0
  16. data/lib/matchers/indexes.rb +84 -0
  17. data/lib/matchers/validations.rb +81 -0
  18. data/lib/matchers/validations/acceptance_of.rb +9 -0
  19. data/lib/matchers/validations/associated.rb +19 -0
  20. data/lib/matchers/validations/confirmation_of.rb +9 -0
  21. data/lib/matchers/validations/custom_validation_of.rb +47 -0
  22. data/lib/matchers/validations/exclusion_of.rb +49 -0
  23. data/lib/matchers/validations/format_of.rb +71 -0
  24. data/lib/matchers/validations/inclusion_of.rb +49 -0
  25. data/lib/matchers/validations/length_of.rb +147 -0
  26. data/lib/matchers/validations/numericality_of.rb +74 -0
  27. data/lib/matchers/validations/presence_of.rb +9 -0
  28. data/lib/matchers/validations/uniqueness_of.rb +82 -0
  29. data/lib/matchers/validations/with_message.rb +27 -0
  30. data/lib/mongoid-rspec.rb +33 -0
  31. data/lib/mongoid-rspec/version.rb +5 -0
  32. data/mongoid-rspec.gemspec +25 -0
  33. data/mongoid4-rspec.gemspec +25 -0
  34. data/spec/models/article.rb +29 -0
  35. data/spec/models/comment.rb +6 -0
  36. data/spec/models/log.rb +4 -0
  37. data/spec/models/movie_article.rb +8 -0
  38. data/spec/models/permalink.rb +5 -0
  39. data/spec/models/person.rb +10 -0
  40. data/spec/models/profile.rb +16 -0
  41. data/spec/models/record.rb +5 -0
  42. data/spec/models/site.rb +9 -0
  43. data/spec/models/user.rb +36 -0
  44. data/spec/spec_helper.rb +32 -0
  45. data/spec/unit/accept_nested_attributes_spec.rb +12 -0
  46. data/spec/unit/associations_spec.rb +42 -0
  47. data/spec/unit/collections_spec.rb +7 -0
  48. data/spec/unit/document_spec.rb +21 -0
  49. data/spec/unit/indexes_spec.rb +13 -0
  50. data/spec/unit/validations_spec.rb +52 -0
  51. data/spec/validators/ssn_validator.rb +16 -0
  52. metadata +137 -0
@@ -0,0 +1,84 @@
1
+ module Mongoid
2
+ module Matchers
3
+ class HaveIndexForMatcher # :nodoc:
4
+ def initialize(index_fields)
5
+ @index_fields = index_fields.symbolize_keys!
6
+ end
7
+
8
+ def with_options(options = { })
9
+ @options = options
10
+ self
11
+ end
12
+
13
+ def matches?(klass)
14
+ @klass = klass.is_a?(Class) ? klass : klass.class
15
+ @errors = []
16
+
17
+ if @klass.respond_to?(:index_options)
18
+ # Mongoid 3
19
+ unless @klass.index_options[@index_fields]
20
+ @errors.push "no index for #{@index_fields}"
21
+ else
22
+ if !@options.nil? && !@options.empty?
23
+ @options.each do |option, option_value|
24
+ if denormalising_options(@klass.index_options[@index_fields])[option] != option_value
25
+ @errors.push "index for #{@index_fields.inspect} with options of #{index_options.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ else
31
+ # Mongoid 4
32
+ unless @klass.index_specifications.map(&:key).include?(@index_fields)
33
+ @errors.push "no index for #{@index_fields}"
34
+ else
35
+ if !@options.nil? && !@options.empty?
36
+ index_options = @klass.index_specifications.select { |is| is.key == @index_fields }.first.options
37
+ @options.each do |option, option_value|
38
+ if index_options[option] != option_value
39
+ @errors.push "index for #{@index_fields.inspect} with options of #{@klass.index_options[@index_fields].inspect}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ @errors.empty?
47
+ end
48
+
49
+ def failure_message_for_should
50
+ "Expected #{@klass.inspect} to #{description}, got #{@errors.to_sentence}"
51
+ end
52
+
53
+ def failure_message_for_should_not
54
+ "Expected #{@klass.inspect} to not #{description}, got #{@klass.inspect} to #{description}"
55
+ end
56
+
57
+ alias :failure_message :failure_message_for_should
58
+ alias :failure_message_when_negated :failure_message_for_should_not
59
+
60
+ def description
61
+ desc = "have an index for #{@index_fields.inspect}"
62
+ desc << " with options of #{@options.inspect}" if @options
63
+ desc
64
+ end
65
+
66
+ private
67
+ MAPPINGS = {
68
+ dropDups: :drop_dups
69
+ }
70
+
71
+ def denormalising_options(opts)
72
+ options = {}
73
+ opts.each_pair do |option, value|
74
+ options[MAPPINGS[option] || option] = value
75
+ end
76
+ options
77
+ end
78
+ end
79
+
80
+ def have_index_for(index_fields)
81
+ HaveIndexForMatcher.new(index_fields)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,81 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+
5
+ class HaveValidationMatcher
6
+
7
+ def initialize(field, validation_type)
8
+ @field = field.to_s
9
+ @type = validation_type.to_s
10
+ @options = {}
11
+ end
12
+
13
+ def matches?(actual)
14
+ @klass = actual.is_a?(Class) ? actual : actual.class
15
+
16
+ @validator = @klass.validators_on(@field).detect{ |v|
17
+ v.kind.to_s == @type and (!v.options[:on] or on_options_matches?(v))
18
+ }
19
+
20
+ if @validator
21
+ @negative_result_message = "#{@type.inspect} validator on #{@field.inspect}"
22
+ @positive_result_message = "#{@type.inspect} validator on #{@field.inspect}"
23
+ else
24
+ @negative_result_message = "no #{@type.inspect} validator on #{@field.inspect}"
25
+ return false
26
+ end
27
+ @result = true
28
+ check_on if @options[:on]
29
+ @result
30
+ end
31
+
32
+ def failure_message_for_should
33
+ "Expected #{@klass.inspect} to #{description}; instead got #{@negative_result_message}"
34
+ end
35
+
36
+ def failure_message_for_should_not
37
+ "Expected #{@klass.inspect} to not #{description}; instead got #{@positive_result_message}"
38
+ end
39
+
40
+ alias :failure_message :failure_message_for_should
41
+ alias :failure_message_when_negated :failure_message_for_should_not
42
+
43
+ def description
44
+ desc = "have #{@type.inspect} validator on #{@field.inspect}"
45
+ desc << " on #{@options[:on]}" if @options[:on]
46
+ desc
47
+ end
48
+
49
+ def on(*on_method)
50
+ @options[:on] = on_method.flatten
51
+ self
52
+ end
53
+
54
+ def check_on
55
+ validator_on_methods = [@validator.options[:on]].flatten
56
+
57
+ if validator_on_methods.any?
58
+ message = " on methods: #{validator_on_methods}"
59
+
60
+ if on_options_covered_by?( @validator )
61
+ @positive_result_message << message
62
+ else
63
+ @negative_result_message << message
64
+ @result = false
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def on_options_matches?(validator)
72
+ @options[:on] and validator.options[:on] and on_options_covered_by?(validator)
73
+ end
74
+
75
+ def on_options_covered_by?(validator)
76
+ ([@options[:on]].flatten - [validator.options[:on]].flatten).empty?
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,9 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ def validate_acceptance_of(field)
5
+ HaveValidationMatcher.new(field, :acceptance)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ class ValidateAssociatedMatcher < HaveValidationMatcher
5
+ def initialize(name)
6
+ super(name, :associated)
7
+ end
8
+
9
+ def description
10
+ "validate associated #{@field.inspect}"
11
+ end
12
+ end
13
+
14
+ def validate_associated(association_name)
15
+ ValidateAssociatedMatcher.new(association_name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ def validate_confirmation_of(field)
5
+ HaveValidationMatcher.new(field, :confirmation)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ class ValidateWithCustomValidatorMatcher < HaveValidationMatcher
5
+ include WithMessage
6
+ def initialize(field)
7
+ super(field, :custom)
8
+ end
9
+
10
+ def with_validator(custom_validator)
11
+ @custom_validator = custom_validator
12
+ self
13
+ end
14
+
15
+ def matches?(actual)
16
+ return false unless (@result = super(actual))
17
+ check_custom_validator if @custom_validator
18
+ check_expected_message if @expected_message
19
+
20
+ @result
21
+ end
22
+
23
+ def description
24
+ options_desc = []
25
+ options_desc << " with custom validator #{@custom_validator.name}" if @validator
26
+ options_desc << " with message '#{@expected_message}'" if @expected_message
27
+ "validate field #{@field.inspect}" << options_desc.to_sentence
28
+ end
29
+
30
+ private
31
+
32
+ def check_custom_validator
33
+ if @validator.kind_of? @custom_validator
34
+ @positive_result_message << " with custom validator of type #{@custom_validator.name}"
35
+ else
36
+ @negative_result_message << " with custom validator not of type #{@custom_validator.name}"
37
+ @result = false
38
+ end
39
+ end
40
+ end
41
+
42
+ def custom_validate(field)
43
+ ValidateWithCustomValidatorMatcher.new(field)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ class ValidateExclusionOfMatcher < HaveValidationMatcher
5
+ def initialize(name)
6
+ super(name, :exclusion)
7
+ end
8
+
9
+ def to_not_allow(*values)
10
+ @not_allowed_values = [values].flatten
11
+ self
12
+ end
13
+
14
+ def matches?(actual)
15
+ return false unless result = super(actual)
16
+
17
+ if @not_allowed_values
18
+ raw_validator_not_allowed_values = @validator.options[:in]
19
+
20
+ validator_not_allowed_values = case raw_validator_not_allowed_values
21
+ when Range then raw_validator_not_allowed_values.to_a
22
+ when Proc then raw_validator_not_allowed_values.call(actual)
23
+ else raw_validator_not_allowed_values end
24
+
25
+ allowed_values = @not_allowed_values - validator_not_allowed_values
26
+ if allowed_values.empty?
27
+ @positive_result_message = @positive_result_message << " not allowing all values mentioned"
28
+ else
29
+ @negative_result_message = @negative_result_message << " allowing the following the ff. values: #{allowed_values.inspect}"
30
+ result = false
31
+ end
32
+ end
33
+
34
+ result
35
+ end
36
+
37
+ def description
38
+ options_desc = []
39
+ options_desc << " not allowing the ff. values: #{@not_allowed_values}" if @not_allowed_values
40
+ super << options_desc.to_sentence
41
+ end
42
+ end
43
+
44
+ def validate_exclusion_of(field)
45
+ ValidateExclusionOfMatcher.new(field)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,71 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ class ValidateFormatOfMatcher < HaveValidationMatcher
5
+ def initialize(field)
6
+ super(field, :format)
7
+ end
8
+
9
+ def with_format(format)
10
+ @format = format
11
+ self
12
+ end
13
+
14
+ def to_allow(valid_value)
15
+ @valid_value = valid_value
16
+ self
17
+ end
18
+
19
+ def not_to_allow(invalid_value)
20
+ @invalid_value = invalid_value
21
+ self
22
+ end
23
+
24
+ def matches?(actual)
25
+ return false unless result = super(actual)
26
+
27
+ if @format
28
+ if @validator.options[:with] == @format
29
+ @positive_result_message = @positive_result_message << " with format #{@validator.options[:format].inspect}"
30
+ else
31
+ @negative_result_message = @negative_result_message << " with format #{@validator.options[:format].inspect}"
32
+ result = false
33
+ end
34
+ end
35
+
36
+ if @valid_value
37
+ if @validator.options[:with] =~ @valid_value
38
+ @positive_result_message = @positive_result_message << " with #{@valid_value.inspect} as a valid value"
39
+ else
40
+ @negative_result_message = @negative_result_message << " with #{@valid_value.inspect} as an invalid value"
41
+ result = false
42
+ end
43
+ end
44
+
45
+ if @invalid_value
46
+ if !(@invalid_value =~ @validator.options[:with])
47
+ @positive_result_message = @positive_result_message << " with #{@invalid_value.inspect} as an invalid value"
48
+ else
49
+ @negative_result_message = @negative_result_message << " with #{@invalid_value.inspect} as a valid value"
50
+ result = false
51
+ end
52
+ end
53
+
54
+ result
55
+ end
56
+
57
+ def description
58
+ options_desc = []
59
+ options_desc << " with format #{@format.inspect}" if @format
60
+ options_desc << " allowing the value #{@valid_value.inspect}" if @valid_value
61
+ options_desc << " not allowing the value #{@invalid_value.inspect}" if @invalid_value
62
+ super << options_desc.to_sentence
63
+ end
64
+ end
65
+
66
+ def validate_format_of(field)
67
+ ValidateFormatOfMatcher.new(field)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,49 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ class ValidateInclusionOfMatcher < HaveValidationMatcher
5
+ def initialize(name)
6
+ super(name, :inclusion)
7
+ end
8
+
9
+ def to_allow(*values)
10
+ @allowed_values = values.map { |v| v.respond_to?(:to_a) ? v.to_a : v }.flatten
11
+ self
12
+ end
13
+
14
+ def matches?(actual)
15
+ return false unless result = super(actual)
16
+
17
+ if @allowed_values
18
+ raw_validator_allowed_values = @validator.options[:in]
19
+
20
+ validator_allowed_values = case raw_validator_allowed_values
21
+ when Range then raw_validator_allowed_values.to_a
22
+ when Proc then raw_validator_allowed_values.call(actual)
23
+ else raw_validator_allowed_values end
24
+
25
+ not_allowed_values = @allowed_values - validator_allowed_values
26
+ if not_allowed_values.empty?
27
+ @positive_result_message = @positive_result_message << " allowing all values mentioned"
28
+ else
29
+ @negative_result_message = @negative_result_message << " not allowing these values: #{not_allowed_values.inspect}"
30
+ result = false
31
+ end
32
+ end
33
+
34
+ result
35
+ end
36
+
37
+ def description
38
+ options_desc = []
39
+ options_desc << " allowing these values: #{@allowed_values}" if @allowed_values
40
+ super << options_desc.to_sentence
41
+ end
42
+ end
43
+
44
+ def validate_inclusion_of(field)
45
+ ValidateInclusionOfMatcher.new(field)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,147 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ class ValidateLengthOfMatcher < HaveValidationMatcher
5
+ include WithMessage
6
+
7
+ def initialize(name)
8
+ super(name, :length)
9
+ end
10
+
11
+ def with_maximum(value)
12
+ @maximum = value
13
+ self
14
+ end
15
+ alias :less_than :with_maximum
16
+
17
+ def with_minimum(value)
18
+ @minimum = value
19
+ self
20
+ end
21
+ alias :greater_than :with_minimum
22
+
23
+ def within(value)
24
+ @within = value
25
+ self
26
+ end
27
+ alias :in :within
28
+
29
+ def as_exactly(value)
30
+ @is = value
31
+ self
32
+ end
33
+ alias :is :as_exactly
34
+
35
+ def with_message(message)
36
+ @expected_message = message
37
+ self
38
+ end
39
+
40
+ def matches?(actual)
41
+ return false unless @result = super(actual)
42
+
43
+ check_maximum if @maximum
44
+ check_minimum if @minimum
45
+ check_range if @within
46
+ check_exact if @is
47
+ check_expected_message if @expected_message
48
+
49
+ @result
50
+ end
51
+
52
+ def description
53
+ options_desc = []
54
+ options_desc << "with minimum of #{@minimum}" if @minimum
55
+ options_desc << "with maximum of #{@maximum}" if @maximum
56
+ options_desc << "within the range of #{@within}" if @within
57
+ options_desc << "as exactly #{@is}" if @is
58
+ options_desc << "with message '#{@expected_message}'" if @expected_message
59
+ super << " #{options_desc.to_sentence}"
60
+ end
61
+
62
+ private
63
+
64
+ def check_maximum
65
+ if actual_max.nil?
66
+ @negative_result_message << " with no maximum"
67
+ @result = false
68
+ elsif actual_max == @maximum
69
+ @positive_result_message << " with maximum of #{@maximum}"
70
+ else
71
+ @negative_result_message << " with maximum of #{actual_max}"
72
+ @result = false
73
+ end
74
+ end
75
+
76
+ def check_minimum
77
+ if actual_min.nil?
78
+ @negative_result_message << " with no minimum"
79
+ @result = false
80
+ elsif actual_min == @minimum
81
+ @positive_result_message << " with minimum of #{@minimum}"
82
+ else
83
+ @negative_result_message << " with minimum of #{actual_min}"
84
+ @result = false
85
+ end
86
+ end
87
+
88
+ def check_range
89
+ min, max = [@within.min, @within.max]
90
+ if !actual_min.nil? and actual_max.nil?
91
+ @negative_result_message << " with no minimum but with maximum of #{actual_max}"
92
+ @result = false
93
+ elsif actual_min.nil? and !actual_max.nil?
94
+ @negative_result_message << " with minimum_of #{actual_min} but no maximum"
95
+ @result = false
96
+ elsif actual_min.nil? and actual_max.nil?
97
+ @negative_result_message << " with no minimum and maximum"
98
+ @result = false
99
+ elsif actual_min == min && actual_max == max
100
+ @positive_result_message << " within the range of #{@within.inspect}"
101
+ else
102
+ @negative_result_message << " within the range of #{(actual_min..actual_max).inspect}"
103
+ @result = false
104
+ end
105
+ end
106
+
107
+ def check_exact
108
+ if actual_is == @is
109
+ @positive_result_message << " as exactly #{@is}"
110
+ else
111
+ @negative_result_message << " as exactly #{actual_is}"
112
+ @result = false
113
+ end
114
+ end
115
+
116
+ def check_expected_message
117
+ actual_message = @validator.options[:message]
118
+ if actual_message.nil?
119
+ @negative_result_message << " with no custom message"
120
+ @result = false
121
+ elsif actual_message == @expected_message
122
+ @positive_result_message << " with custom message '#{@expected_message}'"
123
+ else
124
+ @negative_result_message << " got message '#{actual_message}'"
125
+ @result = false
126
+ end
127
+ end
128
+
129
+ def actual_is
130
+ actual_is = @validator.options[:is]
131
+ end
132
+
133
+ def actual_min
134
+ @validator.options[:minimum] || ((@validator.options[:in] || @validator.options[:within]).try(&:min))
135
+ end
136
+
137
+ def actual_max
138
+ @validator.options[:maximum] || ((@validator.options[:in] || @validator.options[:within]).try(&:max))
139
+ end
140
+ end
141
+
142
+ def validate_length_of(field)
143
+ ValidateLengthOfMatcher.new(field)
144
+ end
145
+ end
146
+ end
147
+ end