<?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>
<entry>
  <title>Meet thoughtbot at Brighton Ruby 2026</title>
  <link rel="alternate" href="https://thoughtbot.com/blog/meet-thoughtbot-at-brighton-ruby-2026"/>
  <author>
    <name>Rémy Hannequin</name>
  </author>
  <id>https://thoughtbot.com/blog/meet-thoughtbot-at-brighton-ruby-2026</id>
  <published>2026-06-18T00:00:00+00:00</published>
  <updated>2026-06-17T14:59:42Z</updated>
  <content type="html">&lt;p&gt;Brighton Ruby 2026 will take place in a few days and the thoughtbot team will be
there to meet you all in real life, learn from all the great talks, and enjoy a
day by the English coast.&lt;/p&gt;

&lt;p&gt;We love Brighton Ruby and enjoyed it for many years. It is a single-day,
single-track conference packed with great energy and great people.&lt;/p&gt;

&lt;p&gt;This year we will have 5 thoughtbotters attending:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://images.thoughtbot.com/8sth2cjnqsrcqzqwi024demlwabm_Ali_Slater.png" alt="Aji Slater's portrait"&gt;&lt;/p&gt;

&lt;p&gt;Aji will be at Brighton Ruby for the first time! They are always happy to talk
about ruby game development, recent conversations on
&lt;a href="https://bikeshed.thoughtbot.com"&gt;The Bike Shed&lt;/a&gt;, tracking reading lists on
&lt;a href="https://app.thestorygraph.com/profile/doodlingdev"&gt;Storygraph&lt;/a&gt;, or (let’s see
what else… &lt;em&gt;::rummages through bag of hobbies::&lt;/em&gt;) linguistic anthropology.
Come say hello!&lt;/p&gt;

&lt;p&gt;&lt;img src="https://images.thoughtbot.com/lwltzeyrmhro7xrbz2l9bbzqxp7v_Chad_Pytel.png" alt="Chad Pytel's portrait"&gt;&lt;/p&gt;

&lt;p&gt;Chad is thoughtbot’s founder and CEO, host of the
&lt;em&gt;Giant Robots Smashing Into Other Giant Robots&lt;/em&gt;‘s
&lt;a href="https://podcast.thoughtbot.com/"&gt;podcast&lt;/a&gt; and eternal player of D&amp;amp;D.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://images.thoughtbot.com/pkyl5eoqpbz36lrquink7xqjusk9_Mina_Slater.png" alt="Mina Slater's portrait"&gt;&lt;/p&gt;

&lt;p&gt;Mina is based in Edinburgh, Scotland, and this will be her first time at
Brighton Ruby. She’s interested in infrastructure as code and closing the gap
between operations responsibilities and application development, is an avid
marathon runner, always looking to strike up a conversation about the works of
Brandon Sanderson or show off pictures of her dogs, Dottie and Henson.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://images.thoughtbot.com/yvj3ky5sd803aoy3704pp4fut63z_rob.png" alt="Rob Whittaker's portrait"&gt;&lt;/p&gt;

&lt;p&gt;Rob is our EMEA Development Director, based in Holmes Chapel, Cheshire and most
likely you have seen him already in Brighton Ruby or other conferences.&lt;/p&gt;

&lt;p&gt;He has a not-so-quiet obsession with best practices and striving for
improvement. He likes to hunt down delicious beers and coffee in his spare time.
Despite the recent ups and downs, he’s an avid Stoke City fan, which is only a
testament to his determination!&lt;/p&gt;

&lt;p&gt;&lt;img src="https://images.thoughtbot.com/rlx4eih0fe93p9fd3sna6io6why1_Sarah%20Lima%202026%20(1).png" alt="Sarah Lima's portrait"&gt;&lt;/p&gt;

&lt;p&gt;Sarah is a developer and team lead, based in Porto, Portugal, and originally
from Brazil. She enjoys working with Ruby especially, but also on any technology
that enables projects to move forward. She loves playings sports like volleyball
and has a great movies and TV series culture.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://images.thoughtbot.com/038aqq7cxmnqu1fwguk5jlj8lq5h_Remy_Hannequin.png" alt="Rémy Hannequin's portrait"&gt;&lt;/p&gt;

&lt;p&gt;And finally, we will also have a speaker at the conference this year: myself,
Rémy Hannequin. I am happy to share that I will be giving a talk on time
management and surprises with Ruby.&lt;/p&gt;

&lt;p&gt;I am based in Paris, France, and I have a serious passion for astronomy. I
created multiple open source projects to combine Ruby and astronomy, with the
main one being &lt;a href="https://github.com/rhannequin/astronoby"&gt;Astronoby&lt;/a&gt;, a Ruby gem to allows to compute celestial events and
positions with extreme precision.&lt;/p&gt;

&lt;p&gt;If you’re attending, come say hello! We’re always up for talking about Ruby,
Rails, that new gem we’re excited about, the eternal Vim vs VS Code debate or we
can just share a drink and talk about something else! Keep an eye on
&lt;a href="https://thoughtbot.social/@thoughtbot"&gt;thoughtbot on Mastodon&lt;/a&gt; and
&lt;a href="https://ruby.social/@purinkle"&gt;Rob’s personal account&lt;/a&gt; to see where we’ll be
hanging out.&lt;/p&gt;

&lt;p&gt;We can’t wait to share the experience with you.&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/2-years-of-hackfests"&gt;2 Years Of Hackfests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/thoughtbot-at-rubyconf-2023"&gt;thoughtbot at RubyConf 2023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/railsconf2012"&gt;Meet thoughtbot at Railsconf 2012&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/aside&gt;
</content>
  <summary>Brighton Ruby 2026 will take place in two weeks, thoughtbot will be attending and speaking, let's meet!</summary>
  <thoughtbot:auto_social_share>true</thoughtbot:auto_social_share>
</entry>
</feed>
