<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thoughtbot="https://thoughtbot.com/feeds/">
  <title>Giant Robots Smashing Into Other Giant Robots</title>
  <subtitle>Written by thoughtbot, your expert partner for design and development.
</subtitle>
  <id>https://robots.thoughtbot.com/</id>
  <link href="https://thoughtbot.com/blog"/>
  <link href="https://feed.thoughtbot.com" rel="self"/>
  <updated>2026-06-23T00:00:00+00:00</updated>
  <author>
    <name>thoughtbot</name>
  </author>
<entry>
  <title>Announcing Shoulda Matchers 8.0: validate multiple attributes in one line</title>
  <link rel="alternate" href="https://thoughtbot.com/blog/announcing-shoulda-matchers-8-0-validate-multiple-attributes-in-one-line"/>
  <author>
    <name>Matheus Sales</name>
  </author>
  <id>https://thoughtbot.com/blog/announcing-shoulda-matchers-8-0-validate-multiple-attributes-in-one-line</id>
  <published>2026-06-23T00:00:00+00:00</published>
  <updated>2026-06-22T12:49:08Z</updated>
  <content type="html">&lt;p&gt;You know the drill if you’ve validated the same rule on a few attributes. One
presence validation is one matcher, and five of them turn into five nearly
identical lines that don’t tell the next reader much:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:first_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/thoughtbot/shoulda-matchers"&gt;Shoulda Matchers&lt;/a&gt; 8.0 is out on &lt;a href="https://rubygems.org/gems/shoulda-matchers"&gt;RubyGems&lt;/a&gt;, and it
gets rid of that repetition. It also moves the gem onto Ruby 4 and the latest
Rails. Let’s dig in.&lt;/p&gt;
&lt;h2 id="one-matcher-many-attributes"&gt;
  
    One matcher, many attributes
  
&lt;/h2&gt;

&lt;p&gt;Validation matchers now can receive more than one attribute, so those three lines
become one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Qualifiers apply to all of them, so a grouped expectation reads just as clearly as
the one-at-a-time version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:arms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:legs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;allow_nil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Grouping doesn’t cost you anything when a spec goes red. The failure message calls
out each attribute that failed and why, so you still land right on the problem.&lt;/p&gt;

&lt;p&gt;These eleven matchers currently support the multiple-attribute syntax:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;validate_presence_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_absence_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_acceptance_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_confirmation_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_length_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_numericality_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_inclusion_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_exclusion_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate_comparison_of&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;have_readonly_attribute&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;have_one_attached&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ready-for-ruby-4"&gt;
  
    Ready for Ruby 4
  
&lt;/h2&gt;

&lt;p&gt;8.0 runs on Ruby 4. We also bumped the supported versions up to the latest stable
Ruby and Rails, so the gem matches the stack your app is probably already on and
you won’t have to hold your test suite back to upgrade.&lt;/p&gt;
&lt;h2 id="keeping-up-with-rails-associations"&gt;
  
    Keeping up with Rails associations
  
&lt;/h2&gt;

&lt;p&gt;Rails is shifting to a new deprecated associations API. 8.0 supports it, so your
association matchers keep working as Rails changes. Nothing for you to do here.&lt;/p&gt;
&lt;h2 id="upgrading"&gt;
  
    Upgrading
  
&lt;/h2&gt;

&lt;p&gt;The only breaking change in 8.0 is dropping support for end-of-life Ruby and Rails
versions, specifically Rails 7.1 and Ruby 3.2. If you’re already on a current Ruby
and Rails, the upgrade should be seamless. Bump the version, run your suite, and
you’re done. The new multiple-attribute syntax is opt-in, so your existing matchers
keep working exactly as before. If you’re still on Rails 7.1 or Ruby 3.2, stick
with the 7.x line until you can move up.&lt;/p&gt;

&lt;aside class="info"&gt;
  &lt;p&gt;A heads up if you upgrade early: 8.0.0 shipped with a bug where &lt;b&gt;validate_uniqueness_of&lt;/b&gt; raised an error when &lt;code&gt;config.enable_reloading&lt;/code&gt; was false. That’s fixed in 8.0.1, so go straight to 8.0.1.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id="thank-you"&gt;
  
    Thank you
  
&lt;/h2&gt;

&lt;p&gt;Thanks to everyone who helped make this release happen.&lt;/p&gt;

&lt;p&gt;Bump your &lt;code&gt;Gemfile&lt;/code&gt; to 8.0.1 and let us know what you’d like grouped next. The
full list of changes is in the &lt;a href="https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md#800---2026-06-12"&gt;CHANGELOG&lt;/a&gt;. Enjoy!&lt;/p&gt;

&lt;aside class="related-articles"&gt;&lt;h2&gt;If you enjoyed this post, you might also like:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/waiting-for-a-factory-bot"&gt;Waiting For a Factory~~Girl~~Bot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/factory-bot-1-2-adding-excitement-to-stale-factories"&gt;FactoryBot 1.2: Adding Excitement to Stale Factories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/factory-bot-rails3"&gt;factory_bot 1.3: integrating effectively with Rails 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/aside&gt;
</content>
  <summary>The latest Shoulda Matchers cuts repetitive validation specs down to a single line, runs on Ruby 4, and keeps up with the newest Rails.</summary>
  <thoughtbot:auto_social_share>true</thoughtbot:auto_social_share>
</entry>
</feed>
