francois-shoulda 2.0.5.4 → 2.10.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.
- data/README.rdoc +60 -10
- data/Rakefile +7 -7
- data/lib/shoulda.rb +7 -15
- data/lib/shoulda/action_controller.rb +28 -0
- data/lib/shoulda/action_controller/helpers.rb +47 -0
- data/lib/shoulda/action_controller/macros.rb +277 -0
- data/lib/shoulda/action_controller/matchers.rb +37 -0
- data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +109 -0
- data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +57 -0
- data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +81 -0
- data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +70 -0
- data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +81 -0
- data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
- data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +87 -0
- data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
- data/lib/shoulda/action_mailer.rb +1 -1
- data/lib/shoulda/action_mailer/assertions.rb +32 -33
- data/lib/shoulda/action_view.rb +10 -0
- data/lib/shoulda/action_view/macros.rb +56 -0
- data/lib/shoulda/active_record.rb +6 -2
- data/lib/shoulda/active_record/assertions.rb +62 -89
- data/lib/shoulda/active_record/helpers.rb +40 -0
- data/lib/shoulda/active_record/macros.rb +520 -684
- data/lib/shoulda/active_record/matchers.rb +42 -0
- data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
- data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
- data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
- data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
- data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
- data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
- data/lib/shoulda/active_record/matchers/have_index_matcher.rb +105 -0
- data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +125 -0
- data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
- data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
- data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
- data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
- data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
- data/lib/shoulda/active_record/matchers/validation_matcher.rb +56 -0
- data/lib/shoulda/assertions.rb +50 -40
- data/lib/shoulda/autoload_macros.rb +46 -0
- data/lib/shoulda/context.rb +124 -126
- data/lib/shoulda/helpers.rb +5 -7
- data/lib/shoulda/macros.rb +63 -64
- data/lib/shoulda/private_helpers.rb +16 -18
- data/lib/shoulda/rails.rb +5 -11
- data/lib/shoulda/rspec.rb +11 -0
- data/lib/shoulda/tasks/list_tests.rake +6 -1
- data/lib/shoulda/test_unit.rb +19 -0
- data/rails/init.rb +7 -1
- data/test/README +2 -2
- data/test/fail_macros.rb +15 -15
- data/test/fixtures/tags.yml +1 -1
- data/test/functional/posts_controller_test.rb +46 -26
- data/test/functional/users_controller_test.rb +0 -19
- data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
- data/test/matchers/active_record/allow_value_matcher_test.rb +41 -0
- data/test/matchers/active_record/association_matcher_test.rb +258 -0
- data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
- data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
- data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
- data/test/matchers/active_record/have_index_matcher_test.rb +74 -0
- data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
- data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
- data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
- data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
- data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
- data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
- data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
- data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
- data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
- data/test/matchers/controller/respond_with_content_type_matcher_test.rb +27 -0
- data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
- data/test/matchers/controller/route_matcher_test.rb +58 -0
- data/test/matchers/controller/set_session_matcher_test.rb +31 -0
- data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
- data/test/model_builder.rb +106 -0
- data/test/other/autoload_macro_test.rb +18 -0
- data/test/other/helpers_test.rb +58 -0
- data/test/other/private_helpers_test.rb +1 -1
- data/test/other/should_test.rb +16 -16
- data/test/rails_root/app/controllers/posts_controller.rb +6 -5
- data/test/rails_root/app/models/pets/dog.rb +10 -0
- data/test/rails_root/app/models/treat.rb +3 -0
- data/test/rails_root/app/models/user.rb +4 -3
- data/test/rails_root/app/views/layouts/posts.rhtml +2 -0
- data/test/rails_root/config/database.yml +1 -1
- data/test/rails_root/config/environment.rb +1 -1
- data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
- data/test/rails_root/db/migrate/001_create_users.rb +3 -2
- data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
- data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
- data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
- data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
- data/test/rspec_test.rb +207 -0
- data/test/test_helper.rb +3 -1
- data/test/unit/address_test.rb +1 -23
- data/test/unit/dog_test.rb +5 -2
- data/test/unit/post_test.rb +7 -3
- data/test/unit/product_test.rb +2 -2
- data/test/unit/tag_test.rb +2 -1
- data/test/unit/user_test.rb +25 -9
- metadata +84 -23
- data/lib/shoulda/controller.rb +0 -30
- data/lib/shoulda/controller/formats/html.rb +0 -201
- data/lib/shoulda/controller/formats/xml.rb +0 -170
- data/lib/shoulda/controller/helpers.rb +0 -64
- data/lib/shoulda/controller/macros.rb +0 -316
- data/lib/shoulda/controller/resource_options.rb +0 -236
- data/test/rails_root/app/models/dog.rb +0 -5
@@ -0,0 +1,87 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
# Ensure that the attribute's value is in the range specified
|
6
|
+
#
|
7
|
+
# Options:
|
8
|
+
# * <tt>in_range</tt> - the range of allowed values for this attribute
|
9
|
+
# * <tt>with_low_message</tt> - value the test expects to find in
|
10
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
11
|
+
# translation for :inclusion.
|
12
|
+
# * <tt>with_high_message</tt> - value the test expects to find in
|
13
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
14
|
+
# translation for :inclusion.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
# it { should ensure_inclusion_of(:age).in_range(0..100) }
|
18
|
+
#
|
19
|
+
def ensure_inclusion_of(attr)
|
20
|
+
EnsureInclusionOfMatcher.new(attr)
|
21
|
+
end
|
22
|
+
|
23
|
+
class EnsureInclusionOfMatcher < ValidationMatcher # :nodoc:
|
24
|
+
|
25
|
+
def in_range(range)
|
26
|
+
@range = range
|
27
|
+
@minimum = range.first
|
28
|
+
@maximum = range.last
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_message(message)
|
33
|
+
if message
|
34
|
+
@low_message = message
|
35
|
+
@high_message = message
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_low_message(message)
|
41
|
+
@low_message = message if message
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def with_high_message(message)
|
46
|
+
@high_message = message if message
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def description
|
51
|
+
"ensure inclusion of #{@attribute} in #{@range.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def matches?(subject)
|
55
|
+
super(subject)
|
56
|
+
|
57
|
+
@low_message ||= :inclusion
|
58
|
+
@high_message ||= :inclusion
|
59
|
+
|
60
|
+
disallows_lower_value &&
|
61
|
+
allows_minimum_value &&
|
62
|
+
disallows_higher_value &&
|
63
|
+
allows_maximum_value
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def disallows_lower_value
|
69
|
+
@minimum == 0 || disallows_value_of(@minimum - 1, @low_message)
|
70
|
+
end
|
71
|
+
|
72
|
+
def disallows_higher_value
|
73
|
+
disallows_value_of(@maximum + 1, @high_message)
|
74
|
+
end
|
75
|
+
|
76
|
+
def allows_minimum_value
|
77
|
+
allows_value_of(@minimum, @low_message)
|
78
|
+
end
|
79
|
+
|
80
|
+
def allows_maximum_value
|
81
|
+
allows_value_of(@maximum, @high_message)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
# Ensures that the length of the attribute is validated.
|
6
|
+
#
|
7
|
+
# Options:
|
8
|
+
# * <tt>is_at_least</tt> - minimum length of this attribute
|
9
|
+
# * <tt>is_at_most</tt> - maximum length of this attribute
|
10
|
+
# * <tt>is_equal_to</tt> - exact requred length of this attribute
|
11
|
+
# * <tt>with_short_message</tt> - value the test expects to find in
|
12
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
13
|
+
# translation for :too_short.
|
14
|
+
# * <tt>with_long_message</tt> - value the test expects to find in
|
15
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
16
|
+
# translation for :too_long.
|
17
|
+
# * <tt>with_message</tt> - value the test expects to find in
|
18
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
19
|
+
# translation for :wrong_length. Used in conjunction with
|
20
|
+
# <tt>is_equal_to</tt>.
|
21
|
+
#
|
22
|
+
# Examples:
|
23
|
+
# it { should ensure_length_of(:password).
|
24
|
+
# is_at_least(6).
|
25
|
+
# is_at_most(20) }
|
26
|
+
# it { should ensure_length_of(:name).
|
27
|
+
# is_at_least(3).
|
28
|
+
# with_short_message(/not long enough/) }
|
29
|
+
# it { should ensure_length_of(:ssn).
|
30
|
+
# is_equal_to(9).
|
31
|
+
# with_message(/is invalid/) }
|
32
|
+
def ensure_length_of(attr)
|
33
|
+
EnsureLengthOfMatcher.new(attr)
|
34
|
+
end
|
35
|
+
|
36
|
+
class EnsureLengthOfMatcher < ValidationMatcher # :nodoc:
|
37
|
+
include Helpers
|
38
|
+
|
39
|
+
def is_at_least(length)
|
40
|
+
@minimum = length
|
41
|
+
@short_message ||= :too_short
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def is_at_most(length)
|
46
|
+
@maximum = length
|
47
|
+
@long_message ||= :too_long
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_equal_to(length)
|
52
|
+
@minimum = length
|
53
|
+
@maximum = length
|
54
|
+
@short_message ||= :wrong_length
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_short_message(message)
|
59
|
+
@short_message = message if message
|
60
|
+
self
|
61
|
+
end
|
62
|
+
alias_method :with_message, :with_short_message
|
63
|
+
|
64
|
+
def with_long_message(message)
|
65
|
+
@long_message = message if message
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def description
|
70
|
+
description = "ensure #{@attribute} has a length "
|
71
|
+
if @minimum && @maximum
|
72
|
+
if @minimum == @maximum
|
73
|
+
description << "of exactly #{@minimum}"
|
74
|
+
else
|
75
|
+
description << "between #{@minimum} and #{@maximum}"
|
76
|
+
end
|
77
|
+
else
|
78
|
+
description << "of at least #{@minimum}" if @minimum
|
79
|
+
description << "of at most #{@maximum}" if @maximum
|
80
|
+
end
|
81
|
+
description
|
82
|
+
end
|
83
|
+
|
84
|
+
def matches?(subject)
|
85
|
+
super(subject)
|
86
|
+
translate_messages!
|
87
|
+
disallows_lower_length &&
|
88
|
+
allows_minimum_length &&
|
89
|
+
((@minimum == @maximum) ||
|
90
|
+
(disallows_higher_length &&
|
91
|
+
allows_maximum_length))
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def translate_messages!
|
97
|
+
if Symbol === @short_message
|
98
|
+
@short_message = default_error_message(@short_message,
|
99
|
+
:count => @minimum)
|
100
|
+
end
|
101
|
+
|
102
|
+
if Symbol === @long_message
|
103
|
+
@long_message = default_error_message(@long_message,
|
104
|
+
:count => @maximum)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def disallows_lower_length
|
109
|
+
@minimum == 0 ||
|
110
|
+
@minimum.nil? ||
|
111
|
+
disallows_length_of(@minimum - 1, @short_message)
|
112
|
+
end
|
113
|
+
|
114
|
+
def disallows_higher_length
|
115
|
+
@maximum.nil? || disallows_length_of(@maximum + 1, @long_message)
|
116
|
+
end
|
117
|
+
|
118
|
+
def allows_minimum_length
|
119
|
+
allows_length_of(@minimum, @short_message)
|
120
|
+
end
|
121
|
+
|
122
|
+
def allows_maximum_length
|
123
|
+
allows_length_of(@maximum, @long_message)
|
124
|
+
end
|
125
|
+
|
126
|
+
def allows_length_of(length, message)
|
127
|
+
length.nil? || allows_value_of(string_of_length(length), message)
|
128
|
+
end
|
129
|
+
|
130
|
+
def disallows_length_of(length, message)
|
131
|
+
length.nil? || disallows_value_of(string_of_length(length), message)
|
132
|
+
end
|
133
|
+
|
134
|
+
def string_of_length(length)
|
135
|
+
'x' * length
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
# Ensures the database column exists.
|
6
|
+
#
|
7
|
+
# Options:
|
8
|
+
# * <tt>of_type</tt> - db column type (:integer, :string, etc.)
|
9
|
+
# * <tt>with_options</tt> - same options available in migrations
|
10
|
+
# (:default, :null, :limit, :precision, :scale)
|
11
|
+
#
|
12
|
+
# Examples:
|
13
|
+
# it { should_not have_db_column(:admin).of_type(:boolean) }
|
14
|
+
# it { should have_db_column(:salary).
|
15
|
+
# of_type(:decimal).
|
16
|
+
# with_options(:precision => 10, :scale => 2) }
|
17
|
+
#
|
18
|
+
def have_db_column(column)
|
19
|
+
HaveDbColumnMatcher.new(:have_db_column, column)
|
20
|
+
end
|
21
|
+
|
22
|
+
class HaveDbColumnMatcher # :nodoc:
|
23
|
+
def initialize(macro, column)
|
24
|
+
@macro = macro
|
25
|
+
@column = column
|
26
|
+
end
|
27
|
+
|
28
|
+
def of_type(column_type)
|
29
|
+
@column_type = column_type
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_options(opts = {})
|
34
|
+
@precision = opts[:precision]
|
35
|
+
@limit = opts[:limit]
|
36
|
+
@default = opts[:default]
|
37
|
+
@null = opts[:null]
|
38
|
+
@scale = opts[:scale]
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def matches?(subject)
|
43
|
+
@subject = subject
|
44
|
+
column_exists? &&
|
45
|
+
correct_column_type? &&
|
46
|
+
correct_precision? &&
|
47
|
+
correct_limit? &&
|
48
|
+
correct_default? &&
|
49
|
+
correct_null? &&
|
50
|
+
correct_scale?
|
51
|
+
end
|
52
|
+
|
53
|
+
def failure_message
|
54
|
+
"Expected #{expectation} (#{@missing})"
|
55
|
+
end
|
56
|
+
|
57
|
+
def negative_failure_message
|
58
|
+
"Did not expect #{expectation}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def description
|
62
|
+
desc = "have db column named #{@column}"
|
63
|
+
desc << " of type #{@column_type}" unless @column_type.nil?
|
64
|
+
desc << " of precision #{@precision}" unless @precision.nil?
|
65
|
+
desc << " of limit #{@limit}" unless @limit.nil?
|
66
|
+
desc << " of default #{@default}" unless @default.nil?
|
67
|
+
desc << " of null #{@null}" unless @null.nil?
|
68
|
+
desc << " of primary #{@primary}" unless @primary.nil?
|
69
|
+
desc << " of scale #{@scale}" unless @scale.nil?
|
70
|
+
desc
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def column_exists?
|
76
|
+
if model_class.column_names.include?(@column.to_s)
|
77
|
+
true
|
78
|
+
else
|
79
|
+
@missing = "#{model_class} does not have a db column named #{@column}."
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def correct_column_type?
|
85
|
+
return true if @column_type.nil?
|
86
|
+
if matched_column.type.to_s == @column_type.to_s
|
87
|
+
true
|
88
|
+
else
|
89
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
90
|
+
"of type #{matched_column.type}, not #{@column_type}."
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def correct_precision?
|
96
|
+
return true if @precision.nil?
|
97
|
+
if matched_column.precision.to_s == @precision.to_s
|
98
|
+
true
|
99
|
+
else
|
100
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
101
|
+
"of precision #{matched_column.precision}, " <<
|
102
|
+
"not #{@precision}."
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def correct_limit?
|
108
|
+
return true if @limit.nil?
|
109
|
+
if matched_column.limit.to_s == @limit.to_s
|
110
|
+
true
|
111
|
+
else
|
112
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
113
|
+
"of limit #{matched_column.limit}, " <<
|
114
|
+
"not #{@limit}."
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def correct_default?
|
120
|
+
return true if @default.nil?
|
121
|
+
if matched_column.default.to_s == @default.to_s
|
122
|
+
true
|
123
|
+
else
|
124
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
125
|
+
"of default #{matched_column.default}, " <<
|
126
|
+
"not #{@default}."
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def correct_null?
|
132
|
+
return true if @null.nil?
|
133
|
+
if matched_column.null.to_s == @null.to_s
|
134
|
+
true
|
135
|
+
else
|
136
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
137
|
+
"of null #{matched_column.null}, " <<
|
138
|
+
"not #{@null}."
|
139
|
+
false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def correct_scale?
|
144
|
+
return true if @scale.nil?
|
145
|
+
if matched_column.scale.to_s == @scale.to_s
|
146
|
+
true
|
147
|
+
else
|
148
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
149
|
+
"of scale #{matched_column.scale}, not #{@scale}."
|
150
|
+
false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def matched_column
|
155
|
+
model_class.columns.detect { |each| each.name == @column.to_s }
|
156
|
+
end
|
157
|
+
|
158
|
+
def model_class
|
159
|
+
@subject.class
|
160
|
+
end
|
161
|
+
|
162
|
+
def expectation
|
163
|
+
expected = "#{model_class.name} to #{description}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
# Ensures that there are DB indices on the given columns or tuples of
|
6
|
+
# columns.
|
7
|
+
#
|
8
|
+
# Options:
|
9
|
+
# * <tt>unique</tt> - whether or not the index has a unique
|
10
|
+
# constraint. Use <tt>true</tt> to explicitly test for a unique
|
11
|
+
# constraint. Use <tt>false</tt> to explicitly test for a non-unique
|
12
|
+
# constraint. Use <tt>nil</tt> if you don't care whether the index is
|
13
|
+
# unique or not. Default = <tt>nil</tt>
|
14
|
+
#
|
15
|
+
# Examples:
|
16
|
+
#
|
17
|
+
# it { should have_index(:age) }
|
18
|
+
# it { should have_index([:commentable_type, :commentable_id]) }
|
19
|
+
# it { should have_index(:ssn).unique(true) }
|
20
|
+
#
|
21
|
+
def have_index(columns)
|
22
|
+
HaveIndexMatcher.new(:have_index, columns)
|
23
|
+
end
|
24
|
+
|
25
|
+
class HaveIndexMatcher # :nodoc:
|
26
|
+
def initialize(macro, columns)
|
27
|
+
@macro = macro
|
28
|
+
@columns = normalize_columns_to_array(columns)
|
29
|
+
end
|
30
|
+
|
31
|
+
def unique(unique)
|
32
|
+
@unique = unique
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches?(subject)
|
37
|
+
@subject = subject
|
38
|
+
index_exists? && correct_unique?
|
39
|
+
end
|
40
|
+
|
41
|
+
def failure_message
|
42
|
+
"Expected #{expectation} (#{@missing})"
|
43
|
+
end
|
44
|
+
|
45
|
+
def negative_failure_message
|
46
|
+
"Did not expect #{expectation}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def description
|
50
|
+
"have a #{index_type} index on columns #{@columns}"
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def index_exists?
|
56
|
+
! matched_index.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
def correct_unique?
|
60
|
+
return true if @unique.nil?
|
61
|
+
if matched_index.unique == @unique
|
62
|
+
true
|
63
|
+
else
|
64
|
+
@missing = "#{table_name} has an index named #{matched_index.name} " <<
|
65
|
+
"of unique #{matched_index.unique}, not #{@unique}."
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def matched_index
|
71
|
+
indexes.detect { |each| each.columns == @columns }
|
72
|
+
end
|
73
|
+
|
74
|
+
def model_class
|
75
|
+
@subject.class
|
76
|
+
end
|
77
|
+
|
78
|
+
def table_name
|
79
|
+
model_class.table_name
|
80
|
+
end
|
81
|
+
|
82
|
+
def indexes
|
83
|
+
::ActiveRecord::Base.connection.indexes(table_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def expectation
|
87
|
+
expected = "#{model_class.name} to #{description}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def index_type
|
91
|
+
@unique ? "unique" : "non-unique"
|
92
|
+
end
|
93
|
+
|
94
|
+
def normalize_columns_to_array(columns)
|
95
|
+
if columns.class == Array
|
96
|
+
columns.collect { |each| each.to_s }
|
97
|
+
else
|
98
|
+
[columns.to_s]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|