giraffesoft-attribute_fu 0.2
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/MIT-LICENSE +20 -0
- data/README +115 -0
- data/Rakefile +22 -0
- data/init.rb +2 -0
- data/lib/attribute_fu.rb +2 -0
- data/lib/attribute_fu/associated_form_helper.rb +139 -0
- data/lib/attribute_fu/associations.rb +124 -0
- data/tasks/attribute_fu_tasks.rake +4 -0
- data/test/Rakefile +10 -0
- data/test/app/controllers/application.rb +10 -0
- data/test/app/helpers/application_helper.rb +3 -0
- data/test/app/models/comment.rb +8 -0
- data/test/app/models/photo.rb +3 -0
- data/test/config/boot.rb +97 -0
- data/test/config/database.yml +15 -0
- data/test/config/environment.rb +15 -0
- data/test/config/environments/development.rb +18 -0
- data/test/config/environments/test.rb +22 -0
- data/test/config/routes.rb +35 -0
- data/test/db/migrate/001_create_photos.rb +14 -0
- data/test/db/migrate/002_create_comments.rb +15 -0
- data/test/db/schema.rb +29 -0
- data/test/script/console +3 -0
- data/test/script/destroy +3 -0
- data/test/script/generate +3 -0
- data/test/script/server +3 -0
- data/test/test/test_helper.rb +6 -0
- data/test/test/unit/associated_form_helper_test.rb +376 -0
- data/test/test/unit/comment_test.rb +6 -0
- data/test/test/unit/photo_test.rb +149 -0
- data/test/vendor/plugins/shoulda/init.rb +3 -0
- data/test/vendor/plugins/shoulda/lib/shoulda.rb +20 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb +338 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/context.rb +143 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/general.rb +119 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/private_helpers.rb +17 -0
- data/uninstall.rb +1 -0
- metadata +110 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class PhotoTest < ActiveSupport::TestCase
|
4
|
+
should_have_many :comments
|
5
|
+
|
6
|
+
context "comment_attributes" do
|
7
|
+
context "with valid children" do
|
8
|
+
setup do
|
9
|
+
create_photo_and_children
|
10
|
+
|
11
|
+
@photo.comment_attributes = { @gob.id.to_s => { :author => "Buster Bluth", :body => "I said it was _our_ nausia..." },
|
12
|
+
:new => { "0" => { :author => "George-Michael", :body => "I was going to smoke the marijuana like a ciggarette." },
|
13
|
+
"-1" => { :author => "Tobias Funke", :body => "I am an actor! An actor for crying out loud!" }}}
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
context "before save" do
|
18
|
+
should "not have deleted anything in the remove array" do
|
19
|
+
assert @photo.comments.any? { |comment| comment.author == "Bob Loblaw" }, "Comment in remove array was removed."
|
20
|
+
end
|
21
|
+
|
22
|
+
should "not have saved any new objects" do
|
23
|
+
assert @photo.comments.any? { |comment| comment.new_record? }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "after save" do
|
28
|
+
setup do
|
29
|
+
@photo.save
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with existing child" do
|
33
|
+
setup do
|
34
|
+
@gob.reload
|
35
|
+
end
|
36
|
+
|
37
|
+
should "update attributes" do
|
38
|
+
assert_equal "Buster Bluth", @gob.author, "Author attribute of child model was not updated."
|
39
|
+
assert_equal "I said it was _our_ nausia...", @gob.body, "Body attribute of child model was not updated."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with new hash" do
|
44
|
+
should "create new comment" do
|
45
|
+
assert @photo.comments.any? { |comment| comment.author == "George-Michael" && comment.body =~ /was going to smoke/i }, "New comment was not created."
|
46
|
+
end
|
47
|
+
|
48
|
+
should "order the negatives after the positives" do
|
49
|
+
assert_equal "Tobias Funke", @photo.comments.last.author, "Tobias is not the last comment: #{@photo.comments.inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with missing associated" do
|
54
|
+
should "remove those children from the parent" do
|
55
|
+
assert !@photo.comments.any? { |comment| comment.author == "Bob Loblaw" }, "Comment not included was not removed."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with comment_attributes = nil" do
|
61
|
+
setup do
|
62
|
+
@photo.save
|
63
|
+
@photo.comment_attributes = nil
|
64
|
+
@photo.save
|
65
|
+
end
|
66
|
+
|
67
|
+
should "remove all comments" do
|
68
|
+
assert @photo.comments.empty?, "one or more comments not removed: #{@photo.comments.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with discard_if => proc { }" do
|
73
|
+
setup do
|
74
|
+
create_photo_with_discard(proc { |comment| comment.author.blank? && comment.body.blank? })
|
75
|
+
end
|
76
|
+
|
77
|
+
teardown do
|
78
|
+
Photo.class_eval do
|
79
|
+
managed_association_attributes[:comments].delete(:discard_if)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
should "discard any child objects for which discard_if evaluates to true" do
|
84
|
+
assert !@photo.comments.any? { |comment| comment.author.blank? && comment.body.blank? }, @photo.comments.inspect
|
85
|
+
end
|
86
|
+
|
87
|
+
should "not discard other objects" do
|
88
|
+
assert_equal 1, @photo.comments.length
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with discard_if => :symbol" do
|
93
|
+
setup do
|
94
|
+
create_photo_with_discard(:blank?)
|
95
|
+
end
|
96
|
+
|
97
|
+
teardown do
|
98
|
+
Photo.class_eval do
|
99
|
+
managed_association_attributes[:comments].delete(:discard_if)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
should "discard any child objects for which discard_if evaluates to true" do
|
104
|
+
assert !@photo.comments.any? { |comment| comment.author.blank? && comment.body.blank? }, @photo.comments.inspect
|
105
|
+
end
|
106
|
+
|
107
|
+
should "not discard other objects" do
|
108
|
+
assert_equal 1, @photo.comments.length
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "updating with invalid children" do
|
114
|
+
setup do
|
115
|
+
@photo = Photo.create
|
116
|
+
@saved = @photo.update_attributes :comment_attributes => {:new => {"0" => {:author => "Tobias"}}}
|
117
|
+
end
|
118
|
+
|
119
|
+
should "not save" do
|
120
|
+
assert !@saved
|
121
|
+
end
|
122
|
+
|
123
|
+
should "have errors on child" do
|
124
|
+
assert @photo.comments.first.errors.on(:body)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def create_photo_and_children
|
131
|
+
@photo = Photo.create
|
132
|
+
@gob = @photo.comments.create :author => "Gob Bluth", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed..."
|
133
|
+
@bob = @photo.comments.create :author => "Bob Loblaw", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed..."
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_photo_with_discard(discard_if)
|
137
|
+
Photo.class_eval do
|
138
|
+
has_many :comments, :attributes => true, :discard_if => discard_if
|
139
|
+
end
|
140
|
+
|
141
|
+
create_photo_and_children
|
142
|
+
|
143
|
+
|
144
|
+
@photo.comment_attributes = { @gob.id.to_s => { :author => "Buster Bluth", :body => "I said it was _our_ nausia..." },
|
145
|
+
@bob.id.to_s => { :author => '', :body => '' },
|
146
|
+
:new => { "0" => { :author => "", :body => "" }}}
|
147
|
+
@photo.save
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'shoulda/private_helpers'
|
3
|
+
require 'shoulda/general'
|
4
|
+
require 'shoulda/context'
|
5
|
+
require 'shoulda/active_record_helpers'
|
6
|
+
|
7
|
+
|
8
|
+
module Test # :nodoc: all
|
9
|
+
module Unit
|
10
|
+
class TestCase
|
11
|
+
|
12
|
+
include ThoughtBot::Shoulda::General
|
13
|
+
|
14
|
+
class << self
|
15
|
+
include ThoughtBot::Shoulda::Context
|
16
|
+
include ThoughtBot::Shoulda::ActiveRecord
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,338 @@
|
|
1
|
+
module ThoughtBot # :nodoc:
|
2
|
+
module Shoulda # :nodoc:
|
3
|
+
# = Macro test helpers for your active record models
|
4
|
+
#
|
5
|
+
# These helpers will test most of the validations and associations for your ActiveRecord models.
|
6
|
+
#
|
7
|
+
# class UserTest < Test::Unit::TestCase
|
8
|
+
# should_require_attributes :name, :phone_number
|
9
|
+
# should_not_allow_values_for :phone_number, "abcd", "1234"
|
10
|
+
# should_allow_values_for :phone_number, "(123) 456-7890"
|
11
|
+
#
|
12
|
+
# should_protect_attributes :password
|
13
|
+
#
|
14
|
+
# should_have_one :profile
|
15
|
+
# should_have_many :dogs
|
16
|
+
# should_have_many :messes, :through => :dogs
|
17
|
+
# should_belong_to :lover
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# For all of these helpers, the last parameter may be a hash of options.
|
21
|
+
#
|
22
|
+
module ActiveRecord
|
23
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not present.
|
24
|
+
# Requires an existing record.
|
25
|
+
#
|
26
|
+
# Options:
|
27
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
28
|
+
# Regexp or string. Default = <tt>/blank/</tt>
|
29
|
+
#
|
30
|
+
# Example:
|
31
|
+
# should_require_attributes :name, :phone_number
|
32
|
+
def should_require_attributes(*attributes)
|
33
|
+
message = get_options!(attributes, :message)
|
34
|
+
message ||= /blank/
|
35
|
+
klass = model_class
|
36
|
+
|
37
|
+
attributes.each do |attribute|
|
38
|
+
should "require #{attribute} to be set" do
|
39
|
+
object = klass.new
|
40
|
+
assert !object.valid?, "#{klass.name} does not require #{attribute}."
|
41
|
+
assert object.errors.on(attribute), "#{klass.name} does not require #{attribute}."
|
42
|
+
assert_contains(object.errors.on(attribute), message)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not unique.
|
48
|
+
# Requires an existing record
|
49
|
+
#
|
50
|
+
# Options:
|
51
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
52
|
+
# Regexp or string. Default = <tt>/taken/</tt>
|
53
|
+
#
|
54
|
+
# Example:
|
55
|
+
# should_require_unique_attributes :keyword, :username
|
56
|
+
def should_require_unique_attributes(*attributes)
|
57
|
+
message, scope = get_options!(attributes, :message, :scoped_to)
|
58
|
+
message ||= /taken/
|
59
|
+
|
60
|
+
klass = model_class
|
61
|
+
attributes.each do |attribute|
|
62
|
+
attribute = attribute.to_sym
|
63
|
+
should "require unique value for #{attribute}#{" scoped to #{scope}" if scope}" do
|
64
|
+
assert existing = klass.find(:first), "Can't find first #{klass}"
|
65
|
+
object = klass.new
|
66
|
+
|
67
|
+
object.send(:"#{attribute}=", existing.send(attribute))
|
68
|
+
if scope
|
69
|
+
assert_respond_to object, :"#{scope}=", "#{klass.name} doesn't seem to have a #{scope} attribute."
|
70
|
+
object.send(:"#{scope}=", existing.send(scope))
|
71
|
+
end
|
72
|
+
|
73
|
+
assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}."
|
74
|
+
assert object.errors.on(attribute), "#{klass.name} does not require a unique value for #{attribute}."
|
75
|
+
|
76
|
+
assert_contains(object.errors.on(attribute), message)
|
77
|
+
|
78
|
+
if scope
|
79
|
+
# Now test that the object is valid when changing the scoped attribute
|
80
|
+
# TODO: actually find all values for scope and create a unique one.
|
81
|
+
object.send(:"#{scope}=", existing.send(scope).nil? ? 1 : existing.send(scope).next)
|
82
|
+
object.errors.clear
|
83
|
+
object.valid?
|
84
|
+
assert_does_not_contain(object.errors.on(attribute), message,
|
85
|
+
"after :#{scope} set to #{object.send(scope.to_sym)}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Ensures that the attribute cannot be set on update
|
92
|
+
# Requires an existing record
|
93
|
+
#
|
94
|
+
# should_protect_attributes :password, :admin_flag
|
95
|
+
def should_protect_attributes(*attributes)
|
96
|
+
get_options!(attributes)
|
97
|
+
klass = model_class
|
98
|
+
attributes.each do |attribute|
|
99
|
+
attribute = attribute.to_sym
|
100
|
+
should "not allow #{attribute} to be changed by update" do
|
101
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
102
|
+
value = object[attribute]
|
103
|
+
# TODO: 1 may not be a valid value for the attribute (due to validations)
|
104
|
+
assert object.update_attributes({ attribute => 1 }),
|
105
|
+
"Cannot update #{klass} with { :#{attribute} => 1 }, #{object.errors.full_messages.to_sentence}"
|
106
|
+
assert object.valid?, "#{klass} isn't valid after changing #{attribute}"
|
107
|
+
assert_equal value, object[attribute], "Was able to change #{klass}##{attribute}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Ensures that the attribute cannot be set to the given values
|
113
|
+
# Requires an existing record
|
114
|
+
#
|
115
|
+
# Options:
|
116
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
117
|
+
# Regexp or string. Default = <tt>/invalid/</tt>
|
118
|
+
#
|
119
|
+
# Example:
|
120
|
+
# should_not_allow_values_for :isbn, "bad 1", "bad 2"
|
121
|
+
def should_not_allow_values_for(attribute, *bad_values)
|
122
|
+
message = get_options!(bad_values, :message)
|
123
|
+
message ||= /invalid/
|
124
|
+
klass = model_class
|
125
|
+
bad_values.each do |v|
|
126
|
+
should "not allow #{attribute} to be set to \"#{v}\"" do
|
127
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
128
|
+
object.send("#{attribute}=", v)
|
129
|
+
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
|
130
|
+
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
|
131
|
+
assert_contains(object.errors.on(attribute), message, "when set to \"#{v}\"")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Ensures that the attribute can be set to the given values.
|
137
|
+
# Requires an existing record
|
138
|
+
#
|
139
|
+
# Options:
|
140
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
141
|
+
# Regexp or string. Default = <tt>/invalid/</tt>
|
142
|
+
#
|
143
|
+
# Example:
|
144
|
+
# should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
|
145
|
+
def should_allow_values_for(attribute, *good_values)
|
146
|
+
message = get_options!(good_values, :message)
|
147
|
+
message ||= /invalid/
|
148
|
+
klass = model_class
|
149
|
+
good_values.each do |v|
|
150
|
+
should "allow #{attribute} to be set to \"#{v}\"" do
|
151
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
152
|
+
object.send("#{attribute}=", v)
|
153
|
+
object.save
|
154
|
+
assert_does_not_contain(object.errors.on(attribute), message, "when set to \"#{v}\"")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Ensures that the length of the attribute is in the given range
|
160
|
+
# Requires an existing record
|
161
|
+
#
|
162
|
+
# Options:
|
163
|
+
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
164
|
+
# Regexp or string. Default = <tt>/short/</tt>
|
165
|
+
# * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
166
|
+
# Regexp or string. Default = <tt>/long/</tt>
|
167
|
+
#
|
168
|
+
# Example:
|
169
|
+
# should_ensure_length_in_range :password, (6..20)
|
170
|
+
def should_ensure_length_in_range(attribute, range, opts = {})
|
171
|
+
short_message, long_message = get_options!([opts], :short_message, :long_message)
|
172
|
+
short_message ||= /short/
|
173
|
+
long_message ||= /long/
|
174
|
+
|
175
|
+
klass = model_class
|
176
|
+
min_length = range.first
|
177
|
+
max_length = range.last
|
178
|
+
|
179
|
+
if min_length > 0
|
180
|
+
min_value = "x" * (min_length - 1)
|
181
|
+
should "not allow #{attribute} to be less than #{min_length} chars long" do
|
182
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
183
|
+
object.send("#{attribute}=", min_value)
|
184
|
+
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
|
185
|
+
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\""
|
186
|
+
assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
max_value = "x" * (max_length + 1)
|
191
|
+
should "not allow #{attribute} to be more than #{max_length} chars long" do
|
192
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
193
|
+
object.send("#{attribute}=", max_value)
|
194
|
+
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\""
|
195
|
+
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{max_value}\""
|
196
|
+
assert_contains(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Ensure that the attribute is in the range specified
|
201
|
+
# Requires an existing record
|
202
|
+
#
|
203
|
+
# Options:
|
204
|
+
# * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
205
|
+
# Regexp or string. Default = <tt>/included/</tt>
|
206
|
+
# * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
207
|
+
# Regexp or string. Default = <tt>/included/</tt>
|
208
|
+
#
|
209
|
+
# Example:
|
210
|
+
# should_ensure_value_in_range :age, (0..100)
|
211
|
+
def should_ensure_value_in_range(attribute, range, opts = {})
|
212
|
+
low_message, high_message = get_options!([opts], :low_message, :high_message)
|
213
|
+
low_message ||= /included/
|
214
|
+
high_message ||= /included/
|
215
|
+
|
216
|
+
klass = model_class
|
217
|
+
min = range.first
|
218
|
+
max = range.last
|
219
|
+
|
220
|
+
should "not allow #{attribute} to be less than #{min}" do
|
221
|
+
v = min - 1
|
222
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
223
|
+
object.send("#{attribute}=", v)
|
224
|
+
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
|
225
|
+
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
|
226
|
+
assert_contains(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
|
227
|
+
end
|
228
|
+
|
229
|
+
should "not allow #{attribute} to be more than #{max}" do
|
230
|
+
v = max + 1
|
231
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
232
|
+
object.send("#{attribute}=", v)
|
233
|
+
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
|
234
|
+
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
|
235
|
+
assert_contains(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Ensure that the attribute is numeric
|
240
|
+
# Requires an existing record
|
241
|
+
#
|
242
|
+
# Options:
|
243
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
244
|
+
# Regexp or string. Default = <tt>/number/</tt>
|
245
|
+
#
|
246
|
+
# Example:
|
247
|
+
# should_only_allow_numeric_values_for :age
|
248
|
+
def should_only_allow_numeric_values_for(*attributes)
|
249
|
+
message = get_options!(attributes, :message)
|
250
|
+
message ||= /number/
|
251
|
+
klass = model_class
|
252
|
+
attributes.each do |attribute|
|
253
|
+
attribute = attribute.to_sym
|
254
|
+
should "only allow numeric values for #{attribute}" do
|
255
|
+
assert object = klass.find(:first), "Can't find first #{klass}"
|
256
|
+
object.send(:"#{attribute}=", "abcd")
|
257
|
+
assert !object.valid?, "Instance is still valid"
|
258
|
+
assert_contains(object.errors.on(attribute), message)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Ensures that the has_many relationship exists.
|
264
|
+
#
|
265
|
+
# Options:
|
266
|
+
# * <tt>:through</tt> - association name for <tt>has_many :through</tt>
|
267
|
+
#
|
268
|
+
# Example:
|
269
|
+
# should_have_many :friends
|
270
|
+
# should_have_many :enemies, :through => :friends
|
271
|
+
def should_have_many(*associations)
|
272
|
+
through = get_options!(associations, :through)
|
273
|
+
klass = model_class
|
274
|
+
associations.each do |association|
|
275
|
+
should "have many #{association}#{" through #{through}" if through}" do
|
276
|
+
reflection = klass.reflect_on_association(association)
|
277
|
+
assert reflection, "#{klass.name} does not have any relationship to #{association}"
|
278
|
+
assert_equal :has_many, reflection.macro
|
279
|
+
if through
|
280
|
+
through_reflection = klass.reflect_on_association(through)
|
281
|
+
assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
|
282
|
+
assert_equal(through, reflection.options[:through])
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Ensures that the has_and_belongs_to_many relationship exists.
|
289
|
+
#
|
290
|
+
# should_have_and_belong_to_many :posts, :cars
|
291
|
+
def should_have_and_belong_to_many(*associations)
|
292
|
+
get_options!(associations)
|
293
|
+
klass = model_class
|
294
|
+
associations.each do |association|
|
295
|
+
should "should have and belong to many #{association}" do
|
296
|
+
assert klass.reflect_on_association(association), "#{klass.name} does not have any relationship to #{association}"
|
297
|
+
assert_equal :has_and_belongs_to_many, klass.reflect_on_association(association).macro
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Ensure that the has_one relationship exists.
|
303
|
+
#
|
304
|
+
# should_have_one :god # unless hindu
|
305
|
+
def should_have_one(*associations)
|
306
|
+
get_options!(associations)
|
307
|
+
klass = model_class
|
308
|
+
associations.each do |association|
|
309
|
+
should "have one #{association}" do
|
310
|
+
assert klass.reflect_on_association(association), "#{klass.name} does not have any relationship to #{association}"
|
311
|
+
assert_equal :has_one, klass.reflect_on_association(association).macro
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Ensure that the belongs_to relationship exists.
|
317
|
+
#
|
318
|
+
# should_belong_to :parent
|
319
|
+
def should_belong_to(*associations)
|
320
|
+
get_options!(associations)
|
321
|
+
klass = model_class
|
322
|
+
associations.each do |association|
|
323
|
+
should "belong_to #{association}" do
|
324
|
+
reflection = klass.reflect_on_association(association)
|
325
|
+
assert reflection, "#{klass.name} does not have any relationship to #{association}"
|
326
|
+
assert_equal :belongs_to, reflection.macro
|
327
|
+
fk = reflection.options[:foreign_key] || "#{association}_id"
|
328
|
+
assert klass.column_names.include?(fk), "#{klass.name} does not have a #{fk} foreign key."
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
|
335
|
+
include ThoughtBot::Shoulda::Private
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|