mongoid-spec 4.0.1

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +373 -0
  4. data/Rakefile +17 -0
  5. data/lib/matchers/accept_nested_attributes.rb +67 -0
  6. data/lib/matchers/allow_mass_assignment.rb +102 -0
  7. data/lib/matchers/associations.rb +330 -0
  8. data/lib/matchers/be_dynamic_document.rb +26 -0
  9. data/lib/matchers/be_mongoid_document.rb +26 -0
  10. data/lib/matchers/be_stored_in.rb +50 -0
  11. data/lib/matchers/have_field.rb +90 -0
  12. data/lib/matchers/have_index_for.rb +63 -0
  13. data/lib/matchers/have_timestamps.rb +61 -0
  14. data/lib/matchers/validations.rb +81 -0
  15. data/lib/matchers/validations/acceptance_of.rb +9 -0
  16. data/lib/matchers/validations/associated.rb +19 -0
  17. data/lib/matchers/validations/confirmation_of.rb +22 -0
  18. data/lib/matchers/validations/custom_validation_of.rb +47 -0
  19. data/lib/matchers/validations/exclusion_of.rb +49 -0
  20. data/lib/matchers/validations/format_of.rb +71 -0
  21. data/lib/matchers/validations/inclusion_of.rb +49 -0
  22. data/lib/matchers/validations/length_of.rb +147 -0
  23. data/lib/matchers/validations/numericality_of.rb +90 -0
  24. data/lib/matchers/validations/presence_of.rb +9 -0
  25. data/lib/matchers/validations/uniqueness_of.rb +82 -0
  26. data/lib/matchers/validations/with_message.rb +27 -0
  27. data/lib/mongoid-rspec.rb +1 -0
  28. data/lib/mongoid/rspec.rb +40 -0
  29. data/lib/mongoid/rspec/version.rb +5 -0
  30. data/spec/models/article.rb +29 -0
  31. data/spec/models/comment.rb +6 -0
  32. data/spec/models/log.rb +9 -0
  33. data/spec/models/movie_article.rb +8 -0
  34. data/spec/models/permalink.rb +5 -0
  35. data/spec/models/person.rb +10 -0
  36. data/spec/models/profile.rb +16 -0
  37. data/spec/models/record.rb +5 -0
  38. data/spec/models/site.rb +9 -0
  39. data/spec/models/user.rb +37 -0
  40. data/spec/spec_helper.rb +35 -0
  41. data/spec/unit/accept_nested_attributes_spec.rb +12 -0
  42. data/spec/unit/associations_spec.rb +42 -0
  43. data/spec/unit/be_dynamic_document_spec.rb +22 -0
  44. data/spec/unit/be_mongoid_document_spec.rb +25 -0
  45. data/spec/unit/be_stored_in.rb +54 -0
  46. data/spec/unit/document_spec.rb +16 -0
  47. data/spec/unit/have_index_for_spec.rb +46 -0
  48. data/spec/unit/have_timestamps_spec.rb +71 -0
  49. data/spec/unit/validations_spec.rb +54 -0
  50. data/spec/validators/ssn_validator.rb +16 -0
  51. metadata +171 -0
