remarkable_activerecord 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGELOG +47 -0
  2. data/LICENSE +20 -0
  3. data/README +2 -0
  4. data/lib/remarkable_activerecord/base.rb +238 -0
  5. data/lib/remarkable_activerecord/human_names.rb +37 -0
  6. data/lib/remarkable_activerecord/matchers/allow_mass_assignment_of_matcher.rb +34 -0
  7. data/lib/remarkable_activerecord/matchers/allow_values_for_matcher.rb +94 -0
  8. data/lib/remarkable_activerecord/matchers/association_matcher.rb +235 -0
  9. data/lib/remarkable_activerecord/matchers/have_column_matcher.rb +68 -0
  10. data/lib/remarkable_activerecord/matchers/have_index_matcher.rb +57 -0
  11. data/lib/remarkable_activerecord/matchers/have_readonly_attributes_matcher.rb +30 -0
  12. data/lib/remarkable_activerecord/matchers/have_scope_matcher.rb +80 -0
  13. data/lib/remarkable_activerecord/matchers/validate_acceptance_of_matcher.rb +51 -0
  14. data/lib/remarkable_activerecord/matchers/validate_associated_matcher.rb +99 -0
  15. data/lib/remarkable_activerecord/matchers/validate_confirmation_of_matcher.rb +45 -0
  16. data/lib/remarkable_activerecord/matchers/validate_exclusion_of_matcher.rb +47 -0
  17. data/lib/remarkable_activerecord/matchers/validate_inclusion_of_matcher.rb +47 -0
  18. data/lib/remarkable_activerecord/matchers/validate_length_of_matcher.rb +123 -0
  19. data/lib/remarkable_activerecord/matchers/validate_numericality_of_matcher.rb +184 -0
  20. data/lib/remarkable_activerecord/matchers/validate_presence_of_matcher.rb +29 -0
  21. data/lib/remarkable_activerecord/matchers/validate_uniqueness_of_matcher.rb +151 -0
  22. data/lib/remarkable_activerecord.rb +29 -0
  23. data/locale/en.yml +253 -0
  24. data/spec/allow_mass_assignment_of_matcher_spec.rb +57 -0
  25. data/spec/allow_values_for_matcher_spec.rb +56 -0
  26. data/spec/association_matcher_spec.rb +616 -0
  27. data/spec/have_column_matcher_spec.rb +73 -0
  28. data/spec/have_index_matcher_spec.rb +68 -0
  29. data/spec/have_readonly_attributes_matcher_spec.rb +47 -0
  30. data/spec/have_scope_matcher_spec.rb +69 -0
  31. data/spec/model_builder.rb +101 -0
  32. data/spec/rcov.opts +2 -0
  33. data/spec/spec.opts +4 -0
  34. data/spec/spec_helper.rb +27 -0
  35. data/spec/validate_acceptance_of_matcher_spec.rb +68 -0
  36. data/spec/validate_associated_matcher_spec.rb +122 -0
  37. data/spec/validate_confirmation_of_matcher_spec.rb +58 -0
  38. data/spec/validate_exclusion_of_matcher_spec.rb +88 -0
  39. data/spec/validate_inclusion_of_matcher_spec.rb +84 -0
  40. data/spec/validate_length_of_matcher_spec.rb +165 -0
  41. data/spec/validate_numericality_of_matcher_spec.rb +180 -0
  42. data/spec/validate_presence_of_matcher_spec.rb +52 -0
  43. data/spec/validate_uniqueness_of_matcher_spec.rb +150 -0
  44. metadata +112 -0
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), 'allow_values_for_matcher')
2
+
3
+ module Remarkable
4
+ module ActiveRecord
5
+ module Matchers
6
+ class ValidateExclusionOfMatcher < AllowValuesForMatcher
7
+
8
+ default_options :message => :exclusion
9
+
10
+ protected
11
+
12
+ def valid_values
13
+ @in_range ? [ @options[:in].first - 1, @options[:in].last + 1 ] : []
14
+ end
15
+
16
+ def invalid_values
17
+ @options[:in]
18
+ end
19
+
20
+ end
21
+
22
+ # Ensures that given values are not valid for the attribute. If a range
23
+ # is given, ensures that the attribute is not valid in the given range.
24
+ #
25
+ # == Options
26
+ #
27
+ # * <tt>:in</tt> - values to test exclusion.
28
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
29
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
30
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
31
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.exclusion')</tt>
32
+ #
33
+ # == Examples
34
+ #
35
+ # it { should validate_exclusion_of(:username, :in => ["admin", "user"]) }
36
+ # it { should validate_exclusion_of(:age, :in => 30..60) }
37
+ #
38
+ # should_validate_exclusion_of :username, :in => ["admin", "user"]
39
+ # should_validate_exclusion_of :age, :in => 30..60
40
+ #
41
+ def validate_exclusion_of(*args)
42
+ ValidateExclusionOfMatcher.new(*args).spec(self)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), 'allow_values_for_matcher')
2
+
3
+ module Remarkable
4
+ module ActiveRecord
5
+ module Matchers
6
+ class ValidateInclusionOfMatcher < AllowValuesForMatcher
7
+
8
+ default_options :message => :inclusion
9
+
10
+ protected
11
+
12
+ def valid_values
13
+ @options[:in]
14
+ end
15
+
16
+ def invalid_values
17
+ @in_range ? [ @options[:in].first - 1, @options[:in].last + 1 ] : []
18
+ end
19
+
20
+ end
21
+
22
+ # Ensures that given values are valid for the attribute. If a range
23
+ # is given, ensures that the attribute is valid in the given range.
24
+ #
25
+ # == Options
26
+ #
27
+ # * <tt>:in</tt> - values to test inclusion.
28
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
29
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
30
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
31
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
32
+ #
33
+ # == Examples
34
+ #
35
+ # should_validate_inclusion_of :size, :in => ["S", "M", "L", "XL"]
36
+ # should_validate_inclusion_of :age, :in => 18..100
37
+ #
38
+ # it { should validate_inclusion_of(:size, :in => ["S", "M", "L", "XL"]) }
39
+ # it { should validate_inclusion_of(:age, :in => 18..100) }
40
+ #
41
+ def validate_inclusion_of(*args)
42
+ ValidateInclusionOfMatcher.new(*args).spec(self)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,123 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class ValidateLengthOfMatcher < Remarkable::ActiveRecord::Base
5
+ arguments :collection => :attributes, :as => :attribute
6
+
7
+ optional :within, :alias => :in
8
+ optional :minimum, :maximum, :is
9
+ optional :allow_nil, :allow_blank, :default => true
10
+ optional :message, :too_short, :too_long, :wrong_length
11
+
12
+ collection_assertions :less_than_min_length?, :exactly_min_length?,
13
+ :more_than_max_length?, :exactly_max_length?,
14
+ :allow_nil?, :allow_blank?
15
+
16
+ before_assert do
17
+ # Reassign :in to :within
18
+ @options[:within] ||= @options.delete(:in) if @options.key?(:in)
19
+
20
+ if @options[:is]
21
+ @min_value, @max_value = @options[:is], @options[:is]
22
+ elsif @options[:within]
23
+ @min_value, @max_value = @options[:within].first, @options[:within].last
24
+ elsif @options[:maximum]
25
+ @min_value, @max_value = nil, @options[:maximum]
26
+ elsif @options[:minimum]
27
+ @min_value, @max_value = @options[:minimum], nil
28
+ end
29
+ end
30
+
31
+ default_options :too_short => :too_short, :too_long => :too_long, :wrong_length => :wrong_length
32
+
33
+ protected
34
+ def allow_nil?
35
+ super(default_message_for(:too_short))
36
+ end
37
+
38
+ def allow_blank?
39
+ super(default_message_for(:too_short))
40
+ end
41
+
42
+ def less_than_min_length?
43
+ @min_value.nil? || @min_value <= 1 || bad?(value_for_length(@min_value - 1), default_message_for(:too_short))
44
+ end
45
+
46
+ def exactly_min_length?
47
+ @min_value.nil? || @min_value <= 0 || good?(value_for_length(@min_value), default_message_for(:too_short))
48
+ end
49
+
50
+ def more_than_max_length?
51
+ @max_value.nil? || bad?(value_for_length(@max_value + 1), default_message_for(:too_long))
52
+ end
53
+
54
+ def exactly_max_length?
55
+ @max_value.nil? || @min_value == @max_value || good?(value_for_length(@max_value), default_message_for(:too_long))
56
+ end
57
+
58
+ def value_for_length(value)
59
+ "x" * value
60
+ end
61
+
62
+ def interpolation_options
63
+ { :minimum => @min_value, :maximum => @max_value }
64
+ end
65
+
66
+ # Returns the default message for the validation type.
67
+ # If user supplied :message, it will return it. Otherwise it will return
68
+ # wrong_length on :is validation and :too_short or :too_long in the other
69
+ # types.
70
+ #
71
+ def default_message_for(validation_type)
72
+ return :message if @options[:message]
73
+ @options.key?(:is) ? :wrong_length : validation_type
74
+ end
75
+ end
76
+
77
+ # Validates the length of the given attributes. You have also to supply
78
+ # one of the following options: minimum, maximum, is or within.
79
+ #
80
+ # Note: this method is also aliased as <tt>validate_size_of</tt>.
81
+ #
82
+ # == Options
83
+ #
84
+ # * <tt>:minimum</tt> - The minimum size of the attribute.
85
+ # * <tt>:maximum</tt> - The maximum size of the attribute.
86
+ # * <tt>:is</tt> - The exact size of the attribute.
87
+ # * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
88
+ # * <tt>:in</tt> - A synonym(or alias) for :within.
89
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
90
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
91
+ # * <tt>:too_short</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt> when attribute is too short.
92
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
93
+ # * <tt>:too_long</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt> when attribute is too long.
94
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
95
+ # * <tt>:wrong_length</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt> when attribute is the wrong length.
96
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % range.last</tt>
97
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
98
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % value</tt>
99
+ #
100
+ # == Gotcha
101
+ #
102
+ # In Rails 2.3.x, when :message is supplied, it overwrites the messages
103
+ # supplied in :wrong_length, :too_short and :too_long. However, in earlier
104
+ # versions, Rails ignores the :message option.
105
+ #
106
+ # == Examples
107
+ #
108
+ # should_validate_length_of :password, :within => 6..20
109
+ # should_validate_length_of :password, :maximum => 20
110
+ # should_validate_length_of :password, :minimum => 6
111
+ # should_validate_length_of :age, :is => 18
112
+ #
113
+ # it { should validate_length_of(:password).within(6..20) }
114
+ # it { should validate_length_of(:password).maximum(20) }
115
+ # it { should validate_length_of(:password).minimum(6) }
116
+ # it { should validate_length_of(:age).is(18) }
117
+ #
118
+ def validate_length_of(*attributes)
119
+ ValidateLengthOfMatcher.new(*attributes).spec(self)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,184 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class ValidateNumericalityOfMatcher < Remarkable::ActiveRecord::Base
5
+
6
+ arguments :collection => :attributes, :as => :attribute
7
+
8
+ optional :equal_to, :greater_than_or_equal_to, :greater_than,
9
+ :less_than_or_equal_to, :less_than, :message
10
+
11
+ optional :only_integer, :odd, :even, :allow_nil, :allow_blank, :default => true
12
+
13
+ collection_assertions :only_numeric_values?, :allow_blank?, :allow_nil?,
14
+ :only_integer?, :only_odd?, :only_even?, :equals_to?,
15
+ :less_than_minimum?, :more_than_maximum?
16
+
17
+ # Before assertions, we rearrange the values.
18
+ #
19
+ # Notice that :less_than gives a maximum value while :more_than given
20
+ # a minimum value. While :equal_to generate both.
21
+ #
22
+ before_assert do
23
+ @maximum_values = {}
24
+ @minimum_values = {}
25
+
26
+ if value = @options[:equal_to]
27
+ @maximum_values[:equal_to] = value
28
+ @minimum_values[:equal_to] = value
29
+ elsif value = @options[:less_than]
30
+ @maximum_values[:less_than] = value - 1
31
+ elsif value = @options[:greater_than]
32
+ @minimum_values[:greater_than] = value + 1
33
+ elsif value = @options[:less_than_or_equal_to]
34
+ @maximum_values[:less_than_or_equal_to] = value
35
+ elsif value = @options[:greater_than_or_equal_to]
36
+ @minimum_values[:greater_than_or_equal_to] = value
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def allow_nil?
43
+ super(default_message_for(:not_a_number))
44
+ end
45
+
46
+ def allow_blank?
47
+ super(default_message_for(:not_a_number))
48
+ end
49
+
50
+ def only_numeric_values?
51
+ bad?("abcd", default_message_for(:not_a_number))
52
+ end
53
+
54
+ def only_integer?
55
+ assert_bad_or_good_if_key(:only_integer, valid_value_for_test.to_f, default_message_for(:not_a_number))
56
+ end
57
+
58
+ # In ActiveRecord, when we supply :even, does not matter the value, it
59
+ # considers that should even values should be accepted.
60
+ #
61
+ def only_even?
62
+ return true unless @options[:even]
63
+ bad?(even_valid_value_for_test + 1, default_message_for(:even))
64
+ end
65
+
66
+ # In ActiveRecord, when we supply :odd, does not matter the value, it
67
+ # considers that should odd values should be accepted.
68
+ #
69
+ def only_odd?
70
+ return true unless @options[:odd]
71
+ bad?(even_valid_value_for_test, default_message_for(:odd))
72
+ end
73
+
74
+ # Check equal_to for all registered values.
75
+ #
76
+ def equals_to?
77
+ values = {}
78
+ @maximum_values.each { |k, v| values[k] = v }
79
+ @minimum_values.each { |k, v| values[k] = v }
80
+
81
+ values.each do |key, value|
82
+ return false, :count => value unless good?(value, default_message_for(key))
83
+ end
84
+ true
85
+ end
86
+
87
+ # Check more_than_maximum? for equal_to, less_than and
88
+ # less_than_or_equal_to options.
89
+ #
90
+ def more_than_maximum?
91
+ @maximum_values.each do |key, value|
92
+ return false, :count => value unless bad?(value + 1, default_message_for(key))
93
+ end
94
+ true
95
+ end
96
+
97
+ # Check less_than_minimum? for equal_to, more_than and
98
+ # more_than_or_equal_to options.
99
+ #
100
+ def less_than_minimum?
101
+ @minimum_values.each do |key, value|
102
+ return false, :count => value unless bad?(value - 1, default_message_for(key))
103
+ end
104
+ true
105
+ end
106
+
107
+ # Returns a valid value for test.
108
+ #
109
+ def valid_value_for_test
110
+ value = @options[:equal_to] || @options[:less_than_or_equal_to] || @options[:greater_than_or_equal_to]
111
+
112
+ value ||= @options[:less_than] - 1 if @options[:less_than]
113
+ value ||= @options[:greater_than] + 1 if @options[:greater_than]
114
+
115
+ value ||= 10
116
+
117
+ if @options[:even]
118
+ value = (value / 2) * 2
119
+ elsif @options[:odd]
120
+ value = ((value / 2) * 2) + 1
121
+ end
122
+
123
+ value
124
+ end
125
+
126
+ # Returns a valid even value for test.
127
+ # The method valid_value_for_test checks for :even option but does not
128
+ # return necessarily an even value
129
+ #
130
+ def even_valid_value_for_test
131
+ (valid_value_for_test / 2) * 2
132
+ end
133
+
134
+ # Returns the default message for each key (:odd, :even, :equal_to, ...).
135
+ # If the user provided a message, we use it, otherwise we should use
136
+ # the given key as message.
137
+ #
138
+ # For example, a default_message_for(:odd), if none is provided, will be
139
+ # :odd. So we have create :odd_message in the options hash, that when
140
+ # called later, will return :odd.
141
+ #
142
+ def default_message_for(key)
143
+ if @options[:message]
144
+ :message
145
+ else
146
+ @options[:"#{key}_message"] = key
147
+ :"#{key}_message"
148
+ end
149
+ end
150
+ end
151
+
152
+ # Ensures that the given attributes accepts only numbers.
153
+ #
154
+ # == Options
155
+ #
156
+ # * <tt>:only_integer</tt> - when supplied, checks if it accepts only integers or not
157
+ # * <tt>:odd</tt> - when supplied, checks if it accepts only odd values or not
158
+ # * <tt>:even</tt> - when supplied, checks if it accepts only even values or not
159
+ # * <tt>:equal_to</tt> - when supplied, checks if attributes are only valid when equal to given value
160
+ # * <tt>:less_than</tt> - when supplied, checks if attributes are only valid when less than given value
161
+ # * <tt>:greater_than</tt> - when supplied, checks if attributes are only valid when greater than given value
162
+ # * <tt>:less_than_or_equal_to</tt> - when supplied, checks if attributes are only valid when less than or equal to given value
163
+ # * <tt>:greater_than_or_equal_to</tt> - when supplied, checks if attributes are only valid when greater than or equal to given value
164
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
165
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
166
+ #
167
+ # == Examples
168
+ #
169
+ # should_validate_numericality_of :age, :price
170
+ # should_validate_numericality_of :price, :only_integer => false, :greater_than => 10
171
+ #
172
+ # it { should validate_numericality_of(:age).odd }
173
+ # it { should validate_numericality_of(:age).even }
174
+ # it { should validate_numericality_of(:age).only_integer }
175
+ # it { should validate_numericality_of(:age, :odd => true) }
176
+ # it { should validate_numericality_of(:age, :even => true) }
177
+ #
178
+ def validate_numericality_of(*attributes)
179
+ ValidateNumericalityOfMatcher.new(*attributes).spec(self)
180
+ end
181
+
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,29 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class ValidatePresenceOfMatcher < Remarkable::ActiveRecord::Base
5
+ arguments :collection => :attributes, :as => :attribute
6
+ optional :message
7
+
8
+ collection_assertions :allow_nil?
9
+ default_options :message => :blank, :allow_nil => false
10
+ end
11
+
12
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
13
+ #
14
+ # == Options
15
+ #
16
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
17
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
18
+ #
19
+ # == Examples
20
+ #
21
+ # should_validate_presence_of :name, :phone_number
22
+ # it { should validate_presence_of(:name, :phone_number) }
23
+ #
24
+ def validate_presence_of(*args)
25
+ ValidatePresenceOfMatcher.new(*args).spec(self)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,151 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class ValidateUniquenessOfMatcher < Remarkable::ActiveRecord::Base
5
+ arguments :collection => :attributes, :as => :attribute
6
+
7
+ optional :message
8
+ optional :scope, :splat => true
9
+ optional :case_sensitive, :allow_nil, :allow_blank, :default => true
10
+
11
+ collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?, :case_sensitive?,
12
+ :valid_with_new_scope?, :allow_nil?, :allow_blank?
13
+
14
+ default_options :message => :taken
15
+
16
+ before_assert do
17
+ @options[:scope] = [*@options[:scope]].compact if @options[:scope]
18
+ end
19
+
20
+ private
21
+
22
+ # Tries to find an object in the database. If allow_nil and/or allow_blank
23
+ # is given, we must find a record which is not nil or not blank.
24
+ #
25
+ # If any of these attempts fail, the validation fail.
26
+ #
27
+ def find_first_object?
28
+ @existing, message = if @options[:allow_nil]
29
+ [ subject_class.find(:first, :conditions => "#{@attribute} IS NOT NULL"), " with #{@attribute} not nil" ]
30
+ elsif @options[:allow_blank]
31
+ [ subject_class.find(:first, :conditions => "#{@attribute} != ''"), " with #{@attribute} not blank" ]
32
+ else
33
+ [ subject_class.find(:first), "" ]
34
+ end
35
+
36
+ return true if @existing
37
+ raise ScriptError, "could not find a #{subject_class} in the database" + message
38
+ end
39
+
40
+ # Set subject scope to be equal to the object found.
41
+ #
42
+ def responds_to_scope?
43
+ (@options[:scope] || []).each do |scope|
44
+ method = :"#{scope}="
45
+ return false, :method => method unless @subject.respond_to?(method)
46
+
47
+ @subject.send(method, @existing.send(scope))
48
+ end
49
+ true
50
+ end
51
+
52
+ # Check if the attribute given is valid and if the validation fails for equal values.
53
+ #
54
+ def is_unique?
55
+ @value = @existing.send(@attribute)
56
+ return bad?(@value)
57
+ end
58
+
59
+ # If :case_sensitive is given and it's false, we swap the case of the
60
+ # value used in :is_unique? and see if the test object remains valid.
61
+ #
62
+ # If :case_sensitive is given and it's true, we swap the case of the
63
+ # value used in is_unique? and see if the test object is not valid.
64
+ #
65
+ # This validation will only occur if the test object is a String.
66
+ #
67
+ def case_sensitive?
68
+ return true unless @value.is_a?(String)
69
+ assert_good_or_bad_if_key(:case_sensitive, @value.swapcase)
70
+ end
71
+
72
+ # Now test that the object is valid when changing the scoped attribute.
73
+ #
74
+ def valid_with_new_scope?
75
+ (@options[:scope] || []).each do |scope|
76
+ previous_scope_value = @subject.send(scope)
77
+
78
+ @subject.send("#{scope}=", new_value_for_scope(scope))
79
+ return false, :method => scope unless good?(@value)
80
+
81
+ @subject.send("#{scope}=", previous_scope_value)
82
+ end
83
+ true
84
+ end
85
+
86
+ # Change the existing object attribute to nil to run allow nil validation.
87
+ #
88
+ def allow_nil?
89
+ @existing.update_attribute(@attribute, nil)
90
+ super
91
+ end
92
+
93
+ # Change the existing object attribute to blank to run allow blank validation.
94
+ #
95
+ def allow_blank?
96
+ @existing.update_attribute(@attribute, '')
97
+ super
98
+ end
99
+
100
+ # Returns a value to be used as new scope. It does a range query in the
101
+ # database and tries to return a new value which does not belong to it.
102
+ #
103
+ def new_value_for_scope(scope)
104
+ new_scope = (@existing.send(scope) || 999).next.to_s
105
+
106
+ # Generate a range of values to search in the database
107
+ values = 100.times.inject([new_scope]) {|v,i| v << v.last.next }
108
+ conditions = { scope => values, @attribute => @value }
109
+
110
+ # Get values from the database, get the scope attribute and map them to string.
111
+ db_values = subject_class.find(:all, :conditions => conditions, :select => scope)
112
+ db_values.map!{ |r| r.send(scope).to_s }
113
+
114
+ if value_to_return = (values - db_values).first
115
+ value_to_return
116
+ else
117
+ raise ScriptError, "Tried to find an unique scope value for #{scope} but I could not. " <<
118
+ "The conditions hash was #{conditions.inspect} and it returned all records."
119
+ end
120
+ end
121
+ end
122
+
123
+ # Ensures that the model cannot be saved if one of the attributes listed
124
+ # is not unique.
125
+ #
126
+ # Requires an existing record in the database. If you supply :allow_nil as
127
+ # option, you need to have in the database a record which is not nil in the
128
+ # given attributes. The same is required for allow_blank option.
129
+ #
130
+ # == Options
131
+ #
132
+ # * <tt>:scope</tt> - field(s) to scope the uniqueness to.
133
+ # * <tt>:case_sensitive</tt> - the matcher look for an exact match.
134
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
135
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
136
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
137
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
138
+ #
139
+ # == Examples
140
+ #
141
+ # it { should validate_uniqueness_of(:keyword, :username) }
142
+ # it { should validate_uniqueness_of(:name, :message => "O NOES! SOMEONE STOELED YER NAME!") }
143
+ # it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
144
+ # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
145
+ #
146
+ def validate_uniqueness_of(*attributes)
147
+ ValidateUniquenessOfMatcher.new(*attributes).spec(self)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,29 @@
1
+ # Load Remarkable
2
+ unless Object.const_defined?('Remarkable')
3
+ begin
4
+ require 'remarkable'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ gem 'remarkable'
8
+ require 'remarkable'
9
+ end
10
+ end
11
+
12
+ # Load Remarkable ActiveRecord files
13
+ dir = File.dirname(__FILE__)
14
+ require File.join(dir, 'remarkable_activerecord', 'base')
15
+ require File.join(dir, 'remarkable_activerecord', 'human_names')
16
+
17
+ # Add locale
18
+ Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml')
19
+
20
+ # Add matchers
21
+ Dir[File.join(dir, 'remarkable_activerecord', 'matchers', '*.rb')].each do |file|
22
+ require file
23
+ end
24
+
25
+ # By default, ActiveRecord matchers are not included in any example group.
26
+ # The responsable for this is RemarkableRails. If you are using ActiveRecord
27
+ # without Rails, put the line below in your spec_helper to include ActiveRecord
28
+ # matchers into rspec globally.
29
+ # Remarkable.include_matchers!(Remarkable::ActiveRecord, Spec::Example::ExampleGroup)