<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wes Oldenbeuving</title>
    <description>The latest articles on DEV Community by Wes Oldenbeuving (@narnach).</description>
    <link>https://dev.to/narnach</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F922904%2F0fd3b95a-0a84-4146-82d0-5474dd38e15d.jpeg</url>
      <title>DEV Community: Wes Oldenbeuving</title>
      <link>https://dev.to/narnach</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/narnach"/>
    <language>en</language>
    <item>
      <title>.gitignore does not unignore my file!</title>
      <dc:creator>Wes Oldenbeuving</dc:creator>
      <pubDate>Mon, 12 Sep 2022 13:17:11 +0000</pubDate>
      <link>https://dev.to/narnach/gitignore-does-not-unignore-my-file-jj9</link>
      <guid>https://dev.to/narnach/gitignore-does-not-unignore-my-file-jj9</guid>
      <description>&lt;p&gt;Today I ran into an interesting edge case that my colleague and I could not easily explain until we checked the &lt;code&gt;git&lt;/code&gt; manual. I’m sharing the details because even after using &lt;code&gt;git&lt;/code&gt; for 15 years, I was mildly surprised by the behavior. Luckily, it &lt;em&gt;does&lt;/em&gt; make sense now that I understand the underlying reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s .gitignore?
&lt;/h2&gt;

&lt;p&gt;Anyone using &lt;a href="https://git-scm.com/"&gt;git&lt;/a&gt; for version control is likely familiar with the &lt;code&gt;.gitignore&lt;/code&gt; file where you can specify which file name patterns &lt;code&gt;git&lt;/code&gt; will ignore by default when you perform various actions, such as &lt;code&gt;git status&lt;/code&gt;, &lt;code&gt;git diff&lt;/code&gt; and &lt;code&gt;git add&lt;/code&gt;. It’s &lt;em&gt;very&lt;/em&gt; useful to ignore generated files and temporary files. You can add a line that starts with a &lt;code&gt;!&lt;/code&gt; to make it NOT ignore files with that pattern. Patterns are evaluated top to bottom, and the last pattern that matches a file will determine if it’s ignored or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did
&lt;/h2&gt;

&lt;p&gt;We wanted to add a simple &lt;code&gt;.gitkeep&lt;/code&gt; file to preserve the &lt;code&gt;tmp/pids&lt;/code&gt; directory in our project on all machines that checked out the project. This arguably trivial operation did not work and took me a while to figure out &lt;em&gt;why&lt;/em&gt; it did not work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Lets git NOT ignore .gitkeep files, i.e. always check them in:&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"!.gitkeep"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;span class="nb"&gt;touch &lt;/span&gt;tmp/pids/.gitkeep
git status
&lt;span class="c"&gt;# ... tmp/pids/.gitkeep is NOT listed as new file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it did not work
&lt;/h2&gt;

