mcmire-shoulda-matchers 2.5.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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +32 -0
- data/.yardopts +7 -0
- data/Appraisals +45 -0
- data/CONTRIBUTING.md +41 -0
- data/Gemfile +31 -0
- data/Gemfile.lock +166 -0
- data/MIT-LICENSE +22 -0
- data/NEWS.md +299 -0
- data/README.md +163 -0
- data/Rakefile +116 -0
- data/doc_config/gh-pages/index.html.erb +9 -0
- data/doc_config/yard/setup.rb +22 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +5967 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +12 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/global.css +45 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +69 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/style.css +283 -0
- data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +32 -0
- data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +1 -0
- data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +8 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/app.js +300 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +1 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +289 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +6 -0
- data/doc_config/yard/templates/default/fulldoc/html/setup.rb +8 -0
- data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +14 -0
- data/doc_config/yard/templates/default/layout/html/fonts.erb +1 -0
- data/doc_config/yard/templates/default/layout/html/layout.erb +23 -0
- data/doc_config/yard/templates/default/layout/html/search.erb +13 -0
- data/doc_config/yard/templates/default/layout/html/setup.rb +8 -0
- data/doc_config/yard/templates/default/method_details/html/source.erb +10 -0
- data/doc_config/yard/templates/default/module/html/box_info.erb +31 -0
- data/features/rails_integration.feature +113 -0
- data/features/step_definitions/rails_steps.rb +162 -0
- data/features/support/env.rb +5 -0
- data/gemfiles/3.0.gemfile +24 -0
- data/gemfiles/3.0.gemfile.lock +150 -0
- data/gemfiles/3.1.gemfile +27 -0
- data/gemfiles/3.1.gemfile.lock +173 -0
- data/gemfiles/3.2.gemfile +27 -0
- data/gemfiles/3.2.gemfile.lock +171 -0
- data/gemfiles/4.0.0.gemfile +28 -0
- data/gemfiles/4.0.0.gemfile.lock +172 -0
- data/gemfiles/4.0.1.gemfile +28 -0
- data/gemfiles/4.0.1.gemfile.lock +172 -0
- data/lib/shoulda-matchers.rb +1 -0
- data/lib/shoulda/matchers.rb +11 -0
- data/lib/shoulda/matchers/action_controller.rb +17 -0
- data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +64 -0
- data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +97 -0
- data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +81 -0
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +117 -0
- data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +114 -0
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +154 -0
- data/lib/shoulda/matchers/action_controller/route_matcher.rb +116 -0
- data/lib/shoulda/matchers/action_controller/route_params.rb +48 -0
- data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +164 -0
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +296 -0
- data/lib/shoulda/matchers/active_model.rb +30 -0
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +167 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +314 -0
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +46 -0
- data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +160 -0
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +417 -0
- data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +337 -0
- data/lib/shoulda/matchers/active_model/errors.rb +10 -0
- data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
- data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +92 -0
- data/lib/shoulda/matchers/active_model/helpers.rb +46 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +75 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +27 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +27 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +26 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +112 -0
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +77 -0
- data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +121 -0
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +380 -0
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +89 -0
- data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +372 -0
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +97 -0
- data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
- data/lib/shoulda/matchers/active_record.rb +22 -0
- data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +204 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +901 -0
- data/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +81 -0
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +65 -0
- data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +94 -0
- data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +63 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +261 -0
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +149 -0
- data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +72 -0
- data/lib/shoulda/matchers/active_record/serialize_matcher.rb +181 -0
- data/lib/shoulda/matchers/assertion_error.rb +19 -0
- data/lib/shoulda/matchers/error.rb +6 -0
- data/lib/shoulda/matchers/integrations/rspec.rb +20 -0
- data/lib/shoulda/matchers/integrations/test_unit.rb +30 -0
- data/lib/shoulda/matchers/rails_shim.rb +50 -0
- data/lib/shoulda/matchers/version.rb +6 -0
- data/lib/shoulda/matchers/warn.rb +8 -0
- data/shoulda-matchers.gemspec +23 -0
- data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +22 -0
- data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +42 -0
- data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +78 -0
- data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +63 -0
- data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +63 -0
- data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +31 -0
- data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +70 -0
- data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
- data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +51 -0
- data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +153 -0
- data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +111 -0
- data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +170 -0
- data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +81 -0
- data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +95 -0
- data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +320 -0
- data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +166 -0
- data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +111 -0
- data/spec/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +20 -0
- data/spec/shoulda/matchers/active_model/helpers_spec.rb +158 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +169 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +57 -0
- data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
- data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +41 -0
- data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +47 -0
- data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +331 -0
- data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +180 -0
- data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +398 -0
- data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +127 -0
- data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +107 -0
- data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +860 -0
- data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
- data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +111 -0
- data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +78 -0
- data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +41 -0
- data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +86 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/active_model_versions.rb +13 -0
- data/spec/support/active_resource_builder.rb +29 -0
- data/spec/support/activemodel_helpers.rb +19 -0
- data/spec/support/capture_helpers.rb +19 -0
- data/spec/support/class_builder.rb +42 -0
- data/spec/support/controller_builder.rb +74 -0
- data/spec/support/fail_with_message_including_matcher.rb +33 -0
- data/spec/support/fail_with_message_matcher.rb +32 -0
- data/spec/support/i18n_faker.rb +10 -0
- data/spec/support/mailer_builder.rb +10 -0
- data/spec/support/model_builder.rb +81 -0
- data/spec/support/rails_versions.rb +18 -0
- data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
- data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
- data/spec/support/test_application.rb +120 -0
- data/yard.watchr +5 -0
- metadata +281 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Shoulda::Matchers::ActiveModel::ValidationMessageFinder do
|
4
|
+
context '#allow_description' do
|
5
|
+
it 'describes its attribute' do
|
6
|
+
finder = build_finder(attribute: :attr)
|
7
|
+
|
8
|
+
description = finder.allow_description('allowed values')
|
9
|
+
|
10
|
+
expect(description).to eq 'allow attr to be set to allowed values'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context '#expected_message_from' do
|
15
|
+
it 'returns the message as-is' do
|
16
|
+
finder = build_finder
|
17
|
+
|
18
|
+
message = finder.expected_message_from('some message')
|
19
|
+
|
20
|
+
expect(message).to eq 'some message'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context '#has_messages?' do
|
25
|
+
it 'has messages when some validations fail' do
|
26
|
+
finder = build_finder(format: /abc/, value: 'xyz')
|
27
|
+
|
28
|
+
result = finder.has_messages?
|
29
|
+
|
30
|
+
expect(result).to eq true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has no messages when all validations pass' do
|
34
|
+
finder = build_finder(format: /abc/, value: 'abc')
|
35
|
+
|
36
|
+
result = finder.has_messages?
|
37
|
+
|
38
|
+
expect(result).to eq false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '#messages' do
|
43
|
+
it 'returns errors for the given attribute' do
|
44
|
+
finder = build_finder(format: /abc/, value: 'xyz')
|
45
|
+
|
46
|
+
messages = finder.messages
|
47
|
+
|
48
|
+
expect(messages).to eq ['is invalid']
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns an empty array if there are no errors for the given attribute' do
|
52
|
+
finder = build_finder
|
53
|
+
|
54
|
+
messages = finder.messages
|
55
|
+
|
56
|
+
expect(messages).to eq([])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context '#messages_description' do
|
61
|
+
it 'describes errors for the given attribute' do
|
62
|
+
finder = build_finder(
|
63
|
+
attribute: :attr,
|
64
|
+
format: /abc/,
|
65
|
+
value: 'xyz'
|
66
|
+
)
|
67
|
+
|
68
|
+
description = finder.messages_description
|
69
|
+
|
70
|
+
expected_messages = ['attr is invalid ("xyz")']
|
71
|
+
expect(description).to eq "errors: #{expected_messages}"
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'describes errors when there are none' do
|
75
|
+
finder = build_finder(format: /abc/, value: 'abc')
|
76
|
+
|
77
|
+
description = finder.messages_description
|
78
|
+
|
79
|
+
expect(description).to eq 'no errors'
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should not fetch attribute values for errors that were copied from an autosaved belongs_to association' do
|
83
|
+
instance = define_model(:example) do
|
84
|
+
validate do |record|
|
85
|
+
record.errors.add('association.association_attribute', 'is invalid')
|
86
|
+
end
|
87
|
+
end.new
|
88
|
+
finder = Shoulda::Matchers::ActiveModel::ValidationMessageFinder.new(instance, :attribute)
|
89
|
+
|
90
|
+
expected_messages = ['association.association_attribute is invalid']
|
91
|
+
expect(finder.messages_description).to eq "errors: #{expected_messages}"
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
context '#source_description' do
|
97
|
+
it 'describes the source of its messages' do
|
98
|
+
finder = build_finder
|
99
|
+
|
100
|
+
description = finder.source_description
|
101
|
+
|
102
|
+
expect(description).to eq 'errors'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def build_finder(arguments = {})
|
107
|
+
arguments[:attribute] ||= :attr
|
108
|
+
instance = build_instance_validating(
|
109
|
+
arguments[:attribute],
|
110
|
+
arguments[:format] || /abc/,
|
111
|
+
arguments[:value] || 'abc'
|
112
|
+
)
|
113
|
+
Shoulda::Matchers::ActiveModel::ValidationMessageFinder.new(
|
114
|
+
instance,
|
115
|
+
arguments[:attribute]
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
def build_instance_validating(attribute, format, value)
|
120
|
+
model_class = define_model(:example, attribute => :string) do
|
121
|
+
attr_accessible attribute
|
122
|
+
validates_format_of attribute, with: format
|
123
|
+
end
|
124
|
+
|
125
|
+
model_class.new(attribute => value)
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Shoulda::Matchers::ActiveRecord::AcceptNestedAttributesForMatcher do
|
4
|
+
it 'accepts an existing declaration' do
|
5
|
+
expect(accepting_children).to accept_nested_attributes_for(:children)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'rejects a missing declaration' do
|
9
|
+
matcher = children_matcher
|
10
|
+
|
11
|
+
expect(matcher.matches?(rejecting_children)).to eq false
|
12
|
+
|
13
|
+
expect(matcher.failure_message).
|
14
|
+
to eq 'Expected Parent to accept nested attributes for children (is not declared)'
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'allow_destroy' do
|
18
|
+
it 'accepts a valid truthy value' do
|
19
|
+
matching = accepting_children(allow_destroy: true)
|
20
|
+
|
21
|
+
expect(matching).to children_matcher.allow_destroy(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'accepts a valid falsey value' do
|
25
|
+
matching = accepting_children(allow_destroy: false)
|
26
|
+
|
27
|
+
expect(matching).to children_matcher.allow_destroy(false)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'rejects an invalid truthy value' do
|
31
|
+
matcher = children_matcher
|
32
|
+
matching = accepting_children(allow_destroy: true)
|
33
|
+
|
34
|
+
expect(matcher.allow_destroy(false).matches?(matching)).to eq false
|
35
|
+
expect(matcher.failure_message).to match(/should not allow destroy/)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'rejects an invalid falsey value' do
|
39
|
+
matcher = children_matcher
|
40
|
+
matching = accepting_children(allow_destroy: false)
|
41
|
+
|
42
|
+
expect(matcher.allow_destroy(true).matches?(matching)).to eq false
|
43
|
+
expect(matcher.failure_message).to match(/should allow destroy/)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'limit' do
|
48
|
+
it 'accepts a correct value' do
|
49
|
+
expect(accepting_children(limit: 3)).to children_matcher.limit(3)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'rejects a false value' do
|
53
|
+
matcher = children_matcher
|
54
|
+
rejecting = accepting_children(limit: 3)
|
55
|
+
|
56
|
+
expect(matcher.limit(2).matches?(rejecting)).to eq false
|
57
|
+
expect(matcher.failure_message).to match(/limit should be 2, got 3/)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'update_only' do
|
62
|
+
it 'accepts a valid truthy value' do
|
63
|
+
expect(accepting_children(update_only: true)).
|
64
|
+
to children_matcher.update_only(true)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'accepts a valid falsey value' do
|
68
|
+
expect(accepting_children(update_only: false)).
|
69
|
+
to children_matcher.update_only(false)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'rejects an invalid truthy value' do
|
73
|
+
matcher = children_matcher.update_only(false)
|
74
|
+
rejecting = accepting_children(update_only: true)
|
75
|
+
|
76
|
+
expect(matcher.matches?(rejecting)).to eq false
|
77
|
+
expect(matcher.failure_message).to match(/should not be update only/)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'rejects an invalid falsey value' do
|
81
|
+
matcher = children_matcher.update_only(true)
|
82
|
+
rejecting = accepting_children(update_only: false)
|
83
|
+
|
84
|
+
expect(matcher.matches?(rejecting)).to eq false
|
85
|
+
expect(matcher.failure_message).to match(/should be update only/)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def accepting_children(options = {})
|
90
|
+
define_model :child, parent_id: :integer
|
91
|
+
define_model :parent do
|
92
|
+
has_many :children
|
93
|
+
accepts_nested_attributes_for :children, options
|
94
|
+
end.new
|
95
|
+
end
|
96
|
+
|
97
|
+
def children_matcher
|
98
|
+
accept_nested_attributes_for(:children)
|
99
|
+
end
|
100
|
+
|
101
|
+
def rejecting_children
|
102
|
+
define_model :child, parent_id: :integer
|
103
|
+
define_model :parent do
|
104
|
+
has_many :children
|
105
|
+
end.new
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,860 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
4
|
+
context 'belong_to' do
|
5
|
+
it 'accepts a good association with the default foreign key' do
|
6
|
+
expect(belonging_to_parent).to belong_to(:parent)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'rejects a nonexistent association' do
|
10
|
+
expect(define_model(:child).new).not_to belong_to(:parent)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'rejects an association of the wrong type' do
|
14
|
+
define_model :parent, child_id: :integer
|
15
|
+
expect(define_model(:child) { has_one :parent }.new).not_to belong_to(:parent)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'rejects an association that has a nonexistent foreign key' do
|
19
|
+
define_model :parent
|
20
|
+
expect(define_model(:child) { belongs_to :parent }.new).not_to belong_to(:parent)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'accepts an association with an existing custom foreign key' do
|
24
|
+
define_model :parent
|
25
|
+
define_model :child, guardian_id: :integer do
|
26
|
+
belongs_to :parent, foreign_key: 'guardian_id'
|
27
|
+
end
|
28
|
+
|
29
|
+
expect(Child.new).to belong_to(:parent)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'accepts a polymorphic association' do
|
33
|
+
define_model :parent
|
34
|
+
define_model :child, parent_type: :string, parent_id: :integer do
|
35
|
+
belongs_to :parent, polymorphic: true
|
36
|
+
end
|
37
|
+
|
38
|
+
expect(Child.new).to belong_to(:parent)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'accepts an association with a valid :dependent option' do
|
42
|
+
expect(belonging_to_parent(dependent: :destroy)).
|
43
|
+
to belong_to(:parent).dependent(:destroy)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'rejects an association with a bad :dependent option' do
|
47
|
+
expect(belonging_to_parent).not_to belong_to(:parent).dependent(:destroy)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'accepts an association with a valid :counter_cache option' do
|
51
|
+
expect(belonging_to_parent(counter_cache: :attribute_count)).
|
52
|
+
to belong_to(:parent).counter_cache(:attribute_count)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'defaults :counter_cache to true' do
|
56
|
+
expect(belonging_to_parent(counter_cache: true)).
|
57
|
+
to belong_to(:parent).counter_cache
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'rejects an association with a bad :counter_cache option' do
|
61
|
+
expect(belonging_to_parent(counter_cache: :attribute_count)).
|
62
|
+
not_to belong_to(:parent).counter_cache(true)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'rejects an association that has no :counter_cache option' do
|
66
|
+
expect(belonging_to_parent).not_to belong_to(:parent).counter_cache
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'accepts an association with a valid :conditions option' do
|
70
|
+
define_model :parent, adopter: :boolean
|
71
|
+
define_model(:child, parent_id: :integer).tap do |model|
|
72
|
+
define_association_with_conditions(model, :belongs_to, :parent, adopter: true)
|
73
|
+
end
|
74
|
+
|
75
|
+
expect(Child.new).to belong_to(:parent).conditions(adopter: true)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'rejects an association with a bad :conditions option' do
|
79
|
+
define_model :parent, adopter: :boolean
|
80
|
+
define_model :child, parent_id: :integer do
|
81
|
+
belongs_to :parent
|
82
|
+
end
|
83
|
+
|
84
|
+
expect(Child.new).not_to belong_to(:parent).conditions(adopter: true)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'accepts an association without a :class_name option' do
|
88
|
+
expect(belonging_to_parent).to belong_to(:parent).class_name('Parent')
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'accepts an association with a valid :class_name option' do
|
92
|
+
define_model :tree_parent
|
93
|
+
define_model :child, parent_id: :integer do
|
94
|
+
belongs_to :parent, class_name: 'TreeParent'
|
95
|
+
end
|
96
|
+
|
97
|
+
expect(Child.new).to belong_to(:parent).class_name('TreeParent')
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'rejects an association with a bad :class_name option' do
|
101
|
+
expect(belonging_to_parent).not_to belong_to(:parent).class_name('TreeChild')
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'rejects an association with non-existent implicit class name' do
|
105
|
+
expect(belonging_to_non_existent_class(:child, :parent)).not_to belong_to(:parent)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'rejects an association with non-existent explicit class name' do
|
109
|
+
expect(belonging_to_non_existent_class(:child, :parent, class_name: 'Parent')).not_to belong_to(:parent)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'adds error message when rejecting an association with non-existent class' do
|
113
|
+
message = 'Expected Child to have a belongs_to association called parent (Parent2 does not exist)'
|
114
|
+
expect {
|
115
|
+
expect(belonging_to_non_existent_class(:child, :parent, class_name: 'Parent2')).to belong_to(:parent)
|
116
|
+
}.to fail_with_message(message)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'accepts an association with a matching :autosave option' do
|
120
|
+
define_model :parent, adopter: :boolean
|
121
|
+
define_model :child, parent_id: :integer do
|
122
|
+
belongs_to :parent, autosave: true
|
123
|
+
end
|
124
|
+
expect(Child.new).to belong_to(:parent).autosave(true)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'rejects an association with a non-matching :autosave option with the correct message' do
|
128
|
+
define_model :parent, adopter: :boolean
|
129
|
+
define_model :child, parent_id: :integer do
|
130
|
+
belongs_to :parent, autosave: false
|
131
|
+
end
|
132
|
+
|
133
|
+
message = 'Expected Child to have a belongs_to association called parent (parent should have autosave set to true)'
|
134
|
+
expect {
|
135
|
+
expect(Child.new).to belong_to(:parent).autosave(true)
|
136
|
+
}.to fail_with_message(message)
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'an association with a :validate option' do
|
140
|
+
[false, true].each do |validate_value|
|
141
|
+
context "when the model has validate: #{validate_value}" do
|
142
|
+
it 'accepts a matching validate option' do
|
143
|
+
expect(belonging_to_parent(validate: validate_value)).
|
144
|
+
to belong_to(:parent).validate(validate_value)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'rejects a non-matching validate option' do
|
148
|
+
expect(belonging_to_parent(validate: validate_value)).
|
149
|
+
not_to belong_to(:parent).validate(!validate_value)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'defaults to validate(true)' do
|
153
|
+
if validate_value
|
154
|
+
expect(belonging_to_parent(validate: validate_value)).
|
155
|
+
to belong_to(:parent).validate
|
156
|
+
else
|
157
|
+
expect(belonging_to_parent(validate: validate_value)).
|
158
|
+
not_to belong_to(:parent).validate
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'will not break matcher when validate option is unspecified' do
|
163
|
+
expect(belonging_to_parent(validate: validate_value)).to belong_to(:parent)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'an association without a :validate option' do
|
170
|
+
it 'accepts validate(false)' do
|
171
|
+
expect(belonging_to_parent).to belong_to(:parent).validate(false)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'rejects validate(true)' do
|
175
|
+
expect(belonging_to_parent).not_to belong_to(:parent).validate(true)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'rejects validate()' do
|
179
|
+
expect(belonging_to_parent).not_to belong_to(:parent).validate
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'an association with a :touch option' do
|
184
|
+
[false, true].each do |touch_value|
|
185
|
+
context "when the model has touch: #{touch_value}" do
|
186
|
+
it 'accepts a matching touch option' do
|
187
|
+
expect(belonging_to_parent(touch: touch_value)).
|
188
|
+
to belong_to(:parent).touch(touch_value)
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'rejects a non-matching touch option' do
|
192
|
+
expect(belonging_to_parent(touch: touch_value)).
|
193
|
+
not_to belong_to(:parent).touch(!touch_value)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'defaults to touch(true)' do
|
197
|
+
if touch_value
|
198
|
+
expect(belonging_to_parent(touch: touch_value)).
|
199
|
+
to belong_to(:parent).touch
|
200
|
+
else
|
201
|
+
expect(belonging_to_parent(touch: touch_value)).
|
202
|
+
not_to belong_to(:parent).touch
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'will not break matcher when touch option is unspecified' do
|
207
|
+
expect(belonging_to_parent(touch: touch_value)).to belong_to(:parent)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'an association without a :touch option' do
|
214
|
+
it 'accepts touch(false)' do
|
215
|
+
expect(belonging_to_parent).to belong_to(:parent).touch(false)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'rejects touch(true)' do
|
219
|
+
expect(belonging_to_parent).not_to belong_to(:parent).touch(true)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'rejects touch()' do
|
223
|
+
expect(belonging_to_parent).not_to belong_to(:parent).touch
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def belonging_to_parent(options = {})
|
228
|
+
define_model :parent
|
229
|
+
define_model :child, parent_id: :integer do
|
230
|
+
belongs_to :parent, options
|
231
|
+
end.new
|
232
|
+
end
|
233
|
+
|
234
|
+
def belonging_to_non_existent_class(model_name, assoc_name, options = {})
|
235
|
+
define_model model_name, "#{assoc_name}_id" => :integer do
|
236
|
+
belongs_to assoc_name, options
|
237
|
+
end.new
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context 'have_many' do
|
242
|
+
it 'accepts a valid association without any options' do
|
243
|
+
expect(having_many_children).to have_many(:children)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'accepts a valid association with a :through option' do
|
247
|
+
define_model :child
|
248
|
+
define_model :conception, child_id: :integer,
|
249
|
+
parent_id: :integer do
|
250
|
+
belongs_to :child
|
251
|
+
end
|
252
|
+
define_model :parent do
|
253
|
+
has_many :conceptions
|
254
|
+
has_many :children, through: :conceptions
|
255
|
+
end
|
256
|
+
expect(Parent.new).to have_many(:children)
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'accepts a valid association with an :as option' do
|
260
|
+
define_model :child, guardian_type: :string, guardian_id: :integer
|
261
|
+
define_model :parent do
|
262
|
+
has_many :children, as: :guardian
|
263
|
+
end
|
264
|
+
|
265
|
+
expect(Parent.new).to have_many(:children)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'rejects an association that has a nonexistent foreign key' do
|
269
|
+
define_model :child
|
270
|
+
define_model :parent do
|
271
|
+
has_many :children
|
272
|
+
end
|
273
|
+
|
274
|
+
expect(Parent.new).not_to have_many(:children)
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'rejects an association with a bad :as option' do
|
278
|
+
define_model :child, caretaker_type: :string,
|
279
|
+
caretaker_id: :integer
|
280
|
+
define_model :parent do
|
281
|
+
has_many :children, as: :guardian
|
282
|
+
end
|
283
|
+
|
284
|
+
expect(Parent.new).not_to have_many(:children)
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'rejects an association that has a bad :through option' do
|
288
|
+
matcher = have_many(:children).through(:conceptions)
|
289
|
+
|
290
|
+
expect(matcher.matches?(having_many_children)).to eq false
|
291
|
+
|
292
|
+
expect(matcher.failure_message).to match(/does not have any relationship to conceptions/)
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'rejects an association that has the wrong :through option' do
|
296
|
+
define_model :child
|
297
|
+
|
298
|
+
define_model :conception, child_id: :integer,
|
299
|
+
parent_id: :integer do
|
300
|
+
belongs_to :child
|
301
|
+
end
|
302
|
+
|
303
|
+
define_model :parent do
|
304
|
+
has_many :conceptions
|
305
|
+
has_many :relationships
|
306
|
+
has_many :children, through: :conceptions
|
307
|
+
end
|
308
|
+
|
309
|
+
matcher = have_many(:children).through(:relationships)
|
310
|
+
expect(matcher.matches?(Parent.new)).to eq false
|
311
|
+
expect(matcher.failure_message).to match(/through relationships, but got it through conceptions/)
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'accepts an association with a valid :dependent option' do
|
315
|
+
expect(having_many_children(dependent: :destroy)).
|
316
|
+
to have_many(:children).dependent(:destroy)
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'rejects an association with a bad :dependent option' do
|
320
|
+
matcher = have_many(:children).dependent(:destroy)
|
321
|
+
|
322
|
+
expect(having_many_children).not_to matcher
|
323
|
+
|
324
|
+
expect(matcher.failure_message).to match(/children should have destroy dependency/)
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'accepts an association with a valid :source option' do
|
328
|
+
expect(having_many_children(source: :user)).
|
329
|
+
to have_many(:children).source(:user)
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'rejects an association with a bad :source option' do
|
333
|
+
matcher = have_many(:children).source(:user)
|
334
|
+
|
335
|
+
expect(having_many_children).not_to matcher
|
336
|
+
|
337
|
+
expect(matcher.failure_message).to match(/children should have user as source option/)
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'accepts an association with a valid :order option' do
|
341
|
+
expect(having_many_children(order: :id)).
|
342
|
+
to have_many(:children).order(:id)
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'rejects an association with a bad :order option' do
|
346
|
+
matcher = have_many(:children).order(:id)
|
347
|
+
|
348
|
+
expect(having_many_children).not_to matcher
|
349
|
+
|
350
|
+
expect(matcher.failure_message).to match(/children should be ordered by id/)
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'accepts an association with a valid :conditions option' do
|
354
|
+
define_model :child, parent_id: :integer, adopted: :boolean
|
355
|
+
define_model(:parent).tap do |model|
|
356
|
+
define_association_with_conditions(model, :has_many, :children, adopted: true)
|
357
|
+
end
|
358
|
+
|
359
|
+
expect(Parent.new).to have_many(:children).conditions(adopted: true)
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'rejects an association with a bad :conditions option' do
|
363
|
+
define_model :child, parent_id: :integer, adopted: :boolean
|
364
|
+
define_model :parent do
|
365
|
+
has_many :children
|
366
|
+
end
|
367
|
+
|
368
|
+
expect(Parent.new).not_to have_many(:children).conditions(adopted: true)
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'accepts an association without a :class_name option' do
|
372
|
+
expect(having_many_children).to have_many(:children).class_name('Child')
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'accepts an association with a valid :class_name option' do
|
376
|
+
define_model :node, parent_id: :integer
|
377
|
+
define_model :parent do
|
378
|
+
has_many :children, class_name: 'Node'
|
379
|
+
end
|
380
|
+
|
381
|
+
expect(Parent.new).to have_many(:children).class_name('Node')
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'rejects an association with a bad :class_name option' do
|
385
|
+
expect(having_many_children).not_to have_many(:children).class_name('Node')
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'rejects an association with non-existent implicit class name' do
|
389
|
+
expect(having_many_non_existent_class(:parent, :children)).not_to have_many(:children)
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'rejects an association with non-existent explicit class name' do
|
393
|
+
expect(having_many_non_existent_class(:parent, :children, class_name: 'Child')).not_to have_many(:children)
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'adds error message when rejecting an association with non-existent class' do
|
397
|
+
message = 'Expected Parent to have a has_many association called children (Child2 does not exist)'
|
398
|
+
expect {
|
399
|
+
expect(having_many_non_existent_class(:parent, :children, class_name: 'Child2')).to have_many(:children)
|
400
|
+
}.to fail_with_message(message)
|
401
|
+
end
|
402
|
+
|
403
|
+
it 'accepts an association with a matching :autosave option' do
|
404
|
+
define_model :child, parent_id: :integer
|
405
|
+
define_model :parent do
|
406
|
+
has_many :children, autosave: true
|
407
|
+
end
|
408
|
+
expect(Parent.new).to have_many(:children).autosave(true)
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'rejects an association with a non-matching :autosave option with the correct message' do
|
412
|
+
define_model :child, parent_id: :integer
|
413
|
+
define_model :parent do
|
414
|
+
has_many :children, autosave: false
|
415
|
+
end
|
416
|
+
|
417
|
+
message = 'Expected Parent to have a has_many association called children (children should have autosave set to true)'
|
418
|
+
expect {
|
419
|
+
expect(Parent.new).to have_many(:children).autosave(true)
|
420
|
+
}.to fail_with_message(message)
|
421
|
+
end
|
422
|
+
|
423
|
+
context 'validate' do
|
424
|
+
it 'accepts when the :validate option matches' do
|
425
|
+
expect(having_many_children(validate: false)).to have_many(:children).validate(false)
|
426
|
+
end
|
427
|
+
|
428
|
+
it 'rejects when the :validate option does not match' do
|
429
|
+
expect(having_many_children(validate: true)).not_to have_many(:children).validate(false)
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'assumes validate() means validate(true)' do
|
433
|
+
expect(having_many_children(validate: false)).not_to have_many(:children).validate
|
434
|
+
end
|
435
|
+
|
436
|
+
it 'matches validate(false) to having no validate option specified' do
|
437
|
+
expect(having_many_children).to have_many(:children).validate(false)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'accepts an association with a nonstandard reverse foreign key, using :inverse_of' do
|
442
|
+
define_model :child, ancestor_id: :integer do
|
443
|
+
belongs_to :ancestor, inverse_of: :children, class_name: :Parent
|
444
|
+
end
|
445
|
+
|
446
|
+
define_model :parent do
|
447
|
+
has_many :children, inverse_of: :ancestor
|
448
|
+
end
|
449
|
+
|
450
|
+
expect(Parent.new).to have_many(:children)
|
451
|
+
end
|
452
|
+
|
453
|
+
it 'rejects an association with a nonstandard reverse foreign key, if :inverse_of is not correct' do
|
454
|
+
define_model :child, mother_id: :integer do
|
455
|
+
belongs_to :mother, inverse_of: :children, class_name: :Parent
|
456
|
+
end
|
457
|
+
|
458
|
+
define_model :parent do
|
459
|
+
has_many :children, inverse_of: :ancestor
|
460
|
+
end
|
461
|
+
|
462
|
+
expect(Parent.new).not_to have_many(:children)
|
463
|
+
end
|
464
|
+
|
465
|
+
def having_many_children(options = {})
|
466
|
+
define_model :child, parent_id: :integer
|
467
|
+
define_model(:parent).tap do |model|
|
468
|
+
if options.key?(:order)
|
469
|
+
order = options.delete(:order)
|
470
|
+
define_association_with_order(model, :has_many, :children, order, options)
|
471
|
+
else
|
472
|
+
model.has_many :children, options
|
473
|
+
end
|
474
|
+
end.new
|
475
|
+
end
|
476
|
+
|
477
|
+
def having_many_non_existent_class(model_name, assoc_name, options = {})
|
478
|
+
define_model model_name do
|
479
|
+
has_many assoc_name, options
|
480
|
+
end.new
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
context 'have_one' do
|
485
|
+
it 'accepts a valid association without any options' do
|
486
|
+
expect(having_one_detail).to have_one(:detail)
|
487
|
+
end
|
488
|
+
|
489
|
+
it 'accepts a valid association with an :as option' do
|
490
|
+
define_model :detail, detailable_id: :integer,
|
491
|
+
detailable_type: :string
|
492
|
+
define_model :person do
|
493
|
+
has_one :detail, as: :detailable
|
494
|
+
end
|
495
|
+
|
496
|
+
expect(Person.new).to have_one(:detail)
|
497
|
+
end
|
498
|
+
|
499
|
+
it 'rejects an association that has a nonexistent foreign key' do
|
500
|
+
define_model :detail
|
501
|
+
define_model :person do
|
502
|
+
has_one :detail
|
503
|
+
end
|
504
|
+
|
505
|
+
expect(Person.new).not_to have_one(:detail)
|
506
|
+
end
|
507
|
+
|
508
|
+
it 'accepts an association with an existing custom foreign key' do
|
509
|
+
define_model :detail, detailed_person_id: :integer
|
510
|
+
define_model :person do
|
511
|
+
has_one :detail, foreign_key: :detailed_person_id
|
512
|
+
end
|
513
|
+
expect(Person.new).to have_one(:detail).with_foreign_key(:detailed_person_id)
|
514
|
+
end
|
515
|
+
|
516
|
+
it 'rejects an association with a bad :as option' do
|
517
|
+
define_model :detail, detailable_id: :integer,
|
518
|
+
detailable_type: :string
|
519
|
+
define_model :person do
|
520
|
+
has_one :detail, as: :describable
|
521
|
+
end
|
522
|
+
|
523
|
+
expect(Person.new).not_to have_one(:detail)
|
524
|
+
end
|
525
|
+
|
526
|
+
it 'accepts an association with a valid :dependent option' do
|
527
|
+
expect(having_one_detail(dependent: :destroy)).
|
528
|
+
to have_one(:detail).dependent(:destroy)
|
529
|
+
end
|
530
|
+
|
531
|
+
it 'rejects an association with a bad :dependent option' do
|
532
|
+
matcher = have_one(:detail).dependent(:destroy)
|
533
|
+
|
534
|
+
expect(having_one_detail).not_to matcher
|
535
|
+
|
536
|
+
expect(matcher.failure_message).to match(/detail should have destroy dependency/)
|
537
|
+
end
|
538
|
+
|
539
|
+
it 'accepts an association with a valid :order option' do
|
540
|
+
expect(having_one_detail(order: :id)).to have_one(:detail).order(:id)
|
541
|
+
end
|
542
|
+
|
543
|
+
it 'rejects an association with a bad :order option' do
|
544
|
+
matcher = have_one(:detail).order(:id)
|
545
|
+
|
546
|
+
expect(having_one_detail).not_to matcher
|
547
|
+
|
548
|
+
expect(matcher.failure_message).to match(/detail should be ordered by id/)
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'accepts an association with a valid :conditions option' do
|
552
|
+
define_model :detail, person_id: :integer, disabled: :boolean
|
553
|
+
define_model(:person).tap do |model|
|
554
|
+
define_association_with_conditions(model, :has_one, :detail, disabled: true)
|
555
|
+
end
|
556
|
+
|
557
|
+
expect(Person.new).to have_one(:detail).conditions(disabled: true)
|
558
|
+
end
|
559
|
+
|
560
|
+
it 'rejects an association with a bad :conditions option' do
|
561
|
+
define_model :detail, person_id: :integer, disabled: :boolean
|
562
|
+
define_model :person do
|
563
|
+
has_one :detail
|
564
|
+
end
|
565
|
+
|
566
|
+
expect(Person.new).not_to have_one(:detail).conditions(disabled: true)
|
567
|
+
end
|
568
|
+
|
569
|
+
it 'accepts an association without a :class_name option' do
|
570
|
+
expect(having_one_detail).to have_one(:detail).class_name('Detail')
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'accepts an association with a matching :autosave option' do
|
574
|
+
define_model :detail, person_id: :integer, disabled: :boolean
|
575
|
+
define_model :person do
|
576
|
+
has_one :detail, autosave: true
|
577
|
+
end
|
578
|
+
expect(Person.new).to have_one(:detail).autosave(true)
|
579
|
+
end
|
580
|
+
|
581
|
+
it 'rejects an association with a non-matching :autosave option with the correct message' do
|
582
|
+
define_model :detail, person_id: :integer, disabled: :boolean
|
583
|
+
define_model :person do
|
584
|
+
has_one :detail, autosave: false
|
585
|
+
end
|
586
|
+
|
587
|
+
message = 'Expected Person to have a has_one association called detail (detail should have autosave set to true)'
|
588
|
+
expect {
|
589
|
+
expect(Person.new).to have_one(:detail).autosave(true)
|
590
|
+
}.to fail_with_message(message)
|
591
|
+
end
|
592
|
+
|
593
|
+
it 'accepts an association with a valid :class_name option' do
|
594
|
+
define_model :person_detail, person_id: :integer
|
595
|
+
define_model :person do
|
596
|
+
has_one :detail, class_name: 'PersonDetail'
|
597
|
+
end
|
598
|
+
|
599
|
+
expect(Person.new).to have_one(:detail).class_name('PersonDetail')
|
600
|
+
end
|
601
|
+
|
602
|
+
it 'rejects an association with a bad :class_name option' do
|
603
|
+
expect(having_one_detail).not_to have_one(:detail).class_name('NotSet')
|
604
|
+
end
|
605
|
+
|
606
|
+
it 'rejects an association with non-existent implicit class name' do
|
607
|
+
expect(having_one_non_existent(:pserson, :detail)).not_to have_one(:detail)
|
608
|
+
end
|
609
|
+
|
610
|
+
it 'rejects an association with non-existent explicit class name' do
|
611
|
+
expect(having_one_non_existent(:person, :detail, class_name: 'Detail')).not_to have_one(:detail)
|
612
|
+
end
|
613
|
+
|
614
|
+
it 'adds error message when rejecting an association with non-existent class' do
|
615
|
+
message = 'Expected Person to have a has_one association called detail (Detail2 does not exist)'
|
616
|
+
expect {
|
617
|
+
expect(having_one_non_existent(:person, :detail, class_name: 'Detail2')).to have_one(:detail)
|
618
|
+
}.to fail_with_message(message)
|
619
|
+
end
|
620
|
+
|
621
|
+
it 'accepts an association with a through' do
|
622
|
+
define_model :detail
|
623
|
+
|
624
|
+
define_model :account do
|
625
|
+
has_one :detail
|
626
|
+
end
|
627
|
+
|
628
|
+
define_model :person do
|
629
|
+
has_one :account
|
630
|
+
has_one :detail, through: :account
|
631
|
+
end
|
632
|
+
|
633
|
+
expect(Person.new).to have_one(:detail).through(:account)
|
634
|
+
end
|
635
|
+
|
636
|
+
it 'rejects an association with a bad through' do
|
637
|
+
expect(having_one_detail).not_to have_one(:detail).through(:account)
|
638
|
+
end
|
639
|
+
|
640
|
+
context 'validate' do
|
641
|
+
it 'accepts when the :validate option matches' do
|
642
|
+
expect(having_one_detail(validate: false)).
|
643
|
+
to have_one(:detail).validate(false)
|
644
|
+
end
|
645
|
+
|
646
|
+
it 'rejects when the :validate option does not match' do
|
647
|
+
expect(having_one_detail(validate: true)).
|
648
|
+
not_to have_one(:detail).validate(false)
|
649
|
+
end
|
650
|
+
|
651
|
+
it 'assumes validate() means validate(true)' do
|
652
|
+
expect(having_one_detail(validate: false)).
|
653
|
+
not_to have_one(:detail).validate
|
654
|
+
end
|
655
|
+
|
656
|
+
it 'matches validate(false) to having no validate option specified' do
|
657
|
+
expect(having_one_detail).to have_one(:detail).validate(false)
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
def having_one_detail(options = {})
|
662
|
+
define_model :detail, person_id: :integer
|
663
|
+
define_model(:person).tap do |model|
|
664
|
+
if options.key?(:order)
|
665
|
+
order = options.delete(:order)
|
666
|
+
define_association_with_order(model, :has_one, :detail, order, options)
|
667
|
+
else
|
668
|
+
model.has_one :detail, options
|
669
|
+
end
|
670
|
+
end.new
|
671
|
+
end
|
672
|
+
|
673
|
+
def having_one_non_existent(model_name, assoc_name, options = {})
|
674
|
+
define_model model_name do
|
675
|
+
has_one assoc_name, options
|
676
|
+
end.new
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
context 'have_and_belong_to_many' do
|
681
|
+
it 'accepts a valid association' do
|
682
|
+
expect(having_and_belonging_to_many_relatives).
|
683
|
+
to have_and_belong_to_many(:relatives)
|
684
|
+
end
|
685
|
+
|
686
|
+
it 'rejects a nonexistent association' do
|
687
|
+
define_model :relative
|
688
|
+
define_model :person
|
689
|
+
define_model :people_relative, id: false, person_id: :integer,
|
690
|
+
relative_id: :integer
|
691
|
+
|
692
|
+
expect(Person.new).not_to have_and_belong_to_many(:relatives)
|
693
|
+
end
|
694
|
+
|
695
|
+
it 'rejects an association with a nonexistent join table' do
|
696
|
+
define_model :relative
|
697
|
+
define_model :person do
|
698
|
+
has_and_belongs_to_many :relatives
|
699
|
+
end
|
700
|
+
|
701
|
+
expect(Person.new).not_to have_and_belong_to_many(:relatives)
|
702
|
+
end
|
703
|
+
|
704
|
+
it 'rejects an association of the wrong type' do
|
705
|
+
define_model :relative, person_id: :integer
|
706
|
+
define_model :person do
|
707
|
+
has_many :relatives
|
708
|
+
end
|
709
|
+
|
710
|
+
expect(Person.new).not_to have_and_belong_to_many(:relatives)
|
711
|
+
end
|
712
|
+
|
713
|
+
it 'accepts an association with a valid :conditions option' do
|
714
|
+
define_model :relative, adopted: :boolean
|
715
|
+
define_model(:person).tap do |model|
|
716
|
+
define_association_with_conditions(model, :has_and_belongs_to_many, :relatives, adopted: true)
|
717
|
+
end
|
718
|
+
define_model :people_relative, id: false, person_id: :integer,
|
719
|
+
relative_id: :integer
|
720
|
+
|
721
|
+
expect(Person.new).to have_and_belong_to_many(:relatives).conditions(adopted: true)
|
722
|
+
end
|
723
|
+
|
724
|
+
it 'rejects an association with a bad :conditions option' do
|
725
|
+
define_model :relative, adopted: :boolean
|
726
|
+
define_model :person do
|
727
|
+
has_and_belongs_to_many :relatives
|
728
|
+
end
|
729
|
+
define_model :people_relative, id: false, person_id: :integer,
|
730
|
+
relative_id: :integer
|
731
|
+
|
732
|
+
expect(Person.new).not_to have_and_belong_to_many(:relatives).conditions(adopted: true)
|
733
|
+
end
|
734
|
+
|
735
|
+
it 'accepts an association without a :class_name option' do
|
736
|
+
expect(having_and_belonging_to_many_relatives).
|
737
|
+
to have_and_belong_to_many(:relatives).class_name('Relative')
|
738
|
+
end
|
739
|
+
|
740
|
+
it 'accepts an association with a valid :class_name option' do
|
741
|
+
define_model :person_relative, adopted: :boolean
|
742
|
+
define_model :person do
|
743
|
+
has_and_belongs_to_many :relatives, class_name: 'PersonRelative'
|
744
|
+
end
|
745
|
+
|
746
|
+
define_model :people_person_relative, person_id: :integer,
|
747
|
+
person_relative_id: :integer
|
748
|
+
|
749
|
+
expect(Person.new).to have_and_belong_to_many(:relatives).class_name('PersonRelative')
|
750
|
+
end
|
751
|
+
|
752
|
+
it 'rejects an association with a bad :class_name option' do
|
753
|
+
expect(having_and_belonging_to_many_relatives).
|
754
|
+
not_to have_and_belong_to_many(:relatives).class_name('PersonRelatives')
|
755
|
+
end
|
756
|
+
|
757
|
+
it 'rejects an association with non-existent implicit class name' do
|
758
|
+
expect(having_and_belonging_to_many_non_existent_class(:person, :relatives)).
|
759
|
+
not_to have_and_belong_to_many(:relatives)
|
760
|
+
end
|
761
|
+
|
762
|
+
it 'rejects an association with non-existent explicit class name' do
|
763
|
+
expect(having_and_belonging_to_many_non_existent_class(:person, :relatives, class_name: 'Relative')).
|
764
|
+
not_to have_and_belong_to_many(:relatives)
|
765
|
+
end
|
766
|
+
|
767
|
+
it 'adds error message when rejecting an association with non-existent class' do
|
768
|
+
message = 'Expected Person to have a has_and_belongs_to_many association called relatives (Relative2 does not exist)'
|
769
|
+
expect {
|
770
|
+
expect(having_and_belonging_to_many_non_existent_class(:person, :relatives, class_name: 'Relative2')).
|
771
|
+
to have_and_belong_to_many(:relatives)
|
772
|
+
}.to fail_with_message(message)
|
773
|
+
end
|
774
|
+
|
775
|
+
it 'accepts an association with a matching :autosave option' do
|
776
|
+
define_model :relatives, adopted: :boolean
|
777
|
+
define_model :person do
|
778
|
+
has_and_belongs_to_many :relatives, autosave: true
|
779
|
+
end
|
780
|
+
define_model :people_relative, person_id: :integer,
|
781
|
+
relative_id: :integer
|
782
|
+
expect(Person.new).to have_and_belong_to_many(:relatives).autosave(true)
|
783
|
+
end
|
784
|
+
|
785
|
+
it 'rejects an association with a non-matching :autosave option with the correct message' do
|
786
|
+
define_model :relatives, adopted: :boolean
|
787
|
+
define_model :person do
|
788
|
+
has_and_belongs_to_many :relatives
|
789
|
+
end
|
790
|
+
define_model :people_relative, person_id: :integer,
|
791
|
+
relative_id: :integer
|
792
|
+
|
793
|
+
message = 'Expected Person to have a has_and_belongs_to_many association called relatives (relatives should have autosave set to true)'
|
794
|
+
expect {
|
795
|
+
expect(Person.new).to have_and_belong_to_many(:relatives).autosave(true)
|
796
|
+
}.to fail_with_message(message)
|
797
|
+
end
|
798
|
+
|
799
|
+
context 'validate' do
|
800
|
+
it 'accepts when the :validate option matches' do
|
801
|
+
expect(having_and_belonging_to_many_relatives(validate: false)).
|
802
|
+
to have_and_belong_to_many(:relatives).validate(false)
|
803
|
+
end
|
804
|
+
|
805
|
+
it 'rejects when the :validate option does not match' do
|
806
|
+
expect(having_and_belonging_to_many_relatives(validate: true)).
|
807
|
+
to have_and_belong_to_many(:relatives).validate(false)
|
808
|
+
end
|
809
|
+
|
810
|
+
it 'assumes validate() means validate(true)' do
|
811
|
+
expect(having_and_belonging_to_many_relatives(validate: false)).
|
812
|
+
not_to have_and_belong_to_many(:relatives).validate
|
813
|
+
end
|
814
|
+
|
815
|
+
it 'matches validate(false) to having no validate option specified' do
|
816
|
+
expect(having_and_belonging_to_many_relatives).
|
817
|
+
to have_and_belong_to_many(:relatives).validate(false)
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
def having_and_belonging_to_many_relatives(options = {})
|
822
|
+
define_model :relative
|
823
|
+
define_model :people_relative, id: false, person_id: :integer,
|
824
|
+
relative_id: :integer
|
825
|
+
define_model :person do
|
826
|
+
has_and_belongs_to_many :relatives
|
827
|
+
end.new
|
828
|
+
end
|
829
|
+
|
830
|
+
def having_and_belonging_to_many_non_existent_class(model_name, assoc_name, options = {})
|
831
|
+
define_model model_name do
|
832
|
+
has_and_belongs_to_many assoc_name, options
|
833
|
+
end.new
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
def define_association_with_conditions(model, macro, name, conditions, other_options={})
|
838
|
+
args = []
|
839
|
+
options = {}
|
840
|
+
if Shoulda::Matchers::RailsShim.active_record_major_version == 4
|
841
|
+
args << lambda { where(conditions) }
|
842
|
+
else
|
843
|
+
options[:conditions] = conditions
|
844
|
+
end
|
845
|
+
args << options
|
846
|
+
model.__send__(macro, name, *args)
|
847
|
+
end
|
848
|
+
|
849
|
+
def define_association_with_order(model, macro, name, order, other_options={})
|
850
|
+
args = []
|
851
|
+
options = {}
|
852
|
+
if Shoulda::Matchers::RailsShim.active_record_major_version == 4
|
853
|
+
args << lambda { order(order) }
|
854
|
+
else
|
855
|
+
options[:order] = order
|
856
|
+
end
|
857
|
+
args << options
|
858
|
+
model.__send__(macro, name, *args)
|
859
|
+
end
|
860
|
+
end
|