mongoid-rspec 1.8.0 → 1.9.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/.travis.yml CHANGED
@@ -1,4 +1,12 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
- - ruby-head
4
+ - 2.0.0
5
+ - ruby-head
6
+ - jruby-19mode
7
+ - rbx-19mode
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
11
+ - rvm: jruby-19mode
12
+ - rvm: rbx-19mode
data/README.md CHANGED
@@ -17,156 +17,181 @@ Add to your Gemfile
17
17
 
18
18
  Drop in existing or dedicated support file in spec/support (spec/support/mongoid.rb)
19
19
 
20
- RSpec.configure do |configuration|
21
- configuration.include Mongoid::Matchers
22
- end
20
+ ```ruby
21
+ RSpec.configure do |configuration|
22
+ configuration.include Mongoid::Matchers
23
+ end
24
+ ```
23
25
 
24
26
  Association Matchers
25
27
  -
26
- describe User do
27
- it { should have_many(:articles).with_foreign_key(:author_id).ordered_by(:title) }
28
28
 
29
- it { should have_one(:record) }
30
- #can verify autobuild is set to true
31
- it { should have_one(:record).with_autobuild }
29
+ ```ruby
30
+ describe User do
31
+ it { should have_many(:articles).with_foreign_key(:author_id).ordered_by(:title) }
32
32
 
33
- it { should have_many :comments }
33
+ it { should have_one(:record) }
34
+ #can verify autobuild is set to true
35
+ it { should have_one(:record).with_autobuild }
34
36
 
35
- #can also specify with_dependent to test if :dependent => :destroy/:destroy_all/:delete is set
36
- it { should have_many(:comments).with_dependent(:destroy) }
37
- #can verify autosave is set to true
38
- it { should have_many(:comments).with_autosave }
37
+ it { should have_many :comments }
39
38
 
40
- it { should embed_one :profile }
39
+ #can also specify with_dependent to test if :dependent => :destroy/:destroy_all/:delete is set
40
+ it { should have_many(:comments).with_dependent(:destroy) }
41
+ #can verify autosave is set to true
42
+ it { should have_many(:comments).with_autosave }
41
43
 
42
- it { should have_and_belong_to_many(:children) }
43
- it { should have_and_belong_to_many(:children).of_type(User) }
44
- end
44
+ it { should embed_one :profile }
45
45
 
46
- describe Profile do
47
- it { should be_embedded_in(:user).as_inverse_of(:profile) }
48
- end
46
+ it { should have_and_belong_to_many(:children) }
47
+ it { should have_and_belong_to_many(:children).of_type(User) }
48
+ end
49
49
 
50
- describe Article do
51
- it { should belong_to(:author).of_type(User).as_inverse_of(:articles) }
52
- it { should belong_to(:author).of_type(User).as_inverse_of(:articles).with_index }
53
- it { should embed_many(:comments) }
54
- end
50
+ describe Profile do
51
+ it { should be_embedded_in(:user).as_inverse_of(:profile) }
52
+ end
55
53
 
56
- describe Comment do
57
- it { should be_embedded_in(:article).as_inverse_of(:comments) }
58
- it { should belong_to(:user).as_inverse_of(:comments) }
59
- end
54
+ describe Article do
55
+ it { should belong_to(:author).of_type(User).as_inverse_of(:articles) }
56
+ it { should belong_to(:author).of_type(User).as_inverse_of(:articles).with_index }
57
+ it { should embed_many(:comments) }
58
+ end
60
59
 
61
- describe Record do
62
- it { should belong_to(:user).as_inverse_of(:record) }
63
- end
60
+ describe Comment do
61
+ it { should be_embedded_in(:article).as_inverse_of(:comments) }
62
+ it { should belong_to(:user).as_inverse_of(:comments) }
63
+ end
64
+
65
+ describe Record do
66
+ it { should belong_to(:user).as_inverse_of(:record) }
67
+ end
68
+
69
+ describe Site do
70
+ it { should have_many(:users).as_inverse_of(:site).ordered_by(:email.asc) }
71
+ end
72
+ ```
64
73
 
65
74
  Mass Assignment Matcher
66
75
  -
