Jekyll2021-06-30T16:35:49+00:00https://www.adiiyengar.com/index.xmlAdi IyengarC, Haskell, Elixir, Ruby, Elm, Erlang, Rust Developer, (Software) Bug Catcher, Thinker & LearnerAdi IyengarMy thoughts on testing Phoenix Controller Plug setup2021-02-25T21:55:15+00:002021-02-25T21:55:15+00:00https://www.adiiyengar.com/blog/20210225/testing-phoenix-controller-plug-calls<p>Phoenix comes with a few handy ways to test its components. For example, it has
<code class="language-plaintext highlighter-rouge">Phoenix.ControllerTest</code> module, which helps us make a request that goes through
the application endpoint all the way up to the controller. Phoenix also
generates a <code class="language-plaintext highlighter-rouge">ConnCase</code> module along with the project to better use
<code class="language-plaintext highlighter-rouge">Phoenix.ControllerTest</code> during testing, with a dedicated <code class="language-plaintext highlighter-rouge">setup</code> block.</p>
<p>There are a few places, however, where we could further streamline testing in a
Phoenix application. One such place is testing how a <code class="language-plaintext highlighter-rouge">Controller</code> is set up
to use a <code class="language-plaintext highlighter-rouge">Plug</code>.</p>
<p>For example, consider this controller:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">PageController</span> <span class="k">do</span>
<span class="kn">use</span> <span class="no">ExampleWeb</span><span class="p">,</span> <span class="ss">:controller</span>
<span class="n">plug</span> <span class="no">RequireClaims</span><span class="p">,</span>
<span class="ss">index:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="ss">show:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="ss">create:</span> <span class="s2">"page:write"</span><span class="p">,</span>
<span class="ss">delete:</span> <span class="s2">"page:write"</span>
<span class="k">def</span> <span class="n">index</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">show</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">create</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">delete</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This is a pretty straightforward Phoenix controller which uses a custom plug,
<code class="language-plaintext highlighter-rouge">RequireClaims</code>. As you might guess, <code class="language-plaintext highlighter-rouge">RequireClaims</code> does authorization based on
claims and checks whether the claims assigned to the connection will allow it to
invoke a controller action.</p>
<p>In the above example, we can see that <code class="language-plaintext highlighter-rouge">index</code> and <code class="language-plaintext highlighter-rouge">show</code> actions need
<code class="language-plaintext highlighter-rouge">"page:read"</code> claim, whereas <code class="language-plaintext highlighter-rouge">create</code> and <code class="language-plaintext highlighter-rouge">delete</code> actions need <code class="language-plaintext highlighter-rouge">"page:write"</code>
claim. Now let us see how we can go about testing it.</p>
<h3 id="request-testing-the-plug-the-conventional-approach">Request Testing the Plug (the conventional approach)</h3>
<p>The <em>regular</em> Phoenix way to test this would be to send a request to this
controller with different sets of claims and test the behavior of route and
the connection.</p>
<p>The test file will look somewhat like this:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">PageControllerTest</span> <span class="k">do</span>
<span class="kn">use</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">ConnCase</span>
<span class="n">describe</span> <span class="s2">"index/2 (GET /page)"</span> <span class="k">do</span>
<span class="c1"># <--- Relevant code starts here</span>
<span class="c1"># Testing without proper claims</span>
<span class="n">test</span> <span class="s2">"responds with 403 when proper claims aren't set"</span><span class="p">,</span> <span class="p">%{</span><span class="ss">conn:</span> <span class="n">conn</span><span class="p">}</span> <span class="k">do</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">put_claims</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="p">[</span><span class="s2">"page:write"</span><span class="p">])</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">get</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s2">"/page"</span><span class="p">)</span>
<span class="n">assert</span> <span class="n">conn</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="mi">409</span>
<span class="n">assert</span> <span class="n">conn</span><span class="o">.</span><span class="n">halt</span>
<span class="k">end</span>
<span class="c1"># <--- Relevant code starts here</span>
<span class="c1"># Testing with proper claims</span>
<span class="n">test</span> <span class="s2">"works when claims are properly set"</span><span class="p">,</span> <span class="p">%{</span><span class="ss">conn:</span> <span class="n">conn</span><span class="p">}</span> <span class="k">do</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">put_claims</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="p">[</span><span class="s2">"page:read"</span><span class="p">])</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">get</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s2">"/page"</span><span class="p">)</span>
<span class="n">refute</span> <span class="n">conn</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="mi">409</span>
<span class="n">refute</span> <span class="n">conn</span><span class="o">.</span><span class="n">halt</span>
<span class="c1"># more assertions</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># .... more code</span>
<span class="k">def</span> <span class="n">put_claims</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">claims</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code that sets the claims for the conn</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This works well to test whether the controller is using the plug correctly.
However, one might say that we’re testing the behavior of the plug itself
instead of testing just the controller’s usage of the plug. But I think that is
fine, since we’re only doing it for one controller.</p>
<p>The problem comes when we start using this plug for many controllers. If we
add these tests for each controller (and its actions) it will be testing the
behavior of this plug over and over again, slowing down our test suite (since
request tests are relatively expensive). Moreover, we’re coupling the controller
tests (which should indicate the controller’s behavior) to the plug’s behavior.
So, if we were to change the behavior of the plug, we will have to update these
tests in all the controllers even if the <code class="language-plaintext highlighter-rouge">plug</code> calls in the controllers stay
the same.</p>
<p>I think you can probably see where I am going with this..</p>
<p>We don’t need to write a traditional controller test to test whether this plug
is correctly being used in the controller file. As long as we have tested the
plug itself (hopefully in its own test file with all the uses cases),
all we need is just to test <em>how</em> it is being used in the controller.</p>
<h3 id="testing-the-controllers-plug-usage-the-goal">Testing the Controller’s Plug usage (The goal)</h3>
<p>Based on what we concluded in the previous section, our goal is to be able to do
something like this:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">PageControllerTest</span> <span class="k">do</span>
<span class="kn">use</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">ConnCase</span>
<span class="kn">import</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">TestHelpers</span><span class="o">.</span><span class="no">ControllerPlugUsage</span> <span class="c1"># <-- line added</span>
<span class="c1"># <------ New describe block added</span>
<span class="n">describe</span> <span class="s2">"plug RequireClaims"</span> <span class="k">do</span>
<span class="n">test</span> <span class="s2">"sets up `RequireClaims` plug with correct options"</span> <span class="k">do</span>
<span class="c1"># uses_plug/2 comes from `ControllerPlugUsage` module</span>
<span class="n">assert</span> <span class="n">uses_plug?</span><span class="p">(</span>
<span class="no">ExampleWeb</span><span class="o">.</span><span class="no">PageController</span><span class="p">,</span>
<span class="ss">plug:</span> <span class="no">RequireClaims</span><span class="p">,</span>
<span class="ss">options:</span> <span class="p">[</span>
<span class="ss">index:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="ss">show:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="ss">create:</span> <span class="s2">"page:write"</span><span class="p">,</span>
<span class="ss">delete:</span> <span class="s2">"page:write"</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># <---- New describe block end</span>
<span class="n">describe</span> <span class="s2">"index/2 (GET /page)"</span> <span class="k">do</span>
<span class="c1"># <------ Tests removed</span>
<span class="c1"># Remove plug specific tests and just keep the happy path to test the</span>
<span class="c1"># controller action's behavior</span>
<span class="n">test</span> <span class="s2">"works when claims are properly set"</span><span class="p">,</span> <span class="p">%{</span><span class="ss">conn:</span> <span class="n">conn</span><span class="p">}</span> <span class="k">do</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">put_claims</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="p">[</span><span class="s2">"page:read"</span><span class="p">])</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">get</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s2">"/page"</span><span class="p">)</span>
<span class="n">refute</span> <span class="n">conn</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="mi">409</span>
<span class="n">refute</span> <span class="n">conn</span><span class="o">.</span><span class="n">halt</span>
<span class="c1"># more assertions</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># .... more code</span>
<span class="k">def</span> <span class="n">put_claims</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">claims</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code that sets the claims for the conn</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>The above file just tests whether the plug <code class="language-plaintext highlighter-rouge">RequireClaims</code> is configured
correctly in the controller, instead of testing the behavior of the plug again.
As a result of this, our tests will run much faster and are easier to follow.</p>
<p>Now let’s work on getting the code above to work.</p>
<h3 id="testing-the-controllers-plug-usage-making-it-work">Testing the Controller’s Plug usage (Making it work)</h3>
<p>Now that we have seen the intended usage, it’s time to write the module which
defines the <code class="language-plaintext highlighter-rouge">uses_plug?</code> function. However, before doing that we need a way to
inspect a controller’s list of plugs.</p>
<p>Upon looking at phoenix’s <a href="https://github.com/phoenixframework/phoenix/blob/master/lib/phoenix/controller/pipeline.ex#L183" target="_blank">Controller Pipeline module</a>
, it appears that every time the <code class="language-plaintext highlighter-rouge">plug</code> macro is called, the module attribute
<code class="language-plaintext highlighter-rouge">@plug</code> accumulates new plug with its parameters. Also, since that module
attribute is never reset, we can conclude towards the end of the compilation of
any phoenix controller all its plugs are present in that module attribute.</p>
<h4 id="optional-breaking-the-black-box-controller-plug-attribute">(Optional) Breaking the black-box: Controller <code class="language-plaintext highlighter-rouge">@plug</code> attribute</h4>
<p>For those interested to dig a little deeper, here’s how <code class="language-plaintext highlighter-rouge">@plugs</code> in the
controller would look like:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span>
<span class="p">{</span><span class="no">RequireClaims</span><span class="p">,</span> <span class="p">[</span><span class="ss">index:</span> <span class="s2">"page:read"</span><span class="p">,</span> <span class="ss">show:</span> <span class="s2">"page:read"</span><span class="o">...</span><span class="p">],</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:put_new_view</span><span class="p">,</span> <span class="no">TestView</span><span class="p">,</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:put_new_layout</span><span class="p">,</span> <span class="p">{</span><span class="no">ExampleWeb</span><span class="o">.</span><span class="no">LayoutView</span><span class="p">,</span> <span class="ss">:app</span><span class="p">},</span> <span class="no">true</span><span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<p>As you can see a plug in the list of <code class="language-plaintext highlighter-rouge">@plugs</code> is a three element tuple, where
the first element is either a plug module or a function, the second element is
a list of options/parameters that the plug takes, and the third element appears
to be a boolean. Actually, the third element is actually used for storing guard
clauses for invoking a plug. At first, we will be ignoring the third element
and set it to <code class="language-plaintext highlighter-rouge">true</code> in order to make the code easier to follow, but
<a href="#i-promised-ill-get-to-it-so-here-it-is">I promise I’ll get to it at the end!</a></p>
<h4 id="adding-__plugs__0-controller-function-for-plug-introspection">Adding <code class="language-plaintext highlighter-rouge">__plugs__/0</code> controller function for plug introspection</h4>
<p>Now we know that <code class="language-plaintext highlighter-rouge">@plugs</code> attribute of a controller has information about its
plugs and their configuration. So, a simple way to get all the plugs would be
to define a function in the controller which returns the module attribute
<code class="language-plaintext highlighter-rouge">@plugs</code>.</p>
<p>Here’s how it will look:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">PageController</span> <span class="k">do</span>
<span class="kn">use</span> <span class="no">ExampleWeb</span><span class="p">,</span> <span class="ss">:controller</span>
<span class="n">plug</span> <span class="no">RequireClaims</span><span class="p">,</span>
<span class="ss">index:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="ss">show:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="ss">create:</span> <span class="s2">"page:write"</span><span class="p">,</span>
<span class="ss">delete:</span> <span class="s2">"page:write"</span>
<span class="k">def</span> <span class="n">index</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">show</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">create</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">delete</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># some code</span>
<span class="k">end</span>
<span class="c1"># <--- New code added here</span>
<span class="c1"># Define a function which returns the module attribute `@plugs`</span>
<span class="k">def</span> <span class="n">__plugs__</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="nv">@plugs</span>
<span class="k">end</span>
</code></pre></div></div>
<p>It is very important to define this function <em>after</em> all the <code class="language-plaintext highlighter-rouge">plug</code> calls
because each <code class="language-plaintext highlighter-rouge">plug</code> call updates the <code class="language-plaintext highlighter-rouge">@plugs</code> attribute as the attribute is
registered <a href="https://github.com/phoenixframework/phoenix/blob/master/lib/phoenix/controller/pipeline.ex#L12" target="_blank">with accumulate set to true</a>.</p>
<p><em>For more information on <code class="language-plaintext highlighter-rouge">accumulate: true</code>, visit <a href="https://hexdocs.pm/elixir/Module.html#register_attribute/3-options" target="_blank">this hexdocs page</a></em></p>
<p>This function would return the list of plugs used by the controller:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">PageController</span><span class="o">.</span><span class="n">__plugs__</span><span class="p">()</span>
<span class="p">[</span>
<span class="p">{</span><span class="no">RequireClaims</span><span class="p">,</span> <span class="p">[</span><span class="ss">index:</span> <span class="s2">"page:read"</span><span class="p">,</span> <span class="ss">show:</span> <span class="s2">"page:read"</span> <span class="o">...</span><span class="p">],</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:put_new_view</span><span class="p">,</span> <span class="no">TestView</span><span class="p">,</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:put_new_layout</span><span class="p">,</span> <span class="p">{</span><span class="no">ExampleWeb</span><span class="o">.</span><span class="no">LayoutView</span><span class="p">,</span> <span class="ss">:app</span><span class="p">},</span> <span class="no">true</span><span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<h4 id="writing-the-test-helper-uses_plug2">Writing the test helper <code class="language-plaintext highlighter-rouge">uses_plug?/2</code></h4>
<p>With <code class="language-plaintext highlighter-rouge">__plugs__/0</code> function all set for the controller, we can go ahead create
the test helper <code class="language-plaintext highlighter-rouge">uses_plug?/2</code> in a new module
<code class="language-plaintext highlighter-rouge">ExampleWeb.TestHelpers.ControllerPlugUsage</code>:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">TestHelpers</span><span class="o">.</span><span class="no">ControllerPlugUsage</span> <span class="k">do</span>
<span class="nv">@moduledoc</span> <span class="sd">"""
Defines a list of functions that can be used to test a controller's plug
usage without having to use request tests to test the plug's behavior
again.
"""</span>
<span class="nv">@doc</span> <span class="sd">"""
Returns `true` if the given plug is used by the given controller with the
given opts (options)
NOTE: Assumes there are no guard clauses in the plug call
"""</span>
<span class="k">def</span> <span class="n">uses_plug?</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="ss">plug:</span> <span class="n">plug</span><span class="p">,</span> <span class="ss">opts:</span> <span class="n">opts</span><span class="p">)</span> <span class="k">do</span>
<span class="n">expected_plug</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">plug</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="no">true</span>
<span class="p">}</span>
<span class="n">expected_plug</span> <span class="ow">in</span> <span class="n">controller</span><span class="o">.</span><span class="n">__plugs__</span><span class="p">()</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Now, we can use this module to test whether a plug is being used by a controller
in the way we would expect it to:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iex</span><span class="o">></span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">TestHelpers</span><span class="o">.</span><span class="no">ControllerPlugUsage</span><span class="o">.</span><span class="n">uses_plug?</span><span class="p">(</span>
<span class="o">...></span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">PageController</span><span class="p">,</span>
<span class="o">...></span> <span class="ss">plug:</span> <span class="no">RequireClaims</span><span class="p">,</span>
<span class="o">...></span> <span class="ss">opts:</span> <span class="p">[</span>
<span class="o">...></span> <span class="ss">index:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="o">...></span> <span class="ss">show:</span> <span class="s2">"page:read"</span><span class="p">,</span>
<span class="o">...></span> <span class="ss">create:</span> <span class="s2">"page:write"</span><span class="p">,</span>
<span class="o">...></span> <span class="ss">delete:</span> <span class="s2">"page:write"</span><span class="p">,</span>
<span class="o">...></span> <span class="p">]</span>
<span class="o">...></span> <span class="p">)</span>
<span class="no">true</span>
</code></pre></div></div>
<h3 id="taking-it-a-step-further-some-metaprogramming-knowledge-would-help-here">Taking it a step further (<em>some</em> metaprogramming knowledge would help here)</h3>
<p>Let’s take it a step further. Instead of having to define <code class="language-plaintext highlighter-rouge">__plugs__/0</code>
function at the end of every controller, it would be nice to programatically
insert that function definition at the end of a controller’s definition. The
perfect place to do something like that is the <code class="language-plaintext highlighter-rouge">lib/example_web.ex</code> file, which
defines quoted blocks that inject behavior into a controller. So, let’s update
<code class="language-plaintext highlighter-rouge">ExampleWeb.controller/0</code> function to accomplish that:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span> <span class="k">do</span>
<span class="c1"># ... some code</span>
<span class="k">def</span> <span class="n">controller</span> <span class="k">do</span>
<span class="kn">quote</span> <span class="k">do</span>
<span class="kn">use</span> <span class="no">Phoenix</span><span class="o">.</span><span class="no">Controller</span><span class="p">,</span> <span class="ss">namespace:</span> <span class="no">ExampleWeb</span>
<span class="kn">import</span> <span class="no">Plug</span><span class="o">.</span><span class="no">Conn</span>
<span class="n">alias</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">Router</span><span class="o">.</span><span class="no">Helpers</span><span class="p">,</span> <span class="ss">as:</span> <span class="no">Routes</span>
<span class="c1"># Add this line</span>
<span class="nv">@before_compile</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">ControllerBeforeCompile</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Add this module which adds a before_compile hook which is responsible</span>
<span class="c1"># for defining `__plugs__/0` function used for testing</span>
<span class="k">defmodule</span> <span class="no">ControllerBeforeCompile</span> <span class="k">do</span>
<span class="nv">@moduledoc</span> <span class="no">false</span>
<span class="k">defmacro</span> <span class="n">__before_compile__</span><span class="p">(</span><span class="n">_macro_env</span><span class="p">)</span> <span class="k">do</span>
<span class="kn">quote</span> <span class="k">do</span>
<span class="k">def</span> <span class="n">__plugs__</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="nv">@plugs</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># .. some more code</span>
<span class="k">end</span>
</code></pre></div></div>
<p>The above <code class="language-plaintext highlighter-rouge">@before_compile</code> hook defines a function at the end of a controller.
We needed to use <code class="language-plaintext highlighter-rouge">@before_compile</code> here instead of just defining it in the
<code class="language-plaintext highlighter-rouge">controller</code> function’s quoted block, because all the <code class="language-plaintext highlighter-rouge">plug</code> calls happen
<em>after</em> the quoted expression is evaluated. This means we need to wait for the
entire body of the controller to be evaluated before defining the function,
otherwise <code class="language-plaintext highlighter-rouge">@plugs</code> attribute won’t be up-to-date. Furthermore, since
we can only use module attributes <em>during</em> the module’s compilation, we had
to use <code class="language-plaintext highlighter-rouge">@before_compile</code> instead of <code class="language-plaintext highlighter-rouge">@after_compile</code>.</p>
<p><em>For more information on <code class="language-plaintext highlighter-rouge">@before_compile</code> and other hooks check out <a href="https://hexdocs.pm/elixir/Module.html#module-before_compile" target="_blank">this hexdocs page</a></em></p>
<p>Now, whenever we define a controller using <code class="language-plaintext highlighter-rouge">use ExampleWeb, :controller</code>, it
will also define a function <code class="language-plaintext highlighter-rouge">__plugs__/0</code> which returns the list of plugs a
controller is configured to work with along with their parameters/options.</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">defmodule</span> <span class="no">TestController</span> <span class="k">do</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="kn">use</span> <span class="no">ExampleWeb</span><span class="p">,</span> <span class="ss">:controller</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="n">plug</span> <span class="ss">:plug1</span><span class="p">,</span> <span class="ss">option1:</span> <span class="ss">:value1</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="n">plug</span> <span class="ss">:plug2</span><span class="p">,</span> <span class="ss">option2:</span> <span class="ss">:value2</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">def</span> <span class="n">plug1</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">conn</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">def</span> <span class="n">plug2</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">conn</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">end</span>
<span class="p">{</span><span class="ss">:module</span><span class="p">,</span> <span class="no">TestController</span><span class="p">,</span>
<span class="o"><<</span><span class="mi">70</span><span class="p">,</span> <span class="mi">79</span><span class="p">,</span> <span class="mi">82</span><span class="p">,</span> <span class="mi">49</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">248</span><span class="p">,</span> <span class="mi">66</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">77</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">85</span><span class="p">,</span> <span class="mi">56</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">80</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">51</span><span class="p">,</span> <span class="mi">21</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">120</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">46</span><span class="p">,</span> <span class="mi">84</span><span class="p">,</span> <span class="mi">101</span><span class="p">,</span> <span class="mi">115</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">67</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span>
<span class="mi">110</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">101</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="o">...>></span><span class="p">,</span> <span class="p">{</span><span class="ss">:plug2</span><span class="p">,</span> <span class="mi">2</span><span class="p">}}</span>
<span class="n">iex</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">></span> <span class="no">TestController</span><span class="o">.</span><span class="n">__plugs__</span><span class="p">()</span>
<span class="p">[</span>
<span class="p">{</span><span class="ss">:plug1</span><span class="p">,</span> <span class="p">[</span><span class="ss">option1:</span> <span class="ss">:value1</span><span class="p">],</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:plug2</span><span class="p">,</span> <span class="p">[</span><span class="ss">option2:</span> <span class="ss">:value2</span><span class="p">],</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:put_new_view</span><span class="p">,</span> <span class="no">TestView</span><span class="p">,</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:put_new_layout</span><span class="p">,</span> <span class="p">{</span><span class="no">ExampleWeb</span><span class="o">.</span><span class="no">LayoutView</span><span class="p">,</span> <span class="ss">:app</span><span class="p">},</span> <span class="no">true</span><span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<h3 id="conclusion">Conclusion</h3>
<p>Controller tests (request tests) allow us to test router + controller + plugs
as a black box. But, in order to speed up plug-specific controller tests and
avoid testing plug behaviors multiple times, we can just test the <code class="language-plaintext highlighter-rouge">plug</code> calls
in a phoenix controller. This post shows an example of how we can leverage some
tools that elixir/phoenix provides to accomplish that.</p>
<p>I have similar posts coming up as I have been thinking of streamlining testing
in the Elixir ecosystem for a few years now. Next up will be: Testing Phoenix
Router Pipelines and Routes.</p>
<h3 id="optional-making-it-work-for-guard-clauses-a-little-bit-more-meta">(Optional) Making it work for guard clauses (a little bit more meta)</h3>
<h5 id="i-promised-ill-get-to-it-so-here-it-is">I promised I’ll get to it, so here it is..</h5>
<p>To make the <code class="language-plaintext highlighter-rouge">uses_plug?/2</code> helper work with guards we will have to understand
how a <code class="language-plaintext highlighter-rouge">Phoenix.Controller</code> <a href="https://github.com/phoenixframework/phoenix/blob/v1.5.8/lib/phoenix/controller/pipeline.ex#L190" target="_blank">stores a guard</a>
corresponding to a plug.</p>
<p>Let’s take a look at a controller that has plugs with guards, and inspect it:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">defmodule</span> <span class="no">TestController</span> <span class="k">do</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="kn">use</span> <span class="no">ExampleWeb</span><span class="p">,</span> <span class="ss">:controller</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="n">plug</span> <span class="ss">:plug1</span><span class="p">,</span> <span class="p">[</span><span class="ss">option1:</span> <span class="ss">:value1</span><span class="p">]</span> <span class="ow">when</span> <span class="n">action</span> <span class="ow">in</span> <span class="p">[</span><span class="ss">:index</span><span class="p">]</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="n">plug</span> <span class="ss">:plug2</span><span class="p">,</span> <span class="p">[</span><span class="ss">option2:</span> <span class="ss">:value2</span><span class="p">]</span> <span class="ow">when</span> <span class="n">action</span> <span class="ow">in</span> <span class="p">[</span><span class="ss">:show</span><span class="p">]</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">def</span> <span class="n">plug1</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">conn</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">def</span> <span class="n">plug2</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">conn</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="k">end</span>
<span class="p">{</span><span class="ss">:module</span><span class="p">,</span> <span class="no">TestController</span><span class="p">,</span>
<span class="o"><<</span><span class="mi">70</span><span class="p">,</span> <span class="mi">79</span><span class="p">,</span> <span class="mi">82</span><span class="p">,</span> <span class="mi">49</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">216</span><span class="p">,</span> <span class="mi">66</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">77</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">85</span><span class="p">,</span> <span class="mi">56</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">91</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">53</span><span class="p">,</span> <span class="mi">21</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">120</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">46</span><span class="p">,</span> <span class="mi">84</span><span class="p">,</span> <span class="mi">101</span><span class="p">,</span> <span class="mi">115</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">67</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span>
<span class="mi">110</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">101</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="o">...>></span><span class="p">,</span> <span class="p">{</span><span class="ss">:plug1</span><span class="p">,</span> <span class="mi">2</span><span class="p">}}</span>
<span class="n">iex</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">></span> <span class="no">TestController</span><span class="o">.</span><span class="n">__plugs__</span><span class="p">()</span>
<span class="p">[</span>
<span class="p">{</span><span class="ss">:plug2</span><span class="p">,</span> <span class="p">[</span><span class="ss">option2:</span> <span class="ss">:value2</span><span class="p">],</span>
<span class="p">{</span><span class="ss">:in</span><span class="p">,</span> <span class="p">[</span><span class="ss">line:</span> <span class="mi">4</span><span class="p">],</span> <span class="p">[{</span><span class="ss">:action</span><span class="p">,</span> <span class="p">[</span><span class="ss">line:</span> <span class="mi">4</span><span class="p">],</span> <span class="no">nil</span><span class="p">},</span> <span class="p">[</span><span class="ss">:show</span><span class="p">]]}},</span>
<span class="p">{</span><span class="ss">:plug1</span><span class="p">,</span> <span class="p">[</span><span class="ss">option1:</span> <span class="ss">:value1</span><span class="p">],</span>
<span class="p">{</span><span class="ss">:in</span><span class="p">,</span> <span class="p">[</span><span class="ss">line:</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[{</span><span class="ss">:action</span><span class="p">,</span> <span class="p">[</span><span class="ss">line:</span> <span class="mi">3</span><span class="p">],</span> <span class="no">nil</span><span class="p">},</span> <span class="p">[</span><span class="ss">:index</span><span class="p">]]}},</span>
<span class="p">{</span><span class="ss">:put_new_view</span><span class="p">,</span> <span class="no">TestView</span><span class="p">,</span> <span class="no">true</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:put_new_layout</span><span class="p">,</span> <span class="p">{</span><span class="no">ExampleWeb</span><span class="o">.</span><span class="no">LayoutView</span><span class="p">,</span> <span class="ss">:app</span><span class="p">},</span> <span class="no">true</span><span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<p>As you can see above, the guards are expressed as quoted expressions. So, my
first thought was that we can send a quoted guard clause as part of the keyword
list options in <code class="language-plaintext highlighter-rouge">uses_plug?/2</code>. So the function will look somewhat like this:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">TestHelpers</span><span class="o">.</span><span class="no">ControllerPlugUsage</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="nv">@doc</span> <span class="sd">"""
Returns `true` if the given plug is used by the given controller with the
given opts (options)
If no `guard` is given, it defaults to `true` (Just like the controller)
"""</span>
<span class="k">def</span> <span class="n">uses_plug?</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="ss">plug:</span> <span class="n">plug</span><span class="p">,</span> <span class="ss">opts:</span> <span class="n">opts</span><span class="p">,</span> <span class="ss">guard:</span> <span class="n">guard</span><span class="p">)</span> <span class="k">do</span>
<span class="n">expected_plug</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">plug</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">guard</span>
<span class="p">}</span>
<span class="n">expected_plug</span> <span class="ow">in</span> <span class="n">controller</span><span class="o">.</span><span class="n">__plugs__</span><span class="p">()</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">uses_plug?</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="ss">plug:</span> <span class="n">plug</span><span class="p">,</span> <span class="ss">opts:</span> <span class="n">opts</span><span class="p">)</span> <span class="k">do</span>
<span class="n">uses_plug?</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="ss">plug:</span> <span class="n">plug</span><span class="p">,</span> <span class="ss">opts:</span> <span class="n">opts</span><span class="p">,</span> <span class="ss">guard:</span> <span class="no">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>You might think this will work, but it gets complicated to evaluate quoted
expressions especially when the contexts are different.</p>
<p>What that means is, this will return false:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">TestHelpers</span><span class="o">.</span><span class="no">ControllerPlugUsage</span><span class="o">.</span><span class="n">uses_plug?</span><span class="p">(</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="no">TestController</span><span class="p">,</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="ss">plug:</span> <span class="ss">:plug1</span><span class="p">,</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="ss">opts:</span> <span class="p">[</span><span class="ss">option1:</span> <span class="ss">:value1</span><span class="p">],</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="ss">guard:</span> <span class="kn">quote</span> <span class="k">do</span><span class="p">:</span> <span class="n">action</span> <span class="ow">in</span> <span class="p">[</span><span class="ss">:index</span><span class="p">]</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="p">)</span>
<span class="no">false</span>
</code></pre></div></div>
<p>Why is that? It’s because the contextual meta-data (the second element of the
quoted expression) of the plug’s guard will be different when it’s quoted in
the controller vs when it’s quoted somewhere else (test file or <code class="language-plaintext highlighter-rouge">iex</code> shell).</p>
<p>I tried many ways to solve this and the simplest way, in my opinion, is to check
if the raw value of the quoted expressions are the same using
<a href="https://hexdocs.pm/elixir/master/Macro.html#to_string/2"><code class="language-plaintext highlighter-rouge">Macro.to_string/1</code></a> function.</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="no">Macro</span><span class="o">.</span><span class="n">to_string</span><span class="p">(</span><span class="kn">quote</span> <span class="k">do</span><span class="p">:</span> <span class="n">action</span> <span class="ow">in</span> <span class="p">[</span><span class="ss">:index</span><span class="p">])</span>
<span class="s2">"action in [:index]"</span>
</code></pre></div></div>
<p>As you can see above, this is independent of the context in which the expression
was quoted. So, let us change our helper to check for equality of stringified
quoted expressions instead of regular quoted expressions:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">TestHelpers</span><span class="o">.</span><span class="no">ControllerPlugUsage</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="nv">@doc</span> <span class="sd">"""
Returns `true` if the given plug is used by the given controller with the
given opts (options) and given `guard`.
If no `guard` is given, it defaults to `true` (Just like the controller)
# <--- Important note here
NOTE: This checks if the raw string of the guards are as expected and doesn't
take into account the ambiguity of the expression.
For example, these two even though are programatically equivalent, they won't
be equal in this case because their string forms are not equal:
`not action in [:create] and not :something in conn.private`
and
`not (action in [:create] or :something in conn.private)`
"""</span>
<span class="k">def</span> <span class="n">uses_plug?</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="ss">plug:</span> <span class="n">plug</span><span class="p">,</span> <span class="ss">opts:</span> <span class="n">opts</span><span class="p">,</span> <span class="ss">guard:</span> <span class="n">guard</span><span class="p">)</span> <span class="k">do</span>
<span class="n">expected_plug</span> <span class="o">=</span> <span class="n">plug_with_stringified_guard</span><span class="p">({</span><span class="n">plug</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">guard</span><span class="p">})</span>
<span class="n">plugs_with_stringified_guard</span> <span class="o">=</span>
<span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">controller</span><span class="o">.</span><span class="n">__plugs__</span><span class="p">(),</span> <span class="o">&</span><span class="n">plug_with_stringified_guard</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
<span class="n">expected_plug</span> <span class="ow">in</span> <span class="n">plugs_with_stringified_guard</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">uses_plug?</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="ss">plug:</span> <span class="n">plug</span><span class="p">,</span> <span class="ss">opts:</span> <span class="n">opts</span><span class="p">)</span> <span class="k">do</span>
<span class="n">uses_plug?</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="ss">plug:</span> <span class="n">plug</span><span class="p">,</span> <span class="ss">opts:</span> <span class="n">opts</span><span class="p">,</span> <span class="ss">guard:</span> <span class="no">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Convert to string if `guard` is `quoted`</span>
<span class="c1"># Do not convert to string if `guard` is `true` or unquoted</span>
<span class="k">defp</span> <span class="n">plug_with_stringified_guard</span><span class="p">({</span><span class="n">plug</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">guard</span><span class="p">})</span> <span class="k">do</span>
<span class="k">if</span> <span class="no">Macro</span><span class="o">.</span><span class="n">quoted_literal?</span><span class="p">(</span><span class="n">guard</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="n">plug</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="no">Macro</span><span class="o">.</span><span class="n">to_string</span><span class="p">(</span><span class="n">guard</span><span class="p">)}</span>
<span class="k">else</span>
<span class="p">{</span><span class="n">plug</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">guard</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>In the above module, we now convert both the quoted guard clause from the
controller and the quoted guard clause from the test file to raw strings. This
modification allows us to use <code class="language-plaintext highlighter-rouge">uses_plug?/2</code> with guard clauses:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="no">ExampleWeb</span><span class="o">.</span><span class="no">TestHelpers</span><span class="o">.</span><span class="no">ControllerPlugUsage</span><span class="o">.</span><span class="n">uses_plug?</span><span class="p">(</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="no">TestController</span><span class="p">,</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="ss">plug:</span> <span class="ss">:plug1</span><span class="p">,</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="ss">opts:</span> <span class="p">[</span><span class="ss">option1:</span> <span class="ss">:value1</span><span class="p">],</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="ss">guard:</span> <span class="kn">quote</span> <span class="k">do</span><span class="p">:</span> <span class="n">action</span> <span class="ow">in</span> <span class="p">[</span><span class="ss">:index</span><span class="p">]</span>
<span class="o">...</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="p">)</span>
<span class="no">true</span>
</code></pre></div></div>
<p>As the function doc for <code class="language-plaintext highlighter-rouge">uses_plug?/2</code> implies, there is a flaw in this
way of testing the equality of guards. We’re not taking their logic into
consideration but only their raw string form. This means this plug tests only
the literal equivalence of guards, and not the logical equivalence.</p>
<p>I’m sure there’s a better way to test guards equivalence using a combination of
<code class="language-plaintext highlighter-rouge">Macro.expand/2</code> function and the fact that we only have access to <code class="language-plaintext highlighter-rouge">conn</code>,
<code class="language-plaintext highlighter-rouge">action</code> and <code class="language-plaintext highlighter-rouge">controller</code> variables in a plug’s guard clause. Let us
leave that for a future blog post… 👋</p>Adi IyengarPhoenix comes with a few handy ways to test its components. For example, it has Phoenix.ControllerTest module, which helps us make a request that goes through the application endpoint all the way up to the controller. Phoenix also generates a ConnCase module along with the project to better use Phoenix.ControllerTest during testing, with a dedicated setup block.Useful Terminal Shortcuts2020-09-10T14:18:45+00:002020-09-10T14:18:45+00:00https://www.adiiyengar.com/blog/20200911/terminal-shortcuts<p>This mini-post contains some key shortcuts to help with terminal navigation.</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">Ctrl + a</code>: Go to the beginning of the line</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Ctrl + e</code>: Go to the end of the line</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Ctrl + XX</code>: Go to the beginning of the line, and pressing <code class="language-plaintext highlighter-rouge">Ctrl + XX</code> again
takes you back where you were.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Ctrl + u</code>: Delete current line</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Ctrl + _</code>: Undo previous key stroke</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Alt + f</code> / <code class="language-plaintext highlighter-rouge">Escape + F</code>: Move forward, one-word</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Alt + b</code> / <code class="language-plaintext highlighter-rouge">Escape + B</code>: Move backwards, one-word</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Alt + d</code>: Delete all characters after the cursor</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Alt + u</code>: Up-case all characters after the cursor</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Alt + t</code>: Swap current word with the previous word</p>
</li>
</ul>Adi IyengarThis mini-post contains some key shortcuts to help with terminal navigation.Git Post-Commit Formatter2020-06-29T19:44:15+00:002020-06-29T19:44:15+00:00https://www.adiiyengar.com/blog/20200629/elixir-git-post-commit-formatter<p>Working in languages like Elixir and Rust is fun for many reasons. But one of
my favorite features is the <strong>formatter</strong>. Elixir (after 1.4) comes with a
formatter which is well integrated with <code class="language-plaintext highlighter-rouge">mix</code> and can be called using
<code class="language-plaintext highlighter-rouge">mix format</code>. At first, the idea of a machine changing the code that <strong>I</strong> wrote
for <strong>my</strong> project didn’t sound very exciting to me. But working in a team
environment and being able to configure the formatter based on what best
practices a project follows, made it all worth it. Moreover, the ability to
run it (<code class="language-plaintext highlighter-rouge">mix format --check-formatted</code> or <code class="language-plaintext highlighter-rouge">cargo fmt --check</code>) as part of the
CI to see if any new code is formatted, was a great added bonus.</p>
<p>I like to add my formatter commits separately since I like to run analytics on
my code and like keeping my formatted commits separate. I found myself
following a “format” for the commit message for the formatter updates, which
always looked like: <code class="language-plaintext highlighter-rouge">Format code (mix format)</code>. So, I wondered why not add a
<code class="language-plaintext highlighter-rouge">post-commit</code> git hook that does that for me. So, below is the <code class="language-plaintext highlighter-rouge">post-commit</code>
hook that I use in my elixir and rust projects (rust uses <code class="language-plaintext highlighter-rouge">cargo fmt</code> instead of
<code class="language-plaintext highlighter-rouge">mix format</code>) to format the files that I changed in the previous commit in a
new commit message:</p>
<h3 id="for-elixir">For Elixir</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
mix format <span class="si">$(</span>git diff-tree <span class="nt">--no-commit-id</span> <span class="nt">--name-only</span> <span class="nt">-r</span> HEAD<span class="si">)</span>
<span class="k">if</span> <span class="o">!</span> git diff-index <span class="nt">--quiet</span> HEAD <span class="nt">--</span><span class="p">;</span><span class="k">then
</span>git add <span class="si">$(</span>git diff-tree <span class="nt">--no-commit-id</span> <span class="nt">--name-only</span> <span class="nt">-r</span> HEAD<span class="si">)</span>
<span class="nb">exec </span>git commit <span class="nt">-m</span><span class="s2">"Format code (</span><span class="se">\`</span><span class="s2">mix format</span><span class="se">\`</span><span class="s2">)"</span>
<span class="k">fi</span>
</code></pre></div></div>
<h3 id="for-rust">For Rust</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
cargo <span class="nb">fmt</span> <span class="si">$(</span>git diff-tree <span class="nt">--no-commit-id</span> <span class="nt">--name-only</span> <span class="nt">-r</span> HEAD<span class="si">)</span>
<span class="k">if</span> <span class="o">!</span> git diff-index <span class="nt">--quiet</span> HEAD <span class="nt">--</span><span class="p">;</span><span class="k">then
</span>git add <span class="si">$(</span>git diff-tree <span class="nt">--no-commit-id</span> <span class="nt">--name-only</span> <span class="nt">-r</span> HEAD<span class="si">)</span>
<span class="nb">exec </span>git commit <span class="nt">-m</span><span class="s2">"Format code (</span><span class="se">\`</span><span class="s2">cargo fmt</span><span class="se">\`</span><span class="s2">)"</span>
<span class="k">fi</span>
</code></pre></div></div>
<p>Feel free to use them and improve on them. Happy Coding!</p>Adi IyengarWorking in languages like Elixir and Rust is fun for many reasons. But one of my favorite features is the formatter. Elixir (after 1.4) comes with a formatter which is well integrated with mix and can be called using mix format. At first, the idea of a machine changing the code that I wrote for my project didn’t sound very exciting to me. But working in a team environment and being able to configure the formatter based on what best practices a project follows, made it all worth it. Moreover, the ability to run it (mix format --check-formatted or cargo fmt --check) as part of the CI to see if any new code is formatted, was a great added bonus.One Piece 3D world Animation2020-04-14T14:15:15+00:002020-04-14T14:15:15+00:00https://www.adiiyengar.com/blog/20200414/one-piece-animation<h4 id="about">About</h4>
<p>I used three.js and some data gathering (using datascrapers and other maps
available online) to build a simple animation of One Piece’s story line. As the
animation goes forward you can see the crew members and their bounties update.
The animation starts off at Dawn Island and ends at Sabaody Archipelago. It’s a
little quirky (like when it goes through the reverse mountain), but I decided to
keep it that way and keep it simple.</p>
<h4 id="scene">Scene</h4>
<script src="/assets/js/three.js"></script>
<script src="/assets/js/OrbitalControls.js"></script>
<script src="/assets/js/GLTFLoader.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/5.15.1/d3.min.js"></script>
<div id="animation-info" style="position: relative; width: 1000px; height: 200px;"></div>
<div id="one-piece-world" style="position: relative; width: 500px; height: 500px;"></div>
<h4 id="motivation-or-rather-demotivation">Motivation (or rather, Demotivation)</h4>
<p>I started this project as a potentially long-term programming project that would
have been loaded with features. I have always felt One Piece isn’t well
represented in the programming community being the top selling manga of all time.
So my vision for this was to expose all the data that I had scraped through an
API for others to use and build apps with. However, due to a substantial number
of side projects needing my attention, I decided to turn this into an animation
and display it in a blog post.</p>
<script type="module">
// Frame variables
var frameWidth = window.innerWidth / 2
var frameHeight = window.innerHeight / 2
// Scene variables
var scene = new THREE.Scene()
var scale = 1
var gltfPath = '/assets/src/one-piece/OnePieceWorld.glb'
var shipGltfPath = '/assets/src/one-piece/scene.gltf'
var locationsPath = '/assets/src/one-piece/locations.json'
var islandTexturePath = '/assets/images/one-piece/island.jpeg'
var shipTexturePath = '/assets/images/one-piece/strawhats-logo.jpg'
var globeTilt = 24 // degres
var grandLineSpeed = 0.00005 * scale
var cloudSpeed = - 0.0005 * scale
var oceanSpeed = - 0.001 * cloudSpeed
var islandRadiusMultiplier = 0.1 * scale// Update later
var shipSpeed = 5 * scale // radians per second
// Camera
var camera = new THREE.PerspectiveCamera(60, frameWidth / frameHeight, 1, 20000 * scale)
camera.position.set(0, 0, 50 * scale)
// Load a Renderer
var renderer = new THREE.WebGLRenderer({alpha: false})
renderer.setClearColor(0xC5C5C3)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(frameWidth, frameHeight)
document.getElementById('one-piece-world').appendChild(renderer.domElement)
// Load Light
var ambientLight = new THREE.AmbientLight(0xffffff)
scene.add(ambientLight)
// Globe/World
var world = new THREE.Group()
world.name = "World"
// Tilt not needed for animation
// world.rotation.z -= Math.PI / 180 * globeTilt // degrees to radians
scene.add(world)
const repeatTextureForGLTFObj = (gltf, name, repeatX, repeatY, matFn = (mat) => mat) => {
let material = gltf.scene.children.filter((obj) => obj.name == name)[0].material
let tex = material.map
tex.wrapS = THREE.RepeatWrapping
tex.wrapT = THREE.RepeatWrapping
tex.repeat.x = repeatX
tex.repeat.y = repeatY
matFn(material)
}
const updateTextures = (gltf) => {
repeatTextureForGLTFObj(gltf, 'Globe', 10 * scale, 10 * scale)
repeatTextureForGLTFObj(gltf, 'UnderWater', 8 * scale, 8 * scale)
repeatTextureForGLTFObj(gltf, 'GrandLine', 5 * scale, 5 * scale)
repeatTextureForGLTFObj(gltf, 'CalmBelt1', 5 * scale, 5 * scale)
repeatTextureForGLTFObj(gltf, 'CalmBelt2', 5 * scale, 5 * scale)
repeatTextureForGLTFObj(gltf, 'GrandLineClouds', 20 * scale, 5 * scale, (material) => {
material.transparent = true
material.opacity = 0.3
})
repeatTextureForGLTFObj(gltf, 'RedLine', 20 * scale, 5 * scale)
}
var loader = new THREE.GLTFLoader()
loader.load(gltfPath, (gltf) => {
gltf.scene.scale.set(scale, scale, scale)
gltf.scene.position.set(0, 0, 0)
gltf.scene.name = 'Scene'
updateTextures(gltf)
world.add(gltf.scene)
})
var xobj = new XMLHttpRequest()
xobj.overrideMimeType('application/json')
xobj.open('GET', locationsPath, false)
xobj.send()
const update2Dto3D = (object, coordinates) => {
let yAngle = coordinates['x'] / 180 * Math.PI
let xAngle = - coordinates['y'] / 180 * Math.PI
let object_cf = new THREE.Matrix4().makeTranslation(
0, 0, 0
)
object_cf.multiply(new THREE.Matrix4().makeRotationY(yAngle))
object_cf.multiply(new THREE.Matrix4().makeRotationX(xAngle))
object_cf.multiply(new THREE.Matrix4().makeTranslation(
0, 0, (20.1 + coordinates['z']) * scale
))
let tran = new THREE.Vector3()
let quat = new THREE.Quaternion()
let vscale = new THREE.Vector3()
object_cf.decompose(tran, quat, vscale)
object.position.copy(tran)
object.quaternion.copy(quat)
}
// fetch locations data
var locationsJSON = JSON.parse(xobj.responseText)
// render all locations
for (var i = 0; i < locationsJSON.length; i++) {
let location = locationsJSON[i]
if (location['type'] != 'island') { continue }
let size = location['size']
let coordinates = {
'x': parseFloat(location['position']['longitude']),
'y': parseFloat(location['position']['latitude']),
'z': parseFloat(location['position']['elevation']),
}
let radius = islandRadiusMultiplier * size
let islandTex = new THREE.TextureLoader().load(islandTexturePath)
let islandMaterial = new THREE.MeshBasicMaterial({ map: islandTex })
let islandGeometry = new THREE.CircleGeometry(radius, 16 * scale)
let island = new THREE.Mesh(islandGeometry, islandMaterial)
update2Dto3D(island, coordinates)
island.name = location['name']
world.add(island)
}
// Initial Ship
var shipLocationIndex = 0
// var shipSize = 2
// var shipRadius = islandRadiusMultiplier * shipSize
// var shipTex = new THREE.TextureLoader().load(shipTexturePath)
// var shipMaterial = new THREE.MeshBasicMaterial({ map: shipTex })
// var shipGeometry = new THREE.CircleGeometry(shipRadius, 16 * scale)
var loader = new THREE.GLTFLoader()
var initialShipLocation = locationsJSON[0]
var initialShipCoordinates = {
'x': parseFloat(initialShipLocation['position']['longitude']),
'y': parseFloat(initialShipLocation['position']['latitude']),
'z': parseFloat(initialShipLocation['position']['elevation'])
}
var shipCoordinates = initialShipCoordinates
loader.load(shipGltfPath, (gltf) => {
let ship = gltf.scene
let shipScale = 0.12 * scale
ship.scale.set(shipScale, shipScale, shipScale)
ship.position.set(0, 0, 0)
ship.name = 'Ship'
update2Dto3D(ship, initialShipCoordinates)
world.add(ship)
})
var prevShipRotation = 0
const updateShipPosition = () => {
let ship = world.children.filter((obj) => obj.name == 'Ship')[0]
let nextLocation = locationsJSON[shipLocationIndex + 1]
if (nextLocation == undefined) { return null }
let nextLocationCoordinates = {
'x': parseFloat(nextLocation['position']['longitude']),
'y': parseFloat(nextLocation['position']['latitude']),
'z': parseFloat(nextLocation['position']['elevation'])
}
if (shipCoordinates['x'] < nextLocationCoordinates['x']) {
shipCoordinates['x'] += shipSpeed
} else if (shipCoordinates['x'] > nextLocationCoordinates['x']) {
shipCoordinates['x'] -= shipSpeed
}
if (shipCoordinates['y'] < nextLocationCoordinates['y']) {
shipCoordinates['y'] += shipSpeed
} else if (shipCoordinates['y'] > nextLocationCoordinates['y']) {
shipCoordinates['y'] -= shipSpeed
}
if (shipCoordinates['z'] < nextLocationCoordinates['z']) {
shipCoordinates['z'] += 1 * scale
} else if (shipCoordinates['z'] > nextLocationCoordinates['z']) {
shipCoordinates['z'] -= 1 * scale
}
// Update ship location index if next location is reached
if (shipCoordinates['x'] == nextLocationCoordinates['x'] &&
shipCoordinates['y'] == nextLocationCoordinates['y'] &&
shipCoordinates['z'] == nextLocationCoordinates['z']) {
shipLocationIndex += 1
}
update2Dto3D(ship, shipCoordinates)
if (shipCoordinates['x'] < nextLocationCoordinates['x']) {
prevShipRotation = Math.PI
ship.rotation.y += prevShipRotation
} else if (shipCoordinates['x'] > nextLocationCoordinates['x']) {
prevShipRotation = 0
} else {
ship.rotation.y += prevShipRotation
}
}
const updateWaterAndCloudsPosition = () => {
let gltfScene = world.children.filter((obj) => obj.name == 'Scene')[0]
if (gltfScene != undefined) {
let grandLine = gltfScene.children.filter((obj) => obj.name == 'GrandLine')[0]
let globe = gltfScene.children.filter((obj) => obj.name == 'Globe')[0]
let clouds = gltfScene.children.filter((obj) => obj.name == 'GrandLineClouds')[0]
grandLine.rotation.y += grandLineSpeed
globe.rotation.y += oceanSpeed
clouds.rotation.y += cloudSpeed
}
}
var animationInfoSvg = d3.select('#animation-info').append('svg')
.attr('width', 1000)
.attr('height', 200)
const stopConditions = () => {
let nextLocation = locationsJSON[shipLocationIndex + 1]
return (nextLocation == undefined)
}
const getCrew = (index = shipLocationIndex) => {
let currentLocation = locationsJSON[index]
if (currentLocation == undefined) { return getCrew(index - 1) }
return currentLocation['crew']
}
const getLocationName = (index = shipLocationIndex) => {
let currentLocation = locationsJSON[index]
if (currentLocation == undefined) { return getLocationName(index - 1) }
return currentLocation['name']
}
const getShip = (index = shipLocationIndex) => {
let currentLocation = locationsJSON[index]
if (currentLocation == undefined) { return getShip(index - 1) }
return currentLocation['ship']
}
const updateCountsLegend = () => {
if (stopConditions() == true) {
let text = 'End of Animation. Camera Controls enabled..'
animationInfoSvg.append('text')
.attr('x', 300)
.attr('y', 25)
.text(text)
.style('font-size', '20px')
.style('fill', '#778899')
.attr('alignment-baseline','middle')
} else {
animationInfoSvg.selectAll('text').remove()
animationInfoSvg.selectAll('rect').remove()
let text = 'Last Location: ' + getLocationName()
animationInfoSvg.append('text')
.attr('x', 10)
.attr('y', 10)
.text(text)
.style('font-size', '15px')
.attr('alignment-baseline','middle')
text = 'Ship: ' + getShip()
animationInfoSvg.append('text')
.attr('x', 10)
.attr('y', 30)
.text(text)
.style('font-size', '15px')
.attr('alignment-baseline','middle')
let crew = getCrew()
animationInfoSvg.append('text')
.attr('x', 25)
.attr('y', 60)
.text('Crew Member')
.style('font-size', '10px')
.attr('alignment-baseline','middle')
animationInfoSvg.append('text')
.attr('x', 125)
.attr('y', 60)
.text('Bounty ($$)')
.style('font-size', '10px')
.attr('alignment-baseline','middle')
for (let i = 0; i < crew.length; i ++) {
let text = crew[i]['name']
animationInfoSvg.append('text')
.attr('x', 25)
.attr('y', 70 + 10 * i)
.text(text)
.style('font-size', '10px')
.attr('alignment-baseline','middle')
let bountyWidth = crew[i]['bounty'] / 10000000
animationInfoSvg.append('rect')
.attr('x', 125)
.attr('y', 65 + 10 * i)
.attr('width', 0.01 + bountyWidth)
.attr('height', 9)
.style('fill', '#90EE90')
animationInfoSvg.append('text')
.attr('x', 125 + 10 + bountyWidth)
.attr('y', 70 + 10 * i)
.text('( $' + crew[i]['bounty'] + ' )')
.style('font-size', '10px')
.attr('alignment-baseline','middle')
}
}
}
const updateCameraPosition = () => {
if (stopConditions() == true) {
// Load the Orbitcontroller
let controls = new THREE.OrbitControls(camera, renderer.domElement)
controls.minDistance = scale * 10
controls.maxDistance = scale * 35
return null
}
let cameraCoordinates = {
'x': shipCoordinates['x'],
'y': shipCoordinates['y'] - 5,
'z': shipCoordinates['z'] + 6
}
update2Dto3D(camera, cameraCoordinates)
}
var animateCount = 1
var shipUpdate = 120 // once every minute
const animate = () => {
requestAnimationFrame(animate)
updateWaterAndCloudsPosition()
if (animateCount % shipUpdate == 0) {
updateShipPosition()
updateCameraPosition()
updateCountsLegend()
}
animateCount += 1
render()
}
const render = () => {
renderer.render(scene, camera)
}
render()
updateCountsLegend()
animate()
</script>Adi IyengarAboutCovid 19 Infection Model2020-04-12T15:05:15+00:002020-04-12T15:05:15+00:00https://www.adiiyengar.com/blog/20200412/covid-19-infection-model<h4 id="covid-19-infection-model-and-simulation">COVID-19 Infection Model and Simulation</h4>
<p>This is a simulation for a simplified version of COVID-19 infection model in a
closed system. This can be used to visualize how the infection rate and the
number of deaths are affected when we change the proportion of actors
that are social distancing or the initial proportion of the actors that are
infected.</p>
<h4 id="simulation">Simulation</h4>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/5.15.1/d3.min.js"></script>
<div id="animation-form-container">
<form>
<div class="grid-container">
<div class="grid-item">
<label for="infectedPercentage">Initial Infection Percentage</label>
</div>
<div class="grid-item">
<input id="infectedPercentage" type="number" value="10" min="0" max="100" />
</div>
<div class="grid-item">
<label for="socialDistancingPercentage">Social Distancing Percentage</label>
</div>
<div class="grid-item">
<input id="socialDistancingPercentage" type="number" value="33" min="0" max="100" />
</div>
<div class="grid-item">
<input id="submit-form" type="submit" value="Submit" />
</div>
</div>
</form>
</div>
<p><br />
<em>The Simulation automatically stops when no <code class="language-plaintext highlighter-rouge">positive</code> actors are left</em>
<br /></p>
<div id="animation-info" style="position: relative; width: 200px; height: 100px;"></div>
<div id="animation" style="position: relative; width: 500px; height: 500px;"></div>
<h4 id="assumptions">Assumptions</h4>
<p>This model makes several assumptions for simplicity’s sake:</p>
<ul>
<li>Once recovered, an actor is immune to COVID 19</li>
<li>Actors do not have <strong>any</strong> contact when social distancing</li>
<li>Average time an actor is infectious is 14 days</li>
<li>Probability of an actor to die due to COVID 19 is <code class="language-plaintext highlighter-rouge">1/3</code></li>
<li>Probability of an actor getting infected when in range of an infectious actor is <code class="language-plaintext highlighter-rouge">1/10</code></li>
<li>Probably more….</li>
</ul>
<script>
var actors = []
var totalActors = 100
var infectedPercentage = 0.1
var infectedCount = totalActors * infectedPercentage
var negativeActorsCount = totalActors - infectedCount
var socialDistancingPercentage = 0.33
var socialDistancingCount = totalActors * socialDistancingPercentage
// 14 days
var averageInfectionPeriod = 14
var actorRadius = 5
var infectionRadius = 20
var velocity = { x: actorRadius * 4, y: actorRadius * 4 }
// positions should be multiple of 20 to avoid overlaps
var multiple = actorRadius * 4
var infectionInterval = 1000 // 3 seconds
var infectionProbabilityPerLoop = 1 / 10
var deathRate = 1 / 3
var dayCount = 0
var states = [
'positive', 'dead', 'negative', 'recovered'
]
var stateColors = {
// 'infectious': '#800000', // maroon
'positive': '#FF0000', // red
'dead': '#000000', // black
'negative': '#90EE90', // light green
'recovered': '#FFFF00' // yellow
}
var socialDistancingColor = '#D3D3D3' // light grey
var animationSvg = d3.select('#animation').append('svg')
.attr('width', 500)
.attr('height', 500)
var animationInfoSvg = d3.select('#animation-info').append('svg')
.attr('width', 500)
.attr('height', 150)
const generatePosition = () => {
let xFactor = Math.ceil(Math.random() * (500 - multiple) / multiple)
let yFactor = Math.ceil(Math.random() * (500 - multiple) / multiple)
return { x: xFactor * multiple, y: yFactor * multiple }
}
const positionOverlaps = (position) => {
return actors.some((val) => {
val.x == position.x && val.y == position.y
})
}
const getTranslate = (actor, axis) => {
if (actor[axis] >= (500 - (multiple + velocity[axis]))) {
return -1 * velocity[axis]
} else if (actor[axis] <= (multiple + velocity[axis])) {
return velocity[axis]
} else {
return actor.velocity[axis]
}
}
const getVelocity = (actor) => {
return {
x: getTranslate(actor, 'x'),
y: getTranslate(actor, 'y')
}
}
const randomVelocity = () => {
let xTranslate = velocity.x
let yTranslate = velocity.y
if (Math.random() < 0.5) { xTranslate = -1 * velocity.x }
if (Math.random() < 0.5) { yTranslate = -1 * velocity.y }
return {
x: xTranslate,
y: yTranslate
}
}
// Generates a non-overlapping actor
const generateActor = (state) => {
position = generatePosition()
if (positionOverlaps(position)) {
return generateActor(state);
} else {
actor = {
x: position.x,
y: position.y,
state: state,
velocity: randomVelocity(),
socialDistancing: false,
infectionTime: null
}
actor.velocity = getVelocity(actor)
actors.push(actor)
}
}
const generateActors = (num, state = 'negative') => {
for (let index = 0; index < num; index ++) {
generateActor(state)
}
}
const infectActee = (actee) => {
if (Math.random() <= infectionProbabilityPerLoop){
actee.state = 'positive'
actee.infectionTime = 0
return actee
} else {
return actee
}
}
const checkNeighboringActee = (actor, actee) => {
if (actee.state == 'positive') { return actee } else
if (actee.state == 'recovered') { return actee } else
if (actee.socialDistancing == true) { return actee } else
if (actee.state == 'dead') { return actee } else
if (Math.abs(actee.x - actor.x) <= infectionRadius && Math.abs(actee.y - actor.y) <= infectionRadius) {
return infectActee(actee)
} else {
return actee
}
}
const infectActorSurrounding = (actor) => {
if (actor.socialDistancing == true) {
return null
} else if (actor.state == 'positive') {
for (let index = 0; index < actors.length; index++) {
let actee = actors[index]
actors[index] = checkNeighboringActee(actor, actee)
}
} else {
return null
}
}
const updatePositiveActorState = (actor) => {
if (Math.random() <= deathRate){
actor.state = 'dead'
actor.infectionTime = null
return actor
} else {
actor.state = 'recovered'
actor.infectionTime = null
return actor
}
}
const updateActorState = (actor) => {
if (actor.state == 'positive' && actor.infectionTime >= averageInfectionPeriod) {
return updatePositiveActorState(actor)
} else if (actor.state == 'positive') {
actor.infectionTime ++;
return actor
} else {
return actor
}
}
const moveActorPosition = (actor) => {
let initialPos = { x: actor.x, y: actor.y }
if (actor.state == 'dead') { return initialPos } else
if (actor.socialDistancing == true) { return initialPos }
else {
return { x: actor.x + actor.velocity.x, y: actor.y + actor.velocity.y }
}
}
const updateActorPosition = (actor) => {
position = moveActorPosition(actor)
if (positionOverlaps(position)) {
return updatedActorPosition(actor);
} else {
actor.velocity = getVelocity(actor)
actor.x = position.x
actor.y = position.y
return actor
}
}
const renderActors = () => {
for (let index = 0; index < actors.length; index++) {
let actor = actors[index]
animationSvg.append('circle')
.attr('cx', actor.x)
.attr('cy', actor.y)
.attr('r', actorRadius)
.style('fill', stateColors[actor.state])
if (actor.socialDistancing == true) {
side = 2.5 * actorRadius
animationSvg.append('rect')
.attr('x', actor.x - (side / 2))
.attr('y', actor.y - (side / 2))
.attr('width', side)
.attr('height', side)
.style('stroke-width', 1)
.style('stroke', socialDistancingColor)
.style('fill', 'none')
}
}
}
const removeActorCircles = () => {
animationSvg.selectAll('circle').remove()
animationSvg.selectAll('rect').remove()
}
const stateCount = (state) => {
return actors.filter((actor) => { return actor.state == state }).length
}
const stopConditions = () => {
return (stateCount('positive') == 0)
}
const updateCountsLegend = () => {
animationInfoSvg.selectAll('text').remove()
animationInfoSvg.selectAll('rect').remove()
if (stopConditions() == true) {
text = 'Simulation Ended'
animationInfoSvg.append('text')
.attr('x', 300)
.attr('y', 25)
.text(text)
.style('font-size', '20px')
.style('fill', '#778899')
.attr('alignment-baseline','middle')
}
text = 'Day #' + dayCount
animationInfoSvg.append('text')
.attr('x', 10)
.attr('y', 10)
.text(text)
.style('font-size', '10px')
.attr('alignment-baseline','middle')
for (let i = 0; i < states.length; i ++) {
animationInfoSvg.append('rect')
.attr('x', 10)
.attr('y', 20 + 10 * i)
.attr('width', 9)
.attr('height', 9)
.style('fill', stateColors[states[i]])
text = states[i] + ' : ' + stateCount(states[i])
animationInfoSvg.append('text')
.attr('x', 25)
.attr('y', 25 + 10 * i)
.text(text)
.style('font-size', '10px')
.attr('alignment-baseline','middle')
animationInfoSvg.append('rect')
.attr('x', 125)
.attr('y', 20 + 10 * i)
.attr('width', 1 + stateCount(states[i]))
.attr('height', 9)
.style('fill', stateColors[states[i]])
}
animationInfoSvg.append('rect')
.attr('x', 10)
.attr('y', 20 + 10 * states.length)
.attr('width', 9)
.attr('height', 9)
.style('stroke-width', 1)
.style('stroke', socialDistancingColor)
.style('fill', 'none')
text = 'social distancing : ' + socialDistancingCount
animationInfoSvg.append('text')
.attr('x', 25)
.attr('y', 25 + 10 * states.length)
.text(text)
.style('font-size', '10px')
.attr('alignment-baseline','middle')
animationInfoSvg.append('rect')
.attr('x', 125)
.attr('y', 20 + 10 * states.length)
.attr('width', 1 + socialDistancingCount)
.attr('height', 9)
.style('fill', socialDistancingColor)
}
const sociallyDistanceActors = () => {
// Shuffle array
let shuffledActors = actors.sort(() => 0.5 - Math.random())
// Get sub-array of first n elements after shuffled
tempActors = shuffledActors.slice(0, socialDistancingCount)
for(let index = 0; index < tempActors.length; index ++) {
tempActor = tempActors[index]
for(let actorIndex = 0; actorIndex < actors.length; actorIndex ++) {
currentActor = actors[actorIndex]
if(tempActor.x == currentActor.x && tempActor.y == currentActor.y) {
tempActor.socialDistancing = true
actors[actorIndex] = tempActor
}
}
}
}
const infectLoop = () => {
if (stopConditions() == true) {
return null
} else {
removeActorCircles()
for (let index = 0; index < actors.length; index ++) {
let updatedActor = updateActorState(actors[index])
infectActorSurrounding(updatedActor)
updatedActor = updateActorPosition(updatedActor)
actors[index] = updatedActor
}
renderActors()
updateCountsLegend()
dayCount ++
}
}
// Start with 50 uninfected actors
generateActors(negativeActorsCount);
// Add 2 infected actors
generateActors(infectedCount, 'positive');
// socially distance actors
sociallyDistanceActors();
renderActors()
const restartAnimation = () => {
removeActorCircles()
actors = []
dayCount = 0
infectedCount = totalActors * infectedPercentage
negativeActorsCount = totalActors - infectedCount
socialDistancingCount = totalActors * socialDistancingPercentage
// Start with 50 uninfected actors
generateActors(negativeActorsCount);
// Add 2 infected actors
generateActors(infectedCount, 'positive');
// socially distance actors
sociallyDistanceActors();
renderActors()
}
document.getElementById('submit-form').addEventListener('click', (event) => {
event.preventDefault()
socialDistancingPercentage =
parseInt(document.getElementById('socialDistancingPercentage').value) / 100
infectedPercentage =
parseInt(document.getElementById('infectedPercentage').value) / 100
restartAnimation()
});
// infection Loop
setInterval(() => { infectLoop() }, infectionInterval);
</script>Adi IyengarCOVID-19 Infection Model and SimulationCovid 19 2D Maps (last updated 4/11/2020)2020-04-11T16:05:15+00:002020-04-11T16:05:15+00:00https://www.adiiyengar.com/blog/20200411/covid-19-world-map<h4 id="summary">Summary</h4>
<p>I wrote a 2D visualization for World COVID-19 cases & US COVID-19 cases
using the covid-19 API recommended by the WHO and D3.JS.</p>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"></script>
<script src="https://datamaps.github.io/scripts/datamaps.all.min.js?v=5"></script>
<h4 id="world-map">World Map</h4>
<div id="world-container" style="position: relative; width: 500px; height: 500px;">
<div id="world-map-legend"></div>
</div>
<h4 id="us-map">US Map</h4>
<div id="us-container" style="position: relative; width: 500px; height: 500px;"></div>
<script>
const getCases = (data) => {
if (data == null) {
return {
confirmed: 'Unknown',
deaths: 'Unknown',
recoveries: 'Unknown'
}
} else {
return {
confirmed: data.TotalConfirmed,
deaths: data.TotalDeaths,
recoveries: data.TotalRecovered
}
}
}
const popupTemplateFun = (geography, data) => {
let cases = getCases(data);
return '<div class="hoverinfo">' +
geography.properties.name + '<br/>' +
'Cases: <strong>' + cases.confirmed + '</strong>' + '<br/>' +
'Deaths: <strong>' + cases.deaths + '</strong>' + '<br/>' +
'Recoveries: <strong>' + cases.recoveries + '</strong>' + '<br/>' +
'</div>'
}
var defaultFill = '#ABDDA4'
var fills = {
defaultFill: defaultFill,
'Very Low': '#DCDCDC',
'Low': '#C0C0C0',
'Moderate': '#A9A9A9',
'High': '#696969',
'Very High': '#778899',
'Critical': '#2F4F4F'
}
var worldMapSvg = d3.select('#world-container').append('svg')
.attr('width', 100)
.attr('height', 100)
var usMapSvg = d3.select('#us-container').append('svg')
.attr('width', 100)
.attr('height', 100)
const addLegend = (svgElement) => {
// Title
svgElement.append('text')
.attr('x', 10)
.attr('y', 10)
.text('-----')
.style('font-size', '10px')
.attr('alignment-baseline','middle')
let index = 1;
for (var key in fills) {
let text = key
if (key == 'defaultFill') {
text = 'Unknown'
}
svgElement.append('rect')
.attr('x', 10)
.attr('y', 15 + 10 * index)
.attr('width', 9)
.attr('height', 9)
.style('fill', fills[key])
svgElement.append('text')
.attr('x', 30)
.attr('y', 19 + 10 * index)
.text(text)
.style('font-size', '7px')
.attr('alignment-baseline','middle')
index ++;
}
}
addLegend(worldMapSvg);
addLegend(usMapSvg);
var worldMap = new Datamap(
{
scope: 'world',
projection: 'mercator',
height: 400,
element: document.getElementById('world-container'),
geographyConfig: {
highlightBorderColor: '#bada55',
popupTemplate: popupTemplateFun,
highlightBorderWidth: 3
},
fills: fills,
data: {}
}
)
var usMap = new Datamap(
{
scope: 'usa',
projection: 'mercator',
height: 400,
element: document.getElementById('us-container'),
geographyConfig: {
highlightBorderColor: '#bada55',
popupTemplate: popupTemplateFun,
highlightBorderWidth: 3
},
fills: fills,
data: {}
}
)
const getFillKey = (confirmedCases) => {
if (confirmedCases < 500) { return 'Very Low' } else
if (confirmedCases < 10000) { return 'Low' } else
if (confirmedCases < 50000) { return 'Moderate' } else
if (confirmedCases < 100000) { return 'High' } else
if (confirmedCases < 200000) { return 'Very High' } else {
return 'Critical'
}
}
const getFillKeyState = (confirmedCases) => {
if (confirmedCases < 500) { return 'VeryLow' } else
if (confirmedCases < 1000) { return 'Low' } else
if (confirmedCases < 5000) { return 'Moderate' } else
if (confirmedCases < 10000) { return 'High' } else
if (confirmedCases < 20000) { return 'VeryHigh' } else {
return 'Critical'
}
}
const getCountryCode = (countryName) => {
return worldMap.worldTopo.objects.world.geometries.reduce((hash, obj) => {
hash[obj.properties["name"]] = obj.id
return hash
})[countryName]
}
const getStateCode = (stateName) => {
return usMap.usaTopo.objects.usa.geometries.reduce((hash, obj) => {
hash[obj.properties["name"]] = obj.id
return hash
})[stateName]
}
const fetchData = () => {
d3.json('/assets/jsons/world-covid19_2020_04_11.json', (error, data) => {
let worldMapData = data.Countries.reduce((hash, obj) => {
let fillKey = getFillKey(obj.TotalConfirmed)
let countryCode = getCountryCode(obj.Country)
if (countryCode != undefined && fillKey != undefined) {
hash[countryCode] = {
TotalConfirmed: obj.TotalConfirmed,
TotalDeaths: obj.TotalDeaths,
TotalRecovered: obj.TotalRecovered,
fillKey: fillKey
}
}
return hash
}, {})
worldMap.updateChoropleth(
worldMapData,
{ reset: true }
)
})
d3.json('/assets/jsons/us-covid19_2020_04_11.json', (error, data) => {
let usMapData = data.reduce((hash, obj) => {
let fillKey = getFillKeyState(obj.Confirmed)
let stateCode = getStateCode(obj.Province)
if (stateCode != undefined && fillKey != undefined) {
hash[stateCode] = {
TotalConfirmed: obj.Confirmed,
TotalDeaths: obj.Deaths,
TotalRecovered: obj.Recovered,
fillKey: fillKey
}
}
return hash
}, {})
usMap.updateChoropleth(
usMapData,
{ reset: true }
)
})
}
fetchData();
</script>Adi IyengarSummaryElixir: Permutations2019-06-09T15:39:45+00:002019-06-09T15:39:45+00:00https://www.adiiyengar.com/blog/20190609/elixir-permutations<p>In the last post we saw how to write a <code class="language-plaintext highlighter-rouge">combinations</code> function. So, in this
post, we will see how to write a <code class="language-plaintext highlighter-rouge">permutations</code> function in elixir, again
using pattern-matching and recursion:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Permutations</span> <span class="k">do</span>
<span class="nv">@doc</span> <span class="sd">"""
This function lists all permutations of all elements from the given `list`
"""</span>
<span class="k">def</span> <span class="n">permutations</span><span class="p">([]),</span> <span class="k">do</span><span class="p">:</span> <span class="p">[[]]</span>
<span class="k">def</span> <span class="n">permutations</span><span class="p">(</span><span class="n">list</span><span class="p">)</span> <span class="k">do</span>
<span class="n">for</span> <span class="n">head</span> <span class="o"><-</span> <span class="n">list</span><span class="p">,</span> <span class="n">tail</span> <span class="o"><-</span> <span class="n">permutations</span><span class="p">(</span><span class="n">list</span> <span class="o">--</span> <span class="p">[</span><span class="n">head</span><span class="p">]),</span> <span class="k">do</span><span class="p">:</span> <span class="p">[</span><span class="n">head</span> <span class="o">|</span> <span class="n">tail</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1">## Examples</span>
<span class="n">iex</span><span class="o">></span> <span class="no">Permutations</span><span class="o">.</span><span class="n">permutations</span><span class="p">([</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">])</span>
<span class="p">[</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:a</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:a</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:a</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:a</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:a</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:a</span><span class="p">]</span>
<span class="p">]</span>
<span class="n">iex</span><span class="o">></span> <span class="no">Permutations</span><span class="o">.</span><span class="n">permutations</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span>
<span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">]]</span>
</code></pre></div></div>Adi IyengarIn the last post we saw how to write a combinations function. So, in this post, we will see how to write a permutations function in elixir, again using pattern-matching and recursion:Elixir: Combinations2019-06-08T15:39:45+00:002019-06-08T15:39:45+00:00https://www.adiiyengar.com/blog/20190608/elixir-combinations<p>Here’s a quick post about how to write a <code class="language-plaintext highlighter-rouge">combinations</code> function in elixir
using pattern-matching and recursion:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Combinations</span> <span class="k">do</span>
<span class="nv">@doc</span> <span class="sd">"""
This function lists all combinations of `num` elements from the given `list`
"""</span>
<span class="k">def</span> <span class="n">combinations</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="n">num</span><span class="p">)</span>
<span class="k">def</span> <span class="n">combinations</span><span class="p">(</span><span class="n">_list</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">[[]]</span>
<span class="k">def</span> <span class="n">combinations</span><span class="p">(</span><span class="n">list</span> <span class="o">=</span> <span class="p">[],</span> <span class="n">_num</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">list</span>
<span class="k">def</span> <span class="n">combinations</span><span class="p">([</span><span class="n">head</span> <span class="o">|</span> <span class="n">tail</span><span class="p">],</span> <span class="n">num</span><span class="p">)</span> <span class="k">do</span>
<span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">combinations</span><span class="p">(</span><span class="n">tail</span><span class="p">,</span> <span class="n">num</span> <span class="o">-</span> <span class="mi">1</span><span class="p">),</span> <span class="o">&</span><span class="p">[</span><span class="n">head</span> <span class="o">|</span> <span class="nv">&1</span><span class="p">])</span> <span class="o">++</span>
<span class="n">combinations</span><span class="p">(</span><span class="n">tail</span><span class="p">,</span> <span class="n">num</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1">## Examples</span>
<span class="n">iex</span><span class="o">></span> <span class="no">Combinations</span><span class="o">.</span><span class="n">combinations</span><span class="p">([</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">,</span> <span class="ss">:e</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">[</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:e</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:b</span><span class="p">,</span> <span class="ss">:e</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:d</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:c</span><span class="p">,</span> <span class="ss">:e</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:e</span><span class="p">]</span>
<span class="p">]</span>
<span class="n">iex</span><span class="o">></span> <span class="no">Combinations</span><span class="o">.</span><span class="n">combinations</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]]</span>
</code></pre></div></div>Adi IyengarHere’s a quick post about how to write a combinations function in elixir using pattern-matching and recursion:Elixir: Cartesian Product2019-05-03T23:39:45+00:002019-05-03T23:39:45+00:00https://www.adiiyengar.com/blog/20190503/elixir-cartesian-product<p>While working on a <a href="https://en.wikipedia.org/wiki/Constraint_satisfaction_problem">CSP (Constraint Satisfaction Problem)</a>
in Elixir, I was in need to compute Cartesian product of a lists of variable
domains. Elixir being a declarative programming language, I was expecting
there to be function <code class="language-plaintext highlighter-rouge">List.cartesian_product/1</code> that takes a list of
enumerables, but nothing like that existed. So, I resorted to the more
imperative list comprehension:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Cartesian</span> <span class="k">do</span>
<span class="k">def</span> <span class="n">cartesian_product</span><span class="p">(</span><span class="n">set1</span><span class="p">,</span> <span class="n">set2</span><span class="p">)</span> <span class="k">do</span>
<span class="n">for</span> <span class="n">elem1</span> <span class="o"><-</span> <span class="n">set1</span><span class="p">,</span> <span class="n">elem2</span> <span class="o"><-</span> <span class="n">set2</span> <span class="k">do</span>
<span class="p">{</span><span class="n">elem1</span><span class="p">,</span> <span class="n">elem2</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1">## Examples</span>
<span class="n">iex</span><span class="o">></span> <span class="no">Cartesian</span><span class="o">.</span><span class="n">cartesian_product</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="ss">:three</span><span class="p">,</span> <span class="ss">:four</span><span class="p">,</span> <span class="ss">:five</span><span class="p">])</span>
<span class="p">[</span>
<span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="ss">:three</span><span class="p">},</span>
<span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="ss">:four</span><span class="p">},</span>
<span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="ss">:five</span><span class="p">},</span>
<span class="p">{</span><span class="mi">2</span><span class="p">,</span> <span class="ss">:three</span><span class="p">},</span>
<span class="p">{</span><span class="mi">2</span><span class="p">,</span> <span class="ss">:four</span><span class="p">},</span>
<span class="p">{</span><span class="mi">2</span><span class="p">,</span> <span class="ss">:five</span><span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>Adi IyengarWhile working on a CSP (Constraint Satisfaction Problem) in Elixir, I was in need to compute Cartesian product of a lists of variable domains. Elixir being a declarative programming language, I was expecting there to be function List.cartesian_product/1 that takes a list of enumerables, but nothing like that existed. So, I resorted to the more imperative list comprehension:Vim: Hexdump2019-04-27T05:39:45+00:002019-04-27T05:39:45+00:00https://www.adiiyengar.com/blog/20190427/vim-hexdump<p>Today, I had the need of being able to view multiple iterations of a csv file
in hex, as I keep updating/saving it. That’s when I came across vim’s <code class="language-plaintext highlighter-rouge">xxd</code>
command.</p>
<p>So, I decided to bind the command to another key and display the <code class="language-plaintext highlighter-rouge">hex</code> output
in a vim split.</p>
<p>Here’s the function:</p>
<div class="language-vimscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span><span class="p">!</span> TerminalPreviewHexDump<span class="p">()</span>
<span class="k">new</span> % <span class="p">|</span> <span class="k">terminal</span> <span class="p">!</span> xxd %
endfu
<span class="nb">map</span> <span class="p"><</span><span class="k">silent</span><span class="p">></span> <span class="p"><</span>leader<span class="p">></span><span class="k">h</span> <span class="p">:</span><span class="k">call</span> TerminalPreviewHexDump<span class="p">()<</span>CR<span class="p">></span>
</code></pre></div></div>
<p>Now, typing <leader> + `h` opens the hexdump of the current file in a vim
split view.</leader></p>
<p><em>For advanced usage type <code class="language-plaintext highlighter-rouge">:help xxd</code> for more information</em></p>Adi IyengarToday, I had the need of being able to view multiple iterations of a csv file in hex, as I keep updating/saving it. That’s when I came across vim’s xxd command.