mongoid-rspec 1.5.0 → 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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +14 -2
  6. data/README.md +188 -108
  7. data/lib/matchers/accept_nested_attributes.rb +65 -0
  8. data/lib/matchers/associations.rb +124 -22
  9. data/lib/matchers/document.rb +43 -4
  10. data/lib/matchers/indexes.rb +22 -10
  11. data/lib/matchers/validations/acceptance_of.rb +2 -2
  12. data/lib/matchers/validations/associated.rb +5 -5
  13. data/lib/matchers/validations/confirmation_of.rb +2 -2
  14. data/lib/matchers/validations/custom_validation_of.rb +2 -19
  15. data/lib/matchers/validations/exclusion_of.rb +8 -1
  16. data/lib/matchers/validations/format_of.rb +21 -21
  17. data/lib/matchers/validations/inclusion_of.rb +20 -13
  18. data/lib/matchers/validations/length_of.rb +66 -19
  19. data/lib/matchers/validations/numericality_of.rb +10 -10
  20. data/lib/matchers/validations/presence_of.rb +2 -2
  21. data/lib/matchers/validations/uniqueness_of.rb +13 -30
  22. data/lib/matchers/validations/with_message.rb +27 -0
  23. data/lib/matchers/validations.rb +45 -26
  24. data/lib/mongoid-rspec/version.rb +1 -1
  25. data/lib/mongoid-rspec.rb +5 -1
  26. data/mongoid-rspec.gemspec +2 -2
  27. data/spec/models/article.rb +18 -9
  28. data/spec/models/comment.rb +2 -2
  29. data/spec/models/movie_article.rb +6 -5
  30. data/spec/models/permalink.rb +1 -1
  31. data/spec/models/person.rb +0 -3
  32. data/spec/models/profile.rb +9 -7
  33. data/spec/models/record.rb +1 -1
  34. data/spec/models/site.rb +2 -3
  35. data/spec/models/user.rb +20 -12
  36. data/spec/unit/accept_nested_attributes_spec.rb +12 -0
  37. data/spec/unit/allow_mass_assignment_spec.rb +1 -1
  38. data/spec/unit/associations_spec.rb +6 -6
  39. data/spec/unit/document_spec.rb +8 -5
  40. data/spec/unit/indexes_spec.rb +3 -2
  41. data/spec/unit/validations_spec.rb +8 -0
  42. data/spec/validators/ssn_validator.rb +3 -1
  43. metadata +40 -28
  44. data/.rvmrc +0 -1
@@ -5,11 +5,21 @@ module Mongoid
5
5
  @attributes = attrs.collect(&:to_s)
6
6
  end
7
7
 
8
+ def localized
9
+ @localized = true
10
+ self
11
+ end
12
+
8
13
  def of_type(type)
9
14
  @type = type
10
15
  self
11
16
  end
12
17
 
18
+ def with_alias(field_alias)
19
+ @field_alias = field_alias
20
+ self
21
+ end
22
+
13
23
  def with_default_value_of(default)
14
24
  @default = default
15
25
  self
@@ -25,11 +35,26 @@ module Mongoid
25
35
  error << " of type #{@klass.fields[attr].type}"
26
36
  end
27
37
 
28
- if !@default.nil? and !@klass.fields[attr].default_val.nil? and @klass.fields[attr].default_val != @default
29
- error << " with default value of #{@klass.fields[attr].default_val}"
38
+ if !@default.nil?
39
+ if @klass.fields[attr].default_val.nil?
40
+ error << " with default not set"
41
+ elsif @klass.fields[attr].default_val != @default
42
+ error << " with default value of #{@klass.fields[attr].default_val}"
43
+ end
44
+ end
45
+
46
+ if @field_alias and @klass.fields[attr].options[:as] != @field_alias
47
+ error << " with alias #{@klass.fields[attr].options[:as]}"
30
48
  end
31
49
 
32
50
  @errors.push("field #{attr.inspect}" << error) unless error.blank?
51
+
52
+ if @localized
53
+ unless @klass.fields[attr].localized?
54
+ @errors.push "is not localized #{attr.inspect}"
55
+ end
56
+ end
57
+
33
58
  else
34
59
  @errors.push "no field named #{attr.inspect}"
35
60
  end
@@ -48,6 +73,7 @@ module Mongoid
48
73
  def description
