<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin Murphy</title>
    <description>The latest articles on DEV Community by Kevin Murphy (@kevin_j_m).</description>
    <link>https://dev.to/kevin_j_m</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F583090%2Fc71e284c-3536-40db-855b-e11c0346a4d6.jpeg</url>
      <title>DEV Community: Kevin Murphy</title>
      <link>https://dev.to/kevin_j_m</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevin_j_m"/>
    <language>en</language>
    <item>
      <title>Rails Controller Callback Order With Concerns</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Tue, 20 May 2025 00:08:28 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/rails-controller-callback-order-with-concerns-273j</link>
      <guid>https://dev.to/kevin_j_m/rails-controller-callback-order-with-concerns-273j</guid>
      <description>&lt;h2&gt;
  
  
  How can I help you today?
&lt;/h2&gt;

&lt;p&gt;We're building an app to display the prompts from an automated phone system. The system will read these out when people call our technical support phone number. We use a &lt;a href="https://guides.rubyonrails.org/action_controller_overview.html#controller-callbacks" rel="noopener noreferrer"&gt;callback&lt;/a&gt; to set the list of prompts to read. Whether you're calling for details about consuming our GraphQL endpoints or looking for tips on charging your phone battery, you're going to hear these messages first.&lt;/p&gt;

&lt;p&gt;Our controller action renders that list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechSupportPromptsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_prompts&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= @prompts.join(' - ') %&amp;gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_prompts&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Thank you for calling technical support."&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Call volume is higher than expected."&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Press 9 to receive a call back when an agent is available."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're curious about avoiding a view for rendering, you can read more about it in the &lt;a href="https://guides.rubyonrails.org/layouts_and_rendering.html#using-render-with-inline" rel="noopener noreferrer"&gt;Rails Guides&lt;/a&gt;. Pay attention to the part where it advises against using it in most cases! I'm doing it here for brevity, which I'm now diminishing the value of by spending a paragraph explaining it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making a test call
&lt;/h2&gt;

&lt;p&gt;We want to make sure that this is working as expected, so let's write a test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"displays the prompts in the proper order"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="n"&gt;tech_support_prompt_path&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" - "&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;eq&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"Thank you for calling technical support."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Call volume is higher than expected."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Press 9 to receive a call back when an agent is available."&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test passes, so we ship it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing the call (Party Line)
&lt;/h2&gt;

&lt;p&gt;We're preparing for our next feature. We will display the prompts for our general customer support phone number. After reading the requirements, we see some consistency. When calling tech support or customer support, we end by telling the caller how to request a callback.&lt;/p&gt;

&lt;p&gt;Before starting on this, we're going to prepare the tech support display for some reuse. We want to share the code that adds these common prompts at the end of the interaction. As a starting point, we separate these later prompts to a separate method. We then invoke it in a separate before action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechSupportPromptsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_prompts&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:add_callback_notices&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= @prompts.join(' - ') %&amp;gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_prompts&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Your call is important to us."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_callback_notices&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Call volume is higher than expected."&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Press 9 to receive a call back when an agent is available."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our test passes, because these callbacks are &lt;a href="https://reganchan.ca/blog/ordering-of-filters-in-rails-controllers/" rel="noopener noreferrer"&gt;invoked in the order in which they're written&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When using &lt;code&gt;before_action&lt;/code&gt;, the filters are called in the order that they are defined&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our next step to reuse these is to move these common prompts into a &lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/Concern.html" rel="noopener noreferrer"&gt;concern&lt;/a&gt;. This will allow us to access the prompts in different controllers. Even better, we surmise, the concern itself can define the &lt;code&gt;before_action&lt;/code&gt;. Now all the controller needs to do is include the concern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Callbackable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:add_callback_notices&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_callback_notices&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Call volume is higher than expected."&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Press 9 to receive a call back when an agent is available."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we include the concern in the controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechSupportPromptsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Callbackable&lt;/span&gt;

  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_prompts&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running our test doesn't pass. Instead it raises an exception!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;NoMethodError:
  undefined method &lt;span class="sb"&gt;`&lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="s1"&gt;' for nil
  # ./app/controllers/concerns/callbackable.rb:6:in `block (2 levels) in &amp;lt;module:Callbackable&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The concern is attempting to shovel a message onto a variable that is currently &lt;code&gt;nil&lt;/code&gt;. &lt;code&gt;nil&lt;/code&gt; does not respond to &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;. The &lt;code&gt;@prompts&lt;/code&gt; variable is not initialized at this point. The rule about callback order applies here for the concern as well.&lt;/p&gt;

&lt;p&gt;We include the concern before calling the callback which initializes &lt;code&gt;@prompts&lt;/code&gt;. Therefore &lt;code&gt;@prompts&lt;/code&gt; has no value.&lt;/p&gt;

&lt;p&gt;We can correct for that by including the concern after the callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechSupportPromptsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_prompts&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Callbackable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our test passes again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling our taste into question
&lt;/h2&gt;

&lt;p&gt;That may not sit well with you. Maybe it offends your sensibilities. Perhaps there are other include statements for your controller. You may want to put them all together, right after defining the class for the controller. However, the order of this include is important. It &lt;em&gt;must&lt;/em&gt; be after the other &lt;code&gt;before_action&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Even if you're totally fine with the include being anywhere, it's unclear that it needs to be after the callback. Luckily we have a test that will catch if someone inadvertently moves it. Even with the test, it's not obvious that the ordering is important. We have no idea what the concern does without looking at it. We don't know there's a callback running in it.&lt;/p&gt;

&lt;p&gt;Alternatively, we can modify the concern to not specify the callback. It still defines the &lt;code&gt;add_callback_notices&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Callbackable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_callback_notices&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Call volume is higher than expected."&lt;/span&gt;
    &lt;span class="vi"&gt;@prompts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"Press 9 to receive a call back when an agent is available."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our controller, we have access to the method by including the concern. However, it's again our responsibility to call it. We do so by adding a second callback, just like we did when this method existed in the controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechSupportPromptsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Callbackable&lt;/span&gt;

  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_prompts&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:add_callback_notices&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this scenario, we still need to know the rule about what order Rails applies these callbacks. I personally find it more clear to see the callback defined in the controller anyway. It also gives you the freedom to include the concern anywhere. Perhaps with the rest of the modules you're including! You don't need to be concerned with the order of includes.&lt;/p&gt;

&lt;p&gt;Give me &lt;a href="https://kevinjmurphy.com/about/#elsewhere-on-the-internet" rel="noopener noreferrer"&gt;a call&lt;/a&gt; to let me know what you think.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Preserving Flash Messages in Rails</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Tue, 18 Mar 2025 00:35:56 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/preserving-flash-messages-in-rails-33g3</link>
      <guid>https://dev.to/kevin_j_m/preserving-flash-messages-in-rails-33g3</guid>
      <description>&lt;h2&gt;
  
  
  Flash Sale!
&lt;/h2&gt;

&lt;p&gt;We're offering our best deals on select products for a limited time. We're going to link to this flash sale from many different pages on our site. The call-to-action (CTA) we display at the top of the flash sale page will change based on which page you access the flash sale from.&lt;/p&gt;

&lt;p&gt;We're going to store that text in a flash message. For example, let's say you're looking for the contact information for everyone at the shop. If you click a link on that page to visit the flash sale, we reference that you were just on the contact page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;FlashSale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on?&lt;/span&gt;
      &lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Thanks to you for looking to contact us"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind this sale may be over in...a flash. In between loading the contacts page and clicking the link to view the flash sale, it may be over. If that's the case, we still want to show the CTA. However, we'll send you to the general products page, rather than the flash sale page.&lt;/p&gt;

&lt;p&gt;We sample some of our requests to log information about them. If we're sampling this request, and they're getting redirected because the flash sale is over, we want to log what CTA brought them there. That way we can figure out what page they were on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FlashSalesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;FlashSale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off?&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;AttemptLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log?&lt;/span&gt;
        &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"CTA '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' used to access flash sale after it's over"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;products_path&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;donner_red_hss_starter_kit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;martin_junior_acoustic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;squier_affinity_strat_junior_hss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The view for the products page displays the flash message. We still have the experience of referencing the page they came from. Even if it isn't our best deals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;All Products&amp;lt;/h1&amp;gt;

&amp;lt;% &lt;span class="k"&gt;if &lt;/span&gt;flash[:sale].present? %&amp;gt;
  &amp;lt;p&amp;gt; &amp;lt;%&lt;span class="o"&gt;=&lt;/span&gt; flash[:sale] %&amp;gt; &amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gone In A Flash
&lt;/h2&gt;

&lt;p&gt;Let's confirm this behavior by writing some &lt;a href="https://guides.rubyonrails.org/testing.html#system-testing" rel="noopener noreferrer"&gt;system tests&lt;/a&gt;. We'll start by verifying the redirect when the flash sale ends before they can view the deals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"redirects to the products page with the flash message when the flash sale has ended"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FlashSale&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:on?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;contacts_path&lt;/span&gt;

  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FlashSale&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:off?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;click_link&lt;/span&gt; &lt;span class="s2"&gt;"View Deals"&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;have_selector&lt;/span&gt; &lt;span class="s2"&gt;"h1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"All Products"&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;have_content&lt;/span&gt; &lt;span class="s2"&gt;"Thanks to you for looking to contact us"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our test passes!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⇒ rspec spec/system/flash_sales_spec.rb:8
