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,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
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
# Ensures that the model is invalid if the given attribute is not unique.
|
6
|
+
#
|
7
|
+
# Internally, this uses values from existing records to test validations,
|
8
|
+
# so this will always fail if you have not saved at least one record for
|
9
|
+
# the model being tested, like so:
|
10
|
+
#
|
11
|
+
# describe User do
|
12
|
+
# before(:each) { User.create!(:email => 'address@example.com') }
|
13
|
+
# it { should validate_uniqueness_of(:email) }
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Options:
|
17
|
+
#
|
18
|
+
# * <tt>with_message</tt> - value the test expects to find in
|
19
|
+
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
|
20
|
+
# Defaults to the translation for <tt>:taken</tt>.
|
21
|
+
# * <tt>scoped_to</tt> - field(s) to scope the uniqueness to.
|
22
|
+
# * <tt>case_insensitive</tt> - ensures that the validation does not
|
23
|
+
# check case. Off by default. Ignored by non-text attributes.
|
24
|
+
#
|
25
|
+
# Examples:
|
26
|
+
# it { should validate_uniqueness_of(:keyword) }
|
27
|
+
# it { should validate_uniqueness_of(:keyword).with_message(/dup/) }
|
28
|
+
# it { should validate_uniqueness_of(:email).scoped_to(:name) }
|
29
|
+
# it { should validate_uniqueness_of(:email).
|
30
|
+
# scoped_to(:first_name, :last_name) }
|
31
|
+
# it { should validate_uniqueness_of(:keyword).case_insensitive }
|
32
|
+
#
|
33
|
+
def validate_uniqueness_of(attr)
|
34
|
+
ValidateUniquenessOfMatcher.new(attr)
|
35
|
+
end
|
36
|
+
|
37
|
+
class ValidateUniquenessOfMatcher < ValidationMatcher # :nodoc:
|
38
|
+
include Helpers
|
39
|
+
|
40
|
+
def initialize(attribute)
|
41
|
+
@attribute = attribute
|
42
|
+
end
|
43
|
+
|
44
|
+
def scoped_to(*scopes)
|
45
|
+
@scopes = [*scopes].flatten
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_message(message)
|
50
|
+
@expected_message = message
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def case_insensitive
|
55
|
+
@case_insensitive = true
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def description
|
60
|
+
result = "require "
|
61
|
+
result << "case sensitive " unless @case_insensitive
|
62
|
+
result << "unique value for #{@attribute}"
|
63
|
+
result << " scoped to #{@scopes.join(', ')}" unless @scopes.blank?
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
def matches?(subject)
|
68
|
+
@subject = subject.class.new
|
69
|
+
@expected_message ||= :taken
|
70
|
+
find_existing &&
|
71
|
+
set_scoped_attributes &&
|
72
|
+
validate_attribute &&
|
73
|
+
validate_after_scope_change
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def find_existing
|
79
|
+
if @existing = @subject.class.find(:first)
|
80
|
+
true
|
81
|
+
else
|
82
|
+
@failure_message = "Can't find first #{class_name}"
|
83
|
+
false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_scoped_attributes
|
88
|
+
unless @scopes.blank?
|
89
|
+
@scopes.each do |scope|
|
90
|
+
setter = :"#{scope}="
|
91
|
+
unless @subject.respond_to?(setter)
|
92
|
+
@failure_message =
|
93
|
+
"#{class_name} doesn't seem to have a #{scope} attribute."
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
@subject.send("#{scope}=", @existing.send(scope))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_attribute
|
103
|
+
disallows_value_of(existing_value, @expected_message)
|
104
|
+
end
|
105
|
+
|
106
|
+
# TODO: There is a chance that we could change the scoped field
|
107
|
+
# to a value that's already taken. An alternative implementation
|
108
|
+
# could actually find all values for scope and create a unique
|
109
|
+
def validate_after_scope_change
|
110
|
+
if @scopes.blank?
|
111
|
+
true
|
112
|
+
else
|
113
|
+
@scopes.all? do |scope|
|
114
|
+
previous_value = @existing.send(scope)
|
115
|
+
|
116
|
+
# Assume the scope is a foreign key if the field is nil
|
117
|
+
previous_value ||= 0
|
118
|
+
|
119
|
+
next_value = previous_value.next
|
120
|
+
|
121
|
+
@subject.send("#{scope}=", next_value)
|
122
|
+
|
123
|
+
if allows_value_of(existing_value, @expected_message)
|
124
|
+
@negative_failure_message <<
|
125
|
+
" (with different value of #{scope})"
|
126
|
+
true
|
127
|
+
else
|
128
|
+
@failure_message << " (with different value of #{scope})"
|
129
|
+
false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def class_name
|
136
|
+
@subject.class.name
|
137
|
+
end
|
138
|
+
|
139
|
+
def existing_value
|
140
|
+
value = @existing.send(@attribute)
|
141
|
+
value.swapcase! if @case_insensitive && value.respond_to?(:swapcase!)
|
142
|
+
value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|