49
74
  desc = "have #{@attributes.size > 1 ? 'fields' : 'field'} named #{@attributes.collect(&:inspect).to_sentence}"
50
75
  desc << " of type #{@type.inspect}" if @type
76
+ desc << " with alias #{@field_alias}" if @field_alias
51
77
  desc << " with default value of #{@default.inspect}" if !@default.nil?
52
78
  desc
53
79
  end
@@ -92,11 +118,24 @@ end
92
118
 
93
119
  RSpec::Matchers.define :be_timestamped_document do
94
120
  match do |doc|
95
- doc.class.included_modules.include?(Mongoid::Timestamps)
121
+ if [*@timestamped_module].any?
122
+ modules = [*@timestamped_module].map{|m| "Mongoid::Timestamps::#{m.to_s.classify}".constantize }
123
+ (modules - doc.class.included_modules).empty?
124
+ else
125
+ doc.class.included_modules.include?(Mongoid::Timestamps) or
126
+ doc.class.included_modules.include?(Mongoid::Timestamps::Created) or
127
+ doc.class.included_modules.include?(Mongoid::Timestamps::Updated)
128
+ end
129
+ end
130
+
131
+ chain :with do |timestamped_module|
132
+ @timestamped_module = timestamped_module
96
133
  end
97
134
 
98
135
  description do
99
- "be a timestamped Mongoid document"
136
+ desc = "be a timestamped Mongoid document"
137
+ desc << " with #{@timestamped_module}" if @timestamped_module
138
+ desc
100
139
  end
101
140
  end
102
141
 
@@ -2,33 +2,32 @@ module Mongoid
2
2
  module Matchers
3
3
  class HaveIndexForMatcher # :nodoc:
4
4
  def initialize(index_fields)
5
- @index_fields = index_fields
5
+ @index_fields = index_fields.symbolize_keys!
6
6
  end
7
-
8
- def with_options(options = {})
7
+
8
+ def with_options(options = { })
9
9
  @options = options
10
10
  self
11
11
  end
12
-
12
+
13
13
  def matches?(klass)
14
- @klass = klass.is_a?(Class) ? klass : klass.class
14
+ @klass = klass.is_a?(Class) ? klass : klass.class
15
15
  @errors = []
16
-
17
16
  unless @klass.index_options[@index_fields]
18
17
  @errors.push "no index for #{@index_fields}"
19
18
  else
20
19
  if !@options.nil? && !@options.empty?
21
20
  @options.each do |option, option_value|
22
- if @klass.index_options[@index_fields].include?(option) && @klass.index_options[@index_fields][option] != option_value
21
+ if denormalising_options(@klass.index_options[@index_fields])[option] != option_value
23
22
  @errors.push "index for #{@index_fields.inspect} with options of #{@klass.index_options[@index_fields].inspect}"
24
23
  end
25
24
  end
26
25
  end
27
26
  end
28
-
27
+
29
28
  @errors.empty?
30
29
  end
31
-
30
+
32
31
  def failure_message_for_should
33
32
  "Expected #{@klass.inspect} to #{description}, got #{@errors.to_sentence}"
34
33
  end
@@ -42,8 +41,21 @@ module Mongoid
42
41
  desc << " with options of #{@options.inspect}" if @options
43
42
  desc
44
43
  end
44
+
45
+ private
46
+ MAPPINGS = {
47
+ dropDups: :drop_dups
48
+ }
49
+
50
+ def denormalising_options(opts)
51
+ options = {}
52
+ opts.each_pair do |option, value|
53
+ options[MAPPINGS[option] || option] = value
54
+ end
55
+ options
56
+ end
45
57
  end
46
-
58
+
47
59
  def have_index_for(index_fields)
48
60
  HaveIndexForMatcher.new(index_fields)
49
61
  end
@@ -1,9 +1,9 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
3
+ module Validations
4
4
  def validate_acceptance_of(field)
5
5
  HaveValidationMatcher.new(field, :acceptance)
6
6
  end
7
7
  end
8
8
  end
9
- end
9
+ end
@@ -1,19 +1,19 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
3
+ module Validations
4
4
  class ValidateAssociatedMatcher < HaveValidationMatcher
5
5
  def initialize(name)
6
6
  super(name, :associated)
7
7
  end
8
-
8
+
9
9
  def description
10
10
  "validate associated #{@field.inspect}"
11
11
  end
12
12
  end