Run options: include &lt;span class="o"&gt;{&lt;/span&gt;:locations&lt;span class="o"&gt;=&amp;gt;{&lt;/span&gt;&lt;span class="s2"&gt;"./spec/system/flash_sales_spec.rb"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;[&lt;/span&gt;8]&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;

Finished &lt;span class="k"&gt;in &lt;/span&gt;0.12213 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 1.89 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
1 example, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we'll iterate on this by verifying the same user behavior when we log the CTA.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"logs the flash message when the flash sale has ended and the log sampler wants the message"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FlashSale&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:on?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;contacts_path&lt;/span&gt;

  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FlashSale&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:off?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AttemptLogger&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:log?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;click_link&lt;/span&gt; &lt;span class="s2"&gt;"View Deals"&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;have_selector&lt;/span&gt; &lt;span class="s2"&gt;"h1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"All Products"&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;have_content&lt;/span&gt; &lt;span class="s2"&gt;"Thanks to you for looking to contact us"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, we have a different result here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⇒ rspec spec/system/flash_sales_spec.rb:17
Run options: include &lt;span class="o"&gt;{&lt;/span&gt;:locations&lt;span class="o"&gt;=&amp;gt;{&lt;/span&gt;&lt;span class="s2"&gt;"./spec/system/flash_sales_spec.rb"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;[&lt;/span&gt;17]&lt;span class="o"&gt;}}&lt;/span&gt;
F

Failures:

  1&lt;span class="o"&gt;)&lt;/span&gt; Flash Sale logs the flash message when the flash sale has ended and the log sampler wants the message
     Failure/Error: expect&lt;span class="o"&gt;(&lt;/span&gt;page&lt;span class="o"&gt;)&lt;/span&gt;.to have_content &lt;span class="s2"&gt;"As thanks to you for looking to contact us"&lt;/span&gt;
       expected to find text &lt;span class="s2"&gt;"As thanks to you for looking to contact us"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"All Products"&lt;/span&gt;
     &lt;span class="c"&gt;# ./spec/system/flash_sales_spec.rb:26:in `block (2 levels) in &amp;lt;top (required)&amp;gt;'&lt;/span&gt;

Finished &lt;span class="k"&gt;in &lt;/span&gt;0.21189 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 1.98 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
1 example, 1 failure

Failed examples:

rspec ./spec/system/flash_sales_spec.rb:17 &lt;span class="c"&gt;# Flash Sale logs the flash message when the flash sale has ended and the log sampler wants the message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our flash message is nowhere to be found. For an almost identical test. After &lt;a href="https://kevinjmurphy.com/posts/searching-for-a-reason/" rel="noopener noreferrer"&gt;searching&lt;/a&gt; through the documentation, we find &lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/Flash.html" rel="noopener noreferrer"&gt;this&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Anything you place in the flash will be exposed to the very next action and then cleared out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That leaves us even &lt;em&gt;more&lt;/em&gt; confused. Our first test passed, even though we went from the flash sale endpoint to the products endpoint. Is that two actions? And if not, why did it clear it before exposing it to the user when we log the message?&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing Source In A Flash
&lt;/h2&gt;

&lt;p&gt;That documentation tells us where to find the implementation for the flash: &lt;code&gt;actionpack/lib/action_dispatch/middleware/flash.rb&lt;/code&gt;. We can use &lt;a href="https://bundler.io/man/bundle-open.1.html" rel="noopener noreferrer"&gt;bundle open&lt;/a&gt; to explore the source code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle open actionpack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We navigate to the flash file and look around. In there we see a &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L71" rel="noopener noreferrer"&gt;method&lt;/a&gt; called &lt;code&gt;commit_flash&lt;/code&gt;. Based only on the name, we guess that might have something to do with keeping the values of the flash. In one of our tests, we aren't keeping the values. Did we find a bug in Rails?&lt;/p&gt;

&lt;p&gt;We drop a breakpoint in the method, changing Rails' source code (temporarily) for our application. Then we can run our tests again to investigate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;commit_flash&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="nb"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;irb&lt;/span&gt; &lt;span class="c1"&gt;# Added by us!&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;

   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flash_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
     &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_session_value&lt;/span&gt;
     &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;

   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loaded?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
     &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Flash In The Pan
&lt;/h2&gt;

&lt;p&gt;We observe the following behavior when running our tests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When we don't log the message in the &lt;code&gt;FlashSalesController#index&lt;/code&gt; method, the value of &lt;code&gt;flash_hash&lt;/code&gt; in &lt;code&gt;commit_flash&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt; at the end of processing the flash sale index action.&lt;/li&gt;
&lt;li&gt;When we &lt;em&gt;do&lt;/em&gt; log the message, &lt;code&gt;flash_hash&lt;/code&gt; has a value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That still seems backwards from the behavior we're seeing. When we try to show the flash message on the products page, it's not there when we log the message. But that's the scenario where &lt;code&gt;flash_hash&lt;/code&gt; has a value. Now we need to take a detour to understand where this &lt;code&gt;flash_hash&lt;/code&gt; comes from. We find it's a &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L67" rel="noopener noreferrer"&gt;method&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flash_hash&lt;/span&gt;
  &lt;span class="n"&gt;get_header&lt;/span&gt; &lt;span class="no"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;KEY&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That leads us to wonder what can &lt;em&gt;set&lt;/em&gt; that header. And we see &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L63" rel="noopener noreferrer"&gt;just above&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;set_header&lt;/span&gt; &lt;span class="no"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flash&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As our eyes continue to look up, we find a &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L57" rel="noopener noreferrer"&gt;method&lt;/a&gt; that calls &lt;code&gt;flash=&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flash&lt;/span&gt;
  &lt;span class="n"&gt;flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;flash&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flash&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FlashHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_session_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feels a bit circular in the beginning. We look to see if &lt;code&gt;flash_hash&lt;/code&gt; is set and return that if so. But we're looking here to see how &lt;code&gt;flash_hash&lt;/code&gt; could get a value. It is the &lt;em&gt;last&lt;/em&gt; line that's setting the header that &lt;code&gt;flash_hash&lt;/code&gt; uses. So, that's called when &lt;code&gt;flash_hash&lt;/code&gt; doesn't have a value. The flash object is grabbed from the session and converted to a &lt;code&gt;FlashHash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That will store that &lt;code&gt;FlashHash&lt;/code&gt; instance in the header. We can then access that by calling the &lt;code&gt;flash_hash&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;We also see a &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L54C9-L54C86" rel="noopener noreferrer"&gt;comment&lt;/a&gt; above the &lt;code&gt;flash&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Access the contents of the flash. Returns a ActionDispatch::Flash::FlashHash.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This&lt;/em&gt; is what we're calling when we call &lt;code&gt;flash[:sale]&lt;/code&gt; in our logging message. &lt;code&gt;FlashHash&lt;/code&gt; defines the &lt;code&gt;[]&lt;/code&gt; &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L169" rel="noopener noreferrer"&gt;method&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flash Back
&lt;/h2&gt;

&lt;p&gt;Let's revisit each conditional in &lt;code&gt;commit_flash&lt;/code&gt; and explore how they relate to our test scenarios.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flash_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_session_value&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this is called after the &lt;code&gt;FlashSalesController#index&lt;/code&gt; action and we &lt;em&gt;don't&lt;/em&gt; log a message, &lt;code&gt;flash_hash&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;. That's because we never call &lt;code&gt;flash&lt;/code&gt;, which would set the header that &lt;code&gt;flash_hash&lt;/code&gt; reads from. &lt;code&gt;flash&lt;/code&gt; never gets called, so the header never gets set. We never enter the conditional block in that scenario.&lt;/p&gt;

&lt;p&gt;However, when we do log the message, the header is there, and &lt;code&gt;flash_hash&lt;/code&gt; is present. That causes the contents of &lt;code&gt;flash_hash&lt;/code&gt; to be converted to a session value and stored back in the session.&lt;/p&gt;

&lt;p&gt;Converting a &lt;code&gt;FlashHash&lt;/code&gt; instance to its &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L143" rel="noopener noreferrer"&gt;session value&lt;/a&gt; does the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_session_value&lt;/span&gt;
  &lt;span class="n"&gt;flashes_to_keep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@flashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;except&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="vi"&gt;@discard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flashes_to_keep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"discard"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s2"&gt;"flashes"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;flashes_to_keep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we make a flash &lt;em&gt;from&lt;/em&gt; the session, we send &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L135" rel="noopener noreferrer"&gt;all the keys&lt;/a&gt; as &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L150" rel="noopener noreferrer"&gt;discard values&lt;/a&gt;. As a result, when we're converting back &lt;em&gt;to&lt;/em&gt; the session, &lt;code&gt;flashes_to_keep&lt;/code&gt; is empty. This returns &lt;code&gt;nil&lt;/code&gt;. And that's what we set &lt;code&gt;session["flash"]&lt;/code&gt; to.&lt;/p&gt;

&lt;p&gt;The second conditional in &lt;code&gt;commit_flash&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loaded?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
  &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we log the message, we load the session by calling &lt;code&gt;flash[:sale]&lt;/code&gt;. That's because we're reading from the session to pass a value to &lt;code&gt;FlashHash.from_session_value&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flash&lt;/span&gt;
  &lt;span class="n"&gt;flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flash_hash&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;flash&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flash&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FlashHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_session_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accessing the session &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/request/session.rb#L115" rel="noopener noreferrer"&gt;loads the session for reading&lt;/a&gt;, which through the &lt;code&gt;load!&lt;/code&gt; &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/request/session.rb#L256" rel="noopener noreferrer"&gt;method&lt;/a&gt; sets the &lt;code&gt;@loaded&lt;/code&gt; &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/request/session.rb#L280" rel="noopener noreferrer"&gt;instance variable&lt;/a&gt; that is used to &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/request/session.rb#L236" rel="noopener noreferrer"&gt;determine if a session is loaded&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because the session is loaded, it has a flash key, &lt;strong&gt;and&lt;/strong&gt; the value is &lt;code&gt;nil&lt;/code&gt; (because converting it to a session value discarded all the keys), then we delete flash from the session entirely.&lt;/p&gt;

&lt;p&gt;If we never invoke &lt;code&gt;flash&lt;/code&gt; before redirecting, we never load it from the session, never convert it back to a session value, and never delete it from the session. It remains available when processing the &lt;code&gt;ProductsController#index&lt;/code&gt; action.&lt;/p&gt;

&lt;p&gt;When we do call &lt;code&gt;flash&lt;/code&gt; to write the log message, we &lt;em&gt;do&lt;/em&gt; delete it from the session, so &lt;code&gt;ProductsController#index&lt;/code&gt; doesn't have it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flash Forward
&lt;/h2&gt;

&lt;p&gt;This is great that we understand &lt;em&gt;why&lt;/em&gt; this is happening. But none of this actually fixes our problem. We want our tests to pass. We &lt;em&gt;don't&lt;/em&gt; want the flash removed from the session. We want the &lt;code&gt;:sale&lt;/code&gt; key to still exist.&lt;/p&gt;

&lt;p&gt;Recall the conditions by which the flash is deleted from the session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loaded?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
  &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"flash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we log the message, we need to load the session to access the flash, so we can't get around that. However, if the value of &lt;code&gt;session["flash"]&lt;/code&gt; wasn't &lt;code&gt;nil&lt;/code&gt;, that'd be enough to preserve it.&lt;/p&gt;

&lt;p&gt;The reason &lt;code&gt;session["flash"]&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt; is because we didn't have any flashes to keep when converting the object to a session value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_session_value&lt;/span&gt;
  &lt;span class="n"&gt;flashes_to_keep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@flashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;except&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="vi"&gt;@discard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flashes_to_keep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"discard"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s2"&gt;"flashes"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;flashes_to_keep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do have an option to explicitly keep the entire flash (or a particular key). It's the aptly-named &lt;a href="https://github.com/rails/rails/blob/v8.0.1/actionpack/lib/action_dispatch/middleware/flash.rb#L250" rel="noopener noreferrer"&gt;keep&lt;/a&gt; method. That removes keys from the &lt;code&gt;@discard&lt;/code&gt; set, so they'll be retained.&lt;/p&gt;

&lt;p&gt;Let's update our flash sale controller to use this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FlashSalesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;FlashSale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off?&lt;/span&gt;
       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;AttemptLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log?&lt;/span&gt;
         &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"CTA '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' used to access flash sale after it's over"&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;        &lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;end&lt;/span&gt;

       &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;products_path&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
     &lt;span class="k"&gt;end&lt;/span&gt;

     &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="n"&gt;donner_red_hss_starter_kit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;martin_junior_acoustic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;squier_affinity_strat_junior_hss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that one call to &lt;code&gt;keep&lt;/code&gt;, both of our tests pass. Whether we log the flash CTA or not, it's available for the &lt;code&gt;ProductsController#index&lt;/code&gt; action to display.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Illuminating Flash
&lt;/h2&gt;

&lt;p&gt;Our goal was to preserve a flash message. Instead, it seems we learned more about the conditions by which we destroy the flash message. That allowed us to back up and explore options to fail that conditional.&lt;/p&gt;

&lt;p&gt;The Rails source code &lt;em&gt;is&lt;/em&gt; vast and dense, but it's also readily available. We discovered the reason behind the behavior we saw &lt;strong&gt;and&lt;/strong&gt; a solution by being willing to explore it. Consider &lt;code&gt;bundle open&lt;/code&gt;ing the next dependency you're confused or interested by. See what you can learn. It may take a while, or you may have your answer in a flash.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>What Is It (in Ruby 3.4)?</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Mon, 13 Jan 2025 20:12:03 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/what-is-it-in-ruby-34-hlf</link>
      <guid>https://dev.to/kevin_j_m/what-is-it-in-ruby-34-hlf</guid>
      <description>&lt;p&gt;This post may appear to be about new functionality in Ruby. However, it may instead be an attempt to write an article with the worst possible SEO.&lt;/p&gt;

&lt;p&gt;Anyway, hit it.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/mTJ542VTMEE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is It?
&lt;/h2&gt;

&lt;p&gt;As of Ruby 3.4, &lt;code&gt;it&lt;/code&gt; is another way to refer to the first parameter in a block. This may be more obvious by way of an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"What is it?"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;" It's it"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&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;The block we pass to &lt;code&gt;map&lt;/code&gt; does not define a name for the parameter to represent the variable we're manipulating in each iteration. Instead, Ruby knows &lt;code&gt;it&lt;/code&gt; refers to that variable.&lt;/p&gt;

&lt;p&gt;Starting in Ruby 2.7, we had access to numbered parameters in a block with an underscore. We can write that same example using numbered parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"What is it?"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;" It's it"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&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;Before Ruby 2.7, we would define the name of a variable in a block, which we can still do with more recent versions of Ruby.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"What is it?"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;" It's it"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"What is it? It's it"&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;h2&gt;
  
  
  Handling nested calls
&lt;/h2&gt;

&lt;p&gt;Should you have nested blocks that refer to &lt;code&gt;it&lt;/code&gt;, Ruby will take &lt;code&gt;it&lt;/code&gt; to refer to the parameter of the innermost block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"You want it all, but you can't have it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Yeah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"It's in your face, but you can't grab it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Yeah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Yeah"&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;it&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="nf"&gt;map&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="nf"&gt;upcase&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&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="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"You want it all, but you can't have it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"YEAH, YEAH, YEAH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"It's in your face, but you can't grab it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"YEAH, YEAH, YEAH"&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;The same is not possible with numbered parameters. An exception is raised due to the ambiguity of what &lt;code&gt;_1&lt;/code&gt; refers to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"You want it all, but you can't have it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Yeah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"It's in your face, but you can't grab it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Yeah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Yeah"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;syntax&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="s2"&gt;"Yeah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="o"&gt;^~&lt;/span&gt; &lt;span class="n"&gt;numbered&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outer&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Method names in scope
&lt;/h2&gt;

&lt;p&gt;Ruby also will infer that &lt;code&gt;it&lt;/code&gt; refers to the block parameter should there be a method called &lt;code&gt;it&lt;/code&gt; in scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This isn't it"&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"Can you feel it, see it, hear it today?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"If you can't, then it doesn't matter anyway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"IT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"Can you feel IT, see IT, hear IT today?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"If you can't, then IT doesn't matter anyway"&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;h2&gt;
  
  
  Variable names in scope
&lt;/h2&gt;

&lt;p&gt;However, if there is a &lt;strong&gt;variable&lt;/strong&gt; defined as &lt;code&gt;it&lt;/code&gt;, that will take precedence over the block parameter in the block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This isn't it"&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"You will never understand it, 'cause it happens too fast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"And it feels so good, it's like walking on glass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"IT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"This isn't IT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"This isn't IT"&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;h2&gt;
  
  
  You've got to share it, so you dare it
&lt;/h2&gt;

&lt;p&gt;If you want to learn more about this new feature, check out the &lt;a href="https://bugs.ruby-lang.org/issues/18980" rel="noopener noreferrer"&gt;proposal&lt;/a&gt; and the &lt;a href="https://docs.ruby-lang.org/en/3.4/Proc.html#class-Proc-label-it" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. Read the &lt;a href="https://rubyreferences.github.io/rubychanges/3.4.html" rel="noopener noreferrer"&gt;Ruby 3.4 changelog&lt;/a&gt; to discover all the fun available in Ruby 3.4.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can touch it, smell it, taste it, so sweet&lt;br&gt;&lt;br&gt;
But it makes no difference 'cause it knocks you off your feet  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it.&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>RSpec Stubs The Object In Memory</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Mon, 16 Sep 2024 12:05:00 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/rspec-stubs-the-object-in-memory-d8</link>
      <guid>https://dev.to/kevin_j_m/rspec-stubs-the-object-in-memory-d8</guid>
      <description>&lt;h2&gt;
  
  
  Publish or Perish
&lt;/h2&gt;

&lt;p&gt;Let's say we've been sitting on a handful of blog posts that are ready to publish, but we haven't released yet. This is a fictional story that in no way mirrors reality of any particular writer. Certainly not the author writing this post now. In a spirit of inspiration, we decide to write a small class to publish all these posts that exist in our system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DraftPost&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish_all&lt;/span&gt;
    &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than actually publish them, we've successfully procrastinated. We have a &lt;em&gt;way&lt;/em&gt; to publish them, eventually. While we're busy not actually publishing them, let's test this method works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prolonged Publication Procrastination
&lt;/h2&gt;

&lt;p&gt;We want to verify that we send the &lt;code&gt;publish&lt;/code&gt; message to each of these posts. We don't actually want to execute the &lt;code&gt;publish&lt;/code&gt; method. Perhaps that integrates with a third-party API. We don't want to manage &lt;a href="https://kevinjmurphy.com/posts/testing-dependencies/" rel="noopener noreferrer"&gt;testing that interaction&lt;/a&gt; here.&lt;/p&gt;

&lt;p&gt;Instead, we'll stub out the response using &lt;a href="https://rspec.info/features/3-12/rspec-mocks/" rel="noopener noreferrer"&gt;RSpec mocks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With that goal, we write our test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"publishes all draft posts"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;draft: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;DraftPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish_all&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&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;have_received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test &lt;em&gt;fails&lt;/em&gt;. We put a breakpoint in our test. We confirm that a post with the same database ID as &lt;code&gt;draft&lt;/code&gt; &lt;strong&gt;does&lt;/strong&gt; get the &lt;code&gt;publish&lt;/code&gt; method called on it.&lt;/p&gt;

&lt;p&gt;Even though these objects pass the equality check, the issue is that they are &lt;em&gt;not&lt;/em&gt; the same object in memory. Compare the &lt;code&gt;object_id&lt;/code&gt; of the &lt;code&gt;draft&lt;/code&gt; object to the object pulled out of the database. They're different. The post published in &lt;code&gt;DraftPost.publish_all&lt;/code&gt; does not have the stub applied to it.&lt;/p&gt;

&lt;p&gt;The stub operates on that &lt;code&gt;draft&lt;/code&gt; object in memory, and only that object. Even though the other object is equal to the &lt;code&gt;draft&lt;/code&gt; object, it is not the &lt;em&gt;same&lt;/em&gt; as the &lt;code&gt;draft&lt;/code&gt; object. Because of that, our assertion does not pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Any Instance Of
&lt;/h2&gt;

&lt;p&gt;At this point, we may try to instead test what's returned from &lt;code&gt;publish_all&lt;/code&gt; while still not having the &lt;code&gt;publish&lt;/code&gt; method called on the posts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"publishes all draft posts"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;draft: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;allow_any_instance_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"called publish"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DraftPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish_all&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;eq&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"called publish"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our test now passes. However, we have some drawbacks. First off, &lt;a href="https://rspec.info/features/3-12/rspec-mocks/working-with-legacy-code/any-instance/" rel="noopener noreferrer"&gt;RSpec itself&lt;/a&gt; considers using &lt;code&gt;allow_any_instance_of&lt;/code&gt; to be suspect.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This feature is sometimes useful when working with legacy code, though in general we discourage its use for a number of reasons&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, this doesn't mean it's &lt;em&gt;wrong&lt;/em&gt;. But, I'd argue to only use it after carefully considering other options.&lt;/p&gt;

&lt;p&gt;The other thing we changed here is our testing philosophy. Now we're testing what's returned by the method, rather than what the method does. And we did this by stubbing out what any Post should return when we call the &lt;code&gt;publish&lt;/code&gt; method on it.&lt;/p&gt;

&lt;p&gt;That again may be fine, but it may not test that we publish all the draft posts, which is our goal. Instead, it confirms what the &lt;code&gt;publish_all&lt;/code&gt; method returns.&lt;/p&gt;

&lt;p&gt;This may be hyperbolic, but if we changed the implementation to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DraftPost&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish_all&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"called publish"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our test that asserts this method is calling &lt;code&gt;publish&lt;/code&gt; still passes, but we're definitely not publishing any posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change the Implementation
&lt;/h2&gt;

&lt;p&gt;Let's change &lt;code&gt;publish_all&lt;/code&gt; so we pass all the posts to publish into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DraftPost&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use the RSpec mock as we want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"publishes all draft posts"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;draft: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;DraftPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish_all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&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;have_received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test passes, and as a bonus, we don't need to access the database in our test anymore. What a speed improvement!&lt;/p&gt;

&lt;p&gt;Once again, this choice comes with some careful consideration. First off, modifying the implementation to satisfy the way we want to test it may be undesirable. I do want to point out that I don't think this is true generally. I do believe in using your tests as your first consumer of your code. If something is tough to test, it may be hard to use or confusing to understand. There's value in receiving that signal and deciding to change the code's design or structure in that case.&lt;/p&gt;

&lt;p&gt;Stepping back here, passing in the posts makes the name of this class pretty meaningless. This should only work on draft posts. However, we can pass any kind of post (really, anything that responds to &lt;code&gt;publish&lt;/code&gt;) and this will still work.&lt;/p&gt;

&lt;p&gt;Maybe we want something that'll generically publish any kind of post. If so, we probably shouldn't have that inside a &lt;code&gt;DraftPost&lt;/code&gt; class. If this should only operate on draft posts, then this may not be the choice we want to make.&lt;/p&gt;

&lt;p&gt;Passing in the posts allows our stub to work. Passing in the posts may allow us to achieve some purity of using dependency injection. Passing in the posts may make this method less usable. Enough to the point where perhaps it shouldn't exist at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stub the DB Interaction
&lt;/h2&gt;

&lt;p&gt;We still want to use the mock. We don't want to use RSpec features that the core team themselves recommend against. We don't want to change the implementation. It turns out we like the benefit of not needing to access the database. We can achieve all those goals by &lt;em&gt;also&lt;/em&gt; stubbing out the database call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"publishes all draft posts"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;draft: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:draft&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
 &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="no"&gt;DraftPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish_all&lt;/span&gt;

 &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&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;have_received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally Publishing The Post
&lt;/h2&gt;

&lt;p&gt;We confirm that we send the right message (&lt;code&gt;publish&lt;/code&gt;) without invoking its implementation. We do give up some confidence in that we don't know for sure that &lt;code&gt;Post.draft&lt;/code&gt; works how we expect. We can regain that confidence by having other tests specific for that method.&lt;/p&gt;

&lt;p&gt;One could argue this gives us more flexibility. If the implementation of &lt;code&gt;Post.draft&lt;/code&gt; changes, this test will continue to work. It can get posts from a database or third-party API. Our method doesn't care.&lt;/p&gt;

&lt;p&gt;On the flip side, one could also argue that this test is tying us to a particular implementation. If &lt;code&gt;DraftPost.publish_all&lt;/code&gt; changed to instead use &lt;code&gt;Post.where(draft: true)&lt;/code&gt;, then this test will no longer work. Even though using the scope or using where will retrieve the same records from the database.&lt;/p&gt;

&lt;p&gt;Unfortunately, there is no &lt;em&gt;right&lt;/em&gt; answer. We must consider the trade-offs we're willing to make to achieve our goal. What ✨magic✨ you're interested in maintaining is up to you.&lt;/p&gt;

&lt;p&gt;Keep in mind that RSpec mocks only work on that particular object in memory. If the implementation pulls the same record fresh out of the database, the mock will not apply. When using RSpec mocks in your tests, objects in memory may be farther than they appear.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>testing</category>
    </item>
    <item>
      <title>Access Request Headers in a Rails Controller</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Tue, 16 Jul 2024 23:43:47 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/access-request-headers-in-a-rails-controller-3b27</link>
      <guid>https://dev.to/kevin_j_m/access-request-headers-in-a-rails-controller-3b27</guid>
      <description>&lt;h2&gt;
  
  
  Heads Up
&lt;/h2&gt;

&lt;p&gt;A coworker presented a failing request spec. They asked if they were passing headers incorrectly in the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"reports to be a teapot when asked to brew coffee"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"X-COMMAND"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"brew coffee"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="n"&gt;drinks_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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;eq&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They wrote the test exactly like I'd &lt;a href="https://rspec.info/features/6-0/rspec-rails/request-specs/request-spec/" rel="noopener noreferrer"&gt;expect&lt;/a&gt;. But, rather than providing the 418, a 200 OK was the status code. I then looked at the controller this request spec was accessing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"X-COMMAND"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"brew coffee"&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="vi"&gt;@drinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Drink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing &lt;em&gt;obvious&lt;/em&gt; caught my attention. But now that I'd been effectively &lt;a href="https://xkcd.com/356/" rel="noopener noreferrer"&gt;nerd sniped&lt;/a&gt;, I had to figure out what was going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heading In For a Closer Look
&lt;/h2&gt;

&lt;p&gt;I added a breakpoint inside the controller to inspect the headers when the test was running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"X-Frame-Options"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"SAMEORIGIN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"X-XSS-Protection"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"X-Content-Type-Options"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"nosniff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"X-Download-Options"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"noopen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"X-Permitted-Cross-Domain-Policies"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"Referrer-Policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"strict-origin-when-cross-origin"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, given the failing test, the &lt;code&gt;X-COMMAND&lt;/code&gt; header was nowhere to be found. But luckily, they did seem familiar to me. They looked to be Rails' &lt;a href="https://github.com/rails/rails/blob/f1aa436d738af1852b610189aeb93a5609bfe3b0/actionpack/lib/action_dispatch/railtie.rb#L30" rel="noopener noreferrer"&gt;default headers&lt;/a&gt;. But those &lt;a href="https://edgeguides.rubyonrails.org/configuring.html#config-action-dispatch-default-headers" rel="noopener noreferrer"&gt;default headers&lt;/a&gt; are for the &lt;strong&gt;response&lt;/strong&gt;, not the &lt;em&gt;request&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I still had my console session with my breakpoint, so I asked what kind of headers we were interacting with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Header&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed we were dealing with the response, not request, headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heads Down
&lt;/h2&gt;

&lt;p&gt;I needed to trace my way backwards from what I have or know. I asked what defines the headers method by asking for the &lt;a href="https://ruby-doc.org/3.2.1/Method.html#method-i-source_location" rel="noopener noreferrer"&gt;source location&lt;/a&gt;. That'll tell me the file and line number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mo"&gt;003&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;source_location&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;".../gems/actionpack-7.0.3.1/lib/action_controller/metal.rb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;147&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/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal.rb#L147" rel="noopener noreferrer"&gt;That line&lt;/a&gt; shows &lt;code&gt;headers&lt;/code&gt; delegated to an &lt;a href="https://guides.rubyonrails.org/active_support_core_extensions.html#internal-attributes" rel="noopener noreferrer"&gt;internal attribute&lt;/a&gt; &lt;code&gt;@_response&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:content_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:media_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"@_response"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That internal attribute is accessible in the controller by calling &lt;code&gt;response&lt;/code&gt;. We can see that from the &lt;code&gt;attr_internal&lt;/code&gt; definition on &lt;a href="https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal.rb#L145" rel="noopener noreferrer"&gt;line 145&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;attr_internal&lt;/span&gt; &lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;response&lt;/code&gt; isn't the ONLY internal attribute on that line though. There's ALSO a &lt;code&gt;request&lt;/code&gt;. In our console, let's see what that request is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mo"&gt;004&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That class also &lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-headers" rel="noopener noreferrer"&gt;responds to&lt;/a&gt; &lt;code&gt;headers&lt;/code&gt;, providing the &lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/Http/Headers.html" rel="noopener noreferrer"&gt;request headers&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heading In For The Close
&lt;/h2&gt;

&lt;p&gt;The change to get our test to pass is small. We don't want the response headers, which is what the &lt;code&gt;headers&lt;/code&gt; variable is. We need the request headers, which are accessible at &lt;code&gt;request.headers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"X-COMMAND"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"brew coffee"&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="vi"&gt;@drinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Drink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we're accessing the headers of the &lt;strong&gt;request&lt;/strong&gt; our test passes.&lt;/p&gt;

&lt;p&gt;Naming is hard. Asking for a controller's headers could be either the request or the response headers. Turns out, Rails will give you the response headers. To access the request headers, explicitly ask for them from the request object.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Four Things To Take Away From RailsConf 2024</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Tue, 14 May 2024 21:19:46 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/four-things-to-take-away-from-railsconf-2024-45o9</link>
      <guid>https://dev.to/kevin_j_m/four-things-to-take-away-from-railsconf-2024-45o9</guid>
      <description>&lt;h2&gt;
  
  
  RailsConf 2024
&lt;/h2&gt;

&lt;p&gt;Now that RailsConf 2024 is behind us, I want to share some key takeaways that I left Detroit with. I also have a &lt;a href="https://kevinjmurphy.com/posts/railsconf-2024-recap/"&gt;full recap&lt;/a&gt; available of what I saw while I was there.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. #RubyFriends Is More Than A Hashtag
&lt;/h2&gt;

&lt;p&gt;I cannot overstate the importance of your professional network. In the Ruby community, we talk a lot about Ruby Friends. How you build and cultivate that group is up to you. Conferences have been a main driver of that for&lt;br&gt;
me.&lt;/p&gt;

&lt;p&gt;The reason I was on the &lt;a href="https://kevinjmurphy.com/posts/railsconf-2024-program-committee/"&gt;Program Committee&lt;/a&gt; is because of the conference relationships I’ve built. I congratulated Ufuk on joining the Ruby Central board at RubyConf last year. I (sincerely) told him to keep me in mind if I can help in the future. He immediately said, "program committee?" And then he remembered when putting together RailsConf's committee. I'm thankful to him for the opportunity.&lt;/p&gt;

&lt;p&gt;The conference is an excuse to connect with my friends before, during, and after the actual event. I can check in on speaker friends throughout the process. This year I developed meaningful relationships with fellow Program Committee members. That persists throughout the conference. And after we can reminisce and recall all those moments.&lt;/p&gt;

&lt;p&gt;And sure, hopefully I'm being helpful to my Ruby Friends, much like I know they'll help me. But really what I appreciate most is having something to reach out to them about.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ruby ❤️ Rails
&lt;/h2&gt;

&lt;p&gt;Rails benefits greatly from the tooling and improvements of Ruby itself. John Hawthorn demonstrated how Vernier can help profile Ruby code. That benefits all our Rails applications. Aaron Patterson described existing and future changes in Ruby. Each one improves our Rails applications. To take advantage of them, we need to stay up-to-date with those changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Build with Rails? Build With Rails.
&lt;/h2&gt;

&lt;p&gt;The theme at RailsConf was "Building with Rails". We heard from people who built businesses with Rails. Built teams with Rails. Built technical advances with Rails. Built careers with Rails. Nadia talked to us about The StoryGraph. I took away an appreciation of how many risks and unknowns you take on starting and running a business. By keeping the tech simple and known, you can allocate your risk budget elsewhere.&lt;/p&gt;

&lt;p&gt;Irina also surveyed various earlier-stage founders on their use of Rails. Hearing what they appreciate about the ecosystem was reaffirming. Learning what they're missing was informative and helpful.&lt;/p&gt;

&lt;p&gt;Marco also called attention to Rails-adjacent projects, like Turbo and Stimulus Reflex. His talk provided a great history lesson on these projects. He gave exciting introductions to new tooling. He shared learning resources. Marco gave many options for how we can leverage this all in our Rails applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. It's (Almost) Over
&lt;/h2&gt;

&lt;p&gt;Yes, RailsConf 2024 is definitely over. However, RailsConf itself is also almost over. RailsConf 2025 is the &lt;a href="https://rubycentral.org/news/anewearforrubycentralevents/"&gt;last planned RailsConf&lt;/a&gt;. Ruby Central will continue supporting RubyConf.&lt;/p&gt;

&lt;p&gt;This will free RubyCentral up to focus their efforts. There are many other important benefits they provide the community. I know for a long time I thought of Ruby Central as the group the puts on the conferences. I took for granted the work they do. I didn’t consider how vital they are. They support the bedrock tooling for our community: Bundler and RubyGems. I hope this shift will help others be more awareness to the rest of the ways Ruby Central supports Ruby.&lt;/p&gt;

&lt;p&gt;But we're not done yet. Ruby Central needs your help shaping RailsConf 2025. Fill out &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSeQIVh1Uje6LBHFbkcSgVMliMPUVKt-kVloHAQ8OuVkvYoopw/viewform?ref=rubycentral.org"&gt;this survey&lt;/a&gt; to tell them where you're excited to see RailsConf. That's right, there isn't a date or location yet. Wherever it is, I hope I see you there.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Making a (Sidekiq) Batch Recipe</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Mon, 15 Apr 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/making-a-sidekiq-batch-recipe-n29</link>
      <guid>https://dev.to/kevin_j_m/making-a-sidekiq-batch-recipe-n29</guid>
      <description>&lt;h2&gt;
  
  
  The Right Number of Cooks in the Kitchen
&lt;/h2&gt;

&lt;p&gt;Today we're going to make a stew. The recipe has three steps that can all run independently. But when they're done, their output needs to come together to finish the stew.&lt;/p&gt;

&lt;p&gt;We'll set each step up as a separate Sidekiq job. The details of each step aren't important for this demonstration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetRawVeggiesWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetBaconWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetCupOfSoupWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can enqueue these to run by themselves no problem. However, we need to know when they're all done so we can finish our recipe. We can group these together using a &lt;a href="https://sidekiq.org/products/pro.html"&gt;Sidekiq Pro&lt;/a&gt; feature: batches.&lt;/p&gt;

&lt;p&gt;We'll write a series of RSpec tests to explore how to use batches to make our recipe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Kitchen Progress
&lt;/h2&gt;

&lt;p&gt;We'll start by creating a batch, and adding our recipe steps to it as jobs. Just like a Sidekiq job has a &lt;code&gt;jid&lt;/code&gt; (job ID), a batch has a &lt;code&gt;bid&lt;/code&gt; (batch ID). We can use that &lt;code&gt;bid&lt;/code&gt; to check on the batch's status thanks to the aptly-named &lt;code&gt;Batch::Status&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"adds jobs to a batch"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;recipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;GetRawVeggiesWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
    &lt;span class="no"&gt;GetBaconWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
    &lt;span class="no"&gt;GetCupOfSoupWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;batch_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_status&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;have_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;total: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;pending: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;complete?: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After making our batch and checking on the status, we see there are three jobs, but none of them ran. That is because this is in a test, and we're using the &lt;a href="https://github.com/sidekiq/sidekiq/wiki/Testing#testing-worker-queueing-fake"&gt;Sidekiq fake adapter&lt;/a&gt; by default.&lt;/p&gt;

&lt;p&gt;Let's update our test to run the batch jobs &lt;a href="https://github.com/sidekiq/sidekiq/wiki/Testing#testing-workers-inline"&gt;inline&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"runs the workers in the batch in inline mode"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;recipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inline!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;GetRawVeggiesWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
      &lt;span class="no"&gt;GetBaconWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
      &lt;span class="no"&gt;GetCupOfSoupWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;batch_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_status&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;have_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;pending: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;complete?: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;total: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jobs executed and the batch is complete. Note that the total number of jobs is five, even though we enqueued three jobs. Interesting! Let's leave that aside as we explore what to do now that we have a batch that completes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling (back) next steps
&lt;/h2&gt;

&lt;p&gt;The reason we created a batch was so we could do something with the results of our jobs when they finished. We wanted them to run independently, so we can take advantage of parallel execution. But we need to have the system take action once they're all done.&lt;/p&gt;

&lt;p&gt;Sidekiq batches respond to &lt;a href="https://github.com/sidekiq/sidekiq/wiki/Batches#callbacks"&gt;callbacks&lt;/a&gt;. We'll focus on two of the three available callbacks: complete and success.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActingLessonCallback&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; went to Craft Services"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Baby, you've got a stew goin'"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each callback method accepts two arguments. One for the status, and another for a set of options. We're using those options in the &lt;code&gt;on_complete&lt;/code&gt; callback to pass a name to the status message.&lt;/p&gt;

&lt;p&gt;Also, I'm sorry I misled you earlier. This isn't really about a recipe. It's an &lt;a href="https://www.youtube.com/watch?v=oDOffqDsV5Q"&gt;acting lesson&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/oDOffqDsV5Q"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting an audition callback
&lt;/h2&gt;

&lt;p&gt;Now we know how to use a callback and we created a class to house our callbacks. Let's put it to use by telling our batch about it, and seeing how they get used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"runs a callback"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;acting_lesson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;acting_lesson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:complete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ActingLessonCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Kevin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;acting_lesson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ActingLessonCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inline!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;acting_lesson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;GetRawVeggiesWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
        &lt;span class="no"&gt;GetBaconWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
        &lt;span class="no"&gt;GetCupOfSoupWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&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;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Kevin went to Craft Services&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
               &lt;span class="s2"&gt;"Baby, you've got a stew goin'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_stdout&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We registered our callbacks with the batch using the &lt;code&gt;on&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Remember those two additional jobs in the prior example? Where we enqueued three jobs, but the total count was five? Those extra jobs were these callbacks firing. Even though we didn't register any callbacks, the events still fired.&lt;/p&gt;

&lt;p&gt;In this test our batch executed, triggering both callback events. As expected, the callbacks output their message. Completion! Success!&lt;/p&gt;

&lt;h2&gt;
  
  
  A complete definition of success
&lt;/h2&gt;

&lt;p&gt;While success and complete may sound similar, they have specific and different meanings. The success callback is perhaps the more obvious one. It triggers when the jobs in the batch have completed successfully.&lt;/p&gt;

&lt;p&gt;That means that a batch can complete and not be successful - leading us to the &lt;code&gt;on_complete&lt;/code&gt; callback. That fires when all the jobs have executed. Some of the jobs could have failed. Some may be in the retry queue. But they have run at least once.&lt;/p&gt;

&lt;p&gt;To show this, let's create a few more Sidekiq jobs. We'll join the steps of our acting lesson together in one job. And we'll create one more that always fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActingLessonWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Job&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HugeMistakeWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Job&lt;/span&gt;
  &lt;span class="n"&gt;sidekiq_options&lt;/span&gt; &lt;span class="ss"&gt;retry: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"I think I'd like my money back"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll run another test, creating a batch with these two jobs. At the end, we'll check the status of the batch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"completes once each job has run once, regardless of success"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;drama_coach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inline!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;drama_coach&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;ActingLessonWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
        &lt;span class="no"&gt;HugeMistakeWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&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;raise_error&lt;/span&gt; &lt;span class="s2"&gt;"I think I'd like my money back"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;batch_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drama_coach&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_status&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;have_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;complete?: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;total: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;pending: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;failures: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;success_pct: &lt;/span&gt;&lt;span class="mf"&gt;75.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though not all the jobs were successful, the batch still reports itself as complete. We can see the failure, and the job that failed as pending. We can also look at the success percentage of the batch to understand that not all the jobs succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dress rehearsal implications
&lt;/h2&gt;

&lt;p&gt;Something to be mindful of when running your batches in tests is when the callback will fire. To really have some fun, let's do something I don't reach for often: use a global variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vg"&gt;$global&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll create a job that increments the global.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Job&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="vg"&gt;$global&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll have a callback after the batch succeeds. It outputs how many times the jobs incremented the counter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BatchCallback&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Jobs run: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$global&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our test, we have a batch that fires the callback and enqueues the counter job twice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"runs the success callback after the first job is run with inline test mode"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BatchCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inline!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;CounterWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
        &lt;span class="no"&gt;CounterWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&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;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Jobs run: 1&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_stdout&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$global&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;eq&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our global says both jobs ran; however, the output from our callback says only one ran. Both are correct - at different points in time! Remember, these jobs in this &lt;em&gt;test&lt;/em&gt; are performed inline. The first counter job was enqueued and run, incrementing the counter. Then Sidekiq checked to see if any other jobs were in the batch. At this point there aren't, so it triggers the callbacks. The success callback outputs that one job ran.&lt;/p&gt;

&lt;p&gt;Then, the second job in the batch was enqueued and run, incrementing the counter. The end of our test verifies that the global variable is currently set to two. The callback is not executed again because it already ran.&lt;/p&gt;

&lt;p&gt;If you're running batches in tests, want to test the callback of a batch, and the callback depends on the state or results of all the jobs in the batch, you may end up with a surprising result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bulking up mid-rehearsal
&lt;/h2&gt;

&lt;p&gt;If you really need to test this, you &lt;em&gt;could&lt;/em&gt; work around this by changing how you enqueue the jobs in your batch. Using &lt;a href="https://github.com/sidekiq/sidekiq/wiki/Bulk-Queueing"&gt;bulk queueing&lt;/a&gt; will get you the result you expect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"runs the success callback after all jobs run when pushed in bulk with inline test mode"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BatchCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inline!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;CounterWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_bulk&lt;/span&gt;&lt;span class="p"&gt;([[],&lt;/span&gt; &lt;span class="p"&gt;[]])&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&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;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Jobs run: 2&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_stdout&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$global&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;eq&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also enqueues two jobs. When run with the inline test adapter, they both run before the batch callbacks fire. So, both the callback and the global are consistent.&lt;/p&gt;

&lt;p&gt;I point this out to show you can do this, and also to perhaps introduce bulk queueing to you. However, if your tests rely on this, I would advocate to reconsider your testing strategy. Test the callback in isolation to verify that part of your code. Trust Sidekiq to manage the orchestration of firing the callback for the batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  A draining performance
&lt;/h2&gt;

&lt;p&gt;You may recall that our first test didn't run the jobs because we were using the fake test adapter. I have one other word of caution to point out that I noticed using the fake adapter. Draining the queue that the jobs are enqueued in for a batch later in the test also does not trigger the callbacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"doesn't run the success callback of a batch when draining the queue in fake test mode"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BatchCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;CounterWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
    &lt;span class="no"&gt;CounterWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;CounterWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;not_to&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Jobs run: 2&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_stdout&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$global&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;eq&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly on testing, to have these callbacks fire in tests, you must &lt;a href="https://github.com/sidekiq/sidekiq/wiki/Batches#testing"&gt;add middleware&lt;/a&gt; to your tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;around&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:example&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_middleware&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;

  &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_middleware&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Batch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Curtain call
&lt;/h2&gt;

&lt;p&gt;This has been an introduction to Sidekiq's batch functionality. It &lt;em&gt;also&lt;/em&gt; provides a valuable life lesson on acting. Batches can be a great option to parallelize work and report the result of, or combine, those pieces. You can also use it to build complex workflows. Dig into batches in more detail on &lt;a href="https://github.com/sidekiq/sidekiq/wiki/Batches"&gt;Sidekiq's wiki&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Tracks Not At RailsConf 2024</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Mon, 18 Mar 2024 00:33:13 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/tracks-not-at-railsconf-2024-5569</link>
      <guid>https://dev.to/kevin_j_m/tracks-not-at-railsconf-2024-5569</guid>
      <description>&lt;h2&gt;
  
  
  RailsConf 2024
&lt;/h2&gt;

&lt;p&gt;I'm on the &lt;a href="https://kevinjmurphy.com/posts/railsconf-2024-program-committee/"&gt;RailsConf 2024 Program Committee&lt;/a&gt;. We just released the &lt;a href="https://railsconf.org/schedule/"&gt;program&lt;/a&gt; for this year's event, and I hope you'll join us!&lt;/p&gt;

&lt;p&gt;One thing we don't have are formally-themed tracks. In past years, our CFP might have included prompts for different topics. Or we'd group some talks in publicly-shared and advertised ways.&lt;/p&gt;

&lt;p&gt;We aren't doing that this year. That lets us focus on the overall conference theme: building with Rails. However, that didn't stop me from brainstorming a list of possible tracks, in case we did want to add them in.&lt;/p&gt;

&lt;p&gt;The following is a list of what could have been, but will not be: tracks that are not part of the RailsConf 2024 program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lend an Enginear
&lt;/h2&gt;

&lt;p&gt;Story Time! Gather round to hear harrowing tales of software development. Was the bug coming from inside the framework? What can we learn from these experiences? Follow the hero's journey of these presenters. Listen to adventures both imagined and too strange for fiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails Train-ing
&lt;/h2&gt;

&lt;p&gt;Unlock new perspectives on using the Rails toolbox. Talks in this track will help you level up on Rails functionality. They'll build up a stronger understanding of framework features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails Engine-uity
&lt;/h2&gt;

&lt;p&gt;This track highlights people combining Rails with other concepts. This may include a variety of front-end tools. It may show connecting with different data sources. I'm sure there are things I can't even imagine that submitters would surprise us with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Railway Maintenance
&lt;/h2&gt;

&lt;p&gt;We're all about setting ourselves up for long-haul success here. How can we prevent future pain in managing our applications? How can we get ourselves out of sticky situations? What tools, systems, and mindsets make our apps more amenable to change? How can we otherwise make our apps better in the future?&lt;/p&gt;

&lt;h2&gt;
  
  
  Express Rail
&lt;/h2&gt;

&lt;p&gt;Here is a one-way ticket to building foundational Rails knowledge. Talks in this track assume no prior experience for the audience. This track will bring you up to speed quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freight Transport
&lt;/h2&gt;

&lt;p&gt;This is where all the heavy technical talks combine in one long train traveling down the same track. Talks may dig into details on Rails source code. They could explore dreams for the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passenger Train
&lt;/h2&gt;

&lt;p&gt;The Ruby community is so delightful because of the people in it. These talks highlight that. They provide insight on how to improve our human connection, to code and each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tunnel Vision
&lt;/h2&gt;

&lt;p&gt;Talks in this track are deep dives on specific topics. Here is a chance for speakers to get into intense detail. Share the knowledge that it feels like only you know. Combine the wisdom of the community on one area in a tight package.&lt;/p&gt;

&lt;h2&gt;
  
  
  RailsConf 2024 Program
&lt;/h2&gt;

&lt;p&gt;While those tracks aren't in this year's program, we have lots of &lt;a href="https://railsconf.org/schedule/"&gt;amazing sessions&lt;/a&gt; that are. Get your &lt;a href="https://ti.to/railsconf/2024"&gt;ticket&lt;/a&gt; today!&lt;/p&gt;

&lt;p&gt;There may also be some subtle ordering to the schedule. What themes do you see? What sessions are you looking forward to? &lt;a href="https://ruby.social/@kevin_j_m/112090218913981735"&gt;Let me know&lt;/a&gt;! &lt;/p&gt;

</description>
      <category>ruby</category>
      <category>development</category>
      <category>careerdevelopment</category>
    </item>
    <item>
      <title>RubyConf 2023 Recap</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Mon, 20 Nov 2023 23:05:49 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/rubyconf-2023-recap-2lh8</link>
      <guid>https://dev.to/kevin_j_m/rubyconf-2023-recap-2lh8</guid>
      <description>&lt;h2&gt;
  
  
  RubyConf 2023
&lt;/h2&gt;

&lt;p&gt;RubyConf recently wrapped up in San Diego, California. This post is meant to highlight the great work from all involved. I hope you'll seek out the full videos of all the sessions that interest you once they are available. Unfortunately, I couldn't be everywhere, so this covers what I saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing
&lt;/h2&gt;

&lt;p&gt;This is a weird brag, but this is the first conference I've attended in a long time as an attendee. It's been a while since I haven't been a speaker or on the speaker waiting list. Being a wait-listed speaker means you prep and have a talk ready. You only get to give it if you need to substitute for another speaker on short notice.&lt;/p&gt;

&lt;p&gt;This left me with a lot more free time during the conference. More importantly, I had more time before the conference. I still had the "conference season" itch though. So I channeled that energy in a few different directions.&lt;/p&gt;

&lt;h3&gt;
  
  
  CFP Coaching
&lt;/h3&gt;

&lt;p&gt;First, just as I did for RailsConf, I joined Ruby Central members for the CFP coaching sessions. We helped others with their proposals in small groups. This was a great opportunity to meet with prospective speakers and workshop ideas to submit for the conference. I'm glad that Ruby Central has started organizing these events. I hope they continue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Podcasting about RubyConf
&lt;/h3&gt;

&lt;p&gt;After the CFP process wound down, I joined Brittany Martin as a temporary &lt;a href="https://kevinjmurphy.com/posts/ruby-on-rails-podcast-cohost/"&gt;co-host&lt;/a&gt; of The Ruby On Rails podcast. We talked with Allison McMillan and Chelsea Kaufman about the plan for RubyConf 2023. I hope that it helped get people excited about the conference. Even better if it helped convince someone to come after listening.&lt;/p&gt;

&lt;p&gt;I also joined Julie and Andrew on the &lt;a href="https://kevinjmurphy.com/posts/ruby-for-all-guest-2023/"&gt;Ruby for All&lt;/a&gt; podcast. The discussion centered around conference speaking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing
&lt;/h3&gt;

&lt;p&gt;I expanded on my blog post from last year where I shared &lt;a href="https://kevinjmurphy.com/posts/sharing-past-conference-proposals/"&gt;past proposals&lt;/a&gt;. I turned it into a series on preparing for conferences. I wrote about &lt;a href="https://kevinjmurphy.com/posts/building-conference-talk-content/"&gt;my process&lt;/a&gt; for turning an accepted proposal into a talk. I followed that up with &lt;a href="https://kevinjmurphy.com/posts/preparing-conference-talk-delivery/"&gt;how I practice&lt;/a&gt; and prepare for the stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helping Speakers
&lt;/h3&gt;

&lt;p&gt;Lastly, I had more time to help friends who were speaking with their talk preparation. Notably, I was a speaker mentor for the first time. I've been hesitant to do it in the past when I've had a talk of my own to prepare. I was really happy to have the time to devote to this. I paired with &lt;a href="https://ruby.social/@paulreece_"&gt;Paul Reece&lt;/a&gt;. Paul gave a great talk about &lt;a href="https://rubyconf-2023.sessionize.com/session/530118"&gt;Ruby Polars&lt;/a&gt;. Paul was very prepared and definitely made the process easy for me. It was super fun to see the evolution of the talk over time from an outsider's perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Day
&lt;/h2&gt;

&lt;p&gt;Even after talking about this on &lt;a href="https://kevinjmurphy.com/posts/ruby-on-rails-podcast-cohost/"&gt;The Ruby on Rails podcast&lt;/a&gt;, I'll admit that I was still pretty unsure how this would all go. I didn't attend any of the workshops as I wanted to see the implementation of the hack space events.&lt;/p&gt;

&lt;p&gt;After an introduction from Allison McMillan and Chelsea Kaufman, the group separated into workshops and the hack space. I spent my day in the hack space. In the morning I spent time at the Hanami table. I'll be honest, I didn't do much of any Hanami work, but I had a great conversation there.&lt;/p&gt;

&lt;p&gt;After lunch, I parked at the Ruby LSP table. I worked with &lt;a href="https://codewithjulie.com/"&gt;Julie&lt;/a&gt; and &lt;a href="https://www.drbragg.dev/"&gt;Drew&lt;/a&gt; on &lt;a href="https://github.com/Shopify/ruby-lsp/pull/1181"&gt;HEREDOC endings&lt;/a&gt;. We also found an &lt;a href="https://github.com/Shopify/ruby-lsp/issues/1182"&gt;issue&lt;/a&gt; that we spent some time digging into, but ran out of time before we had a solution. It was fun spelunking through the code with friends. &lt;a href="https://ufuk.dev/"&gt;Ufuk&lt;/a&gt; joined us in looking at the cursor indentation issue. His sage wisdom helped us navigate through an unfamiliar codebase.&lt;/p&gt;

&lt;p&gt;I enjoyed my time at Community Day. For me, I'm not sure if it's worth removing a full day of talks. I'm a person that really enjoys talks at conferences. I'm a person who will gladly join talks in person. But, I have a difficult time committing to watching recorded talks after. I'm also biased, because I'm a potential speaker (I did not submit to RubyConf this year). One fewer day and fewer tracks means less opportunity for people to have an accepted talk.&lt;/p&gt;

&lt;p&gt;That said, I talked to a handful of people who organized sessions after. And the vibe I got from them was that it was a positive time. They were happy to work with the community directly, in-person, on their project. They liked meeting new people interested in helping and giving them a starting push. They enjoyed the experience and what it meant for their project.&lt;/p&gt;

&lt;p&gt;I'm not sure what the right mix is here. At the least, I think moving Community Day to later in the conference makes sense. That way the organizers can communicate in person with attendees on expectations. Any talks about subjects that will be at Community Day can turn into a call to action to join the hack space.&lt;/p&gt;

&lt;p&gt;Could there be a hack space throughout the conference that people could  join? That would have a similar cost that workshops have in the past, where people need to miss other sessions to join. It would mean having another room at the conference that Ruby Central needs to rent. Maybe having it spread out throughout the conference would mean a smaller room? It would be a bit like the hallway track, but structured towards particular goals.&lt;/p&gt;

&lt;p&gt;Like I said, unfortunately, I have no solid answers here. And I recognize that the way I enjoy conferences is different from many others. I heard resounding praise for Community Day at the conference!&lt;/p&gt;

&lt;p&gt;I do like that Ruby Central is responding to feedback and trying out new experiences. I'm interested to hear a retrospective from them. How do they feel about Community Day and what form(s) it might take going forward?&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 2
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Opening Keynote
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/560574"&gt;Yukihiro "Matz" Matsumoto&lt;/a&gt; started the conference after the opening remarks. This was a pre-recorded message. He talked about learning from the history of language design and evolution, to inform the future of Ruby.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pqPZcFFc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yy2wochxdd7xscm1tub0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pqPZcFFc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yy2wochxdd7xscm1tub0.jpg" alt="Matz at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Time Is It?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/531537"&gt;Joël Quenneville&lt;/a&gt; started the talk sessions. He provided a distinction between looking at a moment in time versus a duration of time. He cautions to think through what types of time can interact with each other (the way that you want or expect). Consider the operator you're using and what it means. Catch the full session to learn more about why you can't add two time instances together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zEgMbHTZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hhlvklmefzj2in6yp8zp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zEgMbHTZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hhlvklmefzj2in6yp8zp.jpg" alt="Joël Quenneville at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Get your Data prod ready, Fast, with Ruby Polars!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/530118"&gt;Paul Reece&lt;/a&gt; gave an introduction to the Polars library. This started with an introduction to the Series and Data Frame data structures. Paul then walked through data cleaning operations available in Polars. He shared a &lt;a href="https://github.com/paulreece/polars_resources"&gt;repository&lt;/a&gt; of resources and code samples at the end of his session.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G2jtwXxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xdl86xfkxjgal71fecg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G2jtwXxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xdl86xfkxjgal71fecg.jpg" alt="Paul Reece at RubyConf 2023" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ruby Content Creators
&lt;/h3&gt;

&lt;p&gt;During Community Day, Peter Cai asked me to participate in this &lt;a href="https://rubyconf-2023.sessionize.com/session/186fce40-218a-4d5b-a3f5-01d133b1f7c5"&gt;open space session&lt;/a&gt;. I sat at the written content table with other bloggers and authors. There were other tables for podcasts, live streaming, and pre-recorded videos. The written content table talked through our processes. We lamented over the difficulties in getting feedback. We shared how to build sustainable writing habits.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Future of Understanding Ruby Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/531482"&gt;Kevin Newton&lt;/a&gt; talked about &lt;a href="https://github.com/ruby/prism"&gt;Prism&lt;/a&gt;, a new Ruby parser. He discussed the challenges that come with parsing Ruby. He shared what's next, and what we can and should expect from our Ruby tooling in the future. He ended with an impassioned discussion about building a contributor community around a single tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KkFQ0dgK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egd8qwgsfgwkvb1mnlex.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KkFQ0dgK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egd8qwgsfgwkvb1mnlex.jpg" alt="Kevin Newton at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Unbreakable Code Whose Breaking Won WWII
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/529111"&gt;Aji Slater&lt;/a&gt; took us back to Bletchley Park to model an Enigma machine with object-oriented principles. This session features monumentally brilliant illustrations and fantastic storytelling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_-Ip3J-l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3it8hsilr4lgwpq49157.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_-Ip3J-l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3it8hsilr4lgwpq49157.jpg" alt="Aji Slater at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing Keynote
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/560579"&gt;Sharon Steed&lt;/a&gt; ended the first day of talks with a keynote on actionable empathy. We heard about its relationship with vulnerability and how trust is the bedrock to empathy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lA7TGTeH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2sdwh6wxmwpz6rsfb0g6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lA7TGTeH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2sdwh6wxmwpz6rsfb0g6.jpg" alt="Sharon Steed at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 3
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Opening Keynote
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/560580"&gt;Saron Yitbarek&lt;/a&gt; opened the last day of RubyConf discussing our superpower. Loving what we do is an underrated source of competitive advantage for us to wield.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m0UU_Pd_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hw03j2txj644afbt085k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m0UU_Pd_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hw03j2txj644afbt085k.jpg" alt="Saron Yitbarek at RubyConf 2023" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Meet the Pragmatic Bookshelf
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/560583"&gt;Noel Rappin&lt;/a&gt; joined Dave Thomas and Dave Copeland for a panel discussion on writing. This particularly focused on books with Pragmatic Programmers. We heard about the entire process, from proposal to publication.&lt;/p&gt;

&lt;p&gt;One note I found interesting was how the voice of their books changes over time. It intentionally starts with an authoritative tone, telling the reader what to do. As the book progresses and the reader is more familiar with the domain, the tone shifts. Now the tone is more collaborative. The author consults with the reader and uses "we" more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3o5Hnczg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ba4x6uiwzvc42qpdw6ks.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3o5Hnczg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ba4x6uiwzvc42qpdw6ks.jpg" alt="Noel Rappin, Dave Thomas, and Dave Copeland at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Secret Ingredient: How To Understand and Resolve Just About Any Flaky Test
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/527141"&gt;Alan Ridlehoover&lt;/a&gt; walked through a progression of steps to resolve tests failing due to non-determinism, order dependence, and race conditions. With an eye to keeping specs to have one, and only one, reason to exist, he proposes tools and methodologies to trace down these test failures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aqFH1CEe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2q9keb7hyqobcgrcjjc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aqFH1CEe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2q9keb7hyqobcgrcjjc.jpg" alt="Alan Ridlehoover at RubyConf 2023" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Livin’ La Vida Hanami
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/518035"&gt;Tim Riley&lt;/a&gt; introduced Hanami as the everything framework. The framework is not our app, but enables our application logic through its layers. While it has support for web essentials, you can choose to not include them and still benefit from the underlying framework benefits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0yn6krYq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2yejr2gn9i4o5rkfnyx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0yn6krYq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2yejr2gn9i4o5rkfnyx.jpg" alt="Tim Riley at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Source Ruby
&lt;/h3&gt;

&lt;p&gt;In this &lt;a href="https://rubyconf-2023.sessionize.com/session/decd439d-f9ec-45be-b016-49ea2f99cd3c"&gt;open space session&lt;/a&gt; I sat at the Sidekiq table. Mike Perham held an informal chat. We discussed open source sustainability and succession planning. There were tables representing other projects, but I'm not sure what they covered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rubyconf-2023.sessionize.com/session/3298cf7c-e3ad-4ade-bb53-61ae24e97b2c"&gt;Adarsh Pandit&lt;/a&gt; wrapped up RubyConf. We learned that RubyConf 2024 will be in Chicago in the fall. That's following a visit to Detroit in the spring for RailsConf.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZXTIot2o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49shwsp5ljzxzy3bp0br.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZXTIot2o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49shwsp5ljzxzy3bp0br.jpg" alt="Adarsh Pandit at RubyConf 2023" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appreciation
&lt;/h2&gt;

&lt;p&gt;Thank you to my employer, &lt;a href="https://www.pubmark.com/"&gt;Pubmark&lt;/a&gt;, for supporting my attendance this year.&lt;/p&gt;

&lt;p&gt;Thank you &lt;a href="https://rubycentral.org"&gt;Ruby Central&lt;/a&gt; for your work to organize these events and supporting the Ruby community all year round.&lt;/p&gt;

&lt;p&gt;Thank you to the Program Committee, Scholarship Committee, volunteers, and venue staff for bringing this conference to life.&lt;/p&gt;

&lt;p&gt;Thank you sponsors for supporting the Ruby community and making it possible for us to come together.&lt;/p&gt;

&lt;p&gt;Thank you Jonan Scheffler and Marty Haught for your years of service and dedication on the Ruby Central board.&lt;/p&gt;

&lt;p&gt;Thanks to all the &lt;a href="https://kevinjmurphy.com/posts/ruby-friends-rubyconf-2023/"&gt;#RubyFriends&lt;/a&gt;, old and new, that I met in San Diego.&lt;/p&gt;

&lt;p&gt;Thank you for reading to the end. Maybe we met at RubyConf 2023. Maybe we didn't. I hope to see you in 2024. I hear Chicago is lovely in November.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>careerdevelopment</category>
    </item>
    <item>
      <title>Preparing Conference Talk Delivery</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Tue, 17 Oct 2023 00:28:45 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/preparing-conference-talk-delivery-h1n</link>
      <guid>https://dev.to/kevin_j_m/preparing-conference-talk-delivery-h1n</guid>
      <description>&lt;p&gt;A conference accepted my proposal and I've built a slide deck. I still have a lot to do before hitting the stage!&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;At this point, I have a continual routine to get more familiar and comfortable with the material. It's also an opportunity to exercise the content on a continuous basis. The editing process isn't over here. However, it's more constrained and specific. I may be toying with the layout of a specific slide. Fighting with exactly the right word to describe a concept. Determining how to best highlight one aspect of what I'm saying.&lt;/p&gt;

&lt;p&gt;I get the hint to make those targeted edits through practicing. I close the door to my office, stand up, and deliver the talk to an empty room. No more than once a day, and not every day, starting about a month out from the conference. And I time it, just like before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GiwUm2WH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3v8swupj7l89mwgxt3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GiwUm2WH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3v8swupj7l89mwgxt3q.png" alt="A spreadsheet keeping track of how long sections of my presentation are taking when practicing." width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I keep track of those times. That helps me reflect on knowing if I'm struggling to get through one section and need to tighten it up. It helps me know if I'm giving each section its appropriate focus. And looking back over the last few practices, it helps me see where I've gotten into a comfortable groove.&lt;/p&gt;

&lt;p&gt;Each practice will have a bit of variation, because I don't follow an exact script. Over time, I'll see that I'm being more consistent in how long each area is taking (plus/minus 10 seconds or so per section). That tells me that I'm able to repeatedly deliver.&lt;/p&gt;

&lt;p&gt;The stage &lt;em&gt;is&lt;/em&gt; a different experience, but this preparation lets me draw from all the practice. It's not like I black out or go on auto-pilot mode, but I'm able to leverage all that work on stage. No one else in the audience knows what I'm going to say next as well as me, and that gives me comfort. People may be smarter than me. People may be more familiar with the topic. No one is going to say it the same way as I am right now, and that gives me confidence.&lt;/p&gt;

&lt;p&gt;For the talk from the spreadsheet above, I logged 20 practice sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;I find other places to practice separated from my slides. When I would commute to work, I would practice (in my head) on the train ride in or back from work. I practice the full talk or sections while on walks. Some times, I run through ideas for my intro or conclusion as I'm drifting off to sleep.&lt;/p&gt;

&lt;p&gt;I tell myself I want to be able to deliver my talk without the help of the slides. In case the power goes out and I need to entertain everyone in the dark, or outside, I can. Note that almost happened at RubyConf Mini 2022, when my laptop &lt;a href="https://kevinjmurphy.com/posts/rubyconf-mini-2022-recap/#appreciation"&gt;wouldn't connect&lt;/a&gt; to the projector.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When I’m practicing, I tell myself I need to prepare enough to give my talk without any slides or notes. I practice on walks, away from my material. I practice looking the other direction (checking occasionally to make sure I’m clicking to the right slide). In those moments on stage, I was wondering if my preparation would become necessary. Would I have to give my full talk without any slides?&lt;/p&gt;

&lt;p&gt;Thanks Drew Bragg for unflinchingly handing me his laptop from the front row, letting me git clone the repo that has my slides on it. I ran the presentation from his computer, having never used it before.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even without that dramatic scenario coming to light, practicing without slides helps me. I'm not tied to my slides. I don't need to read them. I can focus on the audience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;I find a real audience before the conference to practice with. Most times, that ends up being at work. There are also local meetup groups I could request to speak at. I could gather a group of friends.&lt;/p&gt;

&lt;p&gt;My main goal is to give me the experience of delivering the talk for other people. It can help me know what beats might get laughs (rare, but I try). I might be able to see where people are more or less interested. I'll admit, that's hard for me to focus on while I'm also delivering the talk. It's to help &lt;em&gt;me&lt;/em&gt; get more comfortable.&lt;/p&gt;

&lt;p&gt;It's also a natural place for feedback, if you want it. Feedback can be very helpful &lt;strong&gt;and&lt;/strong&gt; very dangerous. You're putting yourself out there and are being vulnerable. You may not like, or want, the feedback. That's something you need to decide for yourself. I &lt;em&gt;consider&lt;/em&gt; the feedback I get, but I do not &lt;strong&gt;incorporate&lt;/strong&gt; all of it. That doesn't mean it's bad - it may be objectively right. But if I can't find a way to incorporate it in a way that makes me comfortable on stage, I ignore it. If it takes away from my goals of what I want the presentation to deliver, I leave it be. I've given it the time and consideration, and thoughtfully chosen to do something else. That's &lt;strong&gt;hard&lt;/strong&gt; to do, but I've gotten better at it, as I've gotten more comfortable knowing what my style is on stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Preparation
&lt;/h2&gt;

&lt;p&gt;I store my slides in a GitHub repo, even while I'm developing it. That repo is public, which has the added benefit of being shareable, should I need to use another computer. I prefer using Keynote to any online tools, but that does limit the ease of access. Something new, given my RubyConf Mini issue, is to have a buddy with the slide deck on their laptop PRIOR. That may be a conference organizer. That way it's already set up, and I can confirm it looks exactly how I want. I use custom fonts and colors, and I want to make sure those get installed and are available too.&lt;/p&gt;

&lt;p&gt;Going back to RubyConf Mini, I was very relieved to use Drew's computer, but it wasn't the experience I wanted it to be. We were installing it live on stage. I didn't have the time (or foresight) to install the fonts and colors I built the deck with. It's a touch that only I could have noticed, and didn't take away too much (I hope) from the experience. But it was still frustrating. It wasn't the exact visual presentation I had spent the time preparing for the audience.&lt;/p&gt;

&lt;p&gt;I build a page on my website about each of my talks now. At the end of the presentation, I share that link. That can provide information about the talk. I include the proposal, the slides, code examples, and related blog posts. Some times I put details about the slides themselves, like photo credits and fonts used.&lt;/p&gt;

&lt;p&gt;Once I'm confident they won't change, I publish my slides for the audience to access. I use &lt;a href="https://speakerdeck.com/kevinmurphy/"&gt;Speaker Deck&lt;/a&gt; to host my slides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packing
&lt;/h2&gt;

&lt;p&gt;Here are some things I bring to a conference in service of the talk. Even if I'm not speaking, I carry these with me, in case another speaker could use them. So, if you're at a conference I'm at and you're looking for one of these, come find me!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A button-up shirt - I wear these shirts so the wire of the lav mic can hide inside the shirt. Then I don't hit it if I'm waving my hands around or something.&lt;/li&gt;
&lt;li&gt;Water bottle - Water is good. You should drink some before and during the talk...but not too much.&lt;/li&gt;
&lt;li&gt;Clear plastic cup - I usually take this from my hotel room. I may not want to fumble with taking the cap on and off my water bottle, so I pour some water in the cup on stage.&lt;/li&gt;
&lt;li&gt;Slide advancer - I like to walk a bit away from the podium in a small box. Holding this gives me the freedom to do that without needing to hop back to the computer to get to the next slide.&lt;/li&gt;
&lt;li&gt;Extra batteries - It'd be a real shame if I couldn't advance the slides from my fancy slide advancer because I used up the batteries with all my practicing.&lt;/li&gt;
&lt;li&gt;Laptop power cable - Especially if you practice with your laptop plugged in, give the talk with your laptop plugged in. You don't want some unexpected power saver setting surprising you on stage.&lt;/li&gt;
&lt;li&gt;A/V connection or cables - The venue or organizers may say they'll have some, and you may need to use them, but better to have one of your own in case.&lt;/li&gt;
&lt;li&gt;Throat lozenges - I put one in my mouth about an hour before I'm on stage, whether I need it or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  At The Conference
&lt;/h2&gt;

&lt;p&gt;I find the room I'm speaking in well before my time. Ideally a day before. At the very least, it resolves the immediate stress of needing to know where to go. I also ask to plug in my computer and make sure it works. This is a great chance to meet the A/V team that'll be helping you. I introduce myself and ask their names. I ask them if there's anything I can do to make their jobs easier. I let them know if I'm playing any audio on my computer. I tell them I'm likely to walk around (a little) on stage, and ask if that's any concern for them.&lt;/p&gt;

&lt;p&gt;I go to the slide that has the smallest font, or most dense visuals on it, and then run (ok, walk) to the back of the room. I ensure it looks legible to me.&lt;/p&gt;

&lt;p&gt;That is the &lt;em&gt;only&lt;/em&gt; time I would consider making &lt;strong&gt;any&lt;/strong&gt; changes to my slides, content, or delivery this late. Anything else would be detrimental to the experience. I've already practiced so much with it another way I do not feel comfortable making any other changes.&lt;/p&gt;

&lt;p&gt;It may turn out there's a giant chandelier that obscures some of the projection. It's probably a good idea to take that into account for your audience.&lt;/p&gt;

&lt;p&gt;If the schedule allows, I also see a talk in that room before my session. I sit in the back.&lt;/p&gt;

&lt;p&gt;I practice the night before, by myself, in my hotel room. And only once. That rule still applies. Other speakers may form groups to do a run through or practice. I'm happy to attend those and support others, but it doesn't work for me to practice that way. It's a lot of energy for me to speak in front of a crowd, so I save it for the stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day Of
&lt;/h2&gt;

&lt;p&gt;As hard as it is, I try not to think too much about my talk before I give it. I've already practiced dozens of times. At this point, it is as good as it's going to be. This is easier said than done, but the goal is to not focus on it.&lt;/p&gt;

&lt;p&gt;Be mindful of the schedule. Is there a short break before the talk? If so, then I stay in that room for the prior session, even if there's somewhere else I'd rather be. Whether there's a long or short break before, plan on people opening the door(s) and coming in late. That makes it less surprising when it happens.&lt;/p&gt;

&lt;p&gt;If given the choice, I do not take questions from the large group. I'm happy to talk to anyone and everyone afterwards individually. I let the audience know where I'll be right after the talk. That's usually right next to the stage, but again, I'm mindful of the schedule. Is there's another talk right after? I want to cede that space to the next speaker to not interrupt their preparation.&lt;/p&gt;

&lt;p&gt;After you're done, listen to your body. Do you need to go disappear and be by yourself? Do that! Do you need the affirmation of others? Find someone in the audience and go talk to them! You did it! Think about the special dessert you'll get at dinner to celebrate, even if that's alone in your hotel room.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building Conference Talk Content</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Mon, 18 Sep 2023 12:52:28 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/building-conference-talk-content-406</link>
      <guid>https://dev.to/kevin_j_m/building-conference-talk-content-406</guid>
      <description>&lt;p&gt;Have you recently had a conference proposal accepted? Congratulations! Are you wondering, "what do I do &lt;em&gt;now&lt;/em&gt;?" Here is the process I follow to turn my proposal into a full-length talk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Generation
&lt;/h2&gt;

&lt;p&gt;My first step is to gather all the content I want to talk about as quickly as possible. I know that sounds reductive. Oh, the first step is JUST to put together all your content? How easy! I might as well tell you &lt;a href="https://knowyourmeme.com/photos/572078-how-to-draw-an-owl"&gt;how to draw an owl&lt;/a&gt; next.&lt;/p&gt;

&lt;p&gt;But, here's the thing - for a conference talk, I'm not starting from scratch. I've already put a lot of time, and thinking, into the &lt;a href="https://kevinjmurphy.com/posts/sharing-past-conference-proposals/"&gt;proposal&lt;/a&gt;. For me, that almost always includes a full outline. So I start by revisiting that and seeing what I need to add.&lt;/p&gt;

&lt;p&gt;I completed most of my research in the proposal, but I may need to brush up on some details. There may be a book or research paper that I skimmed that I need to go back to and dig into.&lt;/p&gt;

&lt;p&gt;Will I be using code examples to demonstrate some points? I'll start writing some code samples, in my usual code editor, that show what I want. I'll make sure they run. At this point, I'm not concerned about how terse or verbose they are. I'm not worried what the domain is. I need something that shows the point I want to make. I will admit that sometimes this code generation process is procrastination. I try to call myself out on that and not put too much effort into it at this point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shameless Green
&lt;/h2&gt;

&lt;p&gt;My next step is to put my thoughts together on slides. I use the default theme. White background with black text. Whatever the default font is. I do not care about slide design &lt;strong&gt;at all&lt;/strong&gt; at this point.&lt;/p&gt;

&lt;p&gt;Some slides are one word. Some are an emoji. Some are a dense block of code. Some start as bullet points. Sometimes it's a thought about what I &lt;em&gt;want&lt;/em&gt; the slide to eventually be, like, "insert picture of an owl". Everything is a placeholder at this point.&lt;/p&gt;

&lt;p&gt;I call this process "Shameless Green". I borrowed this term from the book &lt;a href="https://sandimetz.com/99bottles-sample-ruby#_shameless_green"&gt;"99 Bottles of OOP"&lt;/a&gt; by Sandi Metz, Katrina Owen, and TJ Stankus. It doesn't match exactly, but it's stuck in my head, so I continue to use it. Intentionally reminding myself to be shameless gives me more license to do that. There's no polish here. This isn't even a first draft; it's only for me - there's no chance of another human seeing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Delivery
&lt;/h2&gt;

&lt;p&gt;After this, I deliver the talk. No, I don't get on stage with it. I don't have an audience. I close the door to my office, stand up, and speak out loud as if I were on a stage - with my slides in front of me on my computer.&lt;/p&gt;

&lt;p&gt;This will be &lt;em&gt;rough&lt;/em&gt;. I'll feel uncomfortable. I stop to write down when those feelings hit. Not enough to pull me out of my rhythm, but I don't want to forget it.&lt;/p&gt;

&lt;p&gt;Some things I might write:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slide 7 - awkward intro&lt;/li&gt;
&lt;li&gt;Slide 13 - example doesn't work&lt;/li&gt;
&lt;li&gt;Slide 16 - missing context&lt;/li&gt;
&lt;li&gt;Slide 32 - repeating info from earlier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After I've gone through everything, I take a few minutes to review my notes. Only to make sure everything is legible and makes sense. I take the time to add more details where needed.&lt;/p&gt;

&lt;p&gt;And then I walk away. I'll revisit it later with a fresh perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing
&lt;/h2&gt;

&lt;p&gt;I take a few hours to a day away from my initial attempt. I then review the notes while iterating through the slides. What I'm looking for here is any &lt;em&gt;structural&lt;/em&gt; changes I need to make.&lt;/p&gt;

&lt;p&gt;Is there a different order I should present information in? Maybe the section in Slide 32 needs to happen closer to Slide 16. That'll add more context immediately and eliminate the need to repeat myself later on.&lt;/p&gt;

&lt;p&gt;If there are any transitions that feel awkward, I dig into why. Am I missing information? Do I have unnecessary information? Do I need to reorder the content? Is there a connection that I need to make more explicit, for myself and the audience?&lt;/p&gt;

&lt;p&gt;These changes likely don't get done in one sitting. I revisit a section, or a transition, at a time. Then I review everything in total again. That might spark another round of smaller sections to focus on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theme
&lt;/h2&gt;

&lt;p&gt;Now I'm comfortable with the general flow of information I want to share with the audience. Next, I build my &lt;strong&gt;theme&lt;/strong&gt;. I still don't mean slide design here. I mean the real or imaginary &lt;a href="https://kevinjmurphy.com/posts/sharing-past-conference-proposals/#go-all-in-with-a-theme"&gt;world&lt;/a&gt; that my talk will be about. Some talks have a theme as part of their proposal. For others, I need to develop it at this point.&lt;/p&gt;

&lt;p&gt;A theme is important for me in a talk because it turns my presentation into a story. I have a hard time delivering information over that period of time in hard, technical terms. Other people can, and that's great! It doesn't work for me. I need to have some real or imaginary scenario that the content relates to. Most of the time, this involves creating an imaginary product or company. That will illustrate the topics I'm sharing with the audience.&lt;/p&gt;

&lt;p&gt;I may need to change all my examples to match the theme. The code samples now should reflect that theme. Let's say a talk is using a theme of managing scientific study participants. Code that was using ActiveRecord callbacks to send an email is now sending an &lt;a href="https://en.wikipedia.org/wiki/Informed_consent"&gt;informed consent&lt;/a&gt; document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second Delivery
&lt;/h2&gt;

&lt;p&gt;I've reworked my transitions, moved sections around, and built up a story around my content. Now it's time to run through the presentation again. This time, I take fewer notes, but I also add something.&lt;/p&gt;

&lt;p&gt;In this practice session, I start timing the presentation. Where I have discernible sections, I track those as splits/laps (depending on your timer of choice). Talks need to fit within a defined schedule. This is nowhere near polished. Still, the timing gives me a helpful sign of how far off I am from where I need to be.&lt;/p&gt;

&lt;p&gt;Most of the time, I discover I'm way over on time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing
&lt;/h2&gt;

&lt;p&gt;In this editing session, I'm incorporating the same process from the first edit, but I'm adding to it. What am I going to cut entirely? Usually at this point I know what I want to say will take too long and I need to remove pieces. Even if I'm on time, I may recognize that there are pieces that don't complement my goal.&lt;/p&gt;

&lt;p&gt;This is why I focus on "shameless green" to start. I haven't invested in picking colors or graphics for my slides. I haven't pored over detailed animations and visuals. Getting rid of content isn't &lt;em&gt;easy&lt;/em&gt;, but it's harder (for me) the more work I've put into it.&lt;/p&gt;

&lt;p&gt;This doesn't mean you've lost all that work you cut forever. First off, it was &lt;strong&gt;necessary&lt;/strong&gt; to get what remains to where it is now. You learned something as part of putting that together that'll help you.&lt;/p&gt;

&lt;p&gt;You can also reuse it elsewhere. I've turned sections of presentations I've had to cut into entirely different talks. Some have become blog posts. If you're not willing to let it go, but it doesn't fit in this format, it doesn't need to be the end. It needs to go somewhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Slide Design
&lt;/h2&gt;

&lt;p&gt;I am by no means a designer, but I do my best to deliver pleasant slides. Here are some things I look for.&lt;/p&gt;

&lt;p&gt;Does the slide complement what I'm saying? My speech and the slide should work together, but not be the same. They should highlight what's important without being repetitive. How can I replace words with visuals? I've turned sentences of text into a single emoji. I've replaced them with a picture. I try to limit the text on the screen. I want my audience mostly listening to what I'm saying, not reading what's behind me.&lt;/p&gt;

&lt;p&gt;Are the slides cohesive? I use slide templates to use consistent fonts, color schemes, and layouts. This also makes incorporating large design changes easier later on. Rather than needing to change 80 slides individually, I change the template.&lt;/p&gt;

&lt;p&gt;Are my slides accessible? I run my color scheme through a &lt;a href="https://webaim.org/resources/contrastchecker/"&gt;contrast checker&lt;/a&gt;. I strive for a WCAG AAA level of contrast between my colors. I typically pick no more than four colors. A consistent background color. A consistent text color. No more than two colors for highlighting or drawing attention to parts of the slide.&lt;/p&gt;

&lt;p&gt;Are my slides readable to the audience? I avoid putting any information you need the audience to see on the bottom third of the slides. Someone's head might be in the way. I distill code down to the essentials, even if that means it isn't syntactically valid or complete. I use as large of a font size and as few words as necessary.&lt;/p&gt;

&lt;p&gt;I have what's showing on the slide be what you want the audience to focus on. Not what you're going to tell them in two more minutes. I don't have the entire code method on the slide. I have the one line that I'm talking about right now. Then the next slide adds the second line to it.&lt;/p&gt;

&lt;p&gt;If I ever use any slide builds, each build is a separate slide. I don't use an animation that happens within one slide. It makes it easier to go back and forth between slides, if  necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;I have my slides built at this point, but my work isn't over. Next I go through the laborious, repetitive process to provide a consistent delivery.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Keeping Up With Ruby News All Week Long</title>
      <dc:creator>Kevin Murphy</dc:creator>
      <pubDate>Tue, 22 Aug 2023 00:44:58 +0000</pubDate>
      <link>https://dev.to/kevin_j_m/keeping-up-with-ruby-news-all-week-long-457j</link>
      <guid>https://dev.to/kevin_j_m/keeping-up-with-ruby-news-all-week-long-457j</guid>
      <description>&lt;p&gt;I read a lot of Ruby news throughout the week. When someone asks me what they should follow, I first suggest that they not follow exactly the same blogs I do. Then I share the RSS feeds I follow just in case. But what I do suggest is starting with the following places. These highlight the work of others on a weekly basis. I then suggest they keep up with what resonates with them inside these round-ups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sunday
&lt;/h2&gt;

&lt;p&gt;As you get ready to start your work week, what's new out there that can get you excited to write some code? Check out the &lt;a href="https://www.rubyradar.com/"&gt;Ruby Radar&lt;/a&gt; newsletter. The format shows two social media posts, two articles, two podcasts, two videos, and two code updates (commits, releases, projects).&lt;/p&gt;

&lt;h2&gt;
  
  
  Monday
&lt;/h2&gt;

&lt;p&gt;Waiting in your inbox as you start your week is the &lt;a href="https://newsletter.shortruby.com/"&gt;Short Ruby Newsletter&lt;/a&gt;. Naming is hard, and this is the most comprehensive round-up of information I follow. Lucian curates a highlighted list of information about our community, events, code and Ruby, gems and libraries, and related (not Ruby specific) updates.&lt;/p&gt;

&lt;p&gt;If that's not enough for you, there's more. That's followed with a list of other newsletters, podcasts, videos, and articles of the week. You'll likely have a lot of tabs open after getting through a full edition of Short Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuesday
&lt;/h2&gt;

&lt;p&gt;After all that, you might need a break from reading. Instead, catch up with the newest episode of &lt;a href="https://www.bikeshed.fm/"&gt;The Bike Shed&lt;/a&gt; podcast. While not Ruby-specific, the current hosts Joël Quenneville and Stephanie Minn frequently discuss Ruby topics. This thoughtbot podcast discusses what's new in these consultant's worlds from week to week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wednesday
&lt;/h2&gt;

&lt;p&gt;You can continue with a podcast today with a new episode of &lt;a href="https://www.therubyonrailspodcast.com/"&gt;The Ruby On Rails Podcast&lt;/a&gt;. It's hosted by Brittany Martin and a rotating panel of co-hosts. This is typically structured as an interview with a guest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thursday
&lt;/h2&gt;

&lt;p&gt;Thursday is a big day for Ruby news. The &lt;a href="https://rubyweekly.com/"&gt;Ruby Weekly&lt;/a&gt; newsletter comes out on this day. On top of the hottest news and updates of the week, this highly-curated newsletter provides a sample of tutorials, articles, videos, code, and tools that updated throughout the week.&lt;/p&gt;

&lt;p&gt;Later in the day, dig in to the &lt;a href="https://ruby.libhunt.com/"&gt;Awesome Ruby&lt;/a&gt; newsletter with more popular news and articles, followed with trending projects.&lt;/p&gt;

&lt;p&gt;There is also the &lt;a href="https://www.rubyforall.com/"&gt;Ruby for All&lt;/a&gt; podcast. The topics can sometimes seem targeted to early-career developers. I've found that makes it an engaging conversation for everyone to follow and learn from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friday
&lt;/h2&gt;

&lt;p&gt;You can check out a summary of commits and PRs specific to Rails with the &lt;a href="https://world.hey.com/this.week.in.rails"&gt;This Week In Rails&lt;/a&gt; newsletter.&lt;/p&gt;

&lt;p&gt;Entertain yourself at the end of the week with the &lt;a href="https://remoteruby.com/"&gt;Remote Ruby&lt;/a&gt; podcast. Andrew Mason, Chris Oliver, and Jason Charnes discuss the latest in what they're working on or interested in. Occasionally there are guests. Other times the panel of hosts are conversing amongst themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saturday
&lt;/h2&gt;

&lt;p&gt;Take a break if you need to. Or use the time to catch up on what you've missed from these places throughout the week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Reflection
&lt;/h2&gt;

&lt;p&gt;After taking in the information, decide what sources from these curated experiences to keep up with. If you enjoyed what someone had to say, their latest won't always be in these round-ups. Follow podcast guests on social media. Add blogs you enjoy to your RSS reader. Join writer's newsletters. This is how I've cultivated my list of sources that I follow, whether they end up highlighted in a weekly review or not.&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
  </channel>
</rss>