67
- describe User do
68
- it { should allow_mass_assignment_of(:login) }
69
- it { should allow_mass_assignment_of(:email) }
70
- it { should allow_mass_assignment_of(:age) }
71
- it { should allow_mass_assignment_of(:password) }
72
- it { should allow_mass_assignment_of(:password) }
73
- it { should allow_mass_assignment_of(:role).as(:admin) }
74
76
 
75
- it { should_not allow_mass_assignment_of(:role) }
76
- end
77
+ ```ruby
78
+ describe User do
79
+ it { should allow_mass_assignment_of(:login) }
80
+ it { should allow_mass_assignment_of(:email) }
81
+ it { should allow_mass_assignment_of(:age) }
82
+ it { should allow_mass_assignment_of(:password) }
83
+ it { should allow_mass_assignment_of(:password) }
84
+ it { should allow_mass_assignment_of(:role).as(:admin) }
85
+
86
+ it { should_not allow_mass_assignment_of(:role) }
87
+ end
88
+ ```
77
89
 
78
90
  Validation Matchers
79
91
  -
80
- describe Site do
81
- it { should validate_presence_of(:name) }
82
- it { should validate_uniqueness_of(:name) }
83
- end
84
-
85
- describe User do
86
- it { should validate_presence_of(:login) }
87
- it { should validate_uniqueness_of(:login).scoped_to(:site) }
88
- it { should validate_uniqueness_of(:email).case_insensitive.with_message("is already taken") }
89
- it { should validate_format_of(:login).to_allow("valid_login").not_to_allow("invalid login") }
90
- it { should validate_associated(:profile) }
91
- it { should validate_exclusion_of(:login).to_not_allow("super", "index", "edit") }
92
- it { should validate_inclusion_of(:role).to_allow("admin", "member") }
93
- it { should validate_confirmation_of(:email) }
94
- it { should validate_presence_of(:age).on(:create, :update) }
95
- it { should validate_numericality_of(:age).on(:create, :update) }
96
- it { should validate_inclusion_of(:age).to_allow(23..42).on([:create, :update]) }
97
- it { should validate_presence_of(:password).on(:create) }
98
- it { should validate_presence_of(:provider_uid).on(:create) }
99
- it { should validate_inclusion_of(:locale).to_allow([:en, :ru]) }
100
- end
101
-
102
- describe Article do
103
- it { should validate_length_of(:title).within(8..16) }
104
- end
105
-
106
- describe Profile do
107
- it { should validate_numericality_of(:age).greater_than(0) }
108
- end
109
-
110
- describe MovieArticle do
111
- it { should validate_numericality_of(:rating).to_allow(:greater_than => 0).less_than_or_equal_to(5) }
112
- it { should validate_numericality_of(:classification).to_allow(:even => true, :only_integer => true, :nil => false) }
113
- end
114
-
115
- describe Person do
116
- # in order to be able to use the custom_validate matcher, the custom validator class (in this case SsnValidator)
117
- # should redefine the kind method to return :custom, i.e. "def self.kind() :custom end"
118
- it { should custom_validate(:ssn).with_validator(SsnValidator) }
119
- end
92
+
93
+ ```ruby
94
+ describe Site do
95
+ it { should validate_presence_of(:name) }
96
+ it { should validate_uniqueness_of(:name) }
97
+ end
98
+
99
+ describe User do
100
+ it { should validate_presence_of(:login) }
101
+ it { should validate_uniqueness_of(:login).scoped_to(:site) }
102
+ it { should validate_uniqueness_of(:email).case_insensitive.with_message("is already taken") }
103
+ it { should validate_format_of(:login).to_allow("valid_login").not_to_allow("invalid login") }
104
+ it { should validate_associated(:profile) }
105
+ it { should validate_exclusion_of(:login).to_not_allow("super", "index", "edit") }
106
+ it { should validate_inclusion_of(:role).to_allow("admin", "member") }
107
+ it { should validate_confirmation_of(:email) }
108
+ it { should validate_presence_of(:age).on(:create, :update) }
109
+ it { should validate_numericality_of(:age).on(:create, :update) }
110
+ it { should validate_inclusion_of(:age).to_allow(23..42).on([:create, :update]) }
111
+ it { should validate_presence_of(:password).on(:create) }
112
+ it { should validate_presence_of(:provider_uid).on(:create) }
113
+ it { should validate_inclusion_of(:locale).to_allow([:en, :ru]) }
114
+ end
115
+
116
+ describe Article do
117
+ it { should validate_length_of(:title).within(8..16) }
118
+ end
119
+
120
+ describe Profile do
121
+ it { should validate_numericality_of(:age).greater_than(0) }
122
+ end
123
+
124
+ describe MovieArticle do
125
+ it { should validate_numericality_of(:rating).to_allow(:greater_than => 0).less_than_or_equal_to(5) }
126
+ it { should validate_numericality_of(:classification).to_allow(:even => true, :only_integer => true, :nil => false) }
127
+ end
128
+
129
+ describe Person do
130
+ # in order to be able to use the custom_validate matcher, the custom validator class (in this case SsnValidator)
131
+ # should redefine the kind method to return :custom, i.e. "def self.kind() :custom end"
132
+ it { should custom_validate(:ssn).with_validator(SsnValidator) }
133
+ end
134
+ ```
120
135
 
