forked from thoughtbot/shoulda-matchers
-
Notifications
You must be signed in to change notification settings - Fork 1
/
have_attached_matcher.rb
185 lines (165 loc) · 4.68 KB
/
have_attached_matcher.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
module Shoulda
module Matchers
module ActiveRecord
# The `have_one_attached` matcher tests usage of the
# `has_one_attached` macro.
#
# #### Example
#
# class User < ApplicationRecord
# has_one_attached :avatar
# end
#
# # RSpec
# RSpec.describe User, type: :model do
# it { should have_one_attached(:avatar) }
# end
#
# # Minitest (Shoulda)
# class UserTest < ActiveSupport::TestCase
# should have_one_attached(:avatar)
# end
#
# @return [HaveAttachedMatcher]
#
def have_one_attached(name)
HaveAttachedMatcher.new(:one, name)
end
# The `have_many_attached` matcher tests usage of the
# `has_many_attached` macro.
#
# #### Example
#
# class Message < ApplicationRecord
# has_many_attached :images
# end
#
# # RSpec
# RSpec.describe Message, type: :model do
# it { should have_many_attached(:images) }
# end
#
# # Minitest (Shoulda)
# class MessageTest < ActiveSupport::TestCase
# should have_many_attached(:images)
# end
#
# @return [HaveAttachedMatcher]
#
def have_many_attached(name)
HaveAttachedMatcher.new(:many, name)
end
# @private
class HaveAttachedMatcher
attr_reader :name
def initialize(macro, name)
@macro = macro
@name = name
end
def description
"have a has_#{macro}_attached called #{name}"
end
def failure_message
<<-MESSAGE
Expected #{expectation}, but this could not be proved.
#{@failure}
MESSAGE
end
def failure_message_when_negated
<<-MESSAGE
Did not expect #{expectation}, but it does.
MESSAGE
end
def expectation
"#{model_class.name} to #{description}"
end
def matches?(subject)
@subject = subject
reader_attribute_exists? &&
writer_attribute_exists? &&
attachments_association_exists? &&
blobs_association_exists? &&
eager_loading_scope_exists?
end
private
attr_reader :subject, :macro
def reader_attribute_exists?
if subject.respond_to?(name)
true
else
@failure = "#{model_class.name} does not have a :#{name} method."
false
end
end
def writer_attribute_exists?
if subject.respond_to?("#{name}=")
true
else
@failure = "#{model_class.name} does not have a :#{name}= method."
false
end
end
def attachments_association_exists?
if attachments_association_matcher.matches?(subject)
true
else
@failure = attachments_association_matcher.failure_message
false
end
end
def attachments_association_matcher
@_attachments_association_matcher ||=
AssociationMatcher.new(
:"has_#{macro}",
attachments_association_name,
).
conditions(name: name).
class_name('ActiveStorage::Attachment').
inverse_of(:record)
end
def attachments_association_name
case macro
when :one then "#{name}_attachment"
when :many then "#{name}_attachments"
end
end
def blobs_association_exists?
if blobs_association_matcher.matches?(subject)
true
else
@failure = blobs_association_matcher.failure_message
false
end
end
def blobs_association_matcher
@_blobs_association_matcher ||=
AssociationMatcher.new(
:"has_#{macro}",
blobs_association_name,
).
through(attachments_association_name).
class_name('ActiveStorage::Blob').
source(:blob)
end
def blobs_association_name
case macro
when :one then "#{name}_blob"
when :many then "#{name}_blobs"
end
end
def eager_loading_scope_exists?
if model_class.respond_to?("with_attached_#{name}")
true
else
@failure = "#{model_class.name} does not have a " \
":with_attached_#{name} scope."
false
end
end
def model_class
subject.class
end
end
end
end
end