ratnikov-shoulda 2.0.6.3 → 2.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/README.rdoc +3 -2
- data/Rakefile +1 -1
- data/lib/shoulda/active_record/assertions.rb +10 -31
- data/lib/shoulda/active_record/helpers.rb +40 -0
- data/lib/shoulda/active_record/macros.rb +171 -325
- 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/active_record/matchers.rb +42 -0
- data/lib/shoulda/active_record.rb +4 -0
- data/lib/shoulda/assertions.rb +12 -0
- data/lib/shoulda/autoload_macros.rb +46 -0
- data/lib/shoulda/rails.rb +1 -8
- data/lib/shoulda/rspec.rb +5 -0
- data/lib/shoulda/tasks/list_tests.rake +6 -1
- data/lib/shoulda/test_unit.rb +19 -0
- data/lib/shoulda.rb +5 -17
- data/rails/init.rb +1 -1
- data/test/README +2 -2
- data/test/fail_macros.rb +2 -2
- data/test/matchers/allow_mass_assignment_of_matcher_test.rb +68 -0
- data/test/matchers/allow_value_matcher_test.rb +41 -0
- data/test/matchers/association_matcher_test.rb +258 -0
- data/test/matchers/ensure_inclusion_of_matcher_test.rb +80 -0
- data/test/matchers/ensure_length_of_matcher_test.rb +158 -0
- data/test/matchers/have_db_column_matcher_test.rb +169 -0
- data/test/matchers/have_index_matcher_test.rb +74 -0
- data/test/matchers/have_named_scope_matcher_test.rb +65 -0
- data/test/matchers/have_readonly_attributes_matcher_test.rb +29 -0
- data/test/matchers/validate_acceptance_of_matcher_test.rb +44 -0
- data/test/matchers/validate_numericality_of_matcher_test.rb +52 -0
- data/test/matchers/validate_presence_of_matcher_test.rb +86 -0
- data/test/matchers/validate_uniqueness_of_matcher_test.rb +141 -0
- data/test/model_builder.rb +61 -0
- data/test/other/autoload_macro_test.rb +18 -0
- data/test/other/helpers_test.rb +58 -0
- data/test/rails_root/config/database.yml +1 -1
- data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -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/test_helper.rb +3 -1
- data/test/unit/address_test.rb +1 -1
- data/test/unit/dog_test.rb +1 -1
- data/test/unit/post_test.rb +4 -4
- data/test/unit/product_test.rb +2 -2
- data/test/unit/tag_test.rb +2 -1
- data/test/unit/user_test.rb +8 -7
- metadata +49 -3
|
@@ -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
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module Shoulda # :nodoc:
|
|
2
|
+
module ActiveRecord # :nodoc:
|
|
3
|
+
module Matchers
|
|
4
|
+
|
|
5
|
+
# Ensures that the model has a method named scope_call that returns a
|
|
6
|
+
# NamedScope object with the proxy options set to the options you supply.
|
|
7
|
+
# scope_call can be either a symbol, or a Ruby expression in a String
|
|
8
|
+
# which will be evaled. The eval'd method call has access to all the same
|
|
9
|
+
# instance variables that an example would.
|
|
10
|
+
#
|
|
11
|
+
# Options:
|
|
12
|
+
#
|
|
13
|
+
# * <tt>in_context</tt> - Any of the options that the named scope would
|
|
14
|
+
# pass on to find.
|
|
15
|
+
#
|
|
16
|
+
# Example:
|
|
17
|
+
#
|
|
18
|
+
# it { should have_named_scope(:visible).
|
|
19
|
+
# finding(:conditions => {:visible => true}) }
|
|
20
|
+
#
|
|
21
|
+
# Passes for
|
|
22
|
+
#
|
|
23
|
+
# named_scope :visible, :conditions => {:visible => true}
|
|
24
|
+
#
|
|
25
|
+
# Or for
|
|
26
|
+
#
|
|
27
|
+
# def self.visible
|
|
28
|
+
# scoped(:conditions => {:visible => true})
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# You can test lambdas or methods that return ActiveRecord#scoped calls:
|
|
32
|
+
#
|
|
33
|
+
# it { should have_named_scope('recent(5)').finding(:limit => 5) }
|
|
34
|
+
# it { should have_named_scope('recent(1)').finding(:limit => 1) }
|
|
35
|
+
#
|
|
36
|
+
# Passes for
|
|
37
|
+
# named_scope :recent, lambda {|c| {:limit => c}}
|
|
38
|
+
#
|
|
39
|
+
# Or for
|
|
40
|
+
#
|
|
41
|
+
# def self.recent(c)
|
|
42
|
+
# scoped(:limit => c)
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
def have_named_scope(scope_call)
|
|
46
|
+
HaveNamedScopeMatcher.new(scope_call).in_context(self)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class HaveNamedScopeMatcher # :nodoc:
|
|
50
|
+
|
|
51
|
+
def initialize(scope_call)
|
|
52
|
+
@scope_call = scope_call.to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def finding(finding)
|
|
56
|
+
@finding = finding
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def in_context(context)
|
|
61
|
+
@context = context
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def matches?(subject)
|
|
66
|
+
@subject = subject
|
|
67
|
+
call_succeeds? && returns_scope? && finds_correct_scope?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def failure_message
|
|
71
|
+
"Expected #{@missing_expectation}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def negative_failure_message
|
|
75
|
+
"Didn't expect a named scope for #{@scope_call}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def description
|
|
79
|
+
result = "have a named scope for #{@scope_call}"
|
|
80
|
+
result << " finding #{@finding.inspect}" unless @finding.nil?
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def call_succeeds?
|
|
87
|
+
scope
|
|
88
|
+
true
|
|
89
|
+
rescue Exception => exception
|
|
90
|
+
@missing_expectation = "#{@subject.class.name} " <<
|
|
91
|
+
"to respond to #{@scope_call} " <<
|
|
92
|
+
"but raised error: #{exception.inspect}"
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def scope
|
|
97
|
+
@scope ||= @context.instance_eval("#{@subject.class.name}.#{@scope_call}")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def returns_scope?
|
|
101
|
+
if ::ActiveRecord::NamedScope::Scope === scope
|
|
102
|
+
true
|
|
103
|
+
else
|
|
104
|
+
@missing_expectation = "#{@scope_call} to return a scope"
|
|
105
|
+
false
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def finds_correct_scope?
|
|
110
|
+
return true if @finding.nil?
|
|
111
|
+
if @finding == scope.proxy_options
|
|
112
|
+
true
|
|
113
|
+
else
|
|
114
|
+
@missing_expectation = "#{@scope_call} to return results scoped to "
|
|
115
|
+
@missing_expectation << "#{@finding.inspect} but was scoped to "
|
|
116
|
+
@missing_expectation << scope.proxy_options.inspect
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Shoulda # :nodoc:
|
|
2
|
+
module ActiveRecord # :nodoc:
|
|
3
|
+
module Matchers
|
|
4
|
+
|
|
5
|
+
# Ensures that the attribute cannot be changed once the record has been
|
|
6
|
+
# created.
|
|
7
|
+
#
|
|
8
|
+
# it { should have_readonly_attributes(:password) }
|
|
9
|
+
#
|
|
10
|
+
def have_readonly_attribute(value)
|
|
11
|
+
HaveReadonlyAttributeMatcher.new(value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class HaveReadonlyAttributeMatcher # :nodoc:
|
|
15
|
+
|
|
16
|
+
def initialize(attribute)
|
|
17
|
+
@attribute = attribute.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def matches?(subject)
|
|
21
|
+
@subject = subject
|
|
22
|
+
if readonly_attributes.include?(@attribute)
|
|
23
|
+
@negative_failure_message =
|
|
24
|
+
"Did not expect #{@attribute} to be read-only"
|
|
25
|
+
true
|
|
26
|
+
else
|
|
27
|
+
if readonly_attributes.empty?
|
|
28
|
+
@failure_message = "#{class_name} attribute #{@attribute} " <<
|
|
29
|
+
"is not read-only"
|
|
30
|
+
else
|
|
31
|
+
@failure_message = "#{class_name} is making " <<
|
|
32
|
+
"#{readonly_attributes.to_sentence} " <<
|
|
33
|
+
"read-only, but not #{@attribute}."
|
|
34
|
+
end
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :failure_message, :negative_failure_message
|
|
40
|
+
|
|
41
|
+
def description
|
|
42
|
+
"make #{@attribute} read-only"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def readonly_attributes
|
|
48
|
+
@readonly_attributes ||= (@subject.class.readonly_attributes || [])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def class_name
|
|
52
|
+
@subject.class.name
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Shoulda # :nodoc:
|
|
2
|
+
module ActiveRecord # :nodoc:
|
|
3
|
+
module Matchers
|
|
4
|
+
|
|
5
|
+
# Ensures that the model cannot be saved the given attribute is not
|
|
6
|
+
# accepted.
|
|
7
|
+
#
|
|
8
|
+
# Options:
|
|
9
|
+
# * <tt>with_message</tt> - value the test expects to find in
|
|
10
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
|
11
|
+
# translation for <tt>:accepted</tt>.
|
|
12
|
+
#
|
|
13
|
+
# Example:
|
|
14
|
+
# it { should validate_acceptance_of(:eula) }
|
|
15
|
+
#
|
|
16
|
+
def validate_acceptance_of(attr)
|
|
17
|
+
ValidateAcceptanceOfMatcher.new(attr)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc:
|
|
21
|
+
|
|
22
|
+
def with_message(message)
|
|
23
|
+
@expected_message = message if message
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def matches?(subject)
|
|
28
|
+
super(subject)
|
|
29
|
+
@expected_message ||= :accepted
|
|
30
|
+
disallows_value_of(false, @expected_message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def description
|
|
34
|
+
"require #{@attribute} to be accepted"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Shoulda # :nodoc:
|
|
2
|
+
module ActiveRecord # :nodoc:
|
|
3
|
+
module Matchers
|
|
4
|
+
|
|
5
|
+
# Ensure that the attribute is numeric
|
|
6
|
+
#
|
|
7
|
+
# Options:
|
|
8
|
+
# * <tt>with_message</tt> - value the test expects to find in
|
|
9
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
|
10
|
+
# translation for <tt>:not_a_number</tt>.
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
# it { should validate_numericality_of(:age) }
|
|
14
|
+
#
|
|
15
|
+
def validate_numericality_of(attr)
|
|
16
|
+
ValidateNumericalityOfMatcher.new(attr)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc:
|
|
20
|
+
|
|
21
|
+
def with_message(message)
|
|
22
|
+
@expected_message = message if message
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def matches?(subject)
|
|
27
|
+
super(subject)
|
|
28
|
+
@expected_message ||= :not_a_number
|
|
29
|
+
disallows_value_of('abcd', @expected_message)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def description
|
|
33
|
+
"only allow numeric values for #{@attribute}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Shoulda # :nodoc:
|
|
2
|
+
module ActiveRecord # :nodoc:
|
|
3
|
+
module Matchers
|
|
4
|
+
|
|
5
|
+
# Ensures that the model is not valid if the given attribute is not
|
|
6
|
+
# present.
|
|
7
|
+
#
|
|
8
|
+
# Options:
|
|
9
|
+
# * <tt>with_message</tt> - value the test expects to find in
|
|
10
|
+
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
|
|
11
|
+
# Defaults to the translation for <tt>:blank</tt>.
|
|
12
|
+
#
|
|
13
|
+
# Examples:
|
|
14
|
+
# it { should validate_presence_of(:name) }
|
|
15
|
+
# it { should validate_presence_of(:name).
|
|
16
|
+
# with_message(/is not optional/) }
|
|
17
|
+
#
|
|
18
|
+
def validate_presence_of(attr)
|
|
19
|
+
ValidatePresenceOfMatcher.new(attr)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class ValidatePresenceOfMatcher < ValidationMatcher # :nodoc:
|
|
23
|
+
|
|
24
|
+
def with_message(message)
|
|
25
|
+
@expected_message = message if message
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def matches?(subject)
|
|
30
|
+
super(subject)
|
|
31
|
+
@expected_message ||= :blank
|
|
32
|
+
disallows_value_of(blank_value, @expected_message)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def description
|
|
36
|
+
"require #{@attribute} to be set"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def blank_value
|
|
42
|
+
if collection?
|
|
43
|
+
[]
|
|
44
|
+
else
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def collection?
|
|
50
|
+
if reflection = @subject.class.reflect_on_association(@attribute)
|
|
51
|
+
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
|
52
|
+
else
|
|
53
|
+
false
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|