&lt;p&gt;So why is the file we just told &lt;code&gt;git&lt;/code&gt; we want to NOT ignore still getting ignored? It’s because our &lt;code&gt;.gitignore&lt;/code&gt; already contained this handy line that ignored all temporary files (because they have no place being checked in!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/tmp/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line makes &lt;code&gt;git&lt;/code&gt; ignores all files and directories inside &lt;code&gt;tmp&lt;/code&gt;. It’s also the direct reason why our earlier change did not give the result we want. After some reading of the &lt;a href="https://git-scm.com/docs/gitignore"&gt;manual&lt;/a&gt;, I found this relevant bit (emphasis mine):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An optional prefix “!” which negates the pattern; any matching file excluded by a previous pattern will become included again. &lt;strong&gt;It is not possible to re-include a file if a parent directory of that file is excluded.&lt;/strong&gt; Git doesn’t list excluded directories for performance reasons, so any patterns on contained files have no effect, no matter where they are defined.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It &lt;em&gt;really&lt;/em&gt; makes sense from a performance perspective to &lt;em&gt;not&lt;/em&gt; recurse into children of ignored directories to see if they happen to be &lt;em&gt;not&lt;/em&gt; ignored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR: you need to re-include an ignored parent directory if you want to customize rules for its contents.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to make git really NOT ignore your file
&lt;/h2&gt;

&lt;p&gt;Let’s update &lt;code&gt;.gitignore&lt;/code&gt; using our new knowledge. The &lt;code&gt;git diff&lt;/code&gt; after my changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; /tmp/*
&lt;span class="gi"&gt;+!/tmp/pids
+/tmp/pids/*.pid
+!.gitkeep
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order of operations here is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ignore &lt;code&gt;/tmp/*&lt;/code&gt; and all its children&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;/tmp/pids&lt;/code&gt; back&lt;/li&gt;
&lt;li&gt;Ignore all &lt;code&gt;.pid&lt;/code&gt; files in &lt;code&gt;tmp/pids&lt;/code&gt; (because those are the only ones generated there)&lt;/li&gt;
&lt;li&gt;Globally enable &lt;code&gt;.gitkeep&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternative: use the –force
&lt;/h2&gt;

&lt;p&gt;Alternatively, you can use &lt;code&gt;git add --force tmp/pids/.gitkeep&lt;/code&gt; to add it while ignoring &lt;code&gt;.gitignore&lt;/code&gt; rules. Arguably that’s faster than checking the manual and thinking about it, but it also prevents you from learning &lt;em&gt;why&lt;/em&gt; it failed to work in the first place.&lt;/p&gt;

&lt;p&gt;This worked for me ™, so I hope it helps you as well. If you run into issues, feel free to reach out to me via &lt;a href="http://twitter.com/narnach"&gt;Twitter&lt;/a&gt; or &lt;a href="//mailto:wes@narnach.com"&gt;email&lt;/a&gt;. I’m an experienced &lt;a href="https://www.narnach.com/"&gt;Ruby software developer&lt;/a&gt; with a focus on back-end systems and an obsession with code quality. I even have a few Ruby on Rails &lt;a href="https://www.rormaas.com/"&gt;maintenance services&lt;/a&gt; that I offer. If you still need to upgrade to Rails 6, then grab the handy free &lt;a href="https://www.rormaas.com/rails_upgrades.html#our-rails-upgrade-process"&gt;checklist&lt;/a&gt; or reach out to have me do it for you.&lt;/p&gt;

</description>
      <category>git</category>
      <category>programming</category>
      <category>ignore</category>
      <category>rails</category>
    </item>
    <item>
      <title>System tests in rails 6 + rspec + vcr + capybara</title>
      <dc:creator>Wes Oldenbeuving</dc:creator>
      <pubDate>Thu, 27 May 2021 12:33:03 +0000</pubDate>
      <link>https://dev.to/narnach/system-tests-in-rails-6-rspec-vcr-capybara-36l8</link>
      <guid>https://dev.to/narnach/system-tests-in-rails-6-rspec-vcr-capybara-36l8</guid>
      <description>&lt;p&gt;Getting this setup working took me an hour of searching the web to get some of the interactions working, so I hope this helps you do it faster and avoid some pitfalls I stumbled into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This post describes some of the obstacles I ran into getting proper JS-supported system tests working in my up-to-date Rails app, &lt;a href="https://www.infinity-feed.com/"&gt;Infinity Feed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;used&lt;/em&gt; to be complex to get Capybara working with a JS-enabled headless browser for your integration tests. This has gotten significantly easier with Rails 5.x and got another boost with 6.x.&lt;/p&gt;

&lt;p&gt;If your knowledge about Rails frontend testing still stems from the Rails v4 or v5 era, you might be pleasantly surprised by how easy it can be now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting the scene
&lt;/h2&gt;

&lt;p&gt;For this blog post, the relevant bits of my stack are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;capybara 3.35&lt;/li&gt;
&lt;li&gt;faraday 1.4&lt;/li&gt;
&lt;li&gt;rails 6.1&lt;/li&gt;
&lt;li&gt;rspec 3.10&lt;/li&gt;
&lt;li&gt;rspec-rails 5.0&lt;/li&gt;
&lt;li&gt;turbo-rails 0.5&lt;/li&gt;
&lt;li&gt;vcr 6.0&lt;/li&gt;
&lt;li&gt;webdrivers 4.6&lt;/li&gt;
&lt;li&gt;webmock 3.13&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ve got the usual suspects here that test Rails with RSpec. I use unit tests for models and other plain old Ruby objects (POROs), there are controller tests for specific interactions and there &lt;em&gt;was&lt;/em&gt; a feature test intended to test my frontend interactions. This is done differently in the modern era by using system tests.&lt;/p&gt;

&lt;p&gt;I use Faraday for HTTP calls, so &lt;a href="https://github.com/vcr/vcr"&gt;VCR&lt;/a&gt; + Webmock is part of my test stack to intercept &amp;amp; record HTTP calls so they can be replayed without hitting the network during my tests. This makes tests more consistent and faster. Win-win!&lt;/p&gt;

&lt;p&gt;I had a feature test written with out-of-the-box Capybara which appeared to work just fine, until I wanted to test that &lt;a href="https://turbo.hotwire.dev"&gt;Hotwire Turbo&lt;/a&gt; was doing its magic to make automatic JS-powered HTTP calls to replace my HTML with new HTML. The JS did not get executed in my tests!&lt;/p&gt;

&lt;p&gt;So, I had to figure out how to enable JS. Remembering how much of an ordeal this used to be, and how often the best practices changed, I started searching the internet for how it is done in 2021.&lt;/p&gt;

&lt;h2&gt;
  
  
  System tests have replaced features
&lt;/h2&gt;

&lt;p&gt;Since Rails 5.0/5.1 there are &lt;a href="https://guides.rubyonrails.org/testing.html#system-testing"&gt;system tests&lt;/a&gt;, which are similar to the old feature tests we had, but now Rails &lt;a href="https://github.com/rspec/rspec-rails/tree/5-0-maintenance#feature-specs"&gt;handles all of the overhead&lt;/a&gt; you &lt;em&gt;used&lt;/em&gt; to have to do yourself. You can stop futzing with DatabaseCleaner and configuring Puma to run in-process. It’s all taken care of by Rails now.&lt;/p&gt;

&lt;p&gt;Since Rails 6, integration with browsers for testing happens via the &lt;a href="https://github.com/titusfortner/webdrivers"&gt;webdrivers&lt;/a&gt; project, which handles downloading and updating the browser for you. It just works! Just beware of unexpected VCR interactions (see below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating features to system tests
&lt;/h2&gt;

&lt;p&gt;It’s really easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Move feature files from &lt;code&gt;spec/features/&lt;/code&gt; to &lt;code&gt;spec/system/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Change &lt;code&gt;type: :feature&lt;/code&gt; to &lt;code&gt;type: :system&lt;/code&gt; if needed for these files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:selenium_chrome_headless&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To the top of your system test files to pick a driver that supports JS. You can use this to &lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html"&gt;configure&lt;/a&gt; different browsers and screen sizes.&lt;/p&gt;

&lt;p&gt;And now you have working system tests!&lt;/p&gt;

&lt;p&gt;There’s a few extra things left to configure if you need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Devise integration
&lt;/h2&gt;

&lt;p&gt;If you use &lt;a href="https://github.com/heartcombo/devise"&gt;Devise&lt;/a&gt; to handle your authentication, you should register its integration test helpers to be used in feature and system tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/rails_helper.rb&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IntegrationHelpers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :feature&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IntegrationHelpers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables the &lt;em&gt;very&lt;/em&gt; useful &lt;code&gt;sign_in&lt;/code&gt; helper function, so your tests can focus on testing actual features instead of always having to simulate a login.&lt;/p&gt;

&lt;h2&gt;
  
  
  VCR integration
&lt;/h2&gt;

&lt;p&gt;VCR helps you intercept HTTP calls during tests, as I mentioned earlier. The thing is that Webdrivers automatically checks if the latest version of the browser is installed and downloads it if needed. As you can imagine, this did &lt;em&gt;not&lt;/em&gt; play well with VCR.&lt;/p&gt;

&lt;p&gt;My solution is to force the webdriver to check for an update &lt;em&gt;before&lt;/em&gt; I configure VCR. VCR also needs to be told not to interfere with system test calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/rails_helper.rb&lt;/span&gt;

&lt;span class="c1"&gt;# RSpec.configure do&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="c1"&gt;# end&lt;/span&gt;

&lt;span class="c1"&gt;# Load this and update the driver before loading VCR.&lt;/span&gt;
&lt;span class="c1"&gt;# If you don't, VCR will intercept the version check.&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'webdrivers'&lt;/span&gt;
&lt;span class="no"&gt;Webdrivers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Chromedriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;

&lt;span class="c1"&gt;# Configure VCR to don't interfere with system tests&lt;/span&gt;
&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# 127.0.0.1 is for system tests so whitelist it&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignore_hosts&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;

  &lt;span class="c1"&gt;# My personal settings, feel free to ignore/change&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cassette_library_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'spec/fixtures/vcr_cassettes'&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hook_into&lt;/span&gt; &lt;span class="ss"&gt;:webmock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:faraday&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This worked for me ™, so I hope it helps you as well. If you run into issues, feel free to reach out to me via &lt;a href="http://twitter.com/narnach"&gt;Twitter&lt;/a&gt; or &lt;a href="//mailto:wes@narnach.com"&gt;email&lt;/a&gt;. Besides being close to launching a smart RSS reader over at &lt;a href="https://www.infinity-feed.com"&gt;Infinity Feed&lt;/a&gt;, I’m a &lt;a href="https://www.narnach.com/"&gt;freelance Ruby software developer&lt;/a&gt; with a focus on back-end systems and an obsession with code quality.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>vcr</category>
      <category>systemtests</category>
    </item>
    <item>
      <title>Launching is scary</title>
      <dc:creator>Wes Oldenbeuving</dc:creator>
      <pubDate>Tue, 11 May 2021 16:34:36 +0000</pubDate>
      <link>https://dev.to/narnach/launching-is-scary-1h2p</link>
      <guid>https://dev.to/narnach/launching-is-scary-1h2p</guid>
      <description>&lt;p&gt;Until you decide to make your product available to the world, it’s just &lt;em&gt;yours&lt;/em&gt;. &lt;em&gt;You&lt;/em&gt; get to add to it, remove from it and change it however you want. &lt;em&gt;You&lt;/em&gt; can do this without risk of anyone getting upset that you changed or removed their favorite feature. There is nobody to &lt;em&gt;judge&lt;/em&gt; your product. Yet.&lt;/p&gt;

&lt;p&gt;When you &lt;em&gt;do&lt;/em&gt; launch, everything changes. Suddenly it’s not only &lt;em&gt;yours&lt;/em&gt; anymore, it’s also &lt;em&gt;theirs&lt;/em&gt;. Your users (who preferably turn into your customers) will start to use it. They develop attachments to what is there. They get frustrated with what &lt;em&gt;they&lt;/em&gt; don’t like. They will, collectively, present you with conflicting requests to make changes.&lt;/p&gt;

&lt;p&gt;The worst outcome is that nobody cares, and all that’s left is you and your disappointment.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not&lt;/em&gt; launching, just yet, in order to fix that one bug, to tweak that one thing… it’s so tempting! You get to keep it for yourself, just for a little while longer.&lt;/p&gt;

&lt;p&gt;The problem is that this mindset is similar to analysis paralysis: you can get stuck in it without ever launching. There is always something &lt;em&gt;more&lt;/em&gt; you can do. Something to add, something to fix. It will &lt;em&gt;never&lt;/em&gt; be perfect, but you can trick yourself into trying to achieve it. In reality, you’re just avoiding the potential for disappointment that &lt;em&gt;could&lt;/em&gt; come from an unsuccessful launch.&lt;/p&gt;

&lt;p&gt;I’ve read somewhere that if you are not at least a &lt;em&gt;little&lt;/em&gt; ashamed of what you have launched, you have waited too long. It always sounded wise and useful, but now that I’m getting closer to launching &lt;a href="https://www.infinity-feed.com/"&gt;Infinity Feed&lt;/a&gt;, my mind is spinning and grasping at all kinds of excuses for why I should wait &lt;em&gt;just a little longer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;How I’ve been dealing with it? I’ve started talking to folks about what I’m building. I’ve &lt;a href="https://blog.narnach.com/blog/2021/having-another-look-at-crypto/"&gt;hinted&lt;/a&gt; yesterday in the footer of my blog post that it will go live soon-ish. I’ve put up a marketing placeholder page (which read okay at the time, but now I just want to rip it off and replace it with my new one). Basically: I’ve started building momentum that should result in me launching the thing I’ve been announcing.&lt;/p&gt;

&lt;p&gt;Now if you’ll excuse me, I have a &lt;em&gt;few&lt;/em&gt; more things to do before I hit the “deploy” button for real. Launching really is scary, but this post pushed me another step closer to doing it.&lt;/p&gt;

&lt;p&gt;Curious? Want to say something? Feel free to reach out to me via &lt;a href="http://twitter.com/narnach"&gt;Twitter&lt;/a&gt; or &lt;a href="//mailto:wes@narnach.com"&gt;email&lt;/a&gt;. Besides being close to launching a smart RSS reader over at &lt;a href="https://www.infinity-feed.com"&gt;Infinity Feed&lt;/a&gt;, I’m a &lt;a href="https://www.narnach.com/"&gt;freelance Ruby software developer&lt;/a&gt; with a focus on back-end systems and an obsession with code quality. I’ve also decided to look at cryptocurrencies again, so expect me to mention that again in the future.&lt;/p&gt;

</description>
      <category>product</category>
      <category>launch</category>
      <category>procrastination</category>
      <category>business</category>
    </item>
    <item>
      <title>Having another look at crypto</title>
      <dc:creator>Wes Oldenbeuving</dc:creator>
      <pubDate>Mon, 10 May 2021 20:26:49 +0000</pubDate>
      <link>https://dev.to/narnach/having-another-look-at-crypto-1fcf</link>
      <guid>https://dev.to/narnach/having-another-look-at-crypto-1fcf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Addition on 8 September 2022: note that I never published a follow-up for this due to how obvious it is that a &lt;em&gt;lot&lt;/em&gt; of crypto is a scam, and how ethically dubious a lot of practices are. As a layperson, assume you will get fleeced when you enter the ecosystem. The amount of tinfoil and conspiracy theories in this scene promote Fear/Uncertainty/Doubt (FUD) which aims at getting new people to add money to the crypto ecosystem in order to prop up prices for folks already invested. Sound familiar? Yep, it's a huge pyramid scheme! See also: &lt;a href="https://www.youtube.com/watch?v=YQ_xWvX1n9g"&gt;Line goes up&lt;/a&gt;, a good video that breaks down the ecosystem and issues.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why now?
&lt;/h2&gt;

&lt;p&gt;Two of my friendly colleagues disappeared into a crypto-shaped black hole over the last three months. They re-emerged, talking with great enthusiasm about it. They have doubled their seed money, and talk about all the potential for the technologies in the space. I must admit, they have broken down some of my long held skepticism, so it’s time to look at it again myself and see if their hype is real.&lt;/p&gt;

&lt;p&gt;For context: I’m coming at this as a crypto skeptic software developer. I looked at Bitcoin when it was &lt;em&gt;very&lt;/em&gt; new (5+ years ago?) and did not get it. It seemed like speculation on a virtual coin without intrinsic value or purpose. It smelled a lot like a pyramid scheme. Association with black market trade did not help. In the last few years the negative environmental impact of Bitcoin has not made me a fan either.&lt;/p&gt;

&lt;p&gt;I’ve looked into speculative trade before. I’ve had a good look at &lt;a href="https://en.wikipedia.org/wiki/Foreign_exchange_market"&gt;ForEx&lt;/a&gt; in 2012-2013, doing paper trading, lots of reading, and some programmatic stuff, before deciding the speculative stuff was not really for me. Similarly I’ve read up on stock market investment, including &lt;a href="https://www.investopedia.com/terms/b/bengraham.asp"&gt;Benjamin Graham&lt;/a&gt;’s classic book.&lt;/p&gt;

&lt;p&gt;The crypto world has developed a lot in the last few years. It’s not just Bitcoin anymore, so let’s dive back in to see what’s up with it now.&lt;/p&gt;

&lt;p&gt;In case it’s not obvious: I’m &lt;em&gt;not&lt;/em&gt; an expert, this is not financial advise, etc. Feel free to point me in the direction of more solid info where I can learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting goals
&lt;/h2&gt;

&lt;p&gt;It’s good to clarify what your goals are when you start with something. It helps you direct your search for information, and might help a bit against temptations.&lt;/p&gt;

&lt;p&gt;My high level goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try to understand how it works&lt;/li&gt;
&lt;li&gt;Try to understand what you need to get started&lt;/li&gt;
&lt;li&gt;Try to get a basic setup working to interface with the systems (buy/sell coins, use a smart contract, etc)&lt;/li&gt;
&lt;li&gt;How do you make money? Look at fees, &lt;em&gt;fundamentals&lt;/em&gt; and historic trends/behaviors… is there a minimum amount of money at which you “need” to start to mitigate overhead and fees? How is starting with €100 vs €1000 vs €10000? What trade strategies are there and how do you balance risk vs reward? Are certain coins/tokens better than others? Can stock/ForEx strategies be adapted, index fund strategies? &lt;em&gt;Why (not)?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Actually try to make money if the previous goal resulted in something which gave me enough confidence that it might work. Again, try to come up with a plan that balances risk vs reward (I’m inclined towards spreading risk and draining profits at set percentage increases; ideally I withdraw a fixed percentage of my profits so eventually I’m only working with my profits)&lt;/li&gt;
&lt;li&gt;Making stuff: how hard is it to write a smart contract?&lt;/li&gt;
&lt;li&gt;Making stuff: how hard is it to build tools that interface with the systems? DAPs (?) / distributed applications are a thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The very basics: how does it work?
&lt;/h2&gt;

&lt;p&gt;First goal: I want to &lt;strong&gt;understand&lt;/strong&gt; how things work in crypto in 2021. Below is my understanding so far. It will probably be wrong on a lot of accounts, but that’s why it’s nice to write down so I can correct it later.&lt;/p&gt;

&lt;p&gt;Which pieces are there on the market? What types of coins/tokens/contracts/etc are there? My very limited understanding right now is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coins. Stable or not. Serious or not. I’ve heard the term “shitcoins” multiple times already, so not all coins have the same esteem.&lt;/li&gt;
&lt;li&gt;Tokens, fungible or not (fungible is another word for interchangeable, fiat currency is fungible, because each euro is the same as another euro. A non-fungible token is closer to a signed and numbered limited edition collector’s item, one is &lt;em&gt;not&lt;/em&gt; the same as another). What is the difference between a coin and a token?&lt;/li&gt;
&lt;li&gt;Smart contracts.

&lt;ul&gt;
&lt;li&gt;Programming angle: is there one programming language, or many? How generic or domain specific is this? What are best practices? What are common pitfalls? How does debugging work? How does your toolchain look: is there versioning and source control? How does testing look? Static analysis? Are there code libraries or dependencies?&lt;/li&gt;
&lt;li&gt;Functional angle: how are they &lt;em&gt;used&lt;/em&gt;? What other classes of products are stacked on top? I heard about contracts being deployed as neutral agents who just operate protocols defined by the contract (or by multiple contracts), with ownership voided so they truly are running the way they are until the end of time. How have folks dealt with bugs, viruses, etc? How does the infrastructure work that powers this? Who pays for it and how?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;DeFi, decentralized finance. Mumble, mumble, “and then you buy your house via DeFi because banks don’t have money so they don’t underwrite mortgages anymore.” Yeah, I’m still a bit puzzled about this. This would involve pretty good real-world legal contracts to defer transfer of ownership to a smart contract, and to have the smart contract somehow enforce that the offline land registry (that’s what we have in the Netherlands at least) is updated to reflect the &lt;em&gt;actual&lt;/em&gt; owner of a piece of land.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where do coins/tokens come from?
&lt;/h2&gt;

&lt;p&gt;I know Bitcoin has miners who burn CPU/GPU/FPGA time to calculate hashes in order to process transactions and create new bitcoins in the process. Transactions are relatively slow and expensive (a Google search led me to &lt;a href="https://privacypros.io/tools/bitcoin-fee-estimator/"&gt;this&lt;/a&gt; which claims 1MB per 10 minutes of transaction bandwidth, currently you pay 3-10 USD per transaction depending on how much of a hurry you are in (10-60 min delay). That’s relatively little if if your trade is for thousands of dollars, but a lot for a cup of coffee.&lt;/p&gt;

&lt;p&gt;Look into how/why Bitcoin has a 4-year periodicity in its prices. There’s something about regularly scheduled restrictions in how many new coins are mined, so total new supply decreases and as such value doubles. This makes huge waves and affects all coins, apparently. My friends keep talking about how the next few months will have huge gains and then it’ll dry up. &lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How do the other coin types do this? There are &lt;strong&gt;Proof of Stake&lt;/strong&gt; coins (vs Bitcoin’s Proof of Work) where there’s a finite supply of coins which are setup via smart contracts, have a buy-in ahead of time and then freely trade once they go live. There’s a thing about burning coins as part of the protocol so they get more valuable over time. There’s also a thing about staking claims, where you freeze your coins temporarily in exchange for a percentage of the transaction fees? Time periods here are daily or weekly, so it’s all incredibly rapid compared to the yearly rates traditional banks pay.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the fundamentals here?
&lt;/h2&gt;

&lt;p&gt;Analogy to the stock market, where each stock represents a piece of ownership of a company. Shares earn dividends for you merely holding on to them. There’s also profit/loss based on the resale value of the share. Companies have revenue, profit, equity, capital reserves, etc. A lot of metrics you can contrast to the share performance.&lt;/p&gt;

&lt;p&gt;Based on this you can choose to go long on a share (i.e. you buy it and just hold on to it), avoid it, or do risky things such as shorting (that is, you borrow a share to sell it now and promise to buy it back later; if it works you pocket the profit, if you fail your loss has no hard limit other than the share price). While at it: don’t &lt;strong&gt;every&lt;/strong&gt; borrow money to invest. Only play with what you can afford to lose.&lt;/p&gt;

&lt;p&gt;Do things like stop/loss orders exist in crypto? What are the new options that have no analogy in the classic markets?&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it connect back to the real/offline world?
&lt;/h2&gt;

&lt;p&gt;How can you use it, right now? Better question: how &lt;em&gt;is&lt;/em&gt; crypt used right now? How mature are the products? What are common pitfalls, and how have those been mitigated?&lt;/p&gt;

&lt;p&gt;How user friendly / approachable is crypto? How close are we to my parents (who are tech late adopters) using this for something useful? How close are we to them being able to explain it to someone else?&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling &amp;amp; usability
&lt;/h2&gt;

&lt;p&gt;My friends mentioned that there’s a lot of rough edges and opportunity for quality of life tools. What would be useful? Is there a reason these don’t exist yet? Is there an opportunity to monetize some of this?&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk management
&lt;/h2&gt;

&lt;p&gt;To manage risk you have to be aware of risks. What are the obvious ones?&lt;/p&gt;

&lt;p&gt;Crypto exchanges / brokers can run away with your coins, and apparently that’s just a risk you have to accept. Crypto is dog-eat-dog world, like the game Eve Online. Don’t trade with what you can’t afford to lose. How can you identify trustworthy ones, besides just going with what everyone is doing? If you can’t do this, how can you identify the (obviously) untrustworthy ones?&lt;/p&gt;

&lt;p&gt;Wallets, ledgers, accounts. Somewhere to store your coins/tokens/etc. In my mind it’s close to how your SSH keys also control your bank account. Public/private key encryption. Is this model accurate? Be cautious and careful. What are good practices for keeping things safe?&lt;/p&gt;

&lt;p&gt;Who are the big fishes in each pond, and what are their interests? When they move, they can generate huge waves. Stop/loss orders which normally might make sense could get triggered &lt;em&gt;hard&lt;/em&gt; due to big fish movements. My friends mention that folks trading on margin tend to have their contracts enforced at the end of the day/week/month/quarter. The quarterly ones were unexpected to them due to the volatility they caused. Knowing what/when happens means you can anticipate it.&lt;/p&gt;

&lt;p&gt;There is the obvious loss of value, loss of trade volume/liquidity, risk of the huge fishes in the market making splashes, problems with underlying things (i.e. if one coin is connected with others, they have a relation).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where/how to start?
&lt;/h2&gt;

&lt;p&gt;Writing this down helped me get a picture of what I &lt;em&gt;think&lt;/em&gt; I know. Plenty of questions and uncertainty, so next is filling in the gaps and double checking myself, then checking with my friends if my understanding matches theirs.&lt;/p&gt;

&lt;p&gt;I’m going to work the high-level goals from top to bottom, I think.&lt;/p&gt;

&lt;p&gt;Feel free to reach out to me via &lt;a href="http://twitter.com/narnach"&gt;Twitter&lt;/a&gt; or &lt;a href="//mailto:wes@narnach.com"&gt;email&lt;/a&gt;. Besides my foray into cryptocurrencies, I’m a &lt;a href="https://www.narnach.com/"&gt;freelance Ruby software developer&lt;/a&gt; with a focus on back-end systems and an obsession with code quality. I’m also developing a smart RSS reader over at &lt;a href="https://www.infinity-feed.com"&gt;Infinity Feed&lt;/a&gt; (beta should go live soon-ish).&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>trade</category>
      <category>speculation</category>
      <category>scepticism</category>
    </item>
    <item>
      <title>Experimenting with recording gameplay on Twitch and Youtube</title>
      <dc:creator>Wes Oldenbeuving</dc:creator>
      <pubDate>Tue, 20 Jan 2015 21:34:09 +0000</pubDate>
      <link>https://dev.to/narnach/experimenting-with-recording-gameplay-on-twitch-and-youtube-5c2h</link>
      <guid>https://dev.to/narnach/experimenting-with-recording-gameplay-on-twitch-and-youtube-5c2h</guid>
      <description>&lt;p&gt;I have been playing videogames for over 20 years as one of my primary hobbies. During the last few years, I have watched other people play games on &lt;a href="https://www.youtube.com/narnach"&gt;Youtube&lt;/a&gt; and &lt;a href="http://twitch.tv"&gt;Twitch.tv&lt;/a&gt;. Watching Youtube videos of people playing games has become my primary means of evaluating whether to get a new game or not. Trailer videos and reviews often paint an inaccurate picture of what the game will be like. Watching someone play the game is the most honest way to see what a game &lt;em&gt;really&lt;/em&gt; is like playing. Some streamers also happen to be very entertaining to watch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The experience
&lt;/h2&gt;

&lt;p&gt;This weekend I experimented with streaming my own Diablo III gameplay on &lt;a href="http://www.twitch.tv/narnach"&gt;my Twitch TV Channel&lt;/a&gt;. This was my first time recording and broadcasting. It was an interesting experience.&lt;/p&gt;

&lt;p&gt;Streaming a game is definitely an different way to play a game. Having the microphone on and your gameplay recorded feels a little bit like giving a presentation. For me, this meant that I felt very nervous, awkward and self-conscious at first. It made me lose my train of thought a couple of times, etc. After a while it got more comfortable, so the side-effects of being nervous went away a little.&lt;/p&gt;

&lt;p&gt;Another thing to get used to is multi-tasking between the game and commentating on what you’re doing.&lt;/p&gt;

&lt;p&gt;After streaming with zero watchers, I wanted to watch my recordings to see how I did. Twitch kept pausing the video to buffer every 30 seconds, despite my bandwidth being more than sufficient. I figured it was a limitation of Twitch. Luckily, Twitch makes it easy to export streams to &lt;a href="https://www.youtube.com/user/Narnach"&gt;my Youtube channel&lt;/a&gt;, which does not have issues with bandwidth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech
&lt;/h2&gt;

&lt;p&gt;For my setup, I started with Nvidia Shadowplay (which comes as part of the video card drivers) to record my gameplay, downscale the footage from my 1440p screen to a 720p video and stream it straight to Twitch TV. I recorded my voice-over with my gaming headset while playing the game.&lt;/p&gt;

&lt;p&gt;Shadowplay was a very easy and low-barrier way to get started. The default settings are &lt;em&gt;very&lt;/em&gt; conservative, so the result looked horrible to my very spoiled eyes. I had to increase the output resolution and bitrate to make it look a little bit better, but the Twitch stream output was nothing like watching Youtube videos in the same 720p resolution.&lt;/p&gt;

&lt;p&gt;Looking for a better way to record and broadcast my video, I followed Twitch’s recommendation of &lt;a href="https://obsproject.com/"&gt;Open Broadcaster Software (OBS)&lt;/a&gt;. They have a &lt;a href="http://help.twitch.tv/customer/portal/articles/1262922-open-broadcaster-software"&gt;good guide on how to configure it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The visual quality went up, but I kept pushing too close against the maximum bitrate of 3500 kbit in order to make the stream look as good as possible. In fact, 3500 kbit is not enough to make the video look decent to me. Twitch starts getting buffering issues above 3000 kbit, but looked noticeably worse when I lowered my bitrate that far.&lt;/p&gt;

&lt;p&gt;So: I was not happy with the quality of my stream on Twitch, yet my plan was to also get the footage on Youtube for later watching. Having zero subscribers to your (new) streaming channel means nobody sees it anyway. So I decided to skip Twitch and upload straight to Youtube. Uploading directly to Youtube allows me to record higher content at a (much) higher bitrate. Heck, I could upload a 1440p video in a crazy high bandwidth.&lt;/p&gt;

&lt;p&gt;To determine what bitrate would be possible, what would be good and what would be a good baseline, I recorded a couple of short clips at different bitrates.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;20mbit 1440p video looked fabulous, but my machine started making noticeably more noise by spinning up fans to cool itself. I’ve got quite a powerful setup, which I’ve tuned to make as little noise as needed even during reasonably high load. When it starts to make noise, I know I’m pushing against limits.&lt;/li&gt;
&lt;li&gt;10mbit 1440p looked very good. It did not cause noisy fans.&lt;/li&gt;
&lt;li&gt;8mbit 1440p still looked very good. In fact, I could not distinguish it from the 10mbit video. This is the bitrate I went with for recording my gameplay.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Youtube video processing process is also interesting to look at. You upload the video, then they start processing it. Once this is done, the lowest quality version of the video (360p) is available. Invisibly in the background they keep processing increasingly higher quality versions (480p, 720p, 1080p and finally 1440p). Once the video list shows (HD) after the title’s name, the 720p version is available. This background process will take a couple of hours, depending on the length of the video.&lt;/p&gt;

&lt;p&gt;I settled on uploading videos at night as unlisted, then making them public the next morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gameplay
&lt;/h2&gt;

&lt;p&gt;Oh, yes. I was recording playing a game.&lt;/p&gt;

&lt;p&gt;I was playing Diablo III: Reaper of Souls, which has been out for over a year now. Last week Blizzard released the &lt;a href="http://eu.battle.net/d3/en/blog/17561388/patch-212-now-live-in-europe-14-01-2015"&gt;2.1.2 patch&lt;/a&gt; in preparation for the Season being reset in early February.&lt;/p&gt;

&lt;p&gt;At the launch of Season 1, I briefly tried it. At the time there were ways to exploit the game to get a maximum level character in two hours. For me this took the fun out of it. How can you compete on a ladder of sorts if you’re up against cheaters?&lt;/p&gt;

&lt;p&gt;Blizzard hotfixed the ways to cheat out reasonably soon, but I had given up on it at that point. Now that a new season is coming up and it looks like people will actually be racing to reach the top, I’m interested in participating again.&lt;/p&gt;

&lt;p&gt;So to see what my chances would be, I created a brand new hardcore character in Season 1. Hardcore characters stay dead once they get killed in game. You only live once. I like the added challenge of keeping a character alive.&lt;/p&gt;

&lt;p&gt;The first night I played 6 hours and reached level 50 by playing the game in adventure mode. The second night I played about 3 hours to reach level 60. The third night I played another 3 or so hours to reach level 70 (the maximum) and reach my goal. Total time taken: 12.5 hours.&lt;/p&gt;

&lt;p&gt;The key is to not push the difficulty level to extreme from the get-go, and to play in Adventure mode instead of the campaign. You have a more focused leveling experience and it’s nice not having to go through the low level parts of the campaign yet again. The gameplay is dumbed down quite a bit because this part serves as a tutorial of sorts. In adventure mode there is no tutorial, because you only unlock it after completing the campaign once.&lt;/p&gt;

</description>
      <category>youtube</category>
      <category>twitch</category>
      <category>streaming</category>
      <category>gaming</category>
    </item>
    <item>
      <title>Boolean Externalities</title>
      <dc:creator>Wes Oldenbeuving</dc:creator>
      <pubDate>Thu, 18 Sep 2014 23:53:00 +0000</pubDate>
      <link>https://dev.to/narnach/boolean-externalities-4n55</link>
      <guid>https://dev.to/narnach/boolean-externalities-4n55</guid>
      <description>&lt;p&gt;This is motivated/inspired by Avdi Grimm’s post on Boolean Externalties&lt;/p&gt;

&lt;p&gt;&lt;a href="http://devblog.avdi.org/2014/09/17/boolean-externalities/"&gt;http://devblog.avdi.org/2014/09/17/boolean-externalities/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In his post he asks the question: if a predicate returns false, why does it do so? If you chain a lot of predicates, it’s hard to figure out why you get the answer you get.&lt;/p&gt;

&lt;p&gt;Consider this example. It implements simple chained predicate logic to determine if the object is scary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleBoo&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scary?&lt;/span&gt;
    &lt;span class="n"&gt;ghost?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;zombie?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ghost?&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;alive?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;regrets?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;zombie?&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;alive?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;hungry_for_brains?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;alive?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;regrets?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hungry_for_brains?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Following the chain of logic, something is scary if it’s either a ghost or a zombie. They are both not alive, but a ghost has regrets and a zombie is hungry for brains. This is the code as I would probably write it for a production app. It’s simple and very easy to read.&lt;/p&gt;

&lt;p&gt;The downside is that if you want to know &lt;em&gt;why&lt;/em&gt; something is scary, you have to go and read the code. You can not ask the object &lt;em&gt;why&lt;/em&gt; it arrived at its conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why
&lt;/h2&gt;

&lt;p&gt;The following is a logical next step in the evolution of the code: I have modified the code so it can explain &lt;em&gt;why&lt;/em&gt; a predicate returns true or false, though there is a tremendous “cost” in length and legibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WhyNotBoo&lt;/span&gt;
  &lt;span class="c1"&gt;# The object is scary if there is a reason for it to be scary.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scary?&lt;/span&gt;
    &lt;span class="n"&gt;why_scary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Why is this object scary?&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;why_scary&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Early termination if this object is *not* scary.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;ghost?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;zombie?&lt;/span&gt;

    &lt;span class="c1"&gt;# Recursively determine why this object is scary.&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:ghost&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;why_ghost&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ghost?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:zombie&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;why_zombie&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;zombie?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# For the "why not" question we re-implement the "why" logic in reverse.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;why_not_scary&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ghost?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;zombie?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:not_ghost&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;why_not_ghost&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;ghost?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:not_zombie&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;why_not_zombie&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;zombie?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ghost?&lt;/span&gt;
    &lt;span class="n"&gt;why_ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;why_ghost&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;alive?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;regrets?&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:not_alive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:regrets&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;why_not_ghost&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ghost?&lt;/span&gt;

    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:alive&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alive?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:no_regrets&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;regrets?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;zombie?&lt;/span&gt;
    &lt;span class="n"&gt;why_zombie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;why_zombie&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;alive?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;hungry_for_brains?&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:not_alive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hungry_for_brains&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;why_not_zombie&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;zombie?&lt;/span&gt;

    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:alive&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alive?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:not_hungry_for_brains&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;hungry_for_brains?&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;alive?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;regrets?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hungry_for_brains?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, that’s a &lt;em&gt;lot&lt;/em&gt; more code. All composite predicates have a “why_[predicate]” and a “why_not_[predicate]” version. Now you can ask if something is scary and why (or why not).&lt;/p&gt;

&lt;p&gt;There are a few problems with this approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The logic is not in &lt;code&gt;scary?&lt;/code&gt;, where you would expect it.&lt;/li&gt;
&lt;li&gt;The logic is duplicated between &lt;code&gt;why_scary&lt;/code&gt; and &lt;code&gt;why_not_scary&lt;/code&gt;. Don’t Repeat Yourself, or you will get logic bugs.&lt;/li&gt;
&lt;li&gt;There is a lot more code. A lot of boilerplate code, but also multiple concerns in the same method: bookkeeping and actual logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cleaner code
&lt;/h2&gt;

&lt;p&gt;Let’s see if we can make the code legible again, while preserving the functionality of “why” and “why not”.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReasonBoo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EitherAll&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scary?&lt;/span&gt;
    &lt;span class="n"&gt;either&lt;/span&gt; &lt;span class="ss"&gt;:ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:zombie&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ghost?&lt;/span&gt;
    &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="ss"&gt;:not_alive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:regrets&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;zombie?&lt;/span&gt;
    &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="ss"&gt;:not_alive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hungry_for_brains&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;alive?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;regrets?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hungry_for_brains?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far, so good. The code is &lt;em&gt;very&lt;/em&gt; legibile, but there is a mysterious superclass &lt;code&gt;EitherAnd&lt;/code&gt;. Before we look at &lt;em&gt;how&lt;/em&gt; it works, let’s look at &lt;em&gt;what&lt;/em&gt; it allows us to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;boo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ReasonBoo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scary?&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;why_scary&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;
&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;why_not_scary&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [{:not_ghost=&amp;gt;[:not_regrets]}, {:not_zombie=&amp;gt;[:not_hungry_for_brains]}]&lt;/span&gt;

&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ghost?&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;why_ghost&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;
&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;why_not_ghost&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [:not_regrets]&lt;/span&gt;

&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zombie?&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;why_zombie&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;
&lt;span class="n"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;why_not_zombie&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [:not_hungry_for_brains]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each predicate that uses &lt;code&gt;either&lt;/code&gt; or &lt;code&gt;all&lt;/code&gt; we can ask why or why not it’s true and the response is a chain of predicate checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we get cleaner code
&lt;/h2&gt;

&lt;p&gt;If you want to make your code legible, there usually has to be some dirty plumbing code. In this example we have hidden this in a superclass, but it could have been a module as well without too much effort.&lt;/p&gt;

&lt;p&gt;In order to keep the code easier to read, I have chosen to not extract duplicate logic into helper methods.&lt;/p&gt;

&lt;p&gt;This class implements two methods: &lt;code&gt;either&lt;/code&gt; and &lt;code&gt;all&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both methods have the same structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup the why_[predicate] and why_not_[predicate] methods.&lt;/li&gt;
&lt;li&gt;Evaluate each predicate until we reach a termination condition.&lt;/li&gt;
&lt;li&gt;Track which predicates were true/false to explain &lt;em&gt;why&lt;/em&gt; we got the result we did.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EitherAll&lt;/span&gt;
  &lt;span class="c1"&gt;# This method mimics the behavior of "||". These two lines are functionally equivalent:&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# ghost? || zombie? # =&amp;gt; false&lt;/span&gt;
  &lt;span class="c1"&gt;# either :ghost, :zombie # =&amp;gt; false&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# The bonus of `either` is that afterwards you can ask why or why not:&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# why_not_scary # =&amp;gt; [{:not_ghost=&amp;gt;[:not_regrets]}, {:not_zombie=&amp;gt;[:not_hungry_for_brains]}]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;either&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;predicate_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Setup up the why_ and why_not_ methods&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;

    &lt;span class="c1"&gt;# Two arrays to track the why and why not reasons.&lt;/span&gt;
    &lt;span class="n"&gt;why_reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;why_not_reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# This is a ruby 2.0 feature that replaces having to regexp parse the `caller` array.&lt;/span&gt;
    &lt;span class="c1"&gt;# Our goal here is to determine the name of the method that called us.&lt;/span&gt;
    &lt;span class="c1"&gt;# In this example it is likely to be the `scary?` method.&lt;/span&gt;
    &lt;span class="n"&gt;context_method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;caller_locations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;

    &lt;span class="c1"&gt;# Strip the trailing question mark&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context_method_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\?$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;

    &lt;span class="c1"&gt;# Set instance variables for why and why not for the current context (calling method name).&lt;/span&gt;
    &lt;span class="c1"&gt;# In our example, this is going to be @why_scary and @why_not_scary.&lt;/span&gt;
    &lt;span class="nb"&gt;instance_variable_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"@why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;why_reasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;instance_variable_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"@why_not_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;why_not_reasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create reader methods for `why_scary` and `why_not_scary`.&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:"why_not_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Evaluate each predicate until one returns true&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;

    &lt;span class="n"&gt;predicate_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# Transform the given predicate name into a predicate method name.&lt;/span&gt;
      &lt;span class="c1"&gt;# We check if the predicate needs to be negated, to support not_&amp;lt;predicate&amp;gt;.&lt;/span&gt;
      &lt;span class="n"&gt;predicate_name_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'not_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;negate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;predicate_method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^not_/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;negate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;predicate_method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Evaluate the predicate&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;negate&lt;/span&gt;
        &lt;span class="c1"&gt;# Negate the return value of a negated predicate.&lt;/span&gt;
        &lt;span class="c1"&gt;# This simplifies the logic for our success case.&lt;/span&gt;
        &lt;span class="c1"&gt;# `value` is always true if it is what we ask for.&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicate_method_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicate_method_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# 3. Track which predicates were true/false to explain *why* we got the answer we did.&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="c1"&gt;# We have a true value, so we found what we are looking for.&lt;/span&gt;

        &lt;span class="c1"&gt;# If possible, follow the chain of reasoning by asking why the predicate is true.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;why_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;why_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;predicate_name&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# Because value is true, clear the reasons why we would not be.&lt;/span&gt;
        &lt;span class="c1"&gt;# They don't matter anymore.&lt;/span&gt;
        &lt;span class="n"&gt;why_not_reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;

        &lt;span class="c1"&gt;# To ensure lazy evaluation, we stop here.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="c1"&gt;# We have a false value, so we continue looking for a true predicate&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;negate&lt;/span&gt;
          &lt;span class="c1"&gt;# Our predicate negated, so we want to use the non-negated version.&lt;/span&gt;
          &lt;span class="c1"&gt;# In our example, if `alive?` were true, we are not a zombie because we are not "not alive".&lt;/span&gt;
          &lt;span class="c1"&gt;# Our check is for :not_alive, so the "why not" reason is :alive.&lt;/span&gt;
          &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^not_/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="c1"&gt;# Our predicate is not negated, so we need to use the negated predicate.&lt;/span&gt;
          &lt;span class="c1"&gt;# In our example, we are not scary because we are not a ghost (or a zombie).&lt;/span&gt;
          &lt;span class="c1"&gt;# Our check is for :scary, so the "why not" reason is :not_ghost.&lt;/span&gt;
          &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"not_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# If possible, follow the chain of reasoning by asking why the predicate is false.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;negative_predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;why_not_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;negative_predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;why_not_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c1"&gt;# We failed because we did not get a true value at all (which would have caused early termination).&lt;/span&gt;
    &lt;span class="c1"&gt;# Clear all positive reasons.&lt;/span&gt;
    &lt;span class="n"&gt;why_reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;

    &lt;span class="c1"&gt;# Explicitly return false to match style with the `return true` a few lines earlier.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# This method works very similar to `either`, which is defined above.&lt;/span&gt;
  &lt;span class="c1"&gt;# I'm only commenting on the differences here.&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# This method mimics the behavior of "&amp;amp;&amp;amp;". These two lines are functionally equivalent:&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# !alive? &amp;amp;&amp;amp; hungry_for_brains?&lt;/span&gt;
  &lt;span class="c1"&gt;# all :not_alive, :hungry_for_brains&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;predicate_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context_method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;caller_locations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context_method_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\?$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
    &lt;span class="n"&gt;why_reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;why_not_reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nb"&gt;instance_variable_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"@why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;why_reasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;instance_variable_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"@why_not_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;why_not_reasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:"why_not_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;predicate_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;predicate_name_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'not_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;negate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;predicate_method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^not_/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;negate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;predicate_method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;negate&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicate_method_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicate_method_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# The logic is the same as `either` until here. The difference is:&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# * Either looks for the first true to declare success&lt;/span&gt;
      &lt;span class="c1"&gt;# * And looks for the first false to declare failure&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# This means we have to reverse our logic.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;why_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;why_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;predicate_name&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;negate&lt;/span&gt;
          &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^not_/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"not_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;predicate_name_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;negative_predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;why_not_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"why_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;negative_predicate_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;why_not_reasons&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;negative_predicate_name&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;why_reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;why_not_reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It is possible to provide traceability for &lt;em&gt;why&lt;/em&gt; a boolean returns its value with less than 200 lines of Ruby code and minor changes to your own code.&lt;/p&gt;

&lt;p&gt;Despite the obvious edge cases and limitations, it’s nice to know there is a potential solution to the problem of not knowing &lt;em&gt;why&lt;/em&gt; a method returns &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

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