13
-
13
+
14
14
  def validate_associated(association_name)
15
15
  ValidateAssociatedMatcher.new(association_name)
16
- end
16
+ end
17
17
  end
18
18
  end
19
- end
19
+ end
@@ -1,9 +1,9 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
3
+ module Validations
4
4
  def validate_confirmation_of(field)
5
5
  HaveValidationMatcher.new(field, :confirmation)
6
6
  end
7
7
  end
8
8
  end
9
- end
9
+ end
@@ -2,6 +2,7 @@ module Mongoid
2
2
  module Matchers
3
3
  module Validations
4
4
  class ValidateWithCustomValidatorMatcher < HaveValidationMatcher
5
+ include WithMessage
5
6
  def initialize(field)
6
7
  super(field, :custom)
7
8
  end
@@ -11,11 +12,6 @@ module Mongoid
11
12
  self
12
13
  end
13
14
 
14
- def with_message(message)
15
- @expected_message = message
16
- self
17
- end
18
-
19
15
  def matches?(actual)
20
16
  return false unless (@result = super(actual))
21
17
  check_custom_validator if @custom_validator
@@ -41,19 +37,6 @@ module Mongoid
41
37
  @result = false
42
38
  end
43
39
  end
44
-
45
- def check_expected_message
46
- actual_message = @validator.options[:message]
47
- if actual_message.nil?
48
- @negative_result_message << " with no custom message"
49
- @result = false
50
- elsif actual_message == @expected_message
51
- @positive_result_message << " with custom message '#{@expected_message}'"
52
- else
53
- @negative_result_message << " got message '#{actual_message}'"
54
- @result = false
55
- end
56
- end
57
40
  end
58
41
 
59
42
  def custom_validate(field)
@@ -61,4 +44,4 @@ module Mongoid
61
44
  end
62
45
  end
63
46
  end
64
- end
47
+ end
@@ -15,7 +15,14 @@ module Mongoid
15
15
  return false unless result = super(actual)
16
16
 
17
17
  if @not_allowed_values
18
- allowed_values = @not_allowed_values - @validator.options[:in]
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
19
26
  if allowed_values.empty?
20
27
  @positive_result_message = @positive_result_message << " not allowing all values mentioned"
21
28
  else
@@ -1,71 +1,71 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
3
+ module Validations
4
4
  class ValidateFormatOfMatcher < HaveValidationMatcher
5
5
  def initialize(field)
6
6
  super(field, :format)
7
7
  end
8
-
8
+
9
9
  def with_format(format)
10
10
  @format = format
11
11
  self
12
12
  end
13
-
13
+
14
14
  def to_allow(valid_value)
15
15
  @valid_value = valid_value
16
- self
16
+ self
17
17
  end
18
-
18
+
19
19
  def not_to_allow(invalid_value)
20
20
  @invalid_value = invalid_value
21
- self
21
+ self
22
22
  end
23
-
23
+
24
24
  def matches?(actual)
25
25
  return false unless result = super(actual)
26
-
26
+
27
27
  if @format
28
28
  if @validator.options[:with] == @format
29
29
  @positive_result_message = @positive_result_message << " with format #{@validator.options[:format].inspect}"
30
30
  else
31
31
  @negative_result_message = @negative_result_message << " with format #{@validator.options[:format].inspect}"
32
32
  result = false
33
- end
33
+ end
34
34
  end
35
-
35
+
36
36
  if @valid_value
37
37
  if @validator.options[:with] =~ @valid_value
38
38
  @positive_result_message = @positive_result_message << " with #{@valid_value.inspect} as a valid value"
39
39
  else
40
40
  @negative_result_message = @negative_result_message << " with #{@valid_value.inspect} as an invalid value"
41
41
  result = false
42
- end
42
+ end
43
43
  end
44
-
45
- if @invalid_value
44
+
45
+ if @invalid_value
46
46
  if !(@invalid_value =~ @validator.options[:with])
47
47
  @positive_result_message = @positive_result_message << " with #{@invalid_value.inspect} as an invalid value"
48
48
  else
49
49
  @negative_result_message = @negative_result_message << " with #{@invalid_value.inspect} as a valid value"
50
50
  result = false
51
- end
51
+ end
52
52
  end
53
-
54
- result
53
+
54
+ result
55
55
  end
56
-
56
+
57
57
  def description
58
58
  options_desc = []
59
59
  options_desc << " with format #{@format.inspect}" if @format
