riot_rails 0.0.8 → 0.0.9.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +122 -0
- data/MIT-LICENSE +1 -1
- data/README.markdown +22 -7
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/lib/riot/action_controller/assertion_macros.rb +48 -34
- data/lib/riot/action_controller/context_helper.rb +12 -0
- data/lib/riot/action_controller/context_macros.rb +58 -12
- data/lib/riot/action_controller/situation_macros.rb +73 -6
- data/lib/riot/action_controller.rb +2 -0
- data/lib/riot/active_record/assertion_macros.rb +3 -158
- data/lib/riot/active_record/context_helper.rb +19 -0
- data/lib/riot/active_record/database_macros.rb +58 -0
- data/lib/riot/active_record/reflection_macros.rb +106 -0
- data/lib/riot/active_record/validation_macros.rb +187 -0
- data/lib/riot/active_record.rb +2 -0
- data/lib/riot/rails.rb +1 -2
- data/lib/riot/rails_context.rb +84 -0
- data/riot_rails.gemspec +35 -9
- data/test/action_controller/controller_context_test.rb +39 -9
- data/test/action_controller/redirected_to_test.rb +6 -11
- data/test/action_controller/renders_template_test.rb +7 -7
- data/test/action_controller/renders_test.rb +6 -6
- data/test/action_controller/response_status_test.rb +11 -11
- data/test/active_record/allowing_values_test.rb +9 -9
- data/test/active_record/attribute_is_invalid_test.rb +20 -0
- data/test/active_record/belongs_to_test.rb +22 -0
- data/test/active_record/has_and_belongs_to_many_test.rb +22 -0
- data/test/active_record/has_database_index_on_test.rb +73 -0
- data/test/active_record/has_many_test.rb +7 -3
- data/test/active_record/has_one_test.rb +22 -0
- data/test/active_record/validates_length_of_test.rb +31 -0
- data/test/active_record/validates_presence_of_test.rb +1 -1
- data/test/active_record/validates_uniqueness_of_test.rb +3 -3
- data/test/rails_context_test.rb +103 -0
- data/test/rails_root/config/environment.rb +46 -0
- data/test/rails_root/config/routes.rb +3 -4
- data/test/rails_root/db/schema.rb +1 -2
- data/test/teststrap.rb +44 -69
- metadata +39 -6
@@ -1,158 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
class AssertionMacro < ::Riot::AssertionMacro
|
5
|
-
def error_from_writing_value(model, attribute, value)
|
6
|
-
model.write_attribute(attribute, value)
|
7
|
-
model.valid?
|
8
|
-
model.errors.on(attribute)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
# An ActiveRecord assertion that expects to fail when a given attribute is validated after a nil value
|
13
|
-
# is provided to it.
|
14
|
-
#
|
15
|
-
# context "a User" do
|
16
|
-
# setup { User.new }
|
17
|
-
# topic.validates_presence_of(:name)
|
18
|
-
# end
|
19
|
-
class ValidatesPresenceOfMacro < AssertionMacro
|
20
|
-
register :validates_presence_of
|
21
|
-
|
22
|
-
def evaluate(actual, attribute)
|
23
|
-
if error_from_writing_value(actual, attribute, nil)
|
24
|
-
pass("validates presence of #{attribute.inspect}")
|
25
|
-
else
|
26
|
-
fail("expected to validate presence of #{attribute.inspect}")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# An ActiveRecord assertion that expects to pass with a given value or set of values for a given
|
32
|
-
# attribute.
|
33
|
-
#
|
34
|
-
# context "a User" do
|
35
|
-
# setup { User.new }
|
36
|
-
# topic.allows_values_for :email, "a@b.cd"
|
37
|
-
# topic.allows_values_for :email, "a@b.cd", "e@f.gh"
|
38
|
-
# end
|
39
|
-
class AllowsValuesForMacro < AssertionMacro
|
40
|
-
register :allows_values_for
|
41
|
-
|
42
|
-
def evaluate(actual, attribute, *values)
|
43
|
-
bad_values = []
|
44
|
-
values.each do |value|
|
45
|
-
bad_values << value if error_from_writing_value(actual, attribute, value)
|
46
|
-
end
|
47
|
-
failure_msg = "expected %s to allow value(s) %s"
|
48
|
-
bad_values.empty? ? pass : fail(failure_msg % [attribute.inspect, bad_values.inspect])
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# An ActiveRecord assertion that expects to fail with a given value or set of values for a given
|
53
|
-
# attribute.
|
54
|
-
#
|
55
|
-
# context "a User" do
|
56
|
-
# setup { User.new }
|
57
|
-
# topic.does_not_allow_values_for :email, "a"
|
58
|
-
# topic.does_not_allow_values_for :email, "a@b", "e f@g.h"
|
59
|
-
# end
|
60
|
-
class DoesNotAllowValuesForMacro < AssertionMacro
|
61
|
-
register :does_not_allow_values_for
|
62
|
-
def evaluate(actual, attribute, *values)
|
63
|
-
good_values = []
|
64
|
-
values.each do |value|
|
65
|
-
good_values << value unless error_from_writing_value(actual, attribute, value)
|
66
|
-
end
|
67
|
-
failure_msg = "expected %s not to allow value(s) %s"
|
68
|
-
good_values.empty? ? pass : fail(failure_msg % [attribute.inspect, good_values.inspect])
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# An ActiveRecord assertion that expects to fail with invalid value for an attribute. Optionally, the
|
73
|
-
# error message can be provided as the exact string or as regular expression.
|
74
|
-
#
|
75
|
-
# context "a User" do
|
76
|
-
# setup { User.new }
|
77
|
-
# topic.is_invalid_when :email, "fake", "is invalid"
|
78
|
-
# topic.is_invalid_when :email, "another fake", /invalid/
|
79
|
-
# end
|
80
|
-
class IsInvalidWhenMacro < AssertionMacro
|
81
|
-
register :is_invalid_when
|
82
|
-
|
83
|
-
def evaluate(actual, attribute, value, expected_error=nil)
|
84
|
-
actual_errors = Array(error_from_writing_value(actual, attribute, value))
|
85
|
-
if actual_errors.empty?
|
86
|
-
fail("expected #{attribute.inspect} to be invalid when value is #{value.inspect}")
|
87
|
-
elsif expected_error && !has_error_message?(expected_error, actual_errors)
|
88
|
-
message = "expected %s to be invalid with error message %s"
|
89
|
-
fail(message % [attribute.inspect, expected_error.inspect])
|
90
|
-
else
|
91
|
-
pass("attribute #{attribute.inspect} is invalid")
|
92
|
-
end
|
93
|
-
end
|
94
|
-
private
|
95
|
-
def has_error_message?(expected, errors)
|
96
|
-
return true unless expected
|
97
|
-
expected.kind_of?(Regexp) ? errors.any? {|e| e =~ expected } : errors.any? {|e| e == expected }
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# An ActiveRecord assertion that expects to fail with an attribute is not valid for record because the
|
102
|
-
# value of the attribute is not unique. Requires the topic of the context to be a created record; one
|
103
|
-
# that returns false for a call to +new_record?+.
|
104
|
-
#
|
105
|
-
# context "a User" do
|
106
|
-
# setup { User.create(:email => "a@b.cde", ... ) }
|
107
|
-
# topic.validates_uniqueness_of :email
|
108
|
-
# end
|
109
|
-
class ValidatesUniquenessOfMacro < AssertionMacro
|
110
|
-
register :validates_uniqueness_of
|
111
|
-
|
112
|
-
def evaluate(actual, attribute)
|
113
|
-
actual_record = actual
|
114
|
-
if actual_record.new_record?
|
115
|
-
fail("topic is not a new record when testing uniqueness of #{attribute.inspect}")
|
116
|
-
else
|
117
|
-
copied_model = actual_record.class.new
|
118
|
-
actual_record.attributes.each do |dup_attribute, dup_value|
|
119
|
-
copied_model.write_attribute(dup_attribute, dup_value)
|
120
|
-
end
|
121
|
-
copied_value = actual_record.read_attribute(attribute)
|
122
|
-
if error_from_writing_value(copied_model, attribute, copied_value)
|
123
|
-
pass("#{attribute.inspect} is unique")
|
124
|
-
else
|
125
|
-
fail("expected to fail because #{attribute.inspect} is not unique")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as +has_many+
|
132
|
-
# association. Will fail if an association is not defined for the attribute and if the association is
|
133
|
-
# not +has_many.jekyll
|
134
|
-
#
|
135
|
-
# context "a Room" do
|
136
|
-
# setup { Room.new }
|
137
|
-
#
|
138
|
-
# topic.has_many(:doors)
|
139
|
-
# topic.has_many(:floors) # should probably fail given our current universe :)
|
140
|
-
# end
|
141
|
-
class HasManyMacro < AssertionMacro
|
142
|
-
register :has_many
|
143
|
-
|
144
|
-
def evaluate(actual, attribute)
|
145
|
-
reflection = actual.class.reflect_on_association(attribute)
|
146
|
-
static_msg = "expected #{attribute.inspect} to be a has_many association, but was "
|
147
|
-
if reflection.nil?
|
148
|
-
fail(static_msg + "not")
|
149
|
-
elsif "has_many" != reflection.macro.to_s
|
150
|
-
fail(static_msg + "a #{reflection.macro} instead")
|
151
|
-
else
|
152
|
-
pass("has many #{attribute.inspect}")
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
end # ActiveRecord
|
158
|
-
end # RiotRails
|
1
|
+
require 'riot/active_record/validation_macros'
|
2
|
+
require 'riot/active_record/reflection_macros'
|
3
|
+
require 'riot/active_record/database_macros'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RiotRails
|
2
|
+
register_context_helper do
|
3
|
+
def prepare_context(context)
|
4
|
+
context.premium_setup { context.description.new } if active_record_context?(context)
|
5
|
+
|
6
|
+
context.transaction do |&original_block|
|
7
|
+
::ActiveRecord::Base.transaction do
|
8
|
+
original_block.call
|
9
|
+
raise ::ActiveRecord::Rollback
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
private
|
14
|
+
def active_record_context?(context)
|
15
|
+
description = context.description
|
16
|
+
description.kind_of?(Class) && description.ancestors.include?(::ActiveRecord::Base)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end # RiotRails
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RiotRails
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
# An ActiveRecord assertion macro that looks for an index on a given set of attributes in the table
|
5
|
+
# used by the model under test (aka: topic).
|
6
|
+
#
|
7
|
+
# asserts_topic.has_database_index_on :name
|
8
|
+
# asserts_topic.has_database_index_on :email, :group_name
|
9
|
+
#
|
10
|
+
# In the form used above, the assertion will pass if any index is found with the attributes listed
|
11
|
+
# (unique or not). To be specific about uniqueness, provide the +:unique+ option.
|
12
|
+
#
|
13
|
+
# asserts_topic.has_database_index_on :email, :unique => true
|
14
|
+
# asserts_topic.has_database_index_on :name, :unique => false
|
15
|
+
#
|
16
|
+
# The last example will require that the index not be a unique one.
|
17
|
+
class HasDatabaseIndexOnMacro < Riot::AssertionMacro
|
18
|
+
register :has_database_index_on
|
19
|
+
|
20
|
+
def initialize(database_connection=nil) # Good for testing :)
|
21
|
+
@database_connection = database_connection
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate(actual, *attributes_and_options)
|
25
|
+
attributes, options = extract_options_from(attributes_and_options)
|
26
|
+
unique = options[:unique]
|
27
|
+
|
28
|
+
index = find_index(actual, attributes, unique)
|
29
|
+
|
30
|
+
static_message = "#{unique ? "unique" : ""} index on #{attributes.inspect}".strip
|
31
|
+
index.nil? ? fail("expected #{static_message}") : pass("has #{static_message}")
|
32
|
+
end
|
33
|
+
private
|
34
|
+
def database_connection; @database_connection || ::ActiveRecord::Base.connection; end
|
35
|
+
|
36
|
+
def find_index(model, attributes, uniqueness, &block)
|
37
|
+
database_connection.indexes(model.class.table_name).find do |the_index|
|
38
|
+
unique_enough?(uniqueness, the_index) && attributes_match?(attributes, the_index)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unique_enough?(uniqueness, index)
|
43
|
+
return true if uniqueness.nil?
|
44
|
+
index.unique == uniqueness
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes_match?(attributes, index)
|
48
|
+
Set.new(attributes.map(&:to_s)) == Set.new(index.columns)
|
49
|
+
end
|
50
|
+
|
51
|
+
def extract_options_from(attributes)
|
52
|
+
options = attributes.last.kind_of?(Hash) ? attributes.pop : {}
|
53
|
+
[attributes, options]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end # ActiveRecord
|
58
|
+
end # RiotRails
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module RiotRails
|
2
|
+
module ActiveRecord
|
3
|
+
protected
|
4
|
+
|
5
|
+
class ReflectionAssertionMacro < Riot::AssertionMacro
|
6
|
+
private
|
7
|
+
def reflection_match?(expected, reflection)
|
8
|
+
!reflection.nil? && (expected == reflection.macro.to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def options_match_for_reflection?(reflection, options)
|
12
|
+
options.all? { |k, v| reflection.options[k] == v }
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_reflection(expected, record, attribute, options=nil)
|
16
|
+
options ||= {}
|
17
|
+
reflection = record.class.reflect_on_association(attribute)
|
18
|
+
if reflection_match?(expected, reflection)
|
19
|
+
if options_match_for_reflection?(reflection, options)
|
20
|
+
pass new_message(attribute).is_a.push(expected).association
|
21
|
+
else
|
22
|
+
fail expected_message.push("#{expected} #{attribute.inspect}").with(options)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
fail new_message(attribute).is_not_a.push(expected).association
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
public
|
31
|
+
|
32
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a +has_many+
|
33
|
+
# association. Will fail if an association is not defined for the attribute or if the association is
|
34
|
+
# not +has_many+.
|
35
|
+
#
|
36
|
+
# context "a Room" do
|
37
|
+
# setup { Room.new }
|
38
|
+
#
|
39
|
+
# asserts_topic.has_many(:doors)
|
40
|
+
# asserts_topic.has_many(:floors) # should probably fail given our current universe :)
|
41
|
+
# end
|
42
|
+
class HasManyMacro < ReflectionAssertionMacro
|
43
|
+
register :has_many
|
44
|
+
|
45
|
+
def evaluate(actual, *expectings)
|
46
|
+
attribute, options = *expectings
|
47
|
+
assert_reflection("has_many", actual, attribute, options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a
|
52
|
+
# +has_one+ association. Will fail if an association is not defined for the attribute or if the
|
53
|
+
# association is not +has_one+.
|
54
|
+
#
|
55
|
+
# context "a Room" do
|
56
|
+
# setup { Room.new }
|
57
|
+
#
|
58
|
+
# asserts_topic.has_one(:floor)
|
59
|
+
# end
|
60
|
+
class HasOneMacro < ReflectionAssertionMacro
|
61
|
+
register :has_one
|
62
|
+
|
63
|
+
def evaluate(actual, *expectings)
|
64
|
+
attribute, options = *expectings
|
65
|
+
assert_reflection("has_one", actual, attribute, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a
|
70
|
+
# +belongs_to+ association. Will fail if an association is not defined for the attribute or if the
|
71
|
+
# association is not +belongs_to+.
|
72
|
+
#
|
73
|
+
# context "a Room" do
|
74
|
+
# setup { Room.new }
|
75
|
+
#
|
76
|
+
# asserts_topic.belongs_to(:house)
|
77
|
+
# end
|
78
|
+
class BelongsToMacro < ReflectionAssertionMacro
|
79
|
+
register :belongs_to
|
80
|
+
|
81
|
+
def evaluate(actual, *expectings)
|
82
|
+
attribute, options = *expectings
|
83
|
+
assert_reflection("belongs_to", actual, attribute, options)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a
|
88
|
+
# +has_and_belongs_to_many+ association. Will fail if an association is not defined for the attribute or
|
89
|
+
# if the association is not +has_and_belongs_to_many+.
|
90
|
+
#
|
91
|
+
# context "a Room" do
|
92
|
+
# setup { Room.new }
|
93
|
+
#
|
94
|
+
# asserts_topic.has_and_belongs_to_many(:walls)
|
95
|
+
# end
|
96
|
+
class HasAndBelongsToManyMacro < ReflectionAssertionMacro
|
97
|
+
register :has_and_belongs_to_many
|
98
|
+
|
99
|
+
def evaluate(actual, *expectings)
|
100
|
+
attribute, options = *expectings
|
101
|
+
assert_reflection("has_and_belongs_to_many", actual, attribute, options)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end # ActiveRecord
|
106
|
+
end # RiotRails
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module RiotRails
|
2
|
+
module ActiveRecord
|
3
|
+
protected
|
4
|
+
|
5
|
+
class ValidationAssertionMacro < Riot::AssertionMacro
|
6
|
+
private
|
7
|
+
def errors_from_writing_value(model, attribute, value)
|
8
|
+
# TODO: Fix me to not use __send__
|
9
|
+
model.__send__(:write_attribute, attribute, value)
|
10
|
+
model.valid?
|
11
|
+
model.errors[attribute]
|
12
|
+
end
|
13
|
+
|
14
|
+
def errors_from_writing_value?(*args)
|
15
|
+
errors_from_writing_value(*args).any?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
public
|
20
|
+
|
21
|
+
# An ActiveRecord assertion that expects to pass with a given value or set of values for a given
|
22
|
+
# attribute.
|
23
|
+
#
|
24
|
+
# rails_context User do
|
25
|
+
# asserts_topic.allows_values_for :email, "a@b.cd"
|
26
|
+
# asserts_topic.allows_values_for :email, "a@b.cd", "e@f.gh"
|
27
|
+
# end
|
28
|
+
class AllowsValuesForMacro < ValidationAssertionMacro
|
29
|
+
register :allows_values_for
|
30
|
+
|
31
|
+
def evaluate(actual, attribute, *values)
|
32
|
+
bad_values = []
|
33
|
+
values.each do |value|
|
34
|
+
bad_values << value if errors_from_writing_value?(actual, attribute, value)
|
35
|
+
end
|
36
|
+
bad_values.empty? ? pass : fail(expected_message(attribute).to_allow_values(bad_values))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# An ActiveRecord assertion that expects to fail with a given value or set of values for a given
|
41
|
+
# attribute.
|
42
|
+
#
|
43
|
+
# rails_context User do
|
44
|
+
# asserts_topic.does_not_allow_values_for :email, "a"
|
45
|
+
# asserts_topic.does_not_allow_values_for :email, "a@b", "e f@g.h"
|
46
|
+
# end
|
47
|
+
class DoesNotAllowValuesForMacro < ValidationAssertionMacro
|
48
|
+
register :does_not_allow_values_for
|
49
|
+
def evaluate(actual, attribute, *values)
|
50
|
+
good_values = []
|
51
|
+
values.each do |value|
|
52
|
+
good_values << value unless errors_from_writing_value?(actual, attribute, value)
|
53
|
+
end
|
54
|
+
good_values.empty? ? pass : fail(expected_message(attribute).not_to_allow_values(good_values))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# An ActiveRecord assertion that expects to fail with invalid value for an attribute. Optionally, the
|
59
|
+
# error message can be provided as the exact string or as regular expression.
|
60
|
+
#
|
61
|
+
# rails_context User do
|
62
|
+
# asserts_topic.is_invalid_when :email, "fake", "is invalid"
|
63
|
+
# asserts_topic.is_invalid_when :email, "another fake", /invalid/
|
64
|
+
# end
|
65
|
+
class IsInvalidWhenMacro < ValidationAssertionMacro
|
66
|
+
register :is_invalid_when
|
67
|
+
|
68
|
+
def evaluate(actual, attribute, value, expected_error=nil)
|
69
|
+
actual_errors = errors_from_writing_value(actual, attribute, value)
|
70
|
+
if actual_errors.empty?
|
71
|
+
fail expected_message(attribute).to_be_invalid_when_value_is(value)
|
72
|
+
elsif expected_error && !has_error_message?(expected_error, actual_errors)
|
73
|
+
fail expected_message(attribute).to_be_invalid_with_error_message(expected_error)
|
74
|
+
else
|
75
|
+
pass new_message.attribute(attribute).is_invalid
|
76
|
+
end
|
77
|
+
end
|
78
|
+
private
|
79
|
+
def has_error_message?(expected, errors)
|
80
|
+
return true unless expected
|
81
|
+
expected.kind_of?(Regexp) ? errors.any? {|e| e =~ expected } : errors.any? {|e| e == expected }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# An ActiveRecord assertion that expects to fail when a given attribute is validated after a nil value
|
86
|
+
# is provided to it.
|
87
|
+
#
|
88
|
+
# rails_context User do
|
89
|
+
# asserts_topic.validates_presence_of(:name)
|
90
|
+
# end
|
91
|
+
class ValidatesPresenceOfMacro < ValidationAssertionMacro
|
92
|
+
register :validates_presence_of
|
93
|
+
|
94
|
+
def evaluate(actual, attribute)
|
95
|
+
if errors_from_writing_value?(actual, attribute, nil)
|
96
|
+
pass new_message.validates_presence_of(attribute)
|
97
|
+
else
|
98
|
+
fail expected_message.to_validate_presence_of(attribute)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# An ActiveRecord assertion that expects to fail with an attribute is not valid for record because the
|
104
|
+
# value of the attribute is not unique. Requires the topic of the context to be a created record; one
|
105
|
+
# that returns false for a call to +new_record?+.
|
106
|
+
#
|
107
|
+
# rails_context User do
|
108
|
+
# setup { User.create(:email => "a@b.cde", ... ) }
|
109
|
+
# asserts_topic.validates_uniqueness_of :email
|
110
|
+
# end
|
111
|
+
class ValidatesUniquenessOfMacro < ValidationAssertionMacro
|
112
|
+
register :validates_uniqueness_of
|
113
|
+
|
114
|
+
def evaluate(actual, attribute)
|
115
|
+
actual_record = actual
|
116
|
+
if actual_record.new_record?
|
117
|
+
fail new_message.must_use_a_persisted_record_when_testing_uniqueness_of(attribute)
|
118
|
+
else
|
119
|
+
copied_model = actual_record.class.new
|
120
|
+
actual_record.attributes.each do |dup_attribute, dup_value|
|
121
|
+
copied_model.__send__(:write_attribute, dup_attribute, dup_value)
|
122
|
+
end
|
123
|
+
copied_value = actual_record.__send__(:read_attribute, attribute)
|
124
|
+
if errors_from_writing_value?(copied_model, attribute, copied_value)
|
125
|
+
pass new_message(attribute).is_unique
|
126
|
+
else
|
127
|
+
fail expected_message.to_fail_because(attribute).is_not_unique
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# An ActiveRecord assertion to test that the length of the value of an attribute must be within a
|
134
|
+
# specified range. Assertion fails if: there are errors when min/max characters are used, there are no
|
135
|
+
# errors when min-1/max+1 characters are used.
|
136
|
+
#
|
137
|
+
# rails_context User do
|
138
|
+
# asserts_topic.validates_length_of :name, (2..36)
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# TODO: allow for options on what to validate
|
142
|
+
class ValidatesLengthOfMacro < ValidationAssertionMacro
|
143
|
+
register :validates_length_of
|
144
|
+
|
145
|
+
def evaluate(actual, attribute, range)
|
146
|
+
min, max = range.first, range.last
|
147
|
+
[min, max].each do |length|
|
148
|
+
if errors_from_writing_value?(actual, attribute, "r" * length)
|
149
|
+
return fail(new_message(attribute).should_be_able_to_be(length).characters)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if (min-1) > 0 && !errors_from_writing_value?(actual, attribute, "r" * (min-1))
|
154
|
+
fail new_message(attribute).expected_to_be_more_than(min - 1).characters
|
155
|
+
elsif !errors_from_writing_value?(actual, attribute, "r" * (max+1))
|
156
|
+
fail new_message(attribute).expected_to_be_less_than(max + 1).characters
|
157
|
+
else
|
158
|
+
pass new_message.validates_length_of(attribute).is_within(range)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# An ActiveRecord assertion to test that an attribute is invalid along with the expected error message
|
164
|
+
# (or error message partial). The assumption is that a value has already been set.
|
165
|
+
#
|
166
|
+
# rails_context User do
|
167
|
+
# hookup { topic.bio = "I'm a goofy clown" }
|
168
|
+
# asserts_topic.attribute_is_invalid :bio, "cannot contain adjectives"
|
169
|
+
# end
|
170
|
+
class AttributeIsInvalidMacro < ValidationAssertionMacro
|
171
|
+
register :attribute_is_invalid
|
172
|
+
|
173
|
+
def evaluate(actual, attribute, error_message)
|
174
|
+
actual.valid?
|
175
|
+
errors = actual.errors[attribute]
|
176
|
+
if errors.empty?
|
177
|
+
fail new_message(attribute).expected_to_be_invalid
|
178
|
+
elsif errors.include?(error_message)
|
179
|
+
pass new_message(attribute).is_invalid
|
180
|
+
else
|
181
|
+
fail new_message(attribute).is_invalid.but(error_message).is_not_a_valid_error_message
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end # ActiveRecord
|
187
|
+
end # RiotRails
|
data/lib/riot/active_record.rb
CHANGED
data/lib/riot/rails.rb
CHANGED
@@ -0,0 +1,84 @@
|
|
1
|
+
module RiotRails
|
2
|
+
def self.helpers; @helpers ||= []; end
|
3
|
+
def self.register_context_helper(&handler_block)
|
4
|
+
helpers << Class.new(&handler_block).new
|
5
|
+
end
|
6
|
+
|
7
|
+
class RailsContext < Riot::Context
|
8
|
+
def initialize(description, parent=nil, &definition)
|
9
|
+
@options = {:transactional => false}
|
10
|
+
transaction { |&default_block| raise Exception, "No transaction handler" }
|
11
|
+
super(description, parent, &definition)
|
12
|
+
apply_helpers
|
13
|
+
end
|
14
|
+
|
15
|
+
# We're going to allow any helper to help out. Not just one.
|
16
|
+
def apply_helpers
|
17
|
+
RiotRails.helpers.each { |helper| helper.prepare_context(self) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set options for the specific rails_context. For instance, you can tell the context to be transactional
|
21
|
+
#
|
22
|
+
# rails_context "Foo" do
|
23
|
+
# set :transactional, true
|
24
|
+
# end
|
25
|
+
def set(property, value) options[property] = value; end
|
26
|
+
|
27
|
+
# Technically, this is a secret ... but whatever. It's ruby. Basically, just make this setup more
|
28
|
+
# important than any of the others.
|
29
|
+
#
|
30
|
+
# Yes I can. See ... I just did!
|
31
|
+
def premium_setup(&definition)
|
32
|
+
@setups.unshift(::Riot::Setup.new(&definition)).first;
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true if current context or a parent context has the transactional option enabled. To enable,
|
36
|
+
# anywhere within a rails_context or a child context thereof, do:
|
37
|
+
#
|
38
|
+
# rails_context Foo do
|
39
|
+
# context "sub context" do
|
40
|
+
# set :transactional, true
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Transactional support is disabled by default. When enabled, all transactions are rolled back when the
|
45
|
+
# context is done executing.
|
46
|
+
def transactional?
|
47
|
+
options[:transactional] || (parent.respond_to?(:transactional?) && parent.transactional?)
|
48
|
+
end
|
49
|
+
|
50
|
+
# TODO: cleanup how we handle transactions and context helpers
|
51
|
+
def transaction(&block) @transaction_block = block; end
|
52
|
+
|
53
|
+
def local_run(*args)
|
54
|
+
transactional? ? @transaction_block.call { super(*args) } : super
|
55
|
+
end
|
56
|
+
private
|
57
|
+
attr_reader :options
|
58
|
+
end
|
59
|
+
|
60
|
+
module Root
|
61
|
+
# Things an Object needs at the root level
|
62
|
+
#
|
63
|
+
# rails_context SomeKindOfClass do
|
64
|
+
# end
|
65
|
+
def rails_context(description, &definition)
|
66
|
+
context(description, RailsContext, &definition)
|
67
|
+
end
|
68
|
+
end # Root
|
69
|
+
|
70
|
+
module Context
|
71
|
+
# Things a running context needs
|
72
|
+
#
|
73
|
+
# context "Something" do
|
74
|
+
# rails_context SomeKindOfClass do
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
def rails_context(description, &definition)
|
78
|
+
new_context(description, RailsContext, &definition)
|
79
|
+
end
|
80
|
+
end # Context
|
81
|
+
end # RiotRails
|
82
|
+
|
83
|
+
Object.instance_eval { include RiotRails::Root }
|
84
|
+
Riot::Context.instance_eval { include RiotRails::Context }
|