tag:x,2008-06-01:kind/x interblah.net 2022-10-06T10:22:47+01:00 Connoisseur tag:interblah.net,2022-10-06:/connoisseur 2022-10-06T10:22:47+01:00 2022-10-06T10:22:47+01:00 interblah.net <p>Listen, here’s what I’m wondering: is it better to be able to appreciate subtle refinements and improvements of a thing, the finest examples of it, if it comes at the expense of enjoying the version of that thing that’s most commonly or easily available?</p> <p>If that “better” thing gives an increase of happiness of X, but the more commonly available instances – in their relative worseness – now gives a <em>decrease</em> of happiness of Y, then how often is the sum of X greater than the sum of Y?</p> <p>Is it better to really get into, say, wine, for all the increased enjoyment that those few really great wines can provide, if it means that you either need to spend a new multiple on acquiring that wine, or suffer increasing disappointment from the wines you can affort or are offered at friends houses?</p> <p>Or is it better – on the whole – to remain largely ignorant of what distinguishes a “great” wine from an “ok” wine, and just enjoy them all to a lesser but more consistently positive extent?</p> <p>It might not be wine, it could be chocolate, or coffee, or art, or pretty much anything else that you can develop a “taste” for. Are the newly-available highs of pleasure worth, overall, the lessened ability to tolerate the instances of that thing which do not match your new palate?</p> <p>This is what I’m wondering. Sound out in the comments<sup id="fnref:comments" role="doc-noteref"><a href="http://interblah.net/#fn:comments" class="footnote">1</a></sup></p> <p>The only clue I can offer is that it’s a literal nightmare figuring out how to spell “connoisseur”. I had to look it up to write this. Twice.</p> <p>The universe weaves clues into its ineffable fabric, I suppose.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:comments" role="doc-endnote"> <p>JK there are no comments here – literal LOL – nah, you go scream into whatever digital silo you prefer. <a href="http://interblah.net/#fnref:comments" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> This is for Patrick tag:interblah.net,2022-06-13:/this-is-for-patrick 2022-06-13T15:52:43+00:00 2022-06-13T15:52:43+00:00 interblah.net <p>This is for Patrick, who doesn’t subscribe to the RSS or Atom feeds.</p> <p>Instead he sets a reminder to check this website every week or two (or perhaps less, I forget), and – upon seeing no new posts – sets another reminder to do the same later that month, or year, or whenever.</p> <p>This is for Patrick, who has lost so many precious seconds checking this site for new posts, only to find nothing.</p> <p>I won’t be responsible for this anymore. I won’t burn his time and energy like electron potential on the bitcoin bonfire. I won’t be the source of that disappointment. My stale blog-type thing won’t be the reflection in that lone tear as it traces its melancholy path down his face. Not anymore.</p> <p>This post, Patrick… <em>buddy</em>… this is for <strong>you</strong>.</p> Don't cache ActiveRecord objects tag:interblah.net,2021-01-29:/don-t-cache-activerecord-objects 2021-01-29T15:52:43+00:00 2021-01-29T15:52:43+00:00 interblah.net <p>Hello, new Rails developer! Welcome to <strong>WidgetCorp</strong>. I hope you enjoyed orientation. Those old corporate videos are a hoot, right? Still, you gotta sit through it, we all did. But anyway, let’s get stuck in.</p> <p>So as you know, here at WidgetCorp, we are the manufacturer, distributor and direct seller of the worlds highest quality <strong>Widgets</strong>. None finer!</p> <p>These widgets are high-value items, it’s a great market to be in, but they’re also pretty complicated, and need to be assembled from any number of different combinations of <em>doo-hickeys</em>, <em>thingamabobs</em> and <em>whatsits</em>, and unless we have the right quantities and types of each, then a particular kind of widget may not actually be available for sale.</p> <p>(If you just came for the advice, you can <a href="http://interblah.net/#whats-going-on">skip this nonsense</a>.)</p> <p>But those are manufacturing details, and you’re a Rails developer, so all it really means for us is this: <strong>figuring out the set of <em>available</em> widgets involves some necessarily-slow querying and calculations, and there’s no way around it</strong>.</p> <p>In our typical Rails app, here’s the code we have relating to showing availble widgets:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># db/schema.rb</span> <span class="n">create_table</span> <span class="ss">:widgets</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:title</span> <span class="c1"># ...</span> <span class="k">end</span> <span class="c1"># app/models/widget.rb</span> <span class="k">class</span> <span class="nc">Widget</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span> <span class="n">scope</span> <span class="ss">:available</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="c1"># some logic that takes a while to run</span> <span class="p">}</span> <span class="k">end</span> <span class="c1"># app/helpers/widget_helper.rb</span> <span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- app/views/widgets/index.html.erb --&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">available_widgets</span><span class="err">.</span><span class="na">each</span> <span class="na">do</span> <span class="err">|</span><span class="na">widget</span><span class="err">|</span> <span class="err">%</span><span class="nt">&gt;</span> <span class="nt">&lt;h2&gt;&lt;</span><span class="err">%=</span> <span class="na">widget</span><span class="err">.</span><span class="na">name</span> <span class="err">%</span><span class="nt">&gt;&lt;/h2&gt;</span> <span class="nt">&lt;</span><span class="err">%=</span> <span class="na">link_to</span> <span class="err">'</span><span class="na">Buy</span> <span class="na">this</span> <span class="na">widget</span><span class="err">',</span> <span class="na">widget_purchase_path</span><span class="err">(</span><span class="na">widget</span><span class="err">)</span> <span class="err">%</span><span class="nt">&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">end</span> <span class="err">%</span><span class="nt">&gt;</span> </code></pre></div></div> <h3 id="its-so-slow">It’s so slow!</h3> <p>The complication of these widgets isn’t our customer’s concern, and we don’t want them to have to wait for even a second before showing them the set of available widgets that might satisfy their widget-buying needs. But the great news is that the widget component factory only runs at midnight, and does all its work instantaneously<sup id="fnref:fast" role="doc-noteref"><a href="http://interblah.net/#fn:fast" class="footnote">1</a></sup>, so this set is the same for the whole day.</p> <p>So what can we do to avoid having to calculate this set of available widgets every time we want to show the listing to the user, or indeed use that set of widgets anywhere else in the app?</p> <p>You guessed it, you clever Rails developer: <strong>we can cache the set of widgets</strong>!</p> <p>For the purposes of this example, let’s add the caching in the helper:<sup id="fnref:view-caching" role="doc-noteref"><a href="http://interblah.net/#fn:view-caching" class="footnote">2</a></sup></p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"available-widgets"</span><span class="p">,</span> <span class="ss">expires_at: </span><span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">)</span> <span class="k">do</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>Bazinga! Our website is now faster than a <em>ZipWidget</em>, which – believe me – is one of the fastest widgets that we here at WidgetCorp sell, and that’s saying something.</p> <p>Anyway, great work, and I’ll see you tomorrow.</p> <h3 id="the-next-day">The next day</h3> <p>Welcome back! I hope you enjoyed first night in the company dorm! Sure, it’s cosy, but we find that the reduced commute times helps us maximise employee efficiency, and just like they said in those videos, say it with me: “An efficient employee is… a…”</p> <p>… more impactful component in the overall WidgetCorp P&amp;L, that’s right. But listen to me, wasting precious developer seconds. Let’s get to work.</p> <p>So corporate have said they want to store the <em>colour</em> of the widget, because it turns out that some of our customers want to coordinate their widgets, uh, aesthetically I suppose. Anyway, I don’t ask questions, but it seems pretty simple, so let’s add ourselves the column to the database and show it on the widget listing:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># db/schema.rb</span> <span class="n">create_table</span> <span class="ss">:widgets</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:title</span> <span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:colour</span> <span class="c1"># ...</span> <span class="k">end</span> </code></pre></div></div> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- app/views/widgets/index.html.erb --&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">available_widgets</span><span class="err">.</span><span class="na">each</span> <span class="na">do</span> <span class="err">|</span><span class="na">widget</span><span class="err">|</span> <span class="err">%</span><span class="nt">&gt;</span> <span class="nt">&lt;h2&gt;&lt;</span><span class="err">%=</span> <span class="na">widget</span><span class="err">.</span><span class="na">title</span> <span class="err">%</span><span class="nt">&gt;&lt;/h2&gt;</span> <span class="nt">&lt;p&gt;</span>This is a <span class="nt">&lt;</span><span class="err">%=</span> <span class="na">widget</span><span class="err">.</span><span class="na">colour</span> <span class="err">%</span><span class="nt">&gt;</span> widget<span class="nt">&lt;/p&gt;</span> <span class="c">&lt;!-- ... --&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">end</span> <span class="err">%</span><span class="nt">&gt;</span> </code></pre></div></div> <p>… aaaaaaaand <em>deploy</em>.</p> <p>Great! You are a credit to the compa <strong>WHOA</strong> hangon. The site is down. <strong>THE SITE IS DOWN</strong>.</p> <p>Exceptions. Exceptions are pouring in.</p> <p>“Undefined method <code>colour</code>”? What? We ran the migrations right? I’m sure we did. I saw it happen. Look here, I’m running it in the console, the attribute is there. What’s going on? Oh no, the red phone is ringing. You answer it. No, I’m telling you, <em>you</em> answer it.</p> <h2 id="whats-going-on">What’s going on</h2> <p>The reason we see exceptions after the deployment above is that the ActiveRecord objects in our cache are fully-marshalled ruby objects, and due to the way ActiveRecord dynamically defines attribute access, those objects only know about the columns and attributes of the <code>Widget</code> class <em>at the time they entered the cache</em>.</p> <p>And so here’s the point of this silly story: <strong><em>never</em> store objects in your cache whose structure may change over time</strong>. And in a nutshell, that’s pretty much <em>any</em> non-value object:</p> <ul> <li><code>ActiveRecord</code> objects can have attributes change via migrations</li> <li>other objects can change when gems are updated (including Rails), or even when you update the version of Ruby itself.</li> </ul> <h3 id="marshalling-data">Marshalling data</h3> <p>If you take a look at how Rails caching actually works, <a href="https://github.com/rails/rails/blob/6-1-stable/activesupport/lib/active_support/cache.rb#L587-L593">you can see that under the hood</a>, the data to be cached is passed to <code>Marshal.dump</code>, which turns that data into an encoded string.</p> <p>We can see what that looks like here:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">widget</span> <span class="o">=</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s1">'ZipWidget'</span><span class="p">)</span> <span class="err">$</span> <span class="n">data</span> <span class="o">=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">dump</span><span class="p">(</span><span class="n">widget</span><span class="p">)</span> <span class="c1"># \x04\bo:\vWidget\x16:\x10@new_recordF:\x10@attributeso:\x1EActiveModel::AttributeSet\x06;</span> <span class="c1"># \a{\aI\"\aid\x06:\x06ETo:)ActiveModel::Attribute::FromDatabase\n:\n@name@\b:\x1C</span> <span class="c1"># @value_before_type_casti\x06:\n@typeo:\x1FActiveModel::Type::Integer\t:\x0F@precision0</span> <span class="c1"># :\v@scale0:\v@limiti\r:\v@rangeo:\nRange\b:\texclT:\nbeginl-\t\x00\x00\x00\x00\x00\x00\x00\x80:\b</span> <span class="c1"># endl+\t\x00\x00\x00\x00\x00\x00\x00\x80:\x18@original_attribute0:\v@valuei\x06I\"\ntitle\x06;</span> <span class="c1"># \tTo;\n\n;\v@\x0E;\fI\"\x0EZipWidget\x06;\tT;\ro:HActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString\b;</span> <span class="c1"># \x0F0;\x100;\x11i\x01\xFF;\x170;\x18I\"\x0EZipWidget\x06;\tT:\x17@association_cache{\x00:\x11</span> <span class="c1"># @primary_keyI\"\aid\x06;\tT:\x0E@readonlyF:\x0F@destroyedF:\x1C@marked_for_destructionF:\x1E</span> <span class="c1"># @destroyed_by_association0:\x1E@_start_transaction_state0:\x17@transaction_state0:\x17</span> <span class="c1"># @inspection_filtero:#ActiveSupport::ParameterFilter\a:\r@filters[\x00:\n@maskU:</span> <span class="c1"># 'ActiveRecord::Core::InspectionMask[\t:\v__v2__[\x00[\x00I\"\x0F[FILTERED]\x06;\tT:$</span> <span class="c1"># @_new_record_before_last_commitT:\x18@validation_context0:\f@errorsU:\x18</span> <span class="c1"># ActiveModel::Errors[\b@\x00{\x00{\x00:\x13@_touch_recordT:\x1D@mutations_from_database0: </span> <span class="c1"># @mutations_before_last_saveo:*ActiveModel::AttributeMutationTracker\a;\ao;\b\x06;\a{\a</span> <span class="c1"># @\bo:%ActiveModel::Attribute::FromUser\n;\v@\b;\fi\x06;\r@\n;\x17o;\n\n;\v@\b;\f0;\r</span> <span class="c1"># @\n;\x170;\x180;\x18i\x06@\x0Eo;0\n;\v@\x0E;\fI\"\x0EZipWidget\x06;\tT;\r@\x11;\x17o;\n\t;</span> <span class="c1"># \v@\x0E;\f0;\r@\x11;\x170;\x18@\x10:\x14@forced_changeso:\bSet\x06:\n@hash}\x00F</span> </code></pre></div></div> <p>If you look closely in there, you can see some of the values (e.g. the value <code>ZipWidget</code>), but there’s plenty of other information about the specific structure and implementation of the ActiveRecord instance – particularly about the model’s understanding of the database – that’s encoded into that dump.</p> <p>You can revive the object by using <code>Marshal.load</code>:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">cached_widget</span> <span class="o">=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># =&gt; #&lt;Widget id: 1, title: "ZipWidget"&gt;</span> </code></pre></div></div> <p>And that works great, until you try and use any <em>new</em> attributes that might exist in the database. Let’s add the <code>colour</code> column, just using the console for convenience:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="p">.</span><span class="nf">add_column</span> <span class="ss">:widgets</span><span class="p">,</span> <span class="ss">:colour</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">default: </span><span class="s1">'beige'</span> </code></pre></div></div> <p>We can check this all works and that our widget in the database gets the right value:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">colour</span> <span class="c1"># =&gt; 'beige'</span> </code></pre></div></div> <p>But what if we try and use that same widget, but from the cache:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">cached_widget</span> <span class="o">=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># =&gt; #&lt;Widget id: 1, title: "ZipWidget"&gt;</span> <span class="err">$</span> <span class="n">cached_widget</span><span class="p">.</span><span class="nf">colour</span> <span class="no">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span> <span class="mi">1</span><span class="p">:</span> <span class="n">from</span> <span class="p">(</span><span class="n">irb</span><span class="p">):</span><span class="mi">14</span> <span class="no">NoMethodError</span> <span class="p">(</span><span class="n">undefined</span> <span class="nb">method</span> <span class="sb">`colour' for #&lt;Widget id: 1, title: "ZipWidget"&gt;) </span></code></pre></div></div> <p>Boom. The cached instance thinks it already knows everything about the schema that’s relevant, so when we try to invoke a method from a schema change, we get an error.</p> <h3 id="workarounds">Workarounds</h3> <p>The only ways to fix this are</p> <ul> <li>clearing your cache, losing all the benefits of it until it’s populated again</li> <li>reloading the object after it’s retrieved from the cache, again losing many of the benefits of it being cached</li> <li>or by adding behaviour to anything that <em>calls</em> <code>Widget#colour</code>, to handle the raised exception if it’s missing.</li> </ul> <p>Not great.</p> <p>But there’s a better solution, which is avoiding this issue in the first place.</p> <h2 id="store-ids-not-objects">Store IDs, not objects</h2> <p>Sometimes it is useful to take a step back from the code and think about what we are trying to acheive. We want to quickly return the set of available widgets, but calculating that set takes a long time. However, there’s a difference between <em>calculating</em> the set, and <em>loading</em> that set from the database. Once we know which objects are a part of the set, actually loading those records is likely to be pretty fast – databases are pretty good at those kinds of queries.</p> <p>So we don’t actually need to cache the fully loaded objects; we just need to cache something that lets us quickly load the <em>right</em> objects – their unique <code>id</code>s.</p> <p>Here’s my proposed fix for WidgetCorp:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="n">ids</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'available-widgets'</span><span class="p">,</span> <span class="ss">expires_at: </span><span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">)</span> <span class="k">do</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span><span class="p">.</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">)</span> <span class="k">end</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">ids: </span><span class="n">ids</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>Yes, it’s less elegant that neatly wrapping the slow code in a cache block, but the alternative is a world of brittle cache data and deployment fragility and pain. If a new column is added to the <code>widgets</code> table, nothing breaks because we’re not caching anything but the IDs.</p> <h2 id="bonus-code-defend-your-app-from-brittle-cache-objects">Bonus code: defend your app from brittle cache objects</h2> <p>It can be easy to forget this when you’re building new features. This is the kind of thing that only bites in production, and it can happen years after the unfortunate cache entered use.</p> <p>So the best way of making sure to avoid it, is to have something in your CI build that automatically checks for these kinds of records entering your cache.</p> <p>In your <code>config/environments/test.rb</code> file, find the line that controls the cache store:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/environments/test.rb</span> <span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:null</span> </code></pre></div></div> <p>and change it to this:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/environments/test.rb</span> <span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="no">NullStoreVerifyingAcceptableData</span><span class="p">.</span><span class="nf">new</span> </code></pre></div></div> <p>And in <code>lib</code>, create this class:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># lib/null_store_verifying_acceptable_data.rb</span> <span class="k">class</span> <span class="nc">NullStoreVerifyingAcceptableData</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Cache</span><span class="o">::</span><span class="no">NullStore</span> <span class="k">class</span> <span class="nc">InvalidDataException</span> <span class="o">&lt;</span> <span class="no">Exception</span><span class="p">;</span> <span class="k">end</span> <span class="kp">private</span> <span class="k">def</span> <span class="nf">write_entry</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">entry</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">)</span> <span class="n">check_value</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="nf">value</span><span class="p">)</span> <span class="kp">true</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">check_value</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">)</span> <span class="o">||</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Relation</span><span class="p">)</span> <span class="k">raise</span> <span class="no">InvalidDataException</span><span class="p">,</span> <span class="s2">"Tried to store </span><span class="si">#{</span><span class="n">value</span><span class="p">.</span><span class="nf">class</span><span class="si">}</span><span class="s2"> in a cache. We cannot do this because \ the behaviour/schema may change between this value being stored, and \ it being retrieved. Please store IDs instead."</span> <span class="k">elsif</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span> <span class="o">||</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Set</span><span class="p">)</span> <span class="n">value</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="n">check_value</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">}</span> <span class="k">elsif</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="n">value</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">flatten</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="n">check_value</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">}</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>With this in place, when your build runs any test that exercises behaviour that ends up trying to persist ActiveRecord objects into the cache will raise an exception, causing the test to fail with an explanatory message.</p> <p>You can extend this to include other classes if you have other objects that may change their interface over time.</p> <h3 id="double-bonus-tests-for-nullstoreverifyingacceptabledata">Double bonus: tests for <code>NullStoreVerifyingAcceptableData</code></h3> <p>How can we be confident that the new cache store is actually going to warn us about behaviours in the test? What if it never ever raises the exception?</p> <p>When I’m introducing non-trivial behaviour into my test suite, I like to test <em>that</em> too. So here’s some tests to sit alongside this new cache store, so we can be confident that we can actually rely on it.</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'test_helper'</span> <span class="k">class</span> <span class="nc">NullStoreVerifyingAcceptableDataTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span> <span class="n">setup</span> <span class="k">do</span> <span class="vi">@typical_app_class</span> <span class="o">=</span> <span class="no">Widget</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises exception when we try to cache an ActiveRecord object'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache a relation'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache an array of ActiveRecord objects'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="p">[</span><span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">]</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache a Set of ActiveRecord objects'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span><span class="p">([</span><span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">])</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache a Hash that contains ActiveRecord objects'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="p">{</span><span class="ss">value: </span><span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">}</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'does not raise anything when caching an ID'</span> <span class="k">do</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">random_key</span><span class="p">)</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">id</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'does not raise anything when caching an array of IDs'</span> <span class="k">do</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">random_key</span><span class="p">)</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">(</span><span class="mi">5</span><span class="p">).</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">)</span> <span class="p">}</span> <span class="k">end</span> <span class="kp">private</span> <span class="k">def</span> <span class="nf">assert_raises_when_caching</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="n">assert_raises</span> <span class="no">NullStoreVerifyingAcceptableData</span><span class="o">::</span><span class="no">InvalidDataException</span> <span class="k">do</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">random_key</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">random_key</span> <span class="no">SecureRandom</span><span class="p">.</span><span class="nf">hex</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>If you extend the cache validator to check for other types of objects, you can add tests to make sure those changes work as you expect.</p> <h3 id="triple-bonus-code-what-if-you-already-have-cached-objects">Triple-bonus code: what if you already have cached objects?</h3> <p>My <em>real</em> fix for WidgetCorp would actually try to mitigate this issue while still respecting the (possibly broken) objects still in the cache:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="n">ids_or_objects</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'available-widets'</span><span class="p">,</span> <span class="ss">expires_at: </span><span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">)</span> <span class="k">do</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span><span class="p">.</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">)</span> <span class="k">end</span> <span class="n">ids</span> <span class="o">=</span> <span class="k">if</span> <span class="n">ids_or_objects</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">present?</span> <span class="o">&amp;&amp;</span> <span class="n">ids_or_objects</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">)</span> <span class="n">ids_or_objects</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">)</span> <span class="k">else</span> <span class="n">ids_or_objects</span> <span class="k">end</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">id: </span><span class="n">ids</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>This allows us to still use the ActiveRecord instances that are cached, while storing any new data in the cache as just IDs. Once this code has been running for a day, all of the existing data in the cache will have expired and the implementation can be changed to <a href="http://interblah.net/#store-ids-not-objects">the simpler one</a>.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:fast" role="doc-endnote"> <p>Pocket dimension, closed timelike curves, above your paygrade, don’t worry about it! <a href="http://interblah.net/#fnref:fast" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:view-caching" role="doc-endnote"> <p>You might imagine we could get around all this by caching the view, and indeed in this case, it wouldn’t raise the same problems, but in a typically mature Rails application we end up using cached data in other places outside of view generation, so the overall point is worth making. Still, award yourself 1 gold credit. <a href="http://interblah.net/#fnref:view-caching" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Two thoughts about maintainable software tag:interblah.net,2020-11-06:/two-thoughts-about-maintainable-software 2020-11-06T14:00:00+00:00 2020-11-06T14:00:00+00:00 interblah.net <p>I really enjoyed <a href="https://www.maintainable.fm/episodes/glenn-vanderburg-dont-ask-for-small-things">the most recent episode</a> of <a href="http://interblah.net/robby-russell">Robby Russell</a>’s podcast “<a href="https://www.maintainable.fm">Maintainable</a>”, in which <a href="https://twitter.com/glv">Glenn Vanderburg</a><sup id="fnref:glenn" role="doc-noteref"><a href="http://interblah.net/#fn:glenn" class="footnote">1</a></sup> talks about capturing explanations about code and systems, along with managing technical debt. It’s a great episode, and I highly recommend pouring it into your ears using whatever software you use to gather pods.</p> <p>The whole thing is good, but there were two specific points brought up fairly early in their conversation that got me thinking. What a great excuse to write a post here, and make me feel less guilty about potentially <em>never</em> publishing the Squirrel Simulator 2000 post I had promised.</p> <p>I know; I’m sorry. Anyway, here’s some tangible words instead of that <em>tease</em>. Enjoy!</p> <h2 id="documenting-the-why">Documenting the why</h2> <p>At the start of the conversation, Robby asks Glenn what he thinks makes “maintainable” software, and part of Glenn’s response is that there should be comments or documents that explain <em>why</em> the system is designed the way it is.</p> <blockquote> <p>Sometimes things will be surprisingly complex to a newcomer, and it’s because you tried something simple and you learned that that wasn’t enough. Or sometimes, things will be surprisingly simple, and it’s good to document that “oh, well we thought we needed it to be more complicated, but it works fine for [various] reasons.”</p> </blockquote> <p>Robby goes on to ask how developers might get better at communicating the “why” – should it be by pointing developers at tickets, or user stories somewhere, or it should be using comments in the code, or maybe some other area where things get documented? And without wanting to directly criticise Glenn’s answer, it brought to mind something that I have come to strongly believe during my career writing software so far: the right place to explain the <em>why</em> is the <strong>commit message</strong>.</p> <h3 id="but-why-not-comments">But why not comments?</h3> <p>I’ve lost count of the number of times that I’ve found a comment in code and realised it was wrong. We just aren’t great at remembering to keep them updated, and sometimes it’s not clear when a change in the implementation will reduce the accuracy of the comment.</p> <h3 id="but-its-already-fully-explained-in-the-user-storybasecamp-threadtrello-card">But it’s already fully explained in the user story/Basecamp thread/Trello card…</h3> <p>I’ve also lost count of the number of times that a commit message contains little more than a link, be it to a defunct Basecamp project, or a since-deleted Trello card, or am irretrievable Lighthouse ticket, or whatever system happened to be in use ten years ago, but is almost certainly archived or gone now, even if the service is still running.</p> <p>And even if we are lucky and that external service <em>is</em> still running, often these artefacts are discussions where a feature or change or bug is explored and understood and evolved. The initial description of a feature or a bug might not reflect the version we are building right now, so do we really want to ask other developers and our future selves to have to go back to that thread/card/ticket and re-synthesise all those comments and questions, every time they have a question about this code?</p> <h3 id="put-it-in-the-commit-message">Put it in the commit message</h3> <p>Comments rot. External systems, over the timescales of a significant software product, are essentially ephemeral. For software to be truly maintainable, it needs to carry as much of the explanation of the why <em>along with it</em>, and where the code itself cannot communicate that, the only place were an explanation doesn’t rot is where it’s completely bound to the implementation at that momment in time. And that’s exactly what the commit message is.</p> <p>So whenever I’m writing a commit, I try and capture as much of the “why” as I can. Yes, I write <a href="https://github.com/norman/friendly_id/commit/4bd4300035b5c250aeb2e5feec4c2feb9bcf2a19">multi-paragraph</a> <a href="https://github.com/gtd/validation_scopes/commit/2c9139af94239444575e1f413b9303badd149eb6">commit</a> <a href="https://github.com/rails/rails/commit/d44b628ad0be4791180eb70863cfe5392ec0f177">messages</a>. The first commit for a new feature typically includes an explanation of why we’re building it at all, what we hope it will do and so on. Where locally I’ve tried out a few approaches before settling on the one I prefer, I try and capture those other options (and why they weren’t selected) inside the commit message for the approach I <em>did</em> pick.</p> <p>Conversely, if I am looking at a line of code or a method and I’m not sure about the <em>why</em> of it, I reach for <code>git blame</code>, which immediately shows me the last time it was touched, and (using <a href="https://magit.vc">magit</a> or any good IDE) I can easily step forward and back and understand the origin and evolution of that code – as long as we’ve taken the time to capture the <em>why</em> for each of those changes. And this applies just as much to the code that I wrote as it does to other developers. <em>Future-me</em> often has no idea why I made certain choices – sometimes I can barely remember writing the code at all!</p> <p>So anyway, in a nutshell, when you’re committing code, try to imagine someone sitting beside you and asking “why?” a few times, and capture what your explanation would be in your commit message.</p> <p><em>BONUS TIP</em>: finding that your “why” explanation is a bit too long? That’s probably a signal that you need to make smaller commits, and take smaller steps implementing your feature. I’ve never regretted taking smaller steps, even though I recognise sometimes the desire to “just get it done” can be strong.</p> <h2 id="using-tests-as-todos">Using tests as TODOs</h2> <p>A little later, the conversation turns to the merits of “TODO” comments in the code, and how to handle time-based issues where code needs to exist but perhaps only for a certain amount of time in its current form, before it need to be revisited. It’s not unusual for startups to need to get something into production quickly, even if they <em>know</em> that they are spending technical debt in doing so. Glenn correctly points out that it can be hard to make sure that comments like that get attention in the future when they should.</p> <blockquote> <p>The problem is that when you put them in there, there’s no good way to make sure you pay attention when the time comes [to address the debt]</p> </blockquote> <p>I spend a fair bit of my professional time working with a startup that has a very mature (13 years old) Rails codebase, and more often than not, moving quickly involves <em>disabling</em> certain features temporarily, as well as adding new ones. For example, on occasion we might need to disable the regular newsletter mechanism and background workers while the company runs a one-off marketing campaign. To achieve this, we need to disable or modify some parts of the system temporarily, but ensure that we put them back in place once the one-off campaign is completed.</p> <p>In situations like this, I have found that we can use the tests themselves to remind us to re-enable the regular code. Here’s a very simple test helper method:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">skip_until</span><span class="p">(</span><span class="n">date_as_string</span><span class="p">,</span> <span class="n">justification</span><span class="p">)</span> <span class="n">skip</span><span class="p">(</span><span class="n">justification</span><span class="p">)</span> <span class="k">unless</span> <span class="no">Date</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">date_as_string</span><span class="p">).</span><span class="nf">past?</span> <span class="k">end</span> </code></pre></div></div> <p>Becase we have tests that check the behaviour of our newsletter system under normal conditions, when we disable the implementation temporarily, these tests will naturally fail, but we can use this helper to avoid the false-negative test failures in our continuous-integration builds, <em>without</em> having to delete the tests entirely and then remember to re-add them later:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">NewsletterWorkerTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span> <span class="n">should</span> <span class="s2">"deliver newsletter to users"</span> <span class="k">do</span> <span class="n">skip_until</span><span class="p">(</span><span class="s1">'2020-12-01'</span><span class="p">,</span> <span class="s1">'newsletters are disabled while Campaign X runs'</span><span class="p">)</span> <span class="c1"># original test body follows</span> <span class="k">end</span> <span class="c1"># other tests </span> <span class="k">end</span> </code></pre></div></div> <p>This way, we get a clear signal from the build when we need to re-enable something, without the use of any easy-to-ignore comments at all.</p> <h2 id="not-all-comments">Not all comments?</h2> <p>I’m not completely opposed to all comments, but it’s become very clear to me that they aren’t the best way to explain either what some code does, or why it exists. If we can use code itself to describe <em>why</em> something exists, or is disabled – and then remind us about it, if appropriate – then why not take advantage of that?</p> <p>And if there’s only one thing you take away from all the above, let it be this: <strong>take the time to write good commit messages</strong>, as if you are trying to answer the questions you could imagine another developer asking if they were sat beside you. You’ll save time in code reviews, and you’ll save time in the future, since you’ll be able to understand the context of that code, the choices and tradeoffs that were made while writing it – whe <em>why</em> of it – much faster than otherwise.</p> <p>Here’s a great talk that further illustrates the value of good commit messages, and good commit hygiene in general: <a href="https://tekin.co.uk/2019/02/a-talk-about-revision-histories">A Branch in Time</a> by <a href="https://tekin.co.uk">Tekin Süleyman</a>.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:glenn" role="doc-endnote"> <p>I met Glenn once, at a dinner during the <a href="http://interblah.net/rubyfools-redux">Ruby Fools</a> conference (such a long time ago now). He said some very nice things to me over that dinner, which I’ve never forgotten. So thanks for that, Glenn! <a href="http://interblah.net/#fnref:glenn" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> LGTM tag:interblah.net,2020-07-28:/a-few-thoughts-on-code-review 2020-07-28T22:52:00+01:00 2020-07-28T22:52:00+01:00 james <p>There are a number of techniques that remote teams can use to collaborate, but the main one <em>the client</em><sup id="fnref:client" role="doc-noteref"><a href="http://interblah.net/#fn:client" class="footnote">1</a></sup> relies on is code review via pull requests.</p> <p>Code review on pull requests can help catch potential issues, but it’s not as good as actual TDD (or any other conscientous testing practice) because once implementation is written, it’s hard to evaluate what aspects of it are intended behaviour and which are side effects, or even bugs.</p> <p>Code review on pull requests can be good for getting a second opinion on a specific implementation, but they’re not as good as pairing, where ideas and approaches can be discussed and evaluated before any investment is made, and hard-to-spot edge cases can be identified before they are committed into code.</p> <p>Code review on pull requests is good for communicating between developers about what everyone is working on, but they’re not as good as actual discussion, ideally documented, because without conscious effort to capture context – the <em>why</em> this is being built, and <em>why</em> in this particular way – in either the tests, the commit messages, the pull request description, or ideally <strong>all three</strong>, it’s often up to the reviewer to try and reverse-engineer the intent and original goals of the piece of work. It’s easy to underestimate how much effort that reverse-engineering can be.</p> <p>Code review on pull requests is good for avoiding unending, ambigious conversations about what <em>might</em> work to solve a problem, because it’s always easier to measure the value of something concrete than something hypothetical, but by the time the review happens there’s already investment in that particular solution and that particular implementation of that solution. I see the same thing with MVPs, which should be built quickly without particular care, then learned from and finally discarded, but instead are built quickly and without particular care, but then put into production to become the foundation for future work… but I’m digressing; the point is that we developers tend to believe that extant code has more value than it really does, and the momentum of that particular approach can override any fundamental questions<sup id="fnref:approach" role="doc-noteref"><a href="http://interblah.net/#fn:approach" class="footnote">2</a></sup> that a code review might raise.</p> <p><em>But</em>… getting good at testing requires effort, and pairing requires a lot more energy than hacking solo, and thoughtfully documenting takes time, and development zen-like detachment from the fruits of our labour doesn’t come naturally either. And all these things require practice, and who has time to practice when there are features to ship!</p> <p>So we do code reviews on pull requests. LGTM<sup id="fnref:lgtm" role="doc-noteref"><a href="http://interblah.net/#fn:lgtm" class="footnote">3</a></sup> :(</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:client" role="doc-endnote"> <p>At the moment, I spend most of my active development time working remotely for a single client. It’s a Rails project that’s been running for approximately 13 years, with around 5 developers of varying skill levels actively contributing to the backend, where I spend most of my time. <a href="http://interblah.net/#fnref:client" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:approach" role="doc-endnote"> <p>“Did you consider, maybe, not implementing this at all?” <a href="http://interblah.net/#fnref:approach" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:lgtm" role="doc-endnote"> <p>“Looks Good To Me”, the hallmark sign that a reviewer has not even checked out and run the code, let along read it carefully and thought about more than syntax. <a href="http://interblah.net/#fnref:lgtm" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> I am an omakase chef tag:interblah.net,2020-07-20:/i-am-an-omakase-chef 2020-07-20T22:32:00+01:00 2020-07-20T22:32:00+01:00 interblah.net <p>I run a restaurant with a single seat, and a single sitting, normally around 10pm.</p> <p>There is only one item on my menu.</p> <p>At that time, my sole patron arrives and sits, watching me while I set to work, my hands moving slowly and deliberately with the practice of hundreds of prior repetitions.</p> <p>The pill is carefully broken in half, and the remainder stashed back in it’s blister.</p> <p>A small piece of mouldable savoury putty is selected, and carefully shaped around the pill, such that it fully surrounds it, but with the pill off-center, leaving a larger amount of putty at one end.</p> <p>A Dreamie<sup id="fnref:dreamies" role="doc-noteref"><a href="http://interblah.net/#fn:dreamies" class="footnote">1</a></sup> is selected, and is carefully split, and one half is held back in the hand while the other is delicately moulded into the waiting putty, such that the whole can now sit with the Dreamies-half on top, sitting on the putty with the pill hidden directly beneath it. This completes the dish. It is a Dreamie nigiri.</p> <p>The small morsel is finally placed in front of the diner, who considers it briefly, then eats, and then leaves.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:dreamies" role="doc-endnote"> <p>The singular of “<a href="https://www.dreamiestreats.co.uk/">Dreamies</a>” is basically unknowable. <a href="http://interblah.net/#fnref:dreamies" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Back on the wagon tag:interblah.net,2020-07-10:/back-on-the-wagon 2020-07-10T11:38:08+01:00 2020-07-10T11:38:08+01:00 interblah.net <p>Hey, it’s been a while. Sorry about that.</p> <p>For the past few months I’ve increasingly felt the tug to express thoughts and such, but while most of the world now does that on Twitter, as with Facebook I don’t feel like I can participate on there without condoning some of the awful aspects of social media.</p> <p>So I’m resurrecting this site, in the hopes that I can write a bit more here to scratch that itch.</p> <p>Obviously there was no way I could possibly resume writing here without a total overhaul of the design and backend. And then having spent days on it, learning <a href="http://tachyons.io">Tachyons</a> in the process, I flicked back to the previous design and, y’know what, it actually wasn’t that bad. But anyway, we’re here now.</p> <p>Speaking of Tachyons, I did read an interesting article about <a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/">CSS utility classes and “separation of concerns”</a> that has finally helped me understand the motivations that have driven front-end developers away from “semantic” CSS. As primarily a backend developer, I struggle to shake the dream of writing simple markup for a “thing” and then having different CSS render it appropriately in whatever contexts it might appear, but this helped me understand the journey. I want to re-read that article, because I have a hunch that the destination it describes isn’t actually that far away from the “semantic” one I hope for, just that the CSS might end up being dynamically composed from utility classes by a pre-processor. Maybe.</p> <p>Some random thoughts I’ve had on return to this site and the codebase behind it:</p> <ul> <li>At the start of this resurrection process, I wondered why I ever bothered writing my own software to run it, but as I got stuck in, once again I enjoyed the simpicity and flexibility that <a href="http://interblah.net/vanilla">Vanilla</a> affords. Building little components like the sidebar on the front page is easy.</li> <li>During the redesign, I wanted to avoid as much hand-written CSS as possible; I just want something that automatically provides a consistent and clean typographic layout with decent line heights and readability. I’m not sure I would call what I’ve achieved “beautiful”, but it’s not awful.</li> <li>There is a <em>lot</em> of outdated content here. The pages about how this site runs are way out of date (it’s now in a Docker container under <a href="http://interblah.net/dokku">Dokku</a> on a VPS, and has been for years). I have now switched to <a href="http://interblah.net/doom-emacs">Doom Emacs</a>, which is similar to <a href="http://interblah.net/spacemacs">Spacemacs</a>. Beyond that, the last real writing is six years old, or more, and I don’t entirely recognise many of those opinions as mine anymore. :shrug:!</li> <li><em>Note to self: figure out some way of getting emojis to render like GitHub and Slack.</em></li> <li>A bunch of snips refer to commenting, which I turned off a long time ago. I should really remove references to that, and potentially embrace <em>webmentions</em> like <a href="https://jamesmead.org/blog/2020-06-27-indieweb-ifying-my-personal-website">James</a> did recently. Or I could allow commenting via Twitter? Or even from/to Mastodon via ActivityPub?</li> </ul> <p>Anyway, I’m back. Or am I? I think I am. I hope I am.</p> <p>I’m back.</p> Getting Started with Spacemacs tag:interblah.net,2018-09-20:/getting-started-with-spacemacs 2018-09-20T15:17:14+00:00 2018-09-20T15:17:14+00:00 interblah.net <p>I wanted to capture what I like about my editor of choice at the moment. If you’re at all interested in modal editing, or are an experienced Vim user looking for a change, I think there’s a chance you might enjoy Spacemacs. I’ve written this to try and capture what I like about it, and some of the things I wish I’d known when I got started. Hopefully it’s useful to some of you!</p> <p><img src="http://interblah.net/images/spacemacs/spacemacs.png" alt="Spacemacs" /></p> <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> <h2 id="table-of-contents">Table of Contents</h2> <ul> <li><a href="http://interblah.net/#my-history-with-text-editors">My history with text editors</a></li> <li><a href="http://interblah.net/#why-spacemacs">Why Spacemacs?</a> <ul> <li><a href="http://interblah.net/#modal-editing">Modal editing</a> <ul> <li><a href="http://interblah.net/#-but-only-if-you-want-it">… but only if you want it</a></li> </ul> </li> <li><a href="http://interblah.net/#conventionality-and-discoverability-via-which-key">Conventionality and discoverability via <code>which-key</code></a> <ul> <li><a href="http://interblah.net/#discovering-new-functions-and-key-bindings">Discovering new functions and key bindings</a></li> <li><a href="http://interblah.net/#layers-and-conventionality">Layers and conventionality</a></li> </ul> </li> <li><a href="http://interblah.net/#magit">Magit</a></li> </ul> </li> <li><a href="http://interblah.net/#so-getting-started">So: Getting started</a> <ul> <li><a href="http://interblah.net/#installation">Installation</a> <ul> <li><a href="http://interblah.net/#ripgrep">Ripgrep</a></li> <li><a href="http://interblah.net/#getting-spacemacs">Getting Spacemacs</a></li> </ul> </li> <li><a href="http://interblah.net/#configuring-your-installation">Configuring your installation</a> <ul> <li><a href="http://interblah.net/#layers">Layers</a></li> <li><a href="http://interblah.net/#other-settings">Other settings</a></li> </ul> </li> <li><a href="http://interblah.net/#youre-a-vimmer-now-arry">You’re a vimmer now, ‘arry</a></li> <li><a href="http://interblah.net/#editing-files-and-projects">Editing files and projects</a></li> </ul> </li> <li><a href="http://interblah.net/#cheat-sheet">Cheat Sheet</a> <ul> <li><a href="http://interblah.net/#modal-editing-1">Modal editing</a></li> <li><a href="http://interblah.net/#general">General</a></li> <li><a href="http://interblah.net/#files-projects-and-buffers">Files, projects and buffers</a></li> <li><a href="http://interblah.net/#windows">Windows</a></li> <li><a href="http://interblah.net/#searching">Searching</a></li> <li><a href="http://interblah.net/#editing">Editing</a></li> <li><a href="http://interblah.net/#development-specific-stuff">Development specific stuff</a></li> <li><a href="http://interblah.net/#git">Git</a></li> <li><a href="http://interblah.net/#spacemacs-itself">Spacemacs itself</a></li> <li><a href="http://interblah.net/#documentation-and-help">Documentation and help</a></li> </ul> </li> <li><a href="http://interblah.net/#help-im-stuck">Help, I’m Stuck!</a></li> <li><a href="http://interblah.net/#other-notes">Other Notes</a> <!-- markdown-toc end --></li> </ul> <h2 id="my-history-with-text-editors">My history with text editors</h2> <p>Probably every 18 months or so, I seem to get an itch about my current editor, and look around for something different. Often it’s because something is behaving slowly, or I can’t get a feature to work. My editors-of-choice over the last 15 years have been, roughly:</p> <ul> <li><a href="https://macromates.com">TextMate</a>, because I saw it in the original Rails demo and loved how simple yet powerful it was, particularly the plugins. I have license #12 for TextMate. I used TextMate for a long time.</li> <li><a href="https://www.sublimetext.com">Sublime Text</a>, because I saw multiple cursors support. I wasn’t thrilled to switch to Python-based plugins, but knew enough to figure most stuff out.</li> <li><a href="https://atom.io">Atom</a>, because SublimeText had started to show weird slow-down that I couldn’t resolve, even by wiping my configuration.</li> <li>Sublime Text again, because Atom started <em>really</em> slowly, and the previous slowdown seemed to have resolved itself.</li> <li><a href="https://www.vim.org">Vim</a>, because a bunch of other people had switched recently and I wanted to see what all the fuss was about. Prior to this I’d only tried Vim a few times and really hated modal editing, but this stint was around 6 months and finally did whatever brain reconfiguration was required to make modal editing make sense to me. Probably I just learned enough of the movement keys to finally feel able to step around files without feeling like I was pecking at the keyboard.</li> <li>Atom (again), because they’d focussed a lot on speed recently, and I felt overwhelmed by all the arcane commands and key combinations I needed to learn to use most of the Vim plugins, and because I found <em>exploring</em> projects in Vim to require a lot of energy for me. Sometimes I want to just be able to use a mouse to click around a project structure!</li> <li><a href="https://github.com/syl20bnr/spacemacs">Spacemacs</a>, because Atom was still relatively slow to launch, and I had hit an annoying edge case in the project search where searching within bundled gems didn’t work smoothly.</li> </ul> <p>I’m sure all of the issues I experienced were largely <em>my</em> fault, rather than any problem with any of these editors, and I’m totally certain that any frustrations I experienced with any of them could be resolved with enough time and patience. However, when I’m sitting down to work on a project that already requires a lot of though, I don’t want to have to give up a lot of that energy to my editor; I need to be able to get it to do what I would like, with a minimum of energy, headscratching, or waiting for a spinny icon to finish.</p> <h2 id="why-spacemacs">Why Spacemacs?</h2> <p>Here’s what I really like about Spacemacs:</p> <h3 id="modal-editing">Modal editing</h3> <p>Much as I was <a href="http://interblah.net/vi">initially resistant</a> to it, putting all that work into using Vim for half a year did finally sink in, and I can pretty happily jump around within a line, or up and down blocks of code, without having to think too hard about it. I don’t know if I would entirely recant my rant, but the simple fact is that I’ve made the sacrifice and my brain has already been trained, so I might as well accept it. I don’t think I’ll ever be zealatous about the benefits of keeping your hands on the keyboard or anything like that, but I can say that once you <em>do</em> get a handle on movement and text manipulation commands, it doesn’t feel too bad.</p> <h4 id="but-only-if-you-want-it">… but only if you want it</h4> <p>Sometimes though, as a beginner, it’s just faster to use the mouse, or the arrow keys. And Spacemacs totally supports that. If I’m in a bad mood and don’t want to learn the modal way to do something, I can drag and select text, or jump the cursor around, and it just works. I can click on pretty much anything, from files and directories in the file tree, to links in a markdown document, and they do what you’d expect.</p> <p>It’s this graceful support for non-modal mechanisms that makes it easy to stick with a modal editor through the rough times and the smooth.</p> <h3 id="conventionality-and-discoverability-via-which-key">Conventionality and discoverability via <code>which-key</code></h3> <p>The biggest barrier to me sticking with Vim was feeling like “I know there’s a way to do this better” but having to enter the depths of the Google mines to figure out how. Vim is super-extensible and customisable, but the flip-side of that is that everyone has their own setup and key-bindings and so until you are an expert, you end up with a cobbled together configuration of stuff you’ve found on the internet. As I said above, I’m <em>sure</em> that eventually you would get a handle on this and be able to smooth any rough edges, but I think it’s a significant barrier for people who are already trying to internalise a whole new way of editing.</p> <p>With Spacemacs, the <em>main</em> way you invoke commands is by hitting the spacebar. Press that nice big giant button and after about half a second, a menu appears with a whole bunch of options about what you might press next, with descriptions of what those are.</p> <p><img src="http://interblah.net/images/spacemacs/menu.png" alt="The menu, invoked via the spacebar" title="Hit space to access the power!" /></p> <p>The commands are organised into sections, all based roughly on mnemonic groupings of what those functions do. So, if you want to do something with a file, chances are that it’s somewhere under the <code>f</code> section. If you want to change the project you’re working on, maybe try <code>p</code>. Want to search for something? Chances are it’ll be under <code>s</code>. Now these mnemonics aren’t perfect (“text” stuff is under <code>x</code> because <code>t</code> was used for “toggles”), but the descriptions are all <em>right there</em>, so the more you look at the menu, the more you internalise which section is which.</p> <p>Once you get more comfortable, you often don’t need to look at the menu at all, because the combinations become internalised. I don’t need to look at the menu when I want to rename a file, because seeing the menu again and again has helped me learn that it’s <code>SPC f R</code>. Checking the git status of the project is <code>SPC g s</code>. What Spacemacs and <code>which-key</code> do really well is support the learning process, and get you to a point where you can invoke all these commands quickly.</p> <h4 id="discovering-new-functions-and-key-bindings">Discovering new functions and key bindings</h4> <p>But it gets even better than that, because you don’t even need to dig around all the the menus until you find the command you were hoping for. If you have an inkling that there should be a more efficient way of, say, making some text uppercase without having to delete and re-type it, you can easily search <em>all of the commands</em> in the editor by hitting <code>SPC SPC</code> and then making a few guesses about what the command name might be. I hit <code>SPC SPC</code> and type “upper”, but none of those look right, so let’s try “upcase”, and bingo, a handful of functions that I might want to use.</p> <p><img src="http://interblah.net/images/spacemacs/upper.gif" alt="Exploring the functions in Spacemacs" /></p> <p>I can invoke one by hitting <code>RET</code>, but the menu also tells me what (if any) key conbination would run that function automatically, so if I end up doing this enough times, I’ll probably start trying that combo, and eventually not need to look it up ever again. Every instance of this helps teach me.</p> <p>Showing the keybindings also shows me where in the menu I would’ve found that key, which over time also gives me clues about where to search for something the next time (“upcase region” is <code>SPC x U</code>, so perhaps there are other text case changing functions under <code>SPC x</code>…).</p> <p>It also shows me when there are even faster ways of invoking the function using the “evil” bindings, in this case <code>g U</code>. Perhaps I’ll try that out, and if I try it enough times, it gets into my muscle memory, and I get faster.</p> <h4 id="layers-and-conventionality">Layers and conventionality</h4> <p>Learning how the menus are organised also leads to what I mean by “convenionality”. But before I explain this, I need to touch on how Spacemacs is organised. Rather than having you install lots of small Emacs packages (what you might consider plugins), Spacemacs gathers collections of related packages into what it calls “layers”. Every Spacemacs layer comes with a set of key bindings, which the community as a whole has developed together, gathering all of the features and functions of the packages within that layer together. So when you install the <a href="http://spacemacs.org/layers/+lang/ruby/README.html" title="Documentation for the Ruby layer in Spacemacs">“ruby” layer</a>, you get keybindings to run tests, to invoke bundler, to do simple refactorings, to use your preferred version manager, and so on, all ready for you to use and explorable via the menu and function search.</p> <p>This is not to say that you can’t change things – you totally can! – but you don’t need to. There are similar distributions for other editors that enable similar “sensible” configurations, so this is not to say that Spacemacs is any better than, say, <a href="https://github.com/carlhuda/janus">Janus</a> for vim. but for any of these powerful editors, having <em>something</em> that provides this kind of support is really great.</p> <h3 id="magit">Magit</h3> <p>From what I can tell, a lot of the love for Emacs comes from people talking about how <code>org-mode</code> is amazing and can do anything and it’s the greatest thing in the world, and that it’s worth switching to Emacs just to be able to harness the awesome power of <code>org</code><sup id="fnref:borg" role="doc-noteref"><a href="http://interblah.net/#fn:borg" class="footnote">1</a></sup>. That might be true. I have not used it. I might discover how to use it tomorrow and achieve a similar text-based nirvana.</p> <p>As a programmer though, there’s a different tool which has me <em>totally</em> sold on Emacs, and that’s <a href="https://magit.vc" title="Magit, the git package for Emacs">Magit</a>, a package for working with the Git version control system.</p> <p>I used to be a command-line Git jockey. I’d use <code>git add .</code> and <code>git commit -m "Blah blah"</code>, and even run interactive rebasing and patch-based adding from the command line. But if you care about creating a great git history, <a href="https://vimeo.com/280579162#t=1017s" title="The relevant section from a great talk by Tekin Suleyman">using git from the command line is working against you</a>.</p> <p>Magit makes it really easy for me to pick files to stage, or even just pick a few lines from those files, and commit just those changes, writing a great commit message while I do, right in my editor.</p> <p><img src="http://interblah.net/images/spacemacs/magit.gif" alt="Using Magit in spacemacs" /></p> <p>I really love Magit. With it can I do pretty much anything I knew how to do at the command line, and more, and it encourages me to write the best commit messages I can, and to keep my history tidy too. As far as I’m concerned, Spacemacs is a pretty decent editor with nice ways of teaching and reinforcing how to efficiently edit text, but Magit is a superpower.</p> <h2 id="so-getting-started">So: Getting started</h2> <p>The title of this post is “getting started”, so I’m going to try to share what I did to get going with Spacemacs, in the hope it helps you get started quickly, and see some of the nice features that I’ve been enjoying.</p> <p>As a caveat, let me say that I use a Mac, and I’m a Ruby developer using Git, so these notes are from that background, rather than a comprehensive set of instructions that will work for everyone.</p> <h3 id="installation">Installation</h3> <p>Installing Spacemacs is pretty simple. Firstly, you’ll need Emacs. There are a few packages available via Homebrew:</p> <ul> <li><code>emacs</code></li> <li><code>emacs-plus</code></li> <li><code>emacs-mac</code></li> </ul> <p>I use <code>emacs-mac</code> because it has patched which enable pixel-level scrolling, and it can also be configured to run without a title bar, which I like because I tend to run Emacs maximised (but not “full screen” since that means something else these days in MacOS).</p> <p><code> brew tap railwaycat/emacsmacport &amp;&amp; brew install emacs-mac --with-no-title-bars --with-spacemacs-icon </code></p> <p>One significant caveat of installing without title bars is that you can’t then drag the window around using your mouse. This can be a real pain, but if you <em>always</em> run Emacs maximised, it doesn’t matter. Unless you know you want this, I’d suggest installing without that option. You can use <code>brew info emacs-mac</code> to see all the installation options if you like.</p> <h4 id="ripgrep">Ripgrep</h4> <p>While we are at it, also install <code>ripgrep</code>, the super fast search tool that understands version control systems like git:</p> <p><code> brew install ripgrep </code></p> <p>Spacemacs will then use this tool to do fast searching. Next, install Spacemacs according to their instructions:</p> <h4 id="getting-spacemacs">Getting Spacemacs</h4> <p>I would encourage you to just follow <a href="https://github.com/syl20bnr/spacemacs">the instructions on the Spacemacs site</a>, but the gist is this:</p> <p><code> git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d </code></p> <p>Currently there’s a general recommendation to use the “develop” branch of the Spacemacs repository, since the “master” branch hasn’t seen much attention in a while. I find this a little odd, but that’s how they like to run the project I guess. So:</p> <p><code> cd ~/.emacs.d &amp;&amp; git checkout develop </code></p> <p>Next, start up Emacs! You should see the Spacemacs home screen, and you’ll be prompted to answer a number of questions about how you’d like Spacemacs to be set up. I’d suggest you pick “Evil” as the editing mode, unless you know you don’t want to try to use the Vim-like modal system. Also, pick the “standard” distribution, rather than the minimalist one. Or at least, that’s what I did.</p> <p>Now Spacemacs will set to downloading a whole bunch of packages and installing them, and this will take a few minutes. This is a perfect time <a href="http://spacemacs.org/doc/QUICK_START.html">read the Quickstart documentation</a> while you <a href="https://www.youtube.com/watch?v=QMZhD_ksmDY">drink your weak lemon drink</a>.</p> <h3 id="configuring-your-installation">Configuring your installation</h3> <p>Once all the packages have been installed, you’ll be presented with a simple help screen. Read it – it’s going to cover the basics fairly well. If you like, you can dive straight into editing files (take a look at the Cheat Sheet below which might help), but personally, I always like to see what I can tweak when I work with a new editor so once you’re done reading that, hit <code>SPC f e d</code> to open your personal configuration file. This is where all of the installed layers are declared and configured. I’m going to share the settings that I’ve used, but by all means tinker.</p> <h4 id="layers">Layers</h4> <p>Here’s roughly what my layer configuration looks like:</p> <p><pre><div class="code common_lisp"><span class="nv">javascript</span> <span class="nv">coffeescript</span> <span class="nv">yaml</span> <span class="p">(</span><span class="nv">osx</span> <span class="ss">:variables</span> <span class="nv">osx-use-option-as-meta</span> <span class="no">nil</span><span class="p">)</span> <span class="nv">helm</span> <span class="p">(</span><span class="nv">auto-completion</span> <span class="ss">:variables</span> <span class="nv">auto-completion-tab-key-behavior</span> <span class="ss">'complete</span><span class="p">)</span> <span class="nv">better-defaults</span> <span class="nv">emacs-lisp</span> <span class="nv">neotree</span> <span class="nv">git</span> <span class="nv">github</span> <span class="nv">markdown</span> <span class="nv">org</span> <span class="p">(</span><span class="nv">shell</span> <span class="ss">:variables</span> <span class="nv">shell-default-height</span> <span class="mi">30</span> <span class="nv">shell-default-position</span> <span class="ss">'bottom</span><span class="p">)</span> <span class="p">(</span><span class="nv">version-control</span> <span class="ss">:variables</span> <span class="nv">version-control-diff-side</span> <span class="ss">'left</span> <span class="nv">version-control-global-margin</span> <span class="no">t</span><span class="p">)</span> <span class="p">(</span><span class="nv">ruby</span> <span class="ss">:variables</span> <span class="nv">ruby-version-manager</span> <span class="ss">'chruby</span> <span class="nv">ruby-deep-indent-paren</span> <span class="no">nil</span><span class="p">)</span> <span class="p">(</span><span class="nv">ruby-on-rails</span> <span class="ss">:variables</span> <span class="nv">feature-use-chruby</span> <span class="no">t</span><span class="p">)</span> <span class="nv">html</span> <span class="nv">dash</span></div></pre></p> <p>The lines which aren’t wrapped in parentheses just use whatever defaults exist for the layer. Those that <em>are</em> (e.g. <code>osx</code> or <code>shell</code>) have been configured by me a little. Looking at the layer documentation will explain those settings, but the interesting parts there are that I’ve “unconfigured” the right alt key as the Emacs “meta” key, because I need that key to type <code>#</code> characters.</p> <p>I’ve also set the Ruby version manager to be “chruby”, but you might need something else. If you want to stick with the defaults, just delete <code>:variables</code> and everything after it, and remove the parentheses.</p> <h4 id="other-settings">Other settings</h4> <p><code>dotspacemacs-maximised-at-startup t</code></p> <p>I tend to run Emacs full screen, so I want it to start that way.</p> <p><code>dotspacemacs-line-numbers t</code></p> <p>Always show line numbers in every file</p> <p><code>dotspacemacs-whitespace-cleanup 'changed</code></p> <p>For any lines that I have edited, make sure there are no trailing whitespace characters, but otherwise leave other lines untouched. I find this is the best compromise between keeping whitespace in check, but not having commits full of unrelated whitespace changes just because I touched a file that had some.</p> <p>That’s it for the Spacemacs options, but you can configure other stuff inside the <code>dotspacemacs/user-config</code> function. Here’s a few things that I have:</p> <p><pre><div class="code common_lisp"><span class="c1">;; Use two spaces as the tab width</span> <span class="p">(</span><span class="nv">setq-default</span> <span class="nv">tab-width</span> <span class="mi">2</span><span class="p">)</span> <span class="c1">;; Make the modeline look good on Mac OS X</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">powerline-default-separator</span> <span class="ss">'utf-8</span><span class="p">)</span> <span class="c1">;; Make the right alt key available as meta in case that's useful</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">ns-right-option-modifier</span> <span class="ss">'meta</span><span class="p">)</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">mac-right-option-modifier</span> <span class="ss">'meta</span><span class="p">)</span> <span class="c1">;; When saving using cmd-s, exit back into normal mode so that other vim-ish commands just work</span> <span class="p">(</span><span class="nv">global-set-key</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"H-s"</span><span class="p">)</span> <span class="p">(</span><span class="k">lambda</span> <span class="no">nil</span> <span class="p">(</span><span class="nv">interactive</span><span class="p">)</span> <span class="p">(</span><span class="nv">evil-force-normal-state</span><span class="p">)</span> <span class="p">(</span><span class="nv">call-interactively</span> <span class="p">(</span><span class="nv">key-binding</span> <span class="s">"^X^S"</span><span class="p">))))</span> <span class="c1">;; Note that the "^X^S" string above actually needs to be the proper `ctrl-x ctrl-s` characters, which I can't embed in my blog. Best copy them directly from my dotfiles at https://raw.githubusercontent.com/lazyatom/dotfiles/spacemacs-develop/dotfiles/spacemacs</span> <span class="c1">;; Make cmd-t open the project file finder, like other editors</span> <span class="p">(</span><span class="nv">evil-define-key</span> <span class="ss">'normal</span> <span class="ss">'global</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"H-t"</span><span class="p">)</span> <span class="ss">'helm-projectile-find-file</span><span class="p">)</span> <span class="c1">;; Include underscore in word movement commands for Evil mode</span> <span class="p">(</span><span class="nb">dolist</span> <span class="p">(</span><span class="nv">mode-hook</span> <span class="o">'</span><span class="p">(</span><span class="nv">ruby-mode-hook</span> <span class="nv">enh-ruby-mode-hook</span> <span class="nv">python-mode-hook</span> <span class="nv">js2-mode-hook</span> <span class="nv">haml-mode-hook</span> <span class="nv">web-mode-hook</span><span class="p">))</span> <span class="p">(</span><span class="nv">add-hook</span> <span class="nv">mode-hook</span> <span class="nf">#'</span><span class="p">(</span><span class="k">lambda</span> <span class="p">()</span> <span class="p">(</span><span class="nv">modify-syntax-entry</span> <span class="nv">?_</span> <span class="s">"w"</span><span class="p">))))</span> <span class="c1">;; don't align if statements to the if</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">ruby-align-to-stmt-keywords</span> <span class="o">'</span><span class="p">(</span><span class="k">if</span> <span class="nv">begin</span> <span class="nb">case</span><span class="p">))</span> <span class="c1">;; set fill-column with for git commits to recommended width</span> <span class="p">(</span><span class="nv">add-hook</span> <span class="ss">'git-commit-mode-hook</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">()</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">fill-column</span> <span class="mi">70</span><span class="p">)))</span> <span class="c1">;; Follow symlinks to files without complaining</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">vc-follow-symlinks</span> <span class="no">t</span><span class="p">)</span></div></pre></p> <p>OK, enough configuration. Let’s actually start editing some stuff!</p> <h3 id="youre-a-vimmer-now-arry">You’re a vimmer now, ‘arry</h3> <p>First thing’s first, you’re going to need to get used to modal editing. Covering that is <em>way</em> outside of the scope of this article, but here’s some basics.</p> <p>You’ll start in “normal” mode, which is for moving around, and for manipulating text without actually typing new stuff (so, like, moving lines, copying and pasting and deleting and so on).</p> <p>If you want to enter some text, you’ll need to enter “insert” mode. There are a few ways to do this, but one is just hitting “i”. Then you can type as much as you like, and hit <code>ESC</code> when you are done to get back to “normal” mode.</p> <p>To move the cursor around in normal mode, the vimmish way is by using the <code>hjkl</code> keys, which correspond to left, down, up and right. Left and right are pretty obvious, but I used to often get confused which of <code>j</code> and <code>k</code> was up or down. The best way I’ve heard of to remember this is that <code>j</code> looks a little bit like a down arrow, if you can stretch your imagination to that. Bear that in mind and it’ll soon become second nature.</p> <p>There is a “lot” more to editing using Vim-like commands, and you’ll spend most of your time doing that, so if you’re not very experienced, I would suggest trying out one of the various vim tutorials. There’s even one built in to Spacemacs (<code>SPC h T</code>)!</p> <h3 id="editing-files-and-projects">Editing files and projects</h3> <p>Spacemacs uses a package called <a href="https://projectile.readthedocs.io">projectile</a> to manage projects, but it can be a little unclear how to actually get started with this. Here’s a simple way to get into it.</p> <p>You can browse the list of projects that Projectile knows about with <code>SPC p p</code>. However, when you get started, this list will almost certainly be empty. Projectile considers a project as any directory that’s under version control. Projects don’t really exist until Projectile discovers them, and the best way of doing that is by opening a file within that directory.</p> <p>Hit <code>SPC f f</code> to start browsing for a file to open. You can start typing and hit <code>TAB</code> to autocomplete directory and file names, or use <code>ctrl-h</code> to jump up to parent directories to move around quickly. Once you’ve found the file, hit <code>RET</code> to open the file.</p> <p>Depending on the type of the file, you <em>might</em> be prompted to add a new layer to get all the features for that type of file; it doesn’t really matter what you choose at this point.</p> <p>Once the file is open, if you hit <code>SPC p p</code> again, assuming you’ve opened a file that was somewhere under version control, you should now see the project in the list, and you can get back to this project easily now.</p> <p>When you <em>do</em> select a project using <code>SPC p p</code>, you’ll next be prompted to pick a file from that project. For a long time, this confused me, particularly as I moved from one project to the other, and back, in a single editing session. However, this is normal – Spacemacs is just asking which buffer or file from that project that you want to start on.</p> <h2 id="cheat-sheet">Cheat Sheet</h2> <p>These are all the commands that I wish I’d known when I got started. It’s also very worth getting a grounding in Vim, including the keys I listed nearer the top of this article; here I’m just including the Spacemacs-specific commands that cover the basics.</p> <p>Spacemacs includes <em>many</em> other commands, and likely some of them will be better in certain situations than the ones I’ve listed. These are just the basic ones that kept me moving.</p> <h3 id="modal-editing-1">Modal editing</h3> <p>These are the basic commands that I use all the time:</p> <ul> <li><code>hjkl</code> for movement, although I will pretty happily use arrow keys to move around a little when I’m in insert mode</li> <li><code>w</code> and <code>b</code> (and <code>&lt;num&gt;w</code> and so on) to skip around words at a time</li> <li><code>x</code> to delete the character under the cursor</li> <li><code>r&lt;char&gt;</code> to replace the character under the cursor with a new one</li> <li><code>f&lt;char&gt;</code> to skip to a specific character (and to a far lesser extent, <code>&lt;num&gt;f&lt;char&gt;</code>, but the time I spend visually counting occurrences almost always outweighs just doing <code>f&lt;char&gt;</code> a few times)</li> <li><code>{</code> and <code>}</code> to jump up and down blocks of code. Basically, it moves to the next blank line, but if you’ve formatted your code nicely, more often than not this is the next function</li> <li><code>cw</code> and <code>ct&lt;char&gt;</code> to change the current work, or change to the given character (e.g. given <code>some_variable_name</code>, with my cursor at the start I might type <code>ct_</code> to edit just the <code>some</code> part)</li> <li><code>dw</code> and <code>dd</code> to delete words and lines</li> <li><code>o</code> to add a new blank line below this one and enter insert mode</li> <li><code>p</code> to paste whatever I’ve recently deleted somewhere else</li> <li><code>A</code> to go to the end of the line and enter insert mode (“Append”)</li> <li><code>^</code> and <code>$</code> to skip around between the start and end of lines</li> <li><code>gg</code> and <code>G</code> to move to the top and bottom of the file respectively</li> <li><code>ve</code> and <code>v$</code> to select the word or line, ready to do something with it (maybe <code>y</code> to copy it…)</li> <li>Getting used to hitting escape as often as possible to leave insert mode<sup id="fnref:savingfiles" role="doc-noteref"><a href="http://interblah.net/#fn:savingfiles" class="footnote">2</a></sup>.</li> </ul> <p>There’s a bunch more that I do know and use, and I am <em>certain</em> that I could be using different commands to do a lot of stuff more efficiently, but I think it was really internalising the commands above that got me through the transition from feeling massively slowed down to reasonably efficient at editing and moving text around.</p> <p>All of these are Vi/Vim commands, but Spacemacs implements them using a package called “evil”, which according to all the reckons on the internet that I’ve read, is hands down the most complete Vim emulation layer available. What this means is that any advice or tricks my Vim-using friends might have, I can also use too, and that’s pretty neat.</p> <h3 id="general">General</h3> <p><code>f d</code> - in rapid succession, will quit out of most menus/commands/minibuffers back into normal mode</p> <p><code>ctrl-g</code> - tell Emacs to quit something that’s taking too long</p> <p><code>SPC q q</code> - quit Emacs</p> <p><code>SPC q R</code> - restart Emacs</p> <p><code>q</code> - if you’re in a non-file window, chances are if you hit <code>q</code>, it’ll close that window. Give it a try.</p> <h3 id="files-projects-and-buffers">Files, projects and buffers</h3> <p><code>SPC f f</code> - browse for files and open them</p> <p><code>SPC f r</code> - browse for a recently-opened file</p> <p><code>SPC f R</code> - rename the current file</p> <p><code>SPC f D</code> - delete the current file</p> <p><code>SPC p p</code> - show list of projects to choose (you will be prompted to open a file in that project afterwards)</p> <p><code>SPC p f</code> - file a file within the current project</p> <p><code>SPC p b</code> - show a list of all open buffers for this project (buffers are sort of like “open files”)</p> <p><code>SPC b b</code> - show a list of all open buffers</p> <p><code>SPC b d</code> - delete a buffer, which basically means “close this file, I don’t want to see it anymore”</p> <p><code>SPC TAB</code> - switch back to the previous buffer</p> <p><code>SPC f t</code> - toggle open a file tree (use <code>shift-k</code> to go up to parent directories in it)</p> <h3 id="windows">Windows</h3> <p><code>SPC w /</code> - split a window vertically</p> <p><code>SPC w s</code> - split a window horizontally</p> <p><code>SPC 1/2/3...9</code> - jump to window 1 to 9 (you should see the window numbers in the little mode line)</p> <p><code>SPC 0</code> - jump to the file tree (if it’s open; might actually open it if it isn’t)</p> <p><code>SPC w d</code> - close the current window</p> <p><code>SPC w m</code> - make the current window the only window</p> <h3 id="searching">Searching</h3> <p><code>SPC /</code> - search every file in the project</p> <p><code>SPC s s</code> - search (“swoop”) in the current buffer</p> <p><code>SPC *</code> - search the current project for the symbol/word under the cursor</p> <p><code>*</code> - enter a powerful search “mode” for the symbol/word under the cursor</p> <h3 id="editing">Editing</h3> <p><code>SPC i k/j</code> - insert a blank line above or below the cursor without entering insert mode</p> <p><code>SPC ; ;</code> - comment out a line of code (although now I know about <code>g c c</code> and all the other Vim-ish <code>g c</code> commands)</p> <h3 id="development-specific-stuff">Development specific stuff</h3> <p><code>, t b</code> - run all the tests/specs in this file</p> <p><code>, t t</code> - run the test/spec at the cursor</p> <p><code>SPC c d</code> - close the test window (“c” is for compilation)</p> <p><code>SPC c k</code> - quit the current test run</p> <p><code>SPC p a</code> - switch to the alternate file for this one (e.g. from controller to controller test, and back)</p> <h3 id="git">Git</h3> <p><code>SPC g s</code> - open git status. Then hit <code>?</code> to see the keys that Magit understands. Note that case is important. Hit <code>q</code> to close the Magit buffer.</p> <h3 id="spacemacs-itself">Spacemacs itself</h3> <p><code>SPC f e d</code> - open your configuration file</p> <p><code>SPC f e R</code> - reload your configuration after editing that file</p> <p><code>SPC f e D</code> - compare your configuration to the default one. This is super useful when you’ve updated Spacemacs, since new configuration options become available. When you do this, you enter an “ediff” mode; hit <code>?</code> to see what keys do, but the basics are: press <code>n</code> and <code>p</code> to move between changes, then <code>a</code> or <code>b</code> to pick which option to use, and <code>q</code> to quit</p> <p><code>SPC SPC</code> - search for a function by name, and run it. This is a <em>great</em> way of getting a sense of what Emacs/Spacemacs can do</p> <h3 id="documentation-and-help">Documentation and help</h3> <p>Emacs and Spacemacs come with a huge amount of documentation built in, which is extremely useful when you’re trying to figure out how to do something.</p> <p><code>SPC h l</code> - search for a layer and then open its documentation, which will show all its configuration options and key bindings</p> <p><code>SPC h d f</code> - search for a function and show its documentation</p> <p><code>SPC h d v</code> - search for a variable and show its documentation, and current and default values</p> <p><code>SPC h d p</code> - search for a package and show information about it</p> <p><code>SPC h d k</code> - then enter any combination of keys, and Spacemacs will tell you what function they are bound to and what it will do.</p> <h2 id="help-im-stuck">Help, I’m Stuck!</h2> <p>As with any complicated bit of software, it’s possible to get stuck in a menu or lost in some options, and want to back out. Sometimes key bindings you expect to work, will not, because you’ve ended up in one of the “system buffers” that Emacs uses to store command output or something else. Don’t worry – it happens to everyone.</p> <p>The best way I’ve found to re-orient myself is to hit <code>SPC f r</code> or <code>SPC b r</code> to open the recent file or buffer list, and then get back to a file that I was working on. From there, I can normally get back to moving around my project like I expected. Worst case, just quit (<code>SPC q q</code>) and open Emacs again.</p> <h2 id="other-notes">Other Notes</h2> <p>Spacemacs works just as well in the console version of Emacs as the GUI one, so you can happily sync your configuration to remote machines and use everything you’ve learned when editing files on remote servers. And take a look at “Tramp” if you’re interested in another Emacs superpower involving remote files!</p> <p>Even though I’ve been using Spacemacs for the best part of a year now, I only recently actually read through the documentation (<code>SPC h SPC RET</code> inside Spacemacs, or <a href="http://spacemacs.org/doc/DOCUMENTATION.html">here</a>). There’s a lot of good stuff in there, particularly about the <code>evil</code> keybindings, and things like managing layouts and workspaces. Once you’ve gotten to grips with some of the basics, I highly recommend just browsing it, and then coming back to it every now and again, to see what jumps out in an “ah! I can use <em>that</em>” kind of way.</p> <p>I hope this has been useful, either if you’re curious about Spacemacs, or are playing around with it. Thanks for your time!</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:borg" role="doc-endnote"> <p>Perhaps, you might say… joining the b<code>org</code>? <em>Groan</em>. <a href="http://interblah.net/#fnref:borg" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:savingfiles" role="doc-endnote"> <p>With Spacemacs I actually have it set up so I can hit <code>cmd-s</code> to both save the file <em>and</em> enter normal mode, though I recently also learned about <code>fd</code> and am trying to practice that. <a href="http://interblah.net/#fnref:savingfiles" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Here's to the crazy ones (RailsConf 2018) tag:interblah.net,2018-04-15:/heres-to-the-crazy-ones-railsconf-2018 2018-04-15T22:00:00+00:00 2018-04-15T22:00:00+00:00 interblah.net <iframe width="560" height="315" src="https://www.youtube.com/embed/h3xe-Rf4A6k" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe> <p><em>This is a transcript of a presentation I gave at <a href="http://raailsconf.org">RailsConf 2018</a>; the actual slides are <a href="https://speakerdeck.com/lazyatom/heres-to-the-crazy-ones">here</a>; the <a href="http://confreaks.tv/videos/railsconf2018-here-s-to-the-crazy-ones">video is from confreaks</a>.</em></p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).001.png" alt="" title="In some ways I hate how this opening slide looks, but it is one of the only two references in the presentation that supports the title. The background images are those produced by Apple for their marketing campaign &quot;Thing Different&quot; (the other is similarly cheesy, later one). The font is the one Apple used pervasively at that time." /></p> <p>My name is James Adam, and I’ve been using <a href="http://www.ruby-lang.org">Ruby</a>, and <a href="http://www.rubyonrails.org">Rails</a>, for a long time now.</p> <p>I’d like to share with you some of my personal history with Rails.</p> <p>Hopefully it won’t be too self-indulgent, and hopefully I won’t bore you before I get to the actually “important” bit at the end. I also want to apologise — every time I come to the US, I seem to get a cold, so please excuse me if I need to cough.</p> <p>Anyway, let’s get started. Sit back, as comfortably as you can.</p> <p>You can feel your limbs and eyelids getting heavier. All your worries are drifting away. Let me gently regress you all the way back to 2005.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).004.png" alt="" title="You are feeeeeeling veeeeerry sleeeeeepy..." /></p> <h2 id="welcome-to-2005">Welcome to 2005</h2> <p>This is me in 2005. I was just finishing <a href="http://assets.lazyatom.com/thesis.pdf">my PhD</a>.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).005.png" alt="" title="Fun fact: actually this picture is from 2001, but I hadn't changed that much, and it supported my point better than other pictures I could find." /></p> <p>I’d actually discovered Ruby a few years earlier, and I’d totally fallen in love with it. I rewrote most of the code I was using for my research in Ruby. I was so excited about Ruby when I discovered it that I sent this in an email to my friends, who’d all got “proper” jobs writing software, most of them using Java at the time.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).006.png" alt="" title="This is genuinely real, although I've editted out some surrounding, more-embarassing hyperbole." /></p> <p>The nature of PhDs is that when you finish, you’ve become a deep expert in a topic that’s so narrow that it can be hard to explain how it is connected to anything outside it. So when I finished my PhD in early 2005, I was worried that I was going to be in a similar state professionally — having fallen in love with a language which I’d never be able to use for work, and instead having to return to Java or C++ or something like that to get a job.</p> <p>I was incredibly lucky. Just as I was finishing my thesis, this <a href="http://david.heinemeierhansson.com">excited Danish guy</a> had been <a href="https://signalvnoise.com/archives/000606.php'">starting to talk</a> about a web framework he was writing with the weird name “Ruby on Rails”.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).007.png" alt="" title="From https://signalvnoise.com/archives/000606.php" /></p> <p>Long story short, a job in London got posted to the <a href="http://blade.nagaokaut.ac.jp/ruby/ruby-talk/index.shtml">ruby mailing list</a>, and so I took the seven hour train journey down from Scotland for an interview, and within a few months I was working, in my first job, being paid to use Ruby, and a very early version of Rails (I think it was 0.9 at the time).</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).008.png" alt="" /></p> <h2 id="working-on-multiple-applications">Working on multiple applications</h2> <p>Our team was like a mini agency within a much bigger media company, and the company used a tangled mess of Excel spreadsheets in various departments, in all sorts of weird and wonderful ways. It was our job to build nice, clean web applications to replace them. We were a small team, never more than five developers while I was there, but we’d work on a whole range of applications, all at the same time.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).013.png" alt="" /></p> <p>These days, and particularly with Rails 5.2 which was released a few days ago, Rails provides us with pretty much everything you need to write a web application, but’s easy to forget a time when Rails had almost none of the features we take for granted now.</p> <p>A time before Active Storage, before encrypted secrets, before Action Cable, before Turbolinks, the Asset Pipeline, before resources or REST, before even Rack!</p> <p>These are the headline features for Rails in Spring 2005, when <a href="https://github.com/rails/rails/releases/tag/v0.13.0">version 0.13</a> was released. Migrations were brand new! Can you even imagine Rails without migrations?</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).031.png" alt="" /></p> <p>In lots of ways, Rails 0.13 is very similar to modern Rails — it has a the same MVC features, models with associations, and controllers with actions, and ERB templates, and action mailer — but in many other ways it was actually closer to <a href="http://sinatrarb.com">Sinatra</a> than what we have in Rails today. To illustrate, the whole framework including dependencies and gems it used was around 45,000 lines of code. By comparison, if you today add Sinatra and ActiveRecord to an empty Gemfile today, and run bundle install, with core dependencies it ends up at around 100k of code.</p> <p>But the core philosophy of Rails was exactly the same then as it is now — <a href="https://rubyonrails.org/doctrine/#convention-over-configuration">convention over configuration</a>, making the most of Ruby’s expressiveness, and providing most things that most people needed to build a web application, all in one coherent package.</p> <h2 id="extracting-engines">Extracting Engines</h2> <p>So it’s Summer 2005, Rails is at 0.13, and in our team we are building all these applications, and we realise that we’re building the same basic features again and again.</p> <p>Specifically, things like code to manage authentication — only allowing the application to be used by certain people — and code to manage authorisation — only allowing a subset of those people to access certain features, like admin sections and so on. At the time, I think we had at least four applications under simultaneous development, which is around one app per developer, and it just seemed counter-productive for each of us to have to build the same feature, each in slightly different ways, making it harder for us to move around between the applications, and increasing the chance that we’d each introduced our own bugs and so on. After all, a big part of the Rails philosophy has always been the concept of DRY — don’t repeat yourself.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).033.png" alt="" /></p> <p>And these authentication and authorisation features weren’t particularly complicated; they had the same concerns you would expect (login, logout, forgot password, a user model, some mailer templates and so on). So it occurred to us that what we really needed to do was to write this once, and then share it between all the applications we were building, so they could all benefit from new features and bug fixes, and we’d be able to move between the applications more easily.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).038.png" alt="" /></p> <p>In modern Rails, we typically share code by creating gems, and those gems integrate with Rails using a mechanism called “<a href="http://guides.rubyonrails.org/initialization.html">Railties</a>”, but that was only introduced at the end of 2009 (on New Years Eve, actually).</p> <p>Before Railties, we had “plugins”, but they didn’t <a href="https://github.com/rails/rails/blob/b437eee41d38188f10f11e041b82d7def0a20629/railties/CHANGELOG#L42">appear until Rails 0.14.1</a>, so they didn’t exist yet either.</p> <p>Even using just regular gems was problematic, because this is before bundler, and before we’d figured out how to reliably lock down gem versions for an application. They’d need to be installed system-wide, and so if you had multiple applications running on the same host, upgrading the gems for one application could end up breaking another. <a href="https://github.com/rails/rails/blob/b437eee41d38188f10f11e041b82d7def0a20629/railties/CHANGELOG#L24">Gem freezing</a> also didn’t appear in Rails until version 0.14.</p> <p>So without any existing mechanisms, we had to roll up our sleeves and invent something ourselves.</p> <p>So in the late summer of 2005, we extracted all of the login and authorisation code, including controllers and models and views and so on, into a separate repository and then wrote a little one-file patch library, which was loaded when Rails started. This library added paths to the controllers and models into the load paths for Rails, and made a few monkey-patches to Rails’ internals to get everything else working nicely.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).043.png" alt="" /></p> <p>Originally this one-file patch was called “frameworks”, but fairly soon after it got renamed to “engines”. The name “engines” was actually the idea of <a href="https://github.com/seanohalpin">Sean O’Halpin</a>, who was my boss at the time and has been programming with Ruby for even longer than me.</p> <h2 id="the-dawn-of-plugins">The dawn of plugins</h2> <p>In October of 2005, a few months later, Jamis Buck added an <a href="http://web.archive.org/web/20060603204512/http://dev.rubyonrails.org/ticket/2335">experimental “plugins” feature</a> to Rails.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).044.png" alt="" /></p> <p>A “plugin” was just a folder of code that contained a “lib” directory and a file called “init.rb”, and the “plugins mechanism” would just iterate through subdirectories of “vendor/plugins”, trying to load files called <code>init.rb</code> if it could find them. Very simple.</p> <p>We spotted this feature being added Rails and so I emailed Jamis and David to say “hey, we’ve been working on something similar — if you were interested, we’d be happy to contribute!”</p> <p>We got a very nice reply, the gist of which was “that sounds very interesting, but perhaps you can package that up as a plugin itself and we’ll see how people use it”. So that’s <a href="https://markmail.org/message/hg7hramkgo7jstcd">exactly what we did</a>, resulting in the “Engines Plugin”, which let you treat other plugins as these vertical MVC slices which could be shared between applications. This is the first homepage for it, hosted on RubyForge.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).045.png" alt="" /></p> <p>I also released the first engine, the “Login Engine”, which wrapped up code from one of the original authentication generators we’d used, along with a few tweaks that we found useful.</p> <p>That was November 1st, 2005.</p> <h2 id="the-engines-plugin-is-released-the-controversy-begins">The engines plugin is released; the controversy begins</h2> <p>People got pretty excited. Like, super-excited. Which was really exciting for me too.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).049.png" alt="" /></p> <p>There was enthusiasm for the demonstration engine that I released, and it seemed that people understood the idea behind what we were doing. Someone tried to turn Typo, which was the first really popular Rails-based blogging platform, into an engine, so they could add blogs into their application.</p> <p>A lot of people got pretty enthusiastic specifically about not having to write the same old login stuff again and again. Some people, I think, hoped that they would never have to write another login system again ever, and that the simple one we released with work seamlessly for everyone.</p> <p>But then somebody got so excited, that they started talking about “engines within engines, depending on other engines” and I think it was this idea that ultimately pushed DHH over the edge, and about 10 days later he wrote the <a href="http://web.archive.org/web/20060408004005/http://weblog.rubyonrails.org:80/articles/2005/11/11/why-engines-and-components-are-not-evil-but-distracting">first post about engines on the official Rails blog</a>.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).053.png" alt="" /></p> <p>In the post, David talked about his distrust of the dream of component-based development, and that it’s better to build your own business logic than try to adapt something that someone else wrote, and that we shouldn’t expect to be able to plug in or swap out these high-level components like forums and galleries and whatever, and never have to build them ourselves.</p> <p>And I agreed with him, but tried to clarified that what engines were great for was extracting code that you’ve written, and sharing it around a bunch of apps that you’re working on at the same time, as long as those features were relatively isolated from the rest of the application. And I think David agreed with that too.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).055.png" alt="" /></p> <p>I could see his perspective: when you just have a single application to work on, like, say … Basecamp, then chances are that you can and should develop as much of the business logic yourself as you can. But if you’re working on 3 or 5 or 10 applications, at the same time, then chances are that the balance of value vs cost of sharing starts to tip the other way.</p> <p>I was pretty happy with that conversation — it seemed like we generally understood and agreed about the potential benefits and dangers of what I was proposing. I’d shared an idea, and David had merely expressed a bit of caution, but a bunch of people had become super excited by it, maybe a little <em>too</em> excited, and on the other side a LOT of other people took David’s post as a declaration that the idea was fundamentally bad and to be avoided at all costs.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).060.png" alt="" /></p> <p>And so that’s basically how things played out for the next three years. Every now and then someone would write a blog post saying “Rails needs something like engines” or “engines are actually pretty useful” and they’d be met with the reaction “Didn’t you know that engines are ‘too much software’ (whatever that means), and like, really bad?”</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).064.png" alt="" title="Source: https://web.archive.org/web/20090925222810/http://glu.ttono.us:80/articles/2006/08/30/guide-things-you-shouldnt-be-doing-in-rails" /></p> <p>And so I’d write a comment or another blog post trying to be reasonable and say “well, it’s more complicated than that” and occasionally the author might add a little clarification to their post but by that point it’s too late and you’ve got people commenting that rails engines are actual evil.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).067.png" alt="" /></p> <p>I call this time the wilderness years.</p> <h2 id="the-wilderness-years">The Wilderness Years</h2> <p>During this time I tried to respond to the criticisms of the engines concept, with varying degrees of success. It was occasionally… quite frustrating.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).069.png" alt="" /></p> <p>I spoke at a bunch of conferences about plugins, and sometimes engines, and also tried to gently steer the development of the plugin architecture in Rails to reduce the amount of patching that the engines plugin needed to do, by adding things like controlling plugin loading order, exposing Rails configuration and initialisation hooks to plugins, and stuff like that.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).070.png" alt="" /></p> <p>Plugins became very popular, and went from being shared as links on a wiki page to having their own directory you could search and comment on.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).071.png" alt="" /></p> <p>Here’s an example of a fun plugin that I wrote for one of those presentations. See, I’m having fun!</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).072.png" alt="" title="The 'acts_as_hasselhoff' plugin did things like replace all images with pictures of David Hasselhoff, and played the Knightrider theme when running tests" /></p> <p>And when, if I did mention engines in those presentations, I tried to explain that there were valid use cases, and sure, you could use them in a terrible way, but that doesn’t mean people should <em>never</em> use them. I hoped that those presentations, if not actually changing anyone’s mind, might’ve at least softened people a little to the idea that engines might not be 100% terrible.</p> <p>But then on the official plugin directory you’d get someone tagging the engines plugin as “shit”, and the cycle would start again. (I never did find out who that was.)</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).074.png" alt="" /></p> <p>Some people would go to lengths to explain why “Rails Engines” were bad, but I’d try to write a short comment to respond to each of their points and hopefully clear up any misconceptions about what the engines plugin did and what engines were good for.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).077.png" alt="" title="Source: http://web.archive.org/web/20080105021818/http://www.pluginaweek.org:80/2006/11/01/rails-engines-too-much-of-a-good-thing/" /></p> <p>In this particular case, though, what was super confusing is that the same people then released their own plugins trying to basically do the same thing!</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).078.png" alt="" /></p> <p>The wilderness period lasted so long that some companies even wrote engines-like plugins without realising that engines even existed! (Brian and I actually had a conversation afterwards, and talked about merging the projects).</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).079.png" alt="" title="Source: https://content.pivotal.io/blog/build-your-own-rails-plugin-platform-with-desert" /></p> <h2 id="rails-3-an-evolution">Rails 3: an evolution</h2> <p>So this is all happening between 2006 and 2008, during which a new Ruby web framework appeared, called <a href="http://web.archive.org/web/20080118054508/http://merbivore.com:80/&quot;">Merb</a>.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).080.png" alt="" title="Source: http://web.archive.org/web/20080118054508/http://merbivore.com:80/" /></p> <p>It was designed to be extremely fast — largely because it didn’t do very much — and be particularly good at handling many simultaneous requests and things like file uploads. Unlike Rails, which was at the time a relatively tightly-coupled set of frameworks, Merb was designed to be extremely modular, so it could (for example) support multiple ORM frameworks. It was also designed to have clear and stable internal APIs, since much of the merb framework was written as optional plugins.</p> <p>One of the developers most involved in Merb was <a href="http://yehudakatz.com/">Yehuda Katz</a>, who eagle-eyed people will have spotted was generally sympathetic to the concept of “engines”, and so it’s probably not surprising that in 2008, Merb introduced their implementation of the idea, called “<a href="http://web.archive.org/web/20100102021855/http://brainspl.at:80/articles/2008/05/21/merb-slices">Merb slices</a>”, to a generally positive response from the Ruby community.</p> <p>But it’s not a huge surprise that this is how the most popular Rails podcast at the time chose to frame that.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).082.png" alt="" /></p> <p>And I don’t blame the presenters for thinking or saying that, it was just a representation of the opinion in the community as a whole, at that a time.</p> <p>This is a painting of “<a href="https://en.wikipedia.org/wiki/Sisyphus">Sysiphus</a>”, who in Greek mythology was cursed by the Gods by being forced to roll an immense boulder up a hill only for it to roll down when it nears the top, repeating this action for eternity. These days it’s a common image invoked to describe tasks that are both laborious and futile.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).084.png" alt="" /></p> <h2 id="a-surprising-development">A surprising development</h2> <p>So we come to the end of 2008. Rails is about to reach version 2.3. The controversy had largely died down — people who got some value out of working with engines were pretty happy, I hope! and the people who thought they were evil seemed to have forgotten about they existed.</p> <p>So you can imagine my surprise when I received this email from DHH with the subject line, “I repent”.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).086.png" alt="" /></p> <p>I think I actually became giddy at the time. Rails core had decided that engines weren’t <em>evil</em>, and that they were going to be integrated into the framework. My work… was done.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).088.png" alt="" /></p> <p>OK, not really. Without going into a huge amount of detail, in Rails 2.3, plugins absorbed some of the core engines behaviour. They could provide controllers, views and most other types of code. This was released in Rails 2.3, in March 2009.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).092.png" alt="" /></p> <p>At the same time, <a href="http://web.archive.org/web/20170630220119/http://weblog.rubyonrails.org/2008/12/23/merb-gets-merged-into-rails-3/">Merb and Rails decided to merge</a>, and Rails 3 would be the end result. The goal of doing this was, in part, to establish some clear, stable APIs within Rails, that other libraries and plugins could rely on, so they they didn’t break when Rails was upgraded. This was a fairly significant rewrite of a lot of the core parts of Rails in order to create those APIs.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).093.png" alt="" /></p> <p>Yehuda and <a href="http://carllerche.com">Carl Lerche</a> did much the work, and as part of it, they decided that rather than having a Rails application, and these “engine” things inside it that looked like a Rails Application and got access to the same hooks and config and so on, that instead, the outer application itself should just be a Rails Engine with a few other bells and whistles. So I guess the “engines inside of engines” person actually got their wish!</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).094.png" alt="" /></p> <p>This was released as Rails 3.0, in 2010.</p> <p>Finally, with Rails 3.1, released in August 2011, the last two bits of work that the engines plugin did — managing migrations from engines, and assets became part of Rails, and the plugin that I had written was officially deprecated.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).096.png" alt="" /></p> <h2 id="the-hypehate-cycle">The Hype/Hate Cycle</h2> <p>You have probably heard of the <a href="https://www.gartner.com/technology/research/methodologies/hype-cycle.jsp">Gartner Hype Cycle</a>, which is a way of understanding how technology trends evolve.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).102.png" alt="" /></p> <p>We have the initial creation or discovery of the technology, then the peak of inflated expectations, where everyone is excited about having jetpacks or living on the moon, but then the trough of disillusionment, when it turns out it’s actually much harder to build a jetpack than we thought, and there are a lot of things we need to built one that aren’t ready yet. But eventually technology starts to climb the slope of enlightenment, as we figure all those little things out and iron out the problems so we don’t set our legs on fire and so on, and we finally get to the plateau of productivity, when zipping around on our jetpacks seems pretty ordinary and we look back at old movies of people moving around using their <em>legs</em> and laugh about how quaint they seem.</p> <p>And I think we can use a similar cycle to understand how the Rails community reacted to the “engines” concept too. For engines, it took just under six years from idea to acceptance.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).107.png" alt="" /></p> <p>We have the same starting point, and the same peak of inflated expectations (“I’ll never need to write login code again!!”), but then we enter what I like to call the TROUGH OF RECEIVED OPINIONS, where some big names in the community have been like “woah woah woah”, and we’ve personally haven’t actually tried using the technology but we’ve heard it sucks and so basically it’s the worst thing ever. And then for about three years, we scrabble up the SLOPE OF FEAR, UNCERTAINTY and DOUBT, where people find themselves thinking “hey, wouldn’t it be great if I could share this slice of functionality between all my apps?”, but when they try, they get bogged down in all the blog posts, often from years ago saying “no! It sucks!” and so they give up. And then, finally we reach the plateau of “oh — are those still a thing?”</p> <p>And as you can see, at the end of the cycle, we are just about neutral. We’re basically back where we started, but at the very least I can finally put the boulder down and stop pushing it up the hill :-)</p> <p>If you’d like a nice summary, I found this quote in the book “<a href="https://www.manning.com/books/rails-3-in-action">Rails 3 in Action</a>”, which was published around the same time.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).108.png" alt="" /></p> <h2 id="engines-there-when-you-need-them">Engines: there when you need them</h2> <p>So, what changed in 2008? Well, I think think it’s quite simple in retrospect. Rails was originally extracted from Basecamp, the software that DHH built and still works on today. At the start of Rails life, Basecamp was the only Rails application that David worked on, but between 2005 and 2008, 37signals added another three flagship applications, along with a few other smaller ones like Writeboard and Ta-da list.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).110.png" alt="" /></p> <p>Their small team — I think it was four developers at that point — had to build and support all those applications… at the same time… that… sounds… familiar, doesn’t it? :)</p> <p>In the “<a href="https://rubyonrails.org/doctrine/">Rails Doctrine</a>”, which is a somewhat-intentionally-provocative essay that David wrote about two years ago, there’s a section called “<a href="https://rubyonrails.org/doctrine/#provide-sharp-knives">Provide sharp knives</a>”.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).113.png" alt="" /></p> <p>What it basically says is that with some of the tools that Rails gives you, it’s definitely possible to get in a mess. But instead of protecting you from misusing them by keeping them from you altogether, we should trust ourselves to use those tools and approaches sensibly. Concerns is one example of a “sharp knife” — some people think they encourage sweeping complexity under the rug, while others think that used appropriately, it’s not a problem and the benefits outweigh the risks.</p> <p>And that’s exactly what the engines concept is: a sharp knife. For around 6 years, it was a little too sharp to be included in Rails’ silverware drawer, but it seems like perhaps now we can be trusted with it. And these days, lots of popular libraries are engines.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).118.png" alt="" /></p> <p><a href="https://github.com/plataformatec/devise">Devise</a>, which is an extremely popular authentication library, is an engine. The <a href="https://github.com/spree/spree">Spree e-commerce platform</a> is an engine, and you can get content management systems like <a href="https://github.com/refinery/refinerycms">Refinery CMS</a>, which is an engine too. Even the new <a href="https://github.com/rails/rails/tree/master/activestorage">Active Storage</a> feature in Rails, is implemented as an engine inside.</p> <h2 id="welcome-back-to-2018">Welcome back to 2018</h2> <p>OK, that’s the end of our trip back to 2005, and we’re now back in the present. This is a good moment to take a stretch.</p> <p>But before we start the third act, I wanted to mention one little thing that has nothing to do with Rails, or engines. Most of what I’ve talked about happened at least ten years ago, and when I was writing this talk, I wanted to make sure that I hadn’t inadvertently distorted how things played out in my memory.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).119.png" alt="" /></p> <p>All of the comments and posts I’ve used are real, but when I tried to find all these original newsgroup posts and articles and blogs so on that were written at the time, what I found was that almost all of sites I have referenced are either totally gone, or only partially available (e.g. all the discussion comments on the rubyonrails blog have disappeared, loud thinking is gone, even Ruby Forge is gone…)</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).120.png" alt="" /></p> <p>I think that history is interesting and important, and it’s kind of mind-boggling that without <a href="http://web.archive.org/web/20080118054508/http://merbivore.com:80/&quot;">archive.org</a>, information that’s only ten years old might otherwise be basically gone forever. So if you can, <a href="https://archive.org/donate/">please support archive.org</a>. They accept donations at their website, and I genuinely believe they are providing one of the most valuable services on the web today.</p> <h2 id="the-history-of-rails">The History of Rails</h2> <p>OK, back to Rails. So at the start of this talk, I did say that I didn’t want this to be too self-indulgent, or to paint myself as some misunderstood genius or hero, finally proven right.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).121.png" alt="" /></p> <p>I am sure there are many other stories like this, in many other Open Source projects. But what I think is interesting about this journey is that it shows that the history of Rails can be viewed as <strong>a history of opinions</strong>.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).122.png" alt="" /></p> <p>Rails is “opinionated software”, which is great because it saves us a lot of time by allowing us to offload lots of decisions about building software, in exchange for accepting some implicit constraints. Following those constraints is what we sometimes call the “Rails Way”.</p> <p>Some of those opinions are about how we use Ruby as a programming language — about how you should be able to express behaviour at the level of a line of code. An example of this are the methods that Rails adds to objects like String and Array.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).123.png" alt="" /></p> <p>Objects in a Rails application tend to have a lot of methods. Some people believe that it’s better to try to minimise the number of methods on an object, but it’s Rails’ opinion that the tradeoff is worth it, in order to be more expressive. Neither is wrong or evil. They are just two different opinions.</p> <p>Other opinions are at a more architectural level, and are ultimately about how we ought to structure the applications we build when using Rails.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).124.png" alt="" /></p> <p>If you build your URLs and controllers in terms of REST and resources, you’ll be able to use a lot more of the abstractions and high-level mechanisms that Rails provides. But if you like to add lots of custom actions into your controllers, Rails can’t stop you, and it won’t stop you, but you’ll have to do a lot more work yourself to wire things up.</p> <p>But that’s not the same as saying “if you don’t use resources, your code is bad” — it’s just the guiding opinion that Rails has.</p> <p>What might not be obvious, though, is that over the 14 years of Rails’ life so far, those opinions haven’t always existed, and haven’t always stayed the same.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).125.png" alt="" /></p> <p>The REST verbs and resource-based architecture weren’t a part of Rails for almost two years. Inline Javascript was fine until 2011 when Rails switched to unobtrusive javascript. If you wanted to send an email when a user signed up, before Rails 4.0 you might’ve written an observer to reduce the coupling between creating a user record and dealing with emails, but they were removed, and in the modern Rails Way, you’d probably create a concern which mixed in callbacks to your User model.</p> <p>I think a particularly interesting is example is Active Resource.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).126.png" alt="" /></p> <p>It used to be a part of the Rails framework, an evolution of the “actionwebservice” framework, which used to support SOAP, Dynamic WSDL generation, XML-RPC, and all the acronyms that David mentioned as “WS-deathstar” in <a href="https://youtu.be/zKyv-IGvgGE?t=48m39s">his keynote yesterday</a>.</p> <p>ActiveResource let you save and load remote data using JSON over HTTP, using the same ruby methods as you’d use on a regular Active Record model. It made it easy build things like micro services and so, I think, acted as a signal that you could and should do that. It was removed in Rails 4.0, which might be one of the first indications of the current opinion that a Majestic Monolith is a more productive way to work overall.</p> <h2 id="the-only-constant-is-change">The only constant is change</h2> <p>The purpose of highlighting these changes of opinion is not to say that DHH, or anyone who is or was in Rails Core is frequently wrong; it’s to show that even in the world of opinionated software, <em>opinions can change</em>.</p> <p>Fundamentally, what is and isn’t in Rails is driven by the needs of the people who write it, and to a greater or lesser extent, that means people building applications like Basecamp. But not everybody is building an application like that. I think more and more of us are working on Rails applications that have been around for a long time, in some cases ten years or more, and those kinds of applications have different needs and experience different pressures than one where the developer controls all the requirements and is free to rewrite it if they choose to, at almost any time.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).129.png" alt="" /></p> <p>According to builtwith.com <a href="https://trends.builtwith.com/framework/Ruby-on-Rails">there are almost 1.1 million sites running on Rails at the moment</a>, so it’s statistically extremely unlikely that Basecamp, as a piece of software or as a company, sits at the exact centre of all the different needs and pressures and tradeoffs those other applications and companies have.</p> <h2 id="we-are-the-stewards-of-the-future-of-rails"><em>We</em> are the stewards of the future of Rails</h2> <p>Right now there are differing opinions in the community about what the future of Rails might include.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).131.png" alt="" /></p> <p>The majestic monolith vs. micro services; concerns and callbacks vs smaller object composition; system tests vs. unit testing &amp; stubs… these tensions are good. We need people to be pushing at the boundaries of the Rails Way, to figure out what’s next.</p> <p>If we just sit back and wait for a relatively small group of people to tell us what the future of Rails looks like, then it will only be a future that suits their particular, unavoidably-limited contexts.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).132.png" alt="" /></p> <p>In 2014, <a href="http://37signals.com">37signals changed their name to Basecamp</a> and returned to maintaining a single application, so some of the motivation from within Rails Core for things like engines is naturally going to diminish. And that’s understandable: it’s an itch they may no longer have. But I wonder how many other software itches there are, which Basecamp doesn’t experience, but hundreds or thousands of other applications and developers do.</p> <p>We need more voices sharing their experiences, good and bad, with the current Rails Way and we need people to build things like <a href="http://trailblazer.to">Trailblazer</a>, <a href="http://rom-rb.org">ROM</a>, and <a href="http://hanamirb.org">Hanami</a>, and <a href="http://dry-rb.org">dry-rb</a>, and then others to try using them and learning from them.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).133.png" alt="" /></p> <p>Probably none of these projects will ever usurp Rails, but they might contain ideas about how to build software, or how to structure web frameworks, which are new and useful. And like Merb, they might end up influencing the direction Rails takes towards something better for many of us. They might already have found some <em><a href="https://youtu.be/zKyv-IGvgGE?t=17m26s">conceptual compression</a></em>, to use the phrase from David’s Keynote, that we can adopt or adapt.</p> <p>And there’s no reason why the people doing that exploration can’t be you, because who else is going to do it? You are the Rails community. You work with Rails all the time. Who better than you to spot situations where a new technique or approach might help. Who better than you to try and distill that experience into beautiful, expressive code that captures a common need.</p> <p>You can be <a href="https://en.wikipedia.org/wiki/Think_different" title="Here's the second of two references that explain the title of this talk. It was pretty cheesy to say it out loud, but otherwise I don't think the title would've made any sense at all!">one of the crazy ones</a>. The misfits. The rebels. The troublemakers. The round pegs in square holes. The ones who see things differently.</p> <p>As it says at the bottom of the “Rails Doctrine”:</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).134.png" alt="" /></p> <p>“We need disagreement. We need dialects. We need diversity of thought and people. It’s in this melting pot of ideas we’ll get the best commons for all to share. Lots of people chipping in their two cents, in code or considered argument.”</p> <h2 id="life-in-the-big-tent">Life in the Big Tent</h2> <p>OK, wonderful. Rails now embraces all manner of opinions under its <a href="https://rubyonrails.org/doctrine/#big-tent">big tent</a>. But what happens when you have your idea, but people don’t quite understand it immediately, and things get a little out of control and suddenly people are decrying it as evil?</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).137.png" alt="" /></p> <p>I feel for you, genuinely, because when I released engines, the main way people expressed these kinds of opinions was in the comment form of a blog. But we are now living in the age of the tweet, where many people don’t think twice about unleashing a salvo of 280 character hot takes out into the world. I’m not sure that we live in an age of “considered opinion” at the moment.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).139.png" alt="" /></p> <p>So what can we do? Well, two things.</p> <h3 id="be-considerate">Be considerate</h3> <p>Firstly, as consumers of open source technology, I think we could all try our best to avoid sharing opinions like that. If you’ve had a bad experience with a technology or a technique, then that’s totally valid and you can and absolutely should share that experience. But don’t do it in a tweet, or if you MUST do it in a tweet, try to at least be balanced.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).141.png" alt="" /></p> <p>Even better, start a blog, or post on Medium, and write as much as you can about your experience and your context, and share a link to THAT on twitter.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).142.png" alt="" /></p> <h3 id="be-patient">Be patient</h3> <p>Secondly, if you are lucky and generous enough to actually try to contribute a new idea to this, or any other community, try not to become demotivated if people don’t understand the point at first. This is that <a href="https://signalvnoise.com/archives/000606.php'">first blog post about Rails on the 37signals blog</a>, in early 2004. Look at the first comment.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).144.png" alt="" /></p> <p>What this shows is that the value of why you’re doing something differently, is often not immediately obvious to people. You will have to patiently explain it. Sometimes again and again, maybe for years and years. And you won’t be able to convince everyone, but you might reach <em>someone</em> who finds it interesting or useful, who might then reach someone else, and before you know it, lots of people are getting value from your little idea, and it could end up making a big difference after all.</p> <h2 id="the-subjective-value-of-ideas-and-how-to-stay-sane">The subjective value of ideas, and how to stay sane</h2> <p>There’s one last thing I’d like to say. When you make something, and it receives criticism, especially on the internet, from strangers, it can be very hard to deal with, sometimes so much so that we might stop creating things altogether, or never even try.</p> <p>When I was a researching this talk, I stumbled across <a href="http://web.archive.org/web/20061028151146/http://www.loudthinking.com/arc/000600.html">an old blog post</a>, from 2006 — actually by DHH, would you believe it — that captured a good way of dealing with situations like this. I’m going to paraphrase it.</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).146.png" alt="" /></p> <p>View your idea, or the thing you’ve made, as a pearl, not a diamond. When someone responds to your idea and points out all the flaws, the situations where it might not work for them, that’s OK, because what they’re asking for is a diamond. They want you to give them something <em>they consider flawless</em>. They want something <em>perfect</em>. But you need to try to remember that however that want is expressed, constructively or vitriolically, or wherever in between, that it’s not your job to make a diamond for them.</p> <p>Instead, all you can offer them is the pearl you’ve made, and if that’s not good enough, then:</p> <p><img src="http://interblah.net/images/crazy-ones/Here's to the crazy ones (RailsConf 2018).147.png" alt="" title="I thought hard about whether or not to include this in the presentation; in some ways it a cheap ending, but I liked being able to make a sly reference to DHH's own infamous 'Fuck You' slide as both a postfacto defiant response to his original thoughts on engines, and a gesture of agreement. Plus, it's a bit of fun and a cheap laugh! Hopefully you'll forgive me." /></p> <p>Thank you.</p> Why is nobody using Refinements? tag:interblah.net,2015-11-15:/why-is-nobody-using-refinements 2015-11-15T12:11:09-06:00 2015-11-15T12:11:09-06:00 interblah.net <p><strong>Future relevancy protection</strong>: As <a href="http://interblah.net/why-is-nobody-using-refinements#comment-2822903554">Tim Garnett</a> correctly points out, at lot of discussion of Refinements suffers from not being clear about which version of Ruby is current at the time of writing. When I gave this talk, the latest version of Ruby was 2.2.3, but I believe the content is still relevant for 2.3.</p> <iframe width="560" height="315" src="http://www.youtube.com/embed/qXC9Gk4dCEw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe> <p><em>This is a transcript of a presentation I gave at <a href="http://rubyconf.org">RubyConf 2015</a>; the actual slides are <a href="https://speakerdeck.com/lazyatom/why-is-nobody-using-refinements">here</a>; the <a href="http://confreaks.tv/videos/rubyconf2015-why-is-nobody-using-refinements">video is from confreaks</a>.</em></p> <p><img src="http://interblah.net/images/refinements/refinements.001.jpeg" alt="" /></p> <p>Chances are, you’ve heard of refinements, but never used them.</p> <p>The Refinements feature has existed as a patch and subsequently an official part of Ruby for around five years, and yet to most of us, it only exists in the background, surrounded by a haze of opinions about how they work, how to use them and, indeed, whether or not using them is a good idea at all.</p> <p>I’d like to spend a little time taking a look at what Refinements are, how to use them and what they can do.</p> <h3 id="disclaimer">Disclaimer</h3> <p>But don’t get me wrong - this is not a sales pitch for refinements! I am not going to try to convince you that they will solve all your problems and that you should all be using them!</p> <p><img src="http://interblah.net/images/refinements/refinements.002.jpeg" alt="" /></p> <p>The title of this presentation is “<em>Why</em> is nobody using refinements?” and that’s a genuine question. I don’t have all the answers!</p> <p>My only goal is that, by the end of this, both you and I will have a better understanding of what they actually are, what they can actually do, when they might be useful and why it might be that they’ve lingered in the background for so long.</p> <h2 id="what-are-refinements">What are refinements?</h2> <p>Simply put, refinements are a mechanism to <em>change</em> the behaviour of an object in a <em>limited</em> and <em>controlled</em> way.</p> <p>By <em>change</em>, I mean add new methods, or redefine existing methods on an object.</p> <p>By <em>limited and controlled</em>, I mean that adding or changing those methods does not have an impact on other parts of our software which might interact with that object.</p> <p>Let’s look at a very simple example:</p> <p><pre><div class="code ruby"><span class="k">module</span> <span class="nn">Shouting</span> <span class="n">refine</span> <span class="no">String</span> <span class="k">do</span> <span class="k">def</span> <span class="nf">shout</span> <span class="nb">self</span><span class="p">.</span><span class="nf">upcase</span> <span class="o">+</span> <span class="s2">"!"</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span></div></pre></p> <p>Refinements are defined inside a module, using the <code>refine</code> method.</p> <p>This method accepts a class – <code>String</code>, in this case – and a block, which contains all the methods to add to that class when the refinement is used. You can refine as many classes as you want within the module, and define as many methods are you’d like within each block.</p> <p>To use a refinement, we call the <code>using</code> method with the name of the enclosing module.</p> <p><pre><div class="code ruby"><span class="k">class</span> <span class="nc">Thing</span> <span class="n">using</span> <span class="no">Shouting</span> <span class="s2">"hello"</span><span class="p">.</span><span class="nf">shout</span> <span class="c1"># =&gt; "HELLO!"</span> <span class="k">end</span></div></pre></p> <p>When we do this, all instances of that class, within the same scope as our <code>using</code> call, will have the refined methods available.</p> <p>Another way of saying that is that the refinement has been “activated” within this scope.</p> <p>However, any strings outside that scope are left unaffected:</p> <p><pre><div class="code ruby"><span class="s2">"hello"</span><span class="p">.</span><span class="nf">shout</span> <span class="c1"># =&gt; NoMethodError: undefined method 'shout' for "Oh no":String</span></div></pre></p> <h4 id="changing-existing-methods">Changing existing methods</h4> <p>Refinements can also change methods that already exist</p> <p><pre><div class="code ruby"><span class="k">module</span> <span class="nn">TexasTime</span> <span class="n">refine</span> <span class="no">Time</span> <span class="k">do</span> <span class="k">def</span> <span class="nf">to_s</span> <span class="k">if</span> <span class="n">hour</span> <span class="o">&gt;</span> <span class="mi">12</span> <span class="s2">"Afternoon, y’all!"</span> <span class="k">else</span> <span class="k">super</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span></div></pre></p> <p>When the refinement is active, it is used instead of the existing method (although the original is still available via the <code>super</code> keyword, which is very useful).</p> <p><pre><div class="code ruby"><span class="k">class</span> <span class="nc">RubyConf</span> <span class="n">using</span> <span class="no">TexasTime</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"12:00"</span><span class="p">).</span><span class="nf">to_s</span> <span class="c1"># =&gt; "2015-11-16 12:00:00"</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"14:15"</span><span class="p">).</span><span class="nf">to_s</span> <span class="c1"># =&gt; "Afternoon, y’all!"</span> <span class="k">end</span></div></pre></p> <p>Anywhere the refinement isn’t active, the original method gets called, exactly as before.</p> <p><pre><div class="code ruby"><span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"14:15"</span><span class="p">).</span><span class="nf">to_s</span> <span class="c1"># =&gt; "2015-11-16 14:15:00"</span></div></pre></p> <p>And that’s really all there is to refinements – two new methods, <code>refine</code>, and <code>using</code>.</p> <p>However, there are some quirks, and if we really want to properly understand refinements, we need to explore them. And the best way of approaching this, is by considering a few more simple examples.</p> <h2 id="using-using">Using <code>using</code></h2> <p>Now we know that we can call the <code>refine</code> method within a module to create refinements, and that’s all relatively straightforward, but it turns out that where and when you call the <code>using</code> method has a profound effect on how the refinement behaves with our code.</p> <p>We’ve seen that invoking <code>using</code> inside a class definition works. We activate the refinement, and we can call refined methods on a String instance:</p> <p><pre><div class="code plaintext">class Thing using Shouting "hello".shout # =&gt; "HELLO!" end</div></pre></p> <p>But we can also move the call to <code>using</code> somewhere outside the class, and still use the refined method as before.</p> <p><pre><div class="code plaintext">using Shouting class Thing "hello".shout # =&gt; "HELLO!" end</div></pre></p> <p>In the examples so far we’ve just been calling the refined method directly, but we can also use them within methods defined in the class.</p> <p><pre><div class="code plaintext">class Thing using Shouting def greet "hello".shout end end Thing.new.greet # =&gt; "HELLO!"</div></pre></p> <p>Again, even if the call to <code>using</code> is outside of the class, our refined behaviour still works.</p> <p><pre><div class="code plaintext">using Shouting class Thing def greet "hello".shout end end Thing.new.greet # =&gt; "HELLO!"</div></pre></p> <p>But this doesn’t work:</p> <p><pre><div class="code plaintext">class Thing using Shouting def greet "hello" end end Thing.new.greet.shout! # =&gt; NoMethodError</div></pre></p> <p>We can’t call <code>shout</code> on the String returned by our method, even though that String object was created within a class where the refinement was activated.</p> <p>And here’s another broken example:</p> <p><pre><div class="code plaintext">class Thing using Shouting end class Thing "hello".shout # =&gt; NoMethodError end</div></pre></p> <p>We’ve activated the refinement inside our class, but when we reopen the class and try to use the refinement, we get <code>NoMethodError</code> again.</p> <p>If we nest a class within another where the refinement is active, it seems to work:</p> <p><pre><div class="code plaintext">class Thing using Shouting class OtherThing "hello".shout # =&gt; "HELLO!" end end</div></pre></p> <p>But it doesn’t work in subclasses:</p> <p><pre><div class="code plaintext">class Thing using Shouting end class OtherThing &lt; Thing "hello".shout # =&gt; NoMethodError end</div></pre></p> <p>Unless they are also nested classes:</p> <p><pre><div class="code plaintext">class Thing using Shouting class OtherThing &lt; Thing "hello".shout # =&gt; "HELLO!" end end</div></pre></p> <p>And even though nested classes work, if you try to define a nested class using the “double-colon” or “compact form”, our refinements have disappeared again:</p> <p><pre><div class="code plaintext">class Thing using Shouting end class Thing::OtherThing "hello".shout # =&gt; NoMethodError end</div></pre></p> <p>Even blocks might seem to act strangely:</p> <p><pre><div class="code plaintext">class Thing using Shouting def run yield end end Thing.new.run do "hello".shout end # =&gt; NoMethodError</div></pre></p> <p>Our class uses the refinement, but when we pass a block to a method in that class, suddenly it breaks.</p> <p>So what’s going on here? For many of us this is quite counter-intuitive; after all, we’re used to being able to re-open classes, or share behaviour between super- and sub-classes, but it seems like that only works intermittently with refinements?</p> <p>It turns out that the key to understanding how and when refinements are available relies on another aspect of how Ruby works that you may have already heard of, or even encountered directly.</p> <p>The key to understanding refinements is understanding about lexical scope.</p> <h2 id="lexical-scope">Lexical scope</h2> <p>To understand about lexical scope, we need to learn about some of the things that happen when Ruby parses our program.</p> <p>Let’s look at the first example again:</p> <p><img src="http://interblah.net/images/refinements/refinements.025.jpeg" alt="" /></p> <p>As Ruby parses this program, it is constantly tracking a handful of things to understand the meaning of the program. Exploring these in detail would take a whole presentation in itself, but for the moment, the one we are interested in is called the “current lexical scope”.</p> <p>Let’s “play computer” and follow Ruby as it processes our simple program here.</p> <h3 id="the-top-level-scope">The top-level scope</h3> <p>When Ruby starts parsing the file, it creates a new structure in memory – a new “lexical scope” – which holds various bits of information that Ruby uses to track what’s happening at that point. We call this the “top-level” lexical scope.</p> <p><img src="http://interblah.net/images/refinements/refinements.026.jpeg" alt="" /></p> <p>When we encounter a class (or module) definition, as well as creating the class and everything that involves, Ruby also creates a new lexical scope, nested “inside” the current one.</p> <p><img src="http://interblah.net/images/refinements/refinements.028.jpeg" alt="" /></p> <p>We can call this lexical scope “A”, just to give it an easy label. Visually it makes sense to show these as nested, but behind the scenes this relationship is modelled by each scope linking to its parent. “A”’s parent is the top level scope, and the top level scope has no parent.</p> <p><img src="http://interblah.net/images/refinements/refinements.030.jpeg" alt="" /></p> <p>As Ruby processes all the code within this class definition, the “current” lexical scope is now A.</p> <p>When we call <code>using</code>, Ruby stores a reference to the refinement <em>within the current lexical scope</em>. We can also say that <em>within lexical scope “A”, the refinement has been activated</em>.</p> <p><img src="http://interblah.net/images/refinements/refinements.032.jpeg" alt="" /></p> <p>We can see now that there are no activated refinements in the top-level scope, but our Shouting refinement is activated for lexical scope A.</p> <p>Next, we can see a call to the method <code>shout</code> on a <code>String</code> instance. The details of method dispatch are complex and interesting in their own right, but one of the things that happens at this point is that Ruby checks to see if there are any activated refinements in the <em>current lexical scope</em> that might affect this method.</p> <p><img src="http://interblah.net/images/refinements/refinements.034.jpeg" alt="" /></p> <p>In this case, we can see that for <em>current lexical scope</em> “A”, there is an activated refinement for the <code>shout</code> method on <code>Strings</code>, which is exactly what we’re calling.</p> <p>Ruby then looks up the correct method body within the refinement module, and invokes <em>that</em> instead of any existing method.</p> <p><img src="http://interblah.net/images/refinements/refinements.036.jpeg" alt="" /></p> <p>And there, we can see that our refinement is working as we hope.</p> <p>So what about when we try and call the method later? Well, once we leave the class definition, the current lexical scope returns to being the parent, which is the top-level one.</p> <p><img src="http://interblah.net/images/refinements/refinements.039.jpeg" alt="" /></p> <p>Then we find our second String instance and a method being called on it.</p> <p>Once again, when ruby dispatches for the <code>shout</code> method, it checks the <em>current lexical scope</em> – the top-level one – for the presence of any refinements, but in this case, there are none. Ruby behaves as normal, which is to call <code>method_missing</code> and this will raise an exception by default.</p> <p><img src="http://interblah.net/images/refinements/refinements.041.jpeg" alt="" /></p> <h3 id="calling-using-at-the-top-level">Calling <code>using</code> at the top-level</h3> <p>If we had called <code>using Shouting</code> outside of the class, at the top level, our use of the refined method both inside and outside the class works perfectly.</p> <p><img src="http://interblah.net/images/refinements/refinements.043.jpeg" alt="" /></p> <p>This is because once a refinement is activated, it is activated for all nested scopes, so calling <code>using</code> at the top level activated the refinement in the top level scope, which means it will be activated in any nested scopes, including “A”. And so, our call to the refined method within the class works too.</p> <p>So this is our first principle of how refinements work:</p> <p><strong>When we activate a refinement with the using method, that refinement is active in the current and any nested lexical scopes.</strong></p> <p>However, once we leave that scope, the refinement is no longer activated, and Ruby behaves as it did before.</p> <h3 id="lexical-scope-and-re-opening-classes">Lexical scope and re-opening classes</h3> <p>Let’s look at another example from earlier. Here we define a class, and activate the refinement, and later re-open that class and try to use it. We’ve already seen that this doesn’t work; the question is why.</p> <p><img src="http://interblah.net/images/refinements/refinements.046.jpeg" alt="" /></p> <p>Watching Ruby build its lexical scopes reveals why this is the case. Once again, the first class definition gives us a new, nested lexical scope A. It’s within this scope, that we activate the refinements. Once we reach the end of that class definition, we return to the top level lexical scope.</p> <p>When we re-open the class, Ruby creates a nested lexical scope as before, but it is <em>distinct</em> from the previous one. Let’s call it B to make that clear.</p> <p><img src="http://interblah.net/images/refinements/refinements.050.jpeg" alt="" /></p> <p>While the refinement is activated in the first lexical scope, when we re-open the class, we are in <em>a new lexical scope</em>, and one where the refinements are no longer active.</p> <p>So our second principle is this:</p> <p><strong>Just because the class is the same, doesn’t mean you’re back in the same lexical scope.</strong></p> <p>This is also the reason why our example with subclasses didn’t behave as we might’ve expected:</p> <p><img src="http://interblah.net/images/refinements/refinements.053.jpeg" alt="" /></p> <p>It should be clear now, that the fact that we are within a subclass actually has no bearing on whether or not the refinement is activated; it’s entirely determined by lexical scope. Any time Ruby encounters a class or module definition via the <code>class</code> (or <code>module</code>) keywords, it creates a new, fresh, lexical scope, even if that class (or module) has already been defined somewhere else.</p> <p>This is also the reason why, even when activated at the top-level of a file, refinements only stay activated until the end of that file – because each file is processed using a new top-level lexical scope.</p> <p>So now we have another two principles about how lexical scope and refinements work.</p> <p>Just as re-opened classes have a different scope, so do subclasses. In fact:</p> <p><strong>The class hierarchy has nothing to do with the lexical scope hierarchy.</strong></p> <p>We also now know that <strong>every file is processed with a new top-level scope, and so refinements activated in one file are not activated in any other files</strong> – unless those other files also explicitly activate the refinement.</p> <h3 id="lexical-scope-and-methods">Lexical scope and methods</h3> <p>Let’s look at one more of our examples from earlier:</p> <p><img src="http://interblah.net/images/refinements/refinements.057.jpeg" alt="" /></p> <p>Here we are activating a refinement within a class, and defining a method in that class which uses the refinement. Later, we create an instance of the class and call our method.</p> <p>We can see that even though the method gets invoked from the top level lexical scope – where our refinement is <em>not</em> activated – our refinement still somehow works. So what’s going on here?</p> <p>When Ruby processes a method definition, it stores with that method a reference to the current lexical scope at the point where the method was defined. So when Ruby processes the <code>greet</code> method definition, it stores a reference to lexical scope A with that:</p> <p><img src="http://interblah.net/images/refinements/refinements.058.jpeg" alt="" /></p> <p>When we call the <code>greet</code> method – from anywhere, even a different file – Ruby evaluates it using the lexical scope associated with its definition. So when Ruby evaluates <code>”hello".shout</code> inside our <code>greet</code> method, and tries to dispatch to the <code>shout</code> method, it checks for activated refinements in lexical scope “A”, even if the method was called from an entirely different lexical scope.</p> <p><img src="http://interblah.net/images/refinements/refinements.060.jpeg" alt="" /></p> <p>We already know that our refinement is active in that scope, and so Ruby can use the method body for “shout” from the refinement.</p> <p>This gives us our fourth principle:</p> <p><strong>Methods are evaluated using the lexical scope at their definition, no matter where those methods are actually called from.</strong></p> <h3 id="lexical-scope-and-blocks">Lexical scope and blocks</h3> <p>A very similar process explains why our block example didn’t work. Here’s that example again – a method defined in a class where the refinement is activated yields to a block, but when we call that method with a block that uses the refinement, we get an error.</p> <p><img src="http://interblah.net/images/refinements/refinements.064.jpeg" alt="" /></p> <p>We can quickly see which lexical scopes Ruby has created as it processed this code. As before, we have a nested lexical scope “A”, and the method defined in our class is associated with it:</p> <p><img src="http://interblah.net/images/refinements/refinements.065.jpeg" alt="" /></p> <p>However, just as methods are associated with the current lexical scope, so are blocks (and procs, lambdas and so on). When we define that block, the current lexical scope is the top level one.</p> <p><img src="http://interblah.net/images/refinements/refinements.066.jpeg" alt="" /></p> <p>When the <code>run</code> method yields to the block, Ruby evaluates that block using the top-level lexical scope, and so Ruby’s method dispatch algorithm finds no active refinements, and therefore no <code>shout</code> method.</p> <p>Our final principle</p> <p><strong>Blocks – and procs, lambdas and so on – are also evaluated using the lexical scope at their definition.</strong></p> <p>With a bit of experimentation, we can also demonstrate to ourselves that even blocks evaluated using tricks like <code>instance_eval</code> or <code>class_eval</code> retain this link to their original lexical scope, even though the value of <code>self</code> might change.</p> <p>This link from methods and blocks to a specific lexical scope might seem strange or even confusing right now, but we’ll see soon that it’s precisely because of this that refinements are so safe to use.</p> <p>But I’ll get to that in a minute. For now, let’s recap what we know:</p> <h3 id="lexical-scope--principles-recap">Lexical scope &amp; principles recap</h3> <ul> <li>Refinements are controlled using the lexical scope structures already present in Ruby.</li> <li>You get a new lexical scope any time you do any of the following: <ul> <li>entering a different file</li> <li>opening a class or module definition</li> <li>running code from a string using <code>eval</code></li> </ul> </li> </ul> <p>As I said earlier: you might find the idea of lexical scope surprising, but it’s actually a very useful property for a language; without it, many aspects of Ruby we take for granted would be much harder, if not impossible to produce. Lexical scope is used as part of how Ruby understands references to constants, for example, and also what makes it possible to pass blocks and procs around as “closures”.</p> <p>We also now have the five basic principles that will enable us to explain how and why refinements behave the way they do:</p> <ol> <li>Once you call <code>using</code>, refinements are activated within the current, and any nested, lexical scopes</li> <li>The nested scope hierarchy is entirely distinct from any class hierarchy in your code; subclasses and superclasses have no effect on refinements; only nested lexical scopes do.</li> <li>Different files get different top-level scopes, so even if we call <code>using</code> at the very top of a file, and activate it for all code in that file, the meaning of code in all other files is unchanged.</li> <li>Methods are evaluated using the current lexical scope at their point of definition, so we can call methods that make use of refinements internally from anywhere in the rest of our codebase.</li> <li>Blocks are also evaluated using the lexical scope, and so it’s impossible for refinements activated elsewhere in our code to change the behaviour of blocks — or indeed, any other methods or code — written where that refinement wasn’t present.</li> </ol> <p>Right! So now we know. But why should we even care? What are refinements actually good for? Anything? Nothing?</p> <p>Let’s try to find out.</p> <h2 id="lets-use-refinements">Let’s use refinements</h2> <p>Now, another disclaimer: these are just some ideas – some less controversial than others – but hopefully they will help frame what refinements might make easier or more elegant or robust.</p> <p>The first will not be a surprise, but I think it’s worth discussing anyway.</p> <h3 id="monkey-patching">Monkey-patching</h3> <p>Monkey-patching is the act of modifying a class or object that we don’t own – that we didn’t write. Because Ruby has open classes, it’s trivial to redefine any method on any object with new or different behaviour.</p> <p>The danger that monkey-patching brings is that those changes are global – they affect every part of the system as it runs. As a result, it can be very hard to tell which parts of our software will be affected.</p> <p>If we change the behaviour of an existing method to suit one use, there’s a good chance that some distant part of the codebase – perhaps hidden within Rails or another gem – is going to call that method expecting the original behaviour (or its own monkey-patched behaviour!), and things are going to get messy.</p> <p>Say I’m writing some code in a gem, and as part of that I want to be able to turn an underscored String into a camelized version. I might re-open the String class and add this simple, innocent-looking method to make it easy to do this transformation.</p> <p><pre><div class="code plaintext">class String def camelize self.split("_").map(&amp;:capitalize).join end end "ruby_conf_2015".camelize # =&gt; "RubyConf2015"</div></pre></p> <p>Unfortunately, as soon as anyone tries to use my gem in a Rails application, their test suite is going to go from passing, not to failing but to ENTIRELY CRASHING with a very obscure error:</p> <pre><code>/app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/inflector/methods.rb:261:in `const_get': wrong constant name Admin/adminHelper (NameError) from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/inflector/methods.rb:261:in `block in constantize' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/inflector/methods.rb:259:in `each' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/inflector/methods.rb:259:in `inject' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/inflector/methods.rb:259:in `constantize' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/core_ext/string/inflections.rb:66:in `constantize' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/abstract_controller/helpers.rb:156:in `block in modules_for_helpers' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/abstract_controller/helpers.rb:144:in `map!' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/abstract_controller/helpers.rb:144:in `modules_for_helpers' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/action_controller/metal/helpers.rb:93:in `modules_for_helpers' </code></pre> <p>You can see the error at the top there - something to do with constant names or something? Looking at the backtrace I don’t see anything about a <code>camelize</code> method anywhere?</p> <p>Now we all know what caused the problem, but if someone else had written that code, I very much doubt it would be so obvious. And this is exactly the problem that Yehuda Katz identified with monkey patching in <a href="http://yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice/">his blog post about refinements almost exactly five years ago</a>.</p> <h4 id="monkey-patching-vs-refinements">Monkey-patching vs refinements</h4> <p>Monkey-patching has two fundamental issues:</p> <p>The first is breaking API expectations. We can see that Rails has some expectation about the behaviour of the <code>camelize</code> method on String, which we obviously broke when we added our own monkey-patch elsewhere.</p> <p>The second is that monkey patching can make it far harder to understand what might be causing unexpected or strange behaviour in our software.</p> <p>Refinements in Ruby address both of these issues.</p> <p><img src="http://interblah.net/images/refinements/refinements.082.jpeg" alt="" /></p> <p>If we change the behaviour of a class using a refinement, we know that it cannot affect parts of the software that we don’t control, because refinements are restricted by lexical scope.</p> <p>We’ve seen already that refinements activated in one file are not activated in any other file, even when re-opening the same classes. If I wanted to use a version of camelize in my gem, I could define and use it via a refinement, but anywhere that refinement wasn’t specifically activated – which it won’t be anywhere inside of Rails, for example – the original behaviour remains.</p> <p>It’s actually impossible to break existing software like Rails using refinements. There’s no way to influence the lexical scope associated with code without editing that code itself, and so the only way we can “poke” some refinement behaviour into a gem is by finding the source code for that gem and literally inserting text into it.</p> <p>This is exactly what I meant by <em>limited and controlled</em> at the start.</p> <p>Refinements also make it easier to understand where unexpected behaviour may be coming from, because they require an explicit call to <code>using</code> somewhere in the same file as the code that uses that behaviour. If there are no <code>using</code> statements in a file, we can be confident – assuming nothing else is doing any monkey-patching – that Ruby will behave as we would normally expect.</p> <p>This is not to say that it’s impossible to produce convoluted code which is tricky to trace or debug – that will always be possible – but if we use refinements, there will always be a visual clue that a refinement is activated.</p> <p>Onto my second example.</p> <h3 id="managing-api-changes">Managing API changes</h3> <p>Sometimes software we depend on changes its behaviour. APIs change in newer versions of libraries, and in some cases even the language can change.</p> <p>For example, in Ruby 2, the behaviour of the <code>chars</code> method on <code>String</code>s changed from returning an enumerator to returning an <code>Array</code> of single-character strings.</p> <p><pre><div class="code plaintext"># Ruby 1.9 "hello".chars # =&gt; #&lt;Enumerator: "hello".each_char&gt; # Ruby 2.0+ "hello".chars # =&gt; ["h", "e", "l", "l", "o"]</div></pre></p> <p>Imagine we’re migrating an application from Ruby 1.9 to Ruby 2 (or later), and we discover that some part of our application which depends on calling <code>chars</code> on a String and expecting an enumerator to be returned.</p> <p>If some parts of our software rely on the old behaviour, we can use refinements to preserve the original API, without impacting any other code that might have already been adapted to the new API.</p> <p>Here’s a simple refinement which we could activate for only the code which depends on the Ruby 1.9 behaviour:</p> <p><pre><div class="code plaintext">module OldChars refine String do def chars; each_char; end end end "hello".chars # =&gt; ["h", "e", "l", "l", “o"] using OldChars "hello".chars # =&gt; #&lt;Enumerator: "hello".each_char&gt;</div></pre></p> <p>The rest of the system remains unaffected, and any dependencies that expect the Ruby 2 behaviour will continue to work into the future.</p> <p>My third example is probably familiar to most people.</p> <h3 id="dsls">DSLs</h3> <p>One of the major strengths of Ruby is that its flexibility can be used to help us write very expressive code, and in particular supporting the creation of DSLs, or “domain specific languages”. These are collections of objects and methods which have been designed to express concepts as closely as possible to the terminology used by non-programmers, and often designed to read more like human language than code.</p> <p>Adding methods to core classes can often help make DSLs more readable and expressive, and so refinements are a natural candidate for doing this in a way that doesn’t leak those methods into other parts of an application.</p> <h4 id="rspec-as-a-dsl">RSpec as a DSL</h4> <p>RSpec is a great example of a DSL for testing. Until recently, this would’ve been a typical example of RSpec usage:</p> <p><pre><div class="code plaintext">describe "Ruby" do it "should bring happiness" do developer = Developer.new(uses: "ruby") developer.should be_happy end end</div></pre></p> <p>One hallmark is the emphasis on writing code that reads fluidly, and we can see that demonstrated in the line <code>developer.should be_happy</code>, which while valid Ruby, reads more like English than code. To enable this, RSpec used monkey-patching to add a <code>should</code> method to all objects.</p> <p>Recently, RSpec moved away from this DSL, and while I cannot speak for the developers who maintain RSpec, I think it’s fair to say that part of the reason was to avoid the monkey-patching of the <code>Object</code> class.</p> <p>However, refinements offer a compromise that balances the readability of the original API with the integrity of our objects.</p> <p><pre><div class="code plaintext">module RSpec refine Object do def should(expectation) expectation.satisfied_by?(self) end end end using RSpec 123.should eq 123 # =&gt; true false.should eq true # =&gt; false</div></pre></p> <p>It’s easy to add a <code>should</code> method to all objects in your spec files using a refinement, but this method doesn’t leak out into the rest of the codebase.</p> <p>The compromise is that you <em>must</em> write <code>using RSpec</code> at the top of every file, which I don’t think is a large price to pay. But, you might disagree and we’ll get to that shortly.</p> <p>RSpec isn’t the only DSL that’s commonly used, and you might not even have thought of it as a DSL – after all, it’s just Ruby. You can also view the routes file in a Rails application as a DSL of sorts, or the query methods ActiveRecord provides. In fact, the Sequel gem actually does, optionally, let you write queries more fluently by using a refinement to add methods and behaviour to strings, symbols and other objects.</p> <p>DSLs are everywhere, and refinements can help make them even more expressive without resorting to monkey-patching or other brittle techniques.</p> <p>Onto my last example.</p> <h3 id="internal-access-control">Internal access control</h3> <p>Refinements might not just be useful for safer monkey-patching or implementing DSLs.</p> <p>We might also be able to harness refinements as a design pattern of sorts, and use them to ensure that certain methods are only callable from specific, potentially-restricted parts of our codebase.</p> <p>For example, consider a Rails application with a model that has some “dangerous” or “expensive” method.</p> <p><pre><div class="code plaintext">module UserAdmin refine User do def purge! user.associated_records.delete_all! user.delete! end end end # in app/controllers/admin_controller.rb class AdminController &lt; ApplicationController using UserAdmin def purge_user User.find(params[:id]).purge! end end</div></pre></p> <p>By using a refinement, the only places we can call this method are where we’ve explicitly activated that refinement.</p> <p>From everywhere else – normal controllers, views or other classes – even though they might be handling the same object – the very same instance, even – the dangerous or expensive method is guaranteed not to be available there.</p> <p>I think this is a really interesting use for refinements – as a design pattern rather than just a solution for monkey-patching – and while I know there could be some obvious objections to that suggestion, I’m certainly curious to explore it a bit more before I decide it’s not worthwhile.</p> <p>So those are some examples of things we might be able to do with refinements. I think they are all potentially very interesting, and potentially useful.</p> <p>So, finally, to the question I’m curious about. If refinements can do all of these things in such elegant and safe ways, why aren’t we seeing more use of them?</p> <h2 id="why-is-nobody-using-refinements">Why is nobody using refinements</h2> <p>It’s been five years since they appeared, and almost three years since they were officially a part of Ruby. And yet, when I search GitHub, almost none of the results are actual uses of refinements.</p> <p><img src="http://interblah.net/images/refinements/refinements.093.jpeg" alt="" /></p> <p>In fact, some of the top hits are gems that actually try to “remove” refinements from the language!</p> <p><img src="http://interblah.net/images/refinements/refinements.095.jpeg" alt="" /></p> <p>You can see in the description: “No one knows what problem they solve or how they work.”! Well, hopefully we at least have some ideas about that now.</p> <h4 id="breaking-bad">Breaking bad</h4> <p><img src="http://interblah.net/images/refinements/refinements.096.jpeg" alt="" /></p> <p>I actually asked another of the speakers at RubyConf2015 — who will remain nameless — what they thought the answer to my question might be, and they said:</p> <blockquote> <p>“Because they’re bad, aren’t they.”</p> </blockquote> <p>As if it was a fact.</p> <p>Now, my initial reaction to this kind of answer is <a href="http://interblah.net/images/refinements/refinements.097.jpeg">somewhat emotionally charged</a>, but my actual answer was more like:</p> <blockquote> <p>“Are they? Why do you think that?”</p> </blockquote> <p>So I don’t find this answer very satisfying. Why are they bad?</p> <p>I asked them why, and they replied</p> <blockquote> <p>“Because they’re just another form of monkey patching, right?”</p> </blockquote> <p>Well – yes, sort of, but also… not really.</p> <p>And just because they might be related in some way to monkey-patching – does that automatically make them bad, or not worth understanding?</p> <p>I can’t shake the feeling that this is the same mode of thinking that leads to ideas like “<em>meta-programming is too much magic</em>” or “<em>using single or double quoted strings consistently is a *very important thing*</em>” or that something – anything – you type into a text editor can be described as “awesome” when that word should be reserved exclusively for moments in your life like seeing the Grand Canyon for the first time, and not when you install the latest gem or anything like that.</p> <p><img src="http://interblah.net/images/refinements/refinements.101.jpeg" alt="" /></p> <p>I am… suspicious… of “awesome”, and so I’m also suspicious of “bad”.</p> <h4 id="slow">Slow?</h4> <p>I asked another friend if they had any ideas about why people weren’t using refinements, and they said “because they’re slow”, again, as if it was a fact.</p> <p>And if that were true, then that would be totally legitimate… <a href="http://blog.honeybadger.io/benchmarking-ruby-refinements/">but it turns out it’s not</a>:</p> <blockquote> <p>““TL;DR: Refinements aren’t slow. Or at least they don’t seem to be slower than ‘normal’ methods”</p> </blockquote> <p>So why aren’t people using refinements? Why do people have these ideas that they are slow, or just plain bad?</p> <p>Is there any solid basis for those opinions?</p> <p>As I told you right at the start, I don’t have a neatly packaged answer, and maybe nobody does, but here are my best guesses, based on tangible evidence and understanding of how refinements actually work</p> <h3 id="lack-of-understanding">1. Lack of understanding?</h3> <p>While refinements have been around for almost five years, the refinements you see now are not the same as those that were introduced half a decade ago. Originally, they weren’t strictly lexically scoped, and while this provides some opportunity for more elegant code than what we’ve seen today – think not having to writing <code>using</code> at the top of every RSpec file, for example – it also breaks the guarantee that refinements cannot affect distant parts of a codebase.</p> <p>It’s also probably true that lexical scope is not a familiar concept for many Ruby developers. I’m not ashamed to say that even though I’ve been using Ruby for over 13 years, it’s only recently that I really understood what lexical scope is actually about. I think you can probably make a lot of money writing Rails applications without ever really caring about lexical scope, and yet, without understanding it, refinements will always seem like confusing and uncontrollable magic.</p> <p>The evolution of refinements hasn’t been smooth, and I think that’s why some people might feel like “nobody knows how they work or what problem they solve”. It doesn’t help, for example, that a lot of the blog posts you’ll find when you search for “refinements” are no longer accurate.</p> <p>Even the official Ruby documentation is actually wrong!</p> <p><img src="http://interblah.net/images/refinements/refinements.107.jpeg" alt="" /></p> <p>This hasn’t been true since Ruby 2.1, I think, but this is what the documentation says right now. Nudge to the ruby-core team: <a href="https://bugs.ruby-lang.org/issues/11681">issue 11681 might fix this</a>…</p> <p><em>UPDATE: since giving the presentation, this patch has <a href="https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/52629">been merged</a>!</em></p> <p>I think some of this … “information rot” can explain a little about why refinements stayed in the background.</p> <p>There were genuine and valid questions about early implementation and design choices, and I think it’s fair to say that some of the excitement about this new feature of Ruby was dampened as a result. But even with all the outdated blog posts, I don’t think this entirely explains why nobody seems to be using them.</p> <p>So perhaps it’s the current implementation that people don’t like.</p> <h3 id="adding-using-everywhere-is-a-giant-pain-in-the-ass">2. Adding using everywhere is a giant pain in the ass?</h3> <p>Maybe the idea of having to write <code>using</code> everywhere goes against the mantra of DRY - don’t repeat yourself - that we’ve generally adopted as a community. After all, who wants to have to remember to write <code>using RSpec</code> or <code>using Sequel</code> or <code>using ActiveSupport</code> at the top of almost every file?</p> <p>It doesn’t sound fun.</p> <p>And this points a another potential reason:</p> <h3 id="rails-and-ruby-doesnt-use-them">3. Rails (and Ruby) doesn’t use them</h3> <p>A huge number of Ruby developers spend most if not all of their time using Rails, and so Rails has a huge amount of influence over which language features are promoted and adopted by the community.</p> <pre><code>$ fgrep 'refine ' -R rails | wc -l # =&gt; 0 </code></pre> <p>Rails contains perhaps the largest collection of monkey-patches ever, in the form of <code>ActiveSupport</code>, but because it doesn’t use refinements, no signal is sent to developers that we should – or even could – be using them.</p> <p><em>UPDATE: Very shortly before this presentation was given, the first use of refinements actually was <a href="https://github.com/rails/rails/commit/8793f77c4a96197bb0f5e29b00828bf5903fcad7">added internally within Rails</a>.</em></p> <p>Now: You might be starting to form the impression that I don’t like Rails, but I’m actually very hesitant to single it out. To be clear: I love Rails – Rails feeds and clothes me, and enables me to fly to Texas and meet all y’all wonderful people. The developers who contribute to Rails are also wonderful human beings who deserve a lot of thanks.</p> <p>I also think it’s easily possible, and perhaps even likely, that there’s just no way for Rails to use refinements as they exist right now to implement something at the scale of ActiveSupport. It’s possible.</p> <p>But even more than this, nothing in the Ruby standard library itself uses refinements!</p> <pre><code>$ fgrep 'refine ' -R ruby-2.2.3/ext | wc -l # =&gt; 0 </code></pre> <p>Many new language features, like keyword arguments, won’t see widespread adoption until Rails and the Ruby standard library starts to promote them.</p> <p>Rails 5 has adopted keyword arguments and so I think we can expect to see them spread into other libraries as a result.</p> <p>Without compelling examples of refinements from the libraries and frameworks we use every day, there’s nothing nudging us towards really understanding when they are appropriate or not.</p> <h3 id="implementation-quirks">4. Implementation quirks</h3> <p>There are a number of quirks or unexpected gotchas that you will encounter if you try to use refinements at scale.</p> <p>For example, even when a refinement is activated, you cannot use methods like <code>send</code> or <code>respond_to?</code> to check for refined methods. You also can’t use them in convenient forms like <code>Symbol#to_proc</code>.</p> <p><pre><div class="code plaintext">using Shouting "hello".shout # =&gt; “HELLO" "hello".respond_to?(:shout) # =&gt; false "hello".send(:shout) # =&gt; NoMethodError ["how", "are", "you"].map(&amp;:shout) # =&gt; NoMethodError</div></pre></p> <p>You can also get into some really weird situations if you try to include a module into a refinement, where methods from that module cannot find other methods define in the same module.</p> <p>But this doesn’t necessarily mean that refinements are broken; all of these are either by design, or a direct consequence of lexical scoping.</p> <p>Even so, they are unintuitive and it could be that aspects like these are a factor limiting the ability to use refinements at the scale of, say, ActiveSupport.</p> <h3 id="refinements-solve-a-problem-that-nobody-has">5. Refinements solve a problem that nobody has?</h3> <p>As easy as it is for me to stand up here and make a logical and rational argument about why monkey-patching is bad, and wrong, and breaks things, it’s impossible to deny that fact that even since you started reading this page, software written using libraries that rely heavily on monkey-patching has made literally millions of dollars.</p> <p>So maybe refinements solve a problem that nobody actually has. Maybe, for all the potential problems that monkey patching might bring, the solutions we already have for managing those problems – test suites, for example – are already doing a good enough job at protecting us.</p> <p>But even if you disagree with that – which I wouldn’t blame you for doing – perhaps it points at another reason that’s more compelling. Maybe refinements aren’t the <em>right</em> solution for the problem of monkey-patching. Maybe the <em>right</em> solution is actually something like: object-oriented design.</p> <h3 id="the-rise-of-object-oriented-design">6. The rise of Object-oriented design</h3> <p>I think it’s fair to say that over the last two or three years, there’s been a significant increase in interest within the Ruby community in “Object Oriented Design”, which you can see in the presentations that Sandi Metz, for example, has given, or in her book, or discussion of patterns like “Hexagonal Architectures”, and “Interactors”, and “Presenters” and so on.</p> <p>The benefits that O-O design tends to bring to software are important and valuable – smaller objects with clearer responsibilities, that are easier and faster to test and change – all of this helps us do our jobs more effectively, and anything which does that must be good.</p> <p>And, from our perspective here, there’s nothing you can do with refinements that cannot also be accomplished by introducing one or more new objects or methods to encapsulate the new or changed behaviour.</p> <p>For example, rather than adding a “shout” method to all Strings, we could introduce a new class that only knows about shouting, and wrap any strings we want shouted in instances of this new class.</p> <p><pre><div class="code plaintext">class Shouter def initialize(string) @string = string end def shout @string.upcase + "!" end end shouted_hello = Shouter.new("hello") shouted_hello.shout # =&gt; "HELLO!"</div></pre></p> <p>I don’t want to discuss whether or not this is actually better than the refinement version, partly because this is a trivial example, so it wouldn’t be realistic to use, but mostly because I think there’s a more interesting point.</p> <p>While good O-O design brings a lot of tangible benefits to software development, the cost of “proper O-O design” is verbosity; just as a DSL tries to hide the act of programming behind language that appears natural, the introduction of many objects can – sometimes – make it harder to quickly grasp what the overall intention of code might be.</p> <p>And the right balance of explicitness and expressiveness will be different for different teams, or for different projects. Not everyone who interacts with software is a developer, let alone someone trained in software design, and so not everybody can be expected to easily adopt sophisticated principles with ease.</p> <p>Software is for its users and sometimes the cost of making them deal with extra objects or methods might not be worth the benefit in terms of design purity. It is – like so many things – often subjective.</p> <p>To be clear – I’m not in any way arguing that O-O design is not good; I’m simply wondering, whether or not it being good necessarily means that other approaches should not be considered in some situations.</p> <h3 id="so-whats-the-right-answer">So what’s the right answer?</h3> <p>And those are the six reasonable reasons that I could come up with as to why nobody is using refinements. So which is the right answer? I don’t know. There’s probably no way to know.</p> <p>I think these are all potentially good, defensible reasons why we might have decided collectively to ignore Refinements.</p> <p>However… I am not sure any of them are the answer that most accurately reflects reality. Unfortunately, I think the answer is more likely to the first one we encountered on this journey:</p> <p><em>Because other people told us they are “bad”.</em></p> <h2 id="conclusion-ish">Conclusion-ish</h2> <p>Let me make a confession.</p> <p><img src="http://interblah.net/images/refinements/refinements.124.jpeg" alt="" /></p> <p>When I said “this is not a sales pitch for refinements”, I really meant it. I’m fully open to the possibility that it might never be a good idea to use them. I think it’s unlikely, but it’s certainly possible.</p> <p>And to be honest, it doesn’t really bother me either way!</p> <p>What I do care about, though, is that we might start to accept and adopt opinions like “that feature is bad”, or “this sucks”, without ever pausing to question them or explore the feature ourselves.</p> <h3 id="sharing-opinions">Sharing opinions</h3> <p>Sharing opinions is good. Nobody has the time the research everything. That would not only be unrealistic, but one of the benefits of being in a community is that we can benefit from each other’s experiences. We can use our collective experience to learn and improve. This is definitely a good thing.</p> <p><img src="http://interblah.net/images/refinements/refinements.127.jpeg" alt="" /></p> <p>But if we just <em>accept</em> opinions as facts, without even asking “why”… I think this is more dangerous. If nobody ever questioned an opinion as fact, then we’d still believe the world was flat!</p> <p>It’s only by questioning opinions that we make new discoveries, and that we learn for ourselves, and that — together — we make progress as a community.</p> <p><img src="http://interblah.net/images/refinements/refinements.128.jpeg" alt="" /></p> <p>The “sucks”/“awesome” binary can be an easy and tempting shorthand, and it’s even fun to use – but it’s an illusion. Nothing is ever that clear cut.</p> <p>There’s a great quote by a British journalist and doctor called Ben Goldacre, that he uses any time someone tries to present a situation as starkly either good or bad:</p> <blockquote> <p>“I think you’ll find it’s a bit more complicated that that.”</p> </blockquote> <p>This is how I feel whenever anyone tells me something “sucks”, or is “awesome”. It might suck for you, but unless you can explain to me why it sucks, then how can I decide how your experience might apply to mine?</p> <p>One person’s “suck” can easily be another person’s “awesome”, and they are not mutually exclusive. It’s up to us to listen and read critically, and then explore for ourselves what we think.</p> <p>And I think this is particularly true when it comes to software development.</p> <h2 id="explore-for-yourselves">Explore for yourselves</h2> <p>If we hand most, if not all responsibility for that exploration to the relatively small number of people who talk at conferences, or have popular blogs, or who tweet a lot, or who maintain these very popular projects and frameworks, then that’s only a very limited perspective compared to the enormous size of the Ruby community.</p> <p>I think we have a responsibility not only to ourselves, but also to each other, to our community, not to use Ruby only in the ways that are either implicitly or explicitly promoted to us, but to explore the fringes, and wrestle with new and experimental features and techniques, so that as many different perspectives as possible inform on the question of “is this good or not”.</p> <p>If you’ll forgive the pun, there are no constants in programming – the opinions that Rails enshrines, even for great benefit, will change, and even the principles of O-O design are only principles, not immutable laws that should be blindly followed for the rest of time. There will be other ways of doing things. Change is inevitable.</p> <p>So we’re at the end now. I might not have been able to tell you precisely why so few people seem to be using refinements, but I do have one small request.</p> <p>Please – make a little time to explore Ruby. Maybe you’ll discover something simple, or maybe something wonderful. And if you do, I hope you’ll share it with everyone.</p>