60
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
61
+ options_desc << " not allowing the value #{@invalid_value.inspect}" if @invalid_value
62
62
  super << options_desc.to_sentence
63
- end
63
+ end
64
64
  end
65
65
 
66
66
  def validate_format_of(field)
67
67
  ValidateFormatOfMatcher.new(field)
68
- end
68
+ end
69
69
  end
70
70
  end
71
- end
71
+ end
@@ -1,42 +1,49 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
3
+ module Validations
4
4
  class ValidateInclusionOfMatcher < HaveValidationMatcher
5
5
  def initialize(name)
6
6
  super(name, :inclusion)
7
7
  end
8
-
8
+
9
9
  def to_allow(*values)
10
10
  @allowed_values = values.map(&:to_a).flatten
11
11
  self
12
12
  end
13
-
13
+
14
14
  def matches?(actual)
15
15
  return false unless result = super(actual)
16
-
16
+
17
17
  if @allowed_values
18
- not_allowed_values = @allowed_values - @validator.options[:in].to_a
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
19
26
  if not_allowed_values.empty?
20
27
  @positive_result_message = @positive_result_message << " allowing all values mentioned"
21
28
  else
22
- @negative_result_message = @negative_result_message << " not allowing the following the ff. values: #{not_allowed_values.inspect}"
29
+ @negative_result_message = @negative_result_message << " not allowing these values: #{not_allowed_values.inspect}"
23
30
  result = false
24
31
  end
25
32
  end
26
-
27
- result
33
+
34
+ result
28
35
  end
29
-
36
+
30
37
  def description
31
38
  options_desc = []
32
- options_desc << " allowing the ff. values: #{@allowed_values}" if @allowed_values
39
+ options_desc << " allowing these values: #{@allowed_values}" if @allowed_values
33
40
  super << options_desc.to_sentence
34
- end
41
+ end
35
42
  end
36
43
 
37
44
  def validate_inclusion_of(field)
38
45
  ValidateInclusionOfMatcher.new(field)
39
- end
46
+ end
40
47
  end
41
48
  end
42
- end
49
+ end
@@ -2,6 +2,8 @@ module Mongoid
2
2
  module Matchers
3
3
  module Validations
4
4
  class ValidateLengthOfMatcher < HaveValidationMatcher
5
+ include WithMessage
6
+
5
7
  def initialize(name)
6
8
  super(name, :length)
7
9
  end
@@ -10,11 +12,13 @@ module Mongoid
10
12
  @maximum = value
11
13
  self
12
14
  end
15
+ alias :less_than :with_maximum
13
16
 
14
17
  def with_minimum(value)
15
18
  @minimum = value
16
19
  self
17
20
  end
21
+ alias :greater_than :with_minimum
18
22
 
19
23
  def within(value)
20
24
  @within = value
@@ -28,6 +32,11 @@ module Mongoid
28
32
  end
29
33
  alias :is :as_exactly
30
34
 
35
+ def with_message(message)
36
+ @expected_message = message
37
+ self
38
+ end
39
+
31
40
  def matches?(actual)
32
41
  return false unless @result = super(actual)
33
42
 
@@ -35,61 +44,99 @@ module Mongoid
35
44
  check_minimum if @minimum
36
45
  check_range if @within
37
46
  check_exact if @is
47
+ check_expected_message if @expected_message
38
48
 
39
49
  @result
40
50
  end
41
51
 
42
52
  def description
43
53
  options_desc = []
44
- options_desc << " with maximum #{@maximum}" if @maximum
45
- options_desc << " with minimum #{@minimum}" if @minimum
46
- options_desc << " within range #{@within}" if @within
47
- options_desc << " as exactly #{@is}" if @is
48
- super << options_desc.to_sentence
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}"
49
60
  end
50
61
 
51
62
  private
52
63
 
53
64
  def check_maximum
54
- actual = @validator.options[:maximum]
55
- if actual == @maximum
65
+ if actual_max.nil?
66
+ @negative_result_message << " with no maximum"
67
+ @result = false
68
+ elsif actual_max == @maximum
56
69
  @positive_result_message << " with maximum of #{@maximum}"
57
70
  else
58
- @negative_result_message << " with maximum of #{actual}"
71
+ @negative_result_message << " with maximum of #{actual_max}"
59
72
  @result = false
60
73
  end
61
74
  end