121
136
  Accepts Nested Attributes Matcher
122
137
  -
123
- describe User do
124
- it { should accept_nested_attributes_for(:articles) }
125
- it { should accept_nested_attributes_for(:comments) }
126
- end
127
138
 
128
- describe Article do
129
- it { should accept_nested_attributes_for(:permalink) }
130
- end
139
+ ```ruby
140
+ describe User do
141
+ it { should accept_nested_attributes_for(:articles) }
142
+ it { should accept_nested_attributes_for(:comments) }
143
+ end
144
+
145
+ describe Article do
146
+ it { should accept_nested_attributes_for(:permalink) }
147
+ end
148
+ ```
131
149
 
132
150
  Index Matcher
133
151
  -
134
- describe Article do
135
- it { should have_index_for(published: 1) }
136
- it { should have_index_for(title: 1).with_options(unique: true, background: true) }
137
- end
138
152
 
139
- describe Profile do
140
- it { should have_index_for(first_name: 1, last_name: 1) }
141
- end
153
+ ```ruby
154
+ describe Article do
155
+ it { should have_index_for(published: 1) }
156
+ it { should have_index_for(title: 1).with_options(unique: true, background: true) }
157
+ end
158
+
159
+ describe Profile do
160
+ it { should have_index_for(first_name: 1, last_name: 1) }
161
+ end
162
+ ```
142
163
 
143
164
  Others
144
165
  -