@@ -0,0 +1,26 @@
1
+ module Mongoid
2
+ module Matchers
3
+ def be_mongoid_document
4
+ BeMongoidDocument.new
5
+ end
6
+
7
+ class BeMongoidDocument
8
+ def matches?(actual)
9
+ @model = actual.is_a?(Class) ? actual : actual.class
10
+ @model.included_modules.include?(Mongoid::Document)
11
+ end
12
+
13
+ def description
14
+ 'include Mongoid::Document'
15
+ end
16
+
17
+ def failure_message
18
+ "expect #{@model.inspect} class to #{description}"
19
+ end
20
+
21
+ def failure_message_when_negated
22
+ "expect #{@model.inspect} class to not #{description}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ module Mongoid
2
+ module Matchers
3
+ def be_stored_in(options)
4
+ BeStoredIn.new(options)
5
+ end
6
+
7
+ class BeStoredIn
8
+ def initialize(expected)
9
+ @expected_options = \
10
+ expected
11
+ .transform_values { |v| v.to_sym rescue v }
12
+ .symbolize_keys
13
+ end
14
+
15
+ def matches?(actual)
16
+ @model = actual.is_a?(Class) ? actual : actual.class
17
+ actual_options == @expected_options
18
+ end
19
+
20
+ def description
21
+ "be stored in #{@expected_options.inspect}"
22
+ end
23
+
24
+ def failure_message
25
+ "Expected #{@model.inspect} to #{description}, got #{actual_options.inspect}"
26
+ end
27
+
28
+ def failure_message_when_negated
29
+ "Expected #{@model.inspect} not to #{description}, got #{actual_options.inspect}"
30
+ end
31
+
32
+ private
33
+
34
+ def actual_options
35
+ @actual_options ||= begin
36
+ hash = @model.storage_options.slice(*@expected_options.keys)
37
+ hash.each do |option, value|
38
+ hash[option] =
39
+ if value.is_a?(Proc)
40
+ evaluated_value = @model.persistence_context.send("#{option}_name")
41
+ evaluated_value.to_sym rescue evaluated_value
42
+ else
43
+ value.to_sym rescue value
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,90 @@
1
+ module Mongoid
2
+ module Matchers
3
+ class HaveField # :nodoc:
4
+ def initialize(*attrs)
5
+ @attributes = attrs.collect(&:to_s)
6
+ end
7
+
8
+ def localized
9
+ @localized = true
10
+ self
11
+ end
12
+
13
+ def of_type(type)
14
+ @type = type
15
+ self
16
+ end
17
+
18
+ def with_alias(field_alias)
19
+ @field_alias = field_alias
20
+ self
21
+ end
22
+
23
+ def with_default_value_of(default)
24
+ @default = default
25
+ self
26
+ end
27
+
28
+ def matches?(klass)
29
+ @klass = klass.is_a?(Class) ? klass : klass.class
30
+ @errors = []
31
+ @attributes.each do |attr|
32
+ if @klass.fields.include?(attr)
33
+ error = ""
34
+ if @type and @klass.fields[attr].type != @type
35
+ error << " of type #{@klass.fields[attr].type}"
36
+ end
37
+
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]}"
48
+ end
49
+
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
+
58
+ else
59
+ @errors.push "no field named #{attr.inspect}"
60
+ end
61
+ end
62
+ @errors.empty?
63
+ end
64
+
65
+ def failure_message_for_should
66
+ "Expected #{@klass.inspect} to #{description}, got #{@errors.to_sentence}"
67
+ end
68
+
69
+ def failure_message_for_should_not
70
+ "Expected #{@klass.inspect} to not #{description}, got #{@klass.inspect} to #{description}"
71
+ end
72
+
73
+ alias :failure_message :failure_message_for_should
74
+ alias :failure_message_when_negated :failure_message_for_should_not
75
+
76
+ def description
77
+ desc = "have #{@attributes.size > 1 ? 'fields' : 'field'} named #{@attributes.collect(&:inspect).to_sentence}"
78
+ desc << " of type #{@type.inspect}" if @type
79
+ desc << " with alias #{@field_alias}" if @field_alias
80
+ desc << " with default value of #{@default.inspect}" if !@default.nil?
81
+ desc
82
+ end
83
+ end
84
+
85
+ def have_field(*args)
86
+ HaveField.new(*args)
87
+ end
88
+ alias_method :have_fields, :have_field
89
+ end
90
+ end
@@ -0,0 +1,63 @@
1
+ module Mongoid
2
+ module Matchers
3
+ def have_index_for(index_key)
4
+ HaveIndexFor.new(index_key)
5
+ end
6
+
7
+ class HaveIndexFor
8
+ def initialize(index_key)
9
+ @index_key = index_key.symbolize_keys
10
+ end
11
+
12
+ def with_options(index_options)
13
+ @index_options = index_options
14
+ self
15
+ end
16
+
17
+ def matches?(actual)
18
+ @model = actual.is_a?(Class) ? actual : actual.class
19
+
20
+ actual_index &&
21
+ expected_index.key == actual_index.key &&
22
+ expected_index.fields == actual_index.fields &&
23
+ (expected_index.options.to_a - actual_index.options.to_a).empty?
24
+ end
25
+
26
+ def failure_message
27
+ message = "Expected #{@model.inspect} to #{description},"
28
+ if actual_index.nil?
29
+ message << " found no index"
30
+ else
31
+ message << " got #{index_description(actual_index)}"
32
+ end
33
+ message
34
+ end
35
+
36
+ def failure_message_when_negated
37
+ "Expected #{@model.inspect} to not #{description}, got #{index_description(actual_index)}"
38
+ end
39
+
40
+ def description
41
+ "have an index #{index_description(expected_index)}"
42
+ end
43
+
44
+ private
45
+
46
+ def index_description(index)
47
+ desc = "#{index.key.inspect}"
48
+ desc << " for fields #{index.fields.inspect}" if index.fields.present?
49
+ desc << " including options #{index.options.inspect}" if index.options.present?
50
+ desc
51
+ end
52
+
53
+ def expected_index
54
+ @expected_index ||=
55
+ Mongoid::Indexable::Specification.new(@model, @index_key, @index_options)
56
+ end
57
+
58
+ def actual_index
59
+ @actual_index ||= @model.index_specification(expected_index.key)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,61 @@
1
+ module Mongoid
2
+ module Matchers
3
+ def have_timestamps
4
+ HaveTimestamps.new
5
+ end
6
+
7
+ class HaveTimestamps
8
+ def initialize
9
+ @root_module = 'Mongoid::Timestamps'
10
+ end
11
+
12
+ def matches?(actual)
13
+ @model = actual.is_a?(Class) ? actual : actual.class
14
+ @model.included_modules.include?(expected_module)
15
+ end
16
+
17
+ def for(phase)
18
+ fail('You\'ve already declared timetamp\'s sub-module via "for" clause') if @submodule
19
+
20
+ case @phase = phase.to_sym
21
+ when :creating then @submodule = 'Created'
22
+ when :updating then @submodule = 'Updated'
23
+ else
24
+ fail('Timestamps can be declared only for creating or updating')
25
+ end
26
+
27
+ self
28
+ end
29
+
30
+ def shortened
31
+ @shortened = true
32
+ self
33
+ end
34
+
35
+ def description
36
+ desc = "be a Mongoid document with"
37
+ desc << " shorted" if @shortened
38
+ desc << " #{@phase}" if @phase
39
+ desc << " timestamps"
40
+ desc
41
+ end
42
+
43
+ def failure_message
44
+ "Expected #{@model.inspect} class to #{description}"
45
+ end
46
+
47
+ def failure_message_when_negated
48
+ "Expected #{@model.inspect} class to not #{description}"
49
+ end
50
+
51
+ private
52
+
53
+ def expected_module
54
+ expected_module = @root_module
55
+ expected_module << "::#{@submodule}" if @submodule
56
+ expected_module << "::Short" if @shortened
57
+ expected_module.constantize
58
+ end
59
+ end
60
+ end
61
+ 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,22 @@
1
+ module Mongoid
2
+ module Matchers
3
+ module Validations
4
+ class ValidateConfirmationOfMatcher < HaveValidationMatcher
5
+ include WithMessage
6
+
7
+ def initialize(name)
8
+ super(name, :confirmation)
9
+ end
10
+
11
+ def with_message(message)
12
+ @expected_message = message
13
+ self
14
+ end
15
+ end
16
+
17
+ def validate_confirmation_of(field)
18
+ ValidateConfirmationOfMatcher.new(field)
19
+ end
20
+ end
21
+ end
22
+ 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