62
75
 
63
76
  def check_minimum
64
- actual = @validator.options[:minimum]
65
- if actual == @minimum
77
+ if actual_min.nil?
78
+ @negative_result_message << " with no minimum"
79
+ @result = false
80
+ elsif actual_min == @minimum
66
81
  @positive_result_message << " with minimum of #{@minimum}"
67
82
  else
68
- @negative_result_message << " with minimum of #{actual}"
83
+ @negative_result_message << " with minimum of #{actual_min}"
69
84
  @result = false
70
85
  end
71
86
  end
72
87
 
73
88
  def check_range
74
89
  min, max = [@within.min, @within.max]
75
- actual = @validator.options
76
- if actual[:minimum] == min && actual[:maximum] == max
77
- @positive_result_message << " with range #{@within.inspect}"
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}"
78
101
  else
79
- @negative_result_message << " with range #{(actual[:minimum]..actual[:maximum]).inspect}"
102
+ @negative_result_message << " within the range of #{(actual_min..actual_max).inspect}"
80
103
  @result = false
81
104
  end
82
105
  end
83
106
 
84
107
  def check_exact
85
- actual = @validator.options[:is]
86
- if actual == @is
87
- @positive_result_message << " is exactly #{@is}"
108
+ if actual_is == @is
109
+ @positive_result_message << " as exactly #{@is}"
88
110
  else
89
- @negative_result_message << " is exactly #{actual}"
111
+ @negative_result_message << " as exactly #{actual_is}"
90
112
  @result = false
91
113
  end
92
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
93
140
  end
94
141
 
95
142
  def validate_length_of(field)
@@ -2,14 +2,14 @@ module Mongoid
2
2
  module Matchers
3
3
  module Validations
4
4
  class ValidateNumericalityOfMatcher < HaveValidationMatcher
5
- @@allowed_options = [:equal_to, :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to,
5
+ @@allowed_options = [:equal_to, :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to,
6
6
  :even, :odd, :only_integer, :allow_nil, :nil]
7
-
7
+
8
8
  def initialize(field)
9
9
  super(field, :numericality)
10
10
  @options = {}
11
11
  end
12
-
12
+
13
13
  def to_allow(options)
14
14
  options[:equal_to] = options if options.is_a?(Numeric)
15
15
  options[:allow_nil] = options.delete(:nil) if options.has_key?(:nil)
@@ -21,7 +21,7 @@ module Mongoid
21
21
 
22
22
  def matches?(actual)
23
23
  return false unless result = super(actual)
24
-
24
+
25
25
  @@allowed_options.each do |comparator|
26
26
  if @options.has_key?(comparator) and !([:even, :odd, :only_integer].include?(comparator) and !@validator.options.include?(comparator))
27
27
  result &= (@validator.options[comparator] == @options[comparator])
@@ -35,12 +35,12 @@ module Mongoid
35
35
  def description
36
36
  super << options_message(@options)
37
37
  end
38
-
38
+
39
39
  protected
40
-
40
+
41
41
  def options_message(options)
42
42
  type_msg = []
43
- comp_msg = []
43
+ comp_msg = []
44
44
  options.each_pair do |key, value|
45
45
  case key
46
46
  when :allow_nil
@@ -52,10 +52,10 @@ module Mongoid
52
52
  comp_msg << "#{key.to_s.gsub("_", " ")} #{value.inspect}"
53
53
  end
54
54
  end
55
- allow_nil = (options[:allow_nil] ? "nil" : "non-nil") if options.has_key?(:allow_nil)
55
+ allow_nil = (options[:allow_nil] ? "nil" : "non-nil") if options.has_key?(:allow_nil)
56
56
  ["", "allowing", allow_nil, type_msg.any? ? type_msg.to_sentence : nil, "values", comp_msg.any? ? comp_msg.to_sentence : nil].compact.join(" ")
57
- end
58
-
57
+ end
58
+
59
59
  def method_missing(m, *args, &block)
60
60
  if @@allowed_options.include?(m.to_sym)
61
61
  raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" if args.length > 1
@@ -1,9 +1,9 @@
1
1
  module Mongoid
2
2
  module Matchers
3
- module Validations
3
+ module Validations
4
4
  def validate_presence_of(field)
5
5
  HaveValidationMatcher.new(field, :presence)
6
6
  end
7
7
  end
8
8
  end
9
- end
9
+ end