145
- describe User do
146
- it { should have_fields(:email, :login) }
147
- it { should have_field(:s).with_alias(:status) }
148
- it { should have_fields(:birthdate, :registered_at).of_type(DateTime) }
149
-
150
- it { should be_timestamped_document } # if you're declaring `include
151
- Mongoid::Timestamps` or any of `include Mongoid::Timestamps::Created` and `Mongoid::Timestamps::Updated`
152
- it { should be_timestamped_document.with(:created) }
153
- it { should_not be_timestamped_document.with(:updated) }
154
-
155
- it { should be_versioned_document } # if you're declaring `include Mongoid::Versioning`
156
- it { should be_paranoid_document } # if you're declaring `include Mongoid::Paranoia`
157
- it { should be_multiparameted_document } # if you're declaring `include Mongoid::MultiParameterAttributes`
158
- end
159
-
160
- describe Log do
161
- it { should be_stored_in :logs }
162
- end
163
-
164
- describe Article do
165
- it { should have_field(:published).of_type(Boolean).with_default_value_of(false) }
166
- it { should have_field(:allow_comments).of_type(Boolean).with_default_value_of(true) }
167
- it { should_not have_field(:allow_comments).of_type(Boolean).with_default_value_of(false) }
168
- it { should_not have_field(:number_of_comments).of_type(Integer).with_default_value_of(1) }
169
- end
166
+
167
+ ```ruby
168
+ describe User do
169
+ it { should have_fields(:email, :login) }
170
+ it { should have_field(:s).with_alias(:status) }
171
+ it { should have_fields(:birthdate, :registered_at).of_type(DateTime) }
172
+
173
+ # if you're declaring 'include Mongoid::Timestamps'
174
+ # or any of 'include Mongoid::Timestamps::Created' and 'Mongoid::Timestamps::Updated'
175
+ it { should be_timestamped_document }
176
+ it { should be_timestamped_document.with(:created) }
177
+ it { should_not be_timestamped_document.with(:updated) }
178
+
179
+ it { should be_versioned_document } # if you're declaring `include Mongoid::Versioning`
180
+ it { should be_paranoid_document } # if you're declaring `include Mongoid::Paranoia`
181
+ it { should be_multiparameted_document } # if you're declaring `include Mongoid::MultiParameterAttributes`
182
+ end
183
+
184
+ describe Log do
185
+ it { should be_stored_in :logs }
186
+ end
187
+
188
+ describe Article do
189
+ it { should have_field(:published).of_type(Boolean).with_default_value_of(false) }
190
+ it { should have_field(:allow_comments).of_type(Boolean).with_default_value_of(true) }
191
+ it { should_not have_field(:allow_comments).of_type(Boolean).with_default_value_of(false) }
192
+ it { should_not have_field(:number_of_comments).of_type(Integer).with_default_value_of(1) }
193
+ end
194
+ ```
170
195
 
171
196
  Known issues
172
197
  -
@@ -43,6 +43,12 @@ module Mongoid
43
43
  raise "#{@association[:type].inspect} does not respond to :order" unless [HAS_MANY, HAS_AND_BELONGS_TO_MANY, EMBEDS_MANY].include?(@association[:type])
44
44
  @association[:order] = association_field_name.to_s
45
45
  @expectation_message << " ordered by #{@association[:order].inspect}"
46
+
47
+ if association_field_name.is_a? Origin::Key
48
+ @association[:order_operator] = association_field_name.operator
49
+ @expectation_message << " #{order_way(@association[:order_operator])}"
50
+ end
51
+
46
52
  self
47
53
  end
48
54
 
@@ -70,8 +76,28 @@ module Mongoid
70
76
  self
71
77
  end
72
78
 
79
+ def with_polymorphism
80
+ @association[:polymorphic] = true
81
+ @expectation_message << " which specifies polymorphic as #{@association[:polymorphic].to_s}"
82
+ self
83
+ end
84
+
85
+ def with_cascading_callbacks
86
+ @association[:cascade_callbacks] = true
87
+ @expectation_message << " which specifies cascade_callbacks as #{@association[:cascade_callbacks].to_s}"
88
+ self
89
+ end
90
+
91
+ def cyclic
92
+ @association[:cyclic] = true
93
+ @expectation_message << " which specifies cyclic as #{@association[:cyclic].to_s}"
94
+ self
95
+ end
96
+
73
97
  def stored_as(store_as)
74
- raise NotImplementedError, "`references_many #{@association[:name]} :stored_as => :array` has been removed in Mongoid 2.0.0.rc, use `references_and_referenced_in_many #{@association[:name]}` instead"
98
+ @association[:store_as] = store_as.to_s
99
+ @expectation_message << " which is stored as #{@association[:store_as].inspect}"
100
+ self
75
101
  end
76
102
 
77
103
  def with_foreign_key(foreign_key)
@@ -124,6 +150,15 @@ module Mongoid
124
150
  end
125
151
  end
126
152
 
153
+ if @association[:order_operator]
154
+ if @association[:order_operator] != metadata.order.operator
155
+ @negative_result_message = "#{@positive_result_message} #{order_way(@association[:order_operator] * -1)}"
156
+ return false
157
+ else
158
+ @positive_result_message = "#{@positive_result_message} #{order_way(@association[:order_operator])}"
159
+ end
160
+ end
161
+
127
162
  if @association[:dependent]
128
163
  if @association[:dependent].to_s != metadata.dependent.to_s
129
164
  @negative_result_message = "#{@positive_result_message} which specified dependent as #{metadata.dependent}"
@@ -151,6 +186,42 @@ module Mongoid
151
186
  end
152
187
  end
153
188
 
189
+ if @association[:polymorphic]
190
+ if metadata.polymorphic? != true
191
+ @negative_result_message = "#{@positive_result_message} which did not set polymorphic"
192
+ return false
193
+ else
194
+ @positive_result_message = "#{@positive_result_message} which set polymorphic"
195
+ end
196
+ end
197
+
198
+ if @association[:cascade_callbacks]
199
+ if metadata.cascading_callbacks? != true
200
+ @negative_result_message = "#{@positive_result_message} which did not set cascade_callbacks"
201
+ return false
202
+ else
203
+ @positive_result_message = "#{@positive_result_message} which set cascade_callbacks"
204
+ end
205
+ end
206
+
207
+ if @association[:cyclic]
208
+ if metadata.cyclic? != true
209
+ @negative_result_message = "#{@positive_result_message} which did not set cyclic"
210
+ return false
211
+ else
212
+ @positive_result_message = "#{@positive_result_message} which set cyclic"
213
+ end
214
+ end
215
+
216
+ if @association[:store_as]
217
+ if metadata.store_as != @association[:store_as]
218
+ @negative_result_message = "#{@positive_result_message} which is stored as #{metadata.store_as}"
219
+ return false
220
+ else
221
+ @positive_result_message = "#{@positive_result_message} which is stored as #{metadata.store_as}"
222
+ end
223
+ end
224
+
154
225
  if @association[:index]
155
226
  if metadata.index != true
156
227
  @negative_result_message = "#{@positive_result_message} which did not set index"
@@ -187,24 +258,30 @@ module Mongoid
187
258
  def type_description(type = nil, passive = true)
188
259
  type ||= @association[:type]
189
260
  case type.name
190
- when EMBEDS_ONE.name
191
- (passive ? 'embed' : 'embeds') << ' one'
192
- when EMBEDS_MANY.name
193
- (passive ? 'embed' : 'embeds') << ' many'
194
- when EMBEDDED_IN.name
195
- (passive ? 'be' : 'is') << ' embedded in'
196
- when HAS_ONE.name
197
- (passive ? 'reference' : 'references') << ' one'
198
- when HAS_MANY.name
199
- (passive ? 'reference' : 'references') << ' many'
200
- when HAS_AND_BELONGS_TO_MANY.name
201
- (passive ? 'reference' : 'references') << ' and referenced in many'
202
- when BELONGS_TO.name
203
- (passive ? 'be referenced in' : 'referenced in')
204
- else
205
- raise "Unknown association type '%s'" % type
261
+ when EMBEDS_ONE.name
262
+ (passive ? 'embed' : 'embeds') << ' one'
263
+ when EMBEDS_MANY.name
264
+ (passive ? 'embed' : 'embeds') << ' many'
265
+ when EMBEDDED_IN.name
266
+ (passive ? 'be' : 'is') << ' embedded in'
267
+ when HAS_ONE.name
268
+ (passive ? 'reference' : 'references') << ' one'
269
+ when HAS_MANY.name
270
+ (passive ? 'reference' : 'references') << ' many'
271
+ when HAS_AND_BELONGS_TO_MANY.name
272
+ (passive ? 'reference' : 'references') << ' and referenced in many'
273
+ when BELONGS_TO.name
274
+ (passive ? 'be referenced in' : 'referenced in')
275
+ else
276
+ raise "Unknown association type '%s'" % type
206
277
  end
207
278
  end
279
+
280
+ private
281
+
282
+ def order_way(operator)
283
+ [nil, "ascending", "descending"][operator]
284
+ end
208
285
  end
209
286
 
210
287
  def embed_one(association_name)
@@ -14,7 +14,7 @@ module Mongoid
14
14
  @type = type
15
15
  self
16
16
  end
17
-
17
+
18
18
  def with_alias(field_alias)
19
19
  @field_alias = field_alias
20
20
  self
@@ -54,7 +54,7 @@ module Mongoid
54
54
  @errors.push "is not localized #{attr.inspect}"
55
55
  end
56
56
  end
57
-
57
+
58
58
  else
59
59
  @errors.push "no field named #{attr.inspect}"
60
60
  end
@@ -43,7 +43,7 @@ module Mongoid
43
43
  desc
44
44
  end
45
45
  end
46
-
46
+
47
47
  def have_index_for(index_fields)
48
48
  HaveIndexForMatcher.new(index_fields)
49
49
  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