<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by J Paul Daigle on Medium]]></title>
        <description><![CDATA[Stories by J Paul Daigle on Medium]]></description>
        <link>https://medium.com/@philosodad?source=rss-189cad5ec980------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*yjytWL-EH8pMDsSt.jpeg</url>
            <title>Stories by J Paul Daigle on Medium</title>
            <link>https://medium.com/@philosodad?source=rss-189cad5ec980------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 21:10:26 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@philosodad/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Preparing to refactor with better Unit Tests]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/preparing-to-refactor-with-better-unit-tests-51e27659fe5e?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/51e27659fe5e</guid>
            <category><![CDATA[elixirlang]]></category>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[property-testing]]></category>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[elixir-language]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Mon, 07 Sep 2020 16:14:27 GMT</pubDate>
            <atom:updated>2020-09-07T16:14:27.655Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>TL/DR: The last time we looked at this code, we wrote property tests. The property tests used generators to create input, but the actually testing of the invariants was just regular Elixir code. In this post, we’ll look at rewriting those property tests as standard ExUnit tests, giving us better unit tests.</p><p>It’s odd when you put your thoughts out into the world, how people see things differently. I posted two articles last month, one of which I thought had some <a href="https://medium.com/perplexinomicon-of-philosodad/preparing-to-refactor-with-property-tests-519d6b3c63bf">good ideas</a>, and one of which was a dry and <a href="https://medium.com/perplexinomicon-of-philosodad/a-function-of-parts-a415a017fd41">painful</a> walk though my own code. Based on statistics, people prefer dry and painful.</p><p>Fortunately for me, also based on my statistics, I don’t do this for a living and can write whatever makes me happy to write. I don’t know if I could handle writing mirror to my code stuff all the time.</p><p>Today I want to finish the refactoring of the code that I property tested in the last article. But before I do, I want better unit tests.</p><p>The code in question is the partiphify!/2 code from the <a href="https://hex.pm/packages/morphix">morphix</a> library. Morphix is a small Elixir library of convenience functions for Enums. partiphify!/2 takes a list <em>l </em>and an integer <em>k</em>, and divides the list as evenly as possible into <em>k</em> sublists. In the language of property testing,Given an input list <em>l</em> and an integer <em>k</em>,the output <em>o </em>of partiphify!/2 has 4 <em>invariants</em>:</p><ol><li><em>o </em>is a list of length <em>k.</em></li><li>Every item in <em>l</em> is in some list in <em>o.</em></li><li>The sum of the lengths of the sublists of <em>o</em> is equal to the length of <em>l</em>.</li><li>No sublist of <em>o</em> has a length that differs by more than 1 from any other sublist.</li></ol><p>There are some other invariants, actually. <em>o</em> must be a list of lists, for example. I’m fairly certain that’s covered by the other 4, but strictly speaking it might be better to actually state it. I’m not a terribly strict person, so I’m going to stick with the invariants I’ve already got.</p><p>The code I’ve currently got in the library looks like this:</p><pre><strong>def</strong> partiphify!(list, k) <strong>when</strong> is_list(list) and is_integer(k) <strong>do</strong><br>    ceil_div = fn a, b -&gt; Float.ceil(a / b) end<br><br>    <strong>with</strong> chunk_size when chunk_size &gt; 0 &lt;-<br>           list<br>           |&gt; Enum.count()<br>           |&gt; Integer.floor_div(k),<br>         true &lt;-<br>           list<br>           |&gt; Enum.count()<br>           |&gt; Integer.mod(k)<br>           |&gt; ceil_div.(chunk_size) &gt; 0 <strong>do</strong><br>      list<br>      |&gt; into_buckets(k, chunk_size)<br>      |&gt; distribute_extra()<br>    <strong>else</strong><br>      0 -&gt;<br>        list = Enum.chunk_every(list, 1, 1, [])<br>        empty_buckets = k - Enum.count(list)<br>        Enum.reduce(1..empty_buckets, list, fn _, acc -&gt; acc ++ [[]] end)<br><br>      false -&gt;<br>        chunk_size =<br>          list<br>          |&gt; Enum.count()<br>          |&gt; Integer.floor_div(k)<br><br>        Enum.chunk_every(list, chunk_size, chunk_size, [])<br>    <strong>end</strong><br><strong>end</strong></pre><pre><strong>defp</strong> into_buckets(list, k, chunk_size) <strong>do</strong><br>    chunks = Enum.chunk_every(list, chunk_size, chunk_size, [])<br>    extra_buckets = Enum.take(chunks, -(Enum.count(chunks) - k))<br>    k_buckets = chunks -- extra_buckets<br>    {extra_buckets, k_buckets}<br><strong>end</strong></pre><pre><strong>defp</strong> distribute(list, buckets) <strong>do</strong><br>    Enum.reduce(list, buckets, fn item, buckets -&gt;<br>      [current_bucket | rest_of_buckets] = buckets<br>      new_bucket = [item | current_bucket]<br>      rest_of_buckets ++ [new_bucket]<br>    end)<br><strong>end</strong><br><br><strong>defp</strong> distribute_extra({lists, buckets}) <strong>do<br></strong>    <strong>with</strong> false &lt;- Enum.empty?(lists) <strong>do</strong><br>      [current_list | rest] = lists<br>      new_buckets = distribute(current_list, buckets)<br>      distribute_extra({rest, new_buckets})<br>    <strong>else</strong><br>      _ -&gt; buckets<br>    <strong>end</strong><br><strong>end</strong></pre><p>If that code doesn’t immediately make sense to you, that’s okay. I dissect it in a <a href="https://medium.com/perplexinomicon-of-philosodad/a-function-of-parts-a415a017fd41">previous post</a>. It made perfect sense when it was being written, and it’s written this way in order to avoid as many calls to ++ as possible. We’re going to refactor it to avoid any calls to ++.</p><p>But our unit tests are still in our way. Here’s an example unit test for this method:</p><pre>iex&gt; Morphix.partiphify!([:a, :b, :c, :d, :e, :f], 4)<br>[[:c], [:d], [:e, :a], [:f, :b]]</pre><p>All the unit tests for this method are actual DocTests. Not good practice, but what’s even worse is that any change to the method that alters the order in which the output occurs will cause the test to fail, even though the order in which the output occurs is really not important at all.</p><p>DocTests are documentation. It’s in the name. They aren’t tests at all, really. So before we refactor, can we write some unit tests that actually test the method?</p><p>Let’s write some unit tests that test our invariants:</p><blockquote>Invariant 1: given a list (<strong>l</strong>) and an integer (<strong>k</strong>), the output should be a list of length <strong>k</strong>.</blockquote><p>We can probably write a test for that. Here is a test that is that test, but it isn’t a very good test:</p><pre><strong>test</strong> &quot;the output list should be the same lenght as the input integer&quot; do<br>    k = (1..100) |&gt; Enum.random()<br>    l = (0..1000)<br>    |&gt; Enum.to_list()<br>    actual = Morphix.partiphify!(l, k)<br>    assert Enum.count(actual) == k<br><strong>end</strong></pre><p>We could improve the test by covering some more cases, for example, we probably want to handle the empty list and a few numbers that are larger or smaller than our value for <em>k</em>.</p><pre><strong>test</strong> &quot;the output list should be the same length as the input integer&quot; <strong>do</strong><br>    k = (1..100) |&gt; Enum.random()<br>    [0, 10, 13, 111, 683, 1013]<br>    |&gt; Enum.each(fn length -&gt;<br>      l = random_list(length)<br>      actual = Morphix.partiphify!(l, k)<br>      assert Enum.count(actual) == k<br>    end)<br><strong>end</strong></pre><pre><strong>defp</strong> random_list(length) <strong>do</strong><br>    (0..length)<br>    |&gt; Enum.map(fn _ -&gt;<br>      random_item()<br>    end)<br><strong>end</strong></pre><pre><strong>defp</strong> random_item() <strong>do</strong><br>    [random_string(), random_atom(), random_int()]<br>    |&gt; Enum.random()<br><strong>end</strong></pre><pre><strong>defp</strong> random_string() <strong>do</strong><br>    [Faker.Lorem.sentence(), Faker.Lorem.word()]<br><strong>end</strong></pre><pre><strong>defp</strong> random_atom() <strong>do</strong><br>    Faker.Lorem.word()<br>    |&gt; String.to_atom()<br><strong>end</strong></pre><pre><strong>defp</strong> random_int() <strong>do</strong><br>    :random.uniform(10000)<br><strong>end</strong></pre><p>So we have a little randomization, and we’re working through a few different possible examples. This test passes with our current implementation, and we can make it break by changing the implementation to modify the value of k.</p><blockquote>Invariant 2: given a list (<strong>l</strong>) and an integer (<strong>k</strong>), every item in <strong>l</strong> should be in the output.</blockquote><p>We can leverage the work that we’ve already done to write this test.</p><pre><strong>test</strong> &quot;all the items in the input list are in the output list&quot; <strong>do</strong><br>    k = (1..100) |&gt; Enum.random()<br>    [0, 10, 13, 111, 1013]<br>    |&gt; Enum.each(fn length -&gt;<br>      l = random_list(length)<br>      actual = Morphix.partiphify!(l, k)<br>      assert Enum.all?(l, fn item -&gt;<br>        Enum.any?(actual, fn sublist -&gt;<br>          Enum.member?(sublist, item)<br>        end)<br>      end)<br>    end)<br><strong>end</strong></pre><p>Breaking this test requires modifying our return value. I split the return list and returned the tail, which gave me a failing value. I like to see things fail.</p><blockquote>Invariant 3: given a list (<strong>l</strong>) and an integer (<strong>k</strong>), the sum of the lengths of the output is equal to the length of <strong>l</strong>.</blockquote><p>Again, our prior work helps us write a test here:</p><pre><strong>test</strong> &quot;the sum of the lengths of the outputs is equal to the length of the input&quot; <strong>do</strong><br>    k = (1..100) |&gt; Enum.random()<br>    [0, 10, 13, 111, 1013]<br>    |&gt; Enum.each(fn length -&gt;<br>      l = random_list(length)<br>      actual = Morphix.partiphify!(l, k)<br>      assert actual<br>            |&gt; Enum.map(fn l -&gt; Enum.count(l) end)<br>            |&gt; Enum.sum() == Enum.count(l)<br>    end)<br><strong>end</strong></pre><blockquote>Invariant 4: No sublist of the output has a length that differs by more than 1 from any other sublist.</blockquote><pre><strong>test</strong> &quot;the lengths of the partitions are within 1 of each other&quot; <strong>do</strong><br>    k = (1..100) |&gt; Enum.random()<br>    [0, 10, 13, 111, 1013]<br>    |&gt; Enum.each(fn length -&gt;<br>      l = random_list(length)<br>      actual = Morphix.partiphify!(l, k)</pre><pre>      {min, max} = actual<br>                   |&gt; Enum.map(fn l -&gt; Enum.count(l) end)<br>                   |&gt; Enum.min_max()</pre><pre>      assert abs(max - min) &lt;= 1<br>    end)<br><strong>end</strong></pre><p>Are we ready to refactor yet? Finally, yes. A key point here is that, if, like me, you’re a proponent of test driven development, you might be thinking that we should have been ready to refactor as soon as we wanted to, because our tests should have already been there to allow us to refactor with wild abandon.</p><p>Exactly.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=51e27659fe5e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/preparing-to-refactor-with-better-unit-tests-51e27659fe5e">Preparing to refactor with better Unit Tests</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Preparing to Refactor with Property Tests]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/preparing-to-refactor-with-property-tests-519d6b3c63bf?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/519d6b3c63bf</guid>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[testing-tools]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Sun, 16 Aug 2020 16:46:14 GMT</pubDate>
            <atom:updated>2020-08-16T16:46:14.550Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-nc/4.0/</cc:license>
            <content:encoded><![CDATA[<h3>TL/DR</h3><p><em>If you can think in properties, with or without a property testing library, your tests will support refactoring rather than being a barrier to it.</em></p><p>I didn’t figure out what this post is about until I finished it, but here it is: This post is about why and how Unit Testing can be broken in your application. It’s about the difference between good DocTests and good Unit Tests. And ultimately, it’s about thinking in properties. We take a while to get there, and there are some detours, but ultimately, that’s where we’ll land.</p><p>So a bit ago I wrote about the partiphify method, a part of the <a href="https://github.com/philosodad/morphix">morphix</a> library for Elixir. My conclusion was that I wasn’t particularly happy about the code I originally wrote for that method. It seemed clumsy. So I wanted to refactor it.</p><p>But that led to another problem, which is example code, and unit testing. What the partiphify! method does is to take a list and divide it up into a fixed number of sublists of equal length, or, as equal length as possible. Given a list like [1, 2,3,4,5,6,7] and the integer 3, partiphify! should return three lists, which might be [[1,2,3], [4,5], [6,7]], but not [[1,2,3],[4,5,6],[7]]. The second partitioning doesn’t keep the lists as close in size as possible, but the first partitioning does.</p><p>The thing about writing example code and unit tests for this kind of a method is that implementation matters. Here’s an example from the DocTests:</p><pre>iex&gt; Morphix.partiphify!([:a, :b, :c, :d, :e], 4)<br>[[:b], [:c], [:d], [:e, :a]]</pre><p>Any implementation that ordered the list differently (for example [[:a,:b], [:c],[:d],[e]], would fail that unit test, even though the implementation might be correct in all the ways that matter. Which is okay in a doctest, which is more of an example than a test, but not great in a unit test.</p><p>Which made this a great time to add <em>property tests</em> to the library. A property test is a test where the developer determines things that are true about the code (these are called <em>invariants</em>) and example inputs are generated by the testing code and tested against these invariants.</p><p>So the first task involved in writing a property test is to figure out what your invariants are. In the case of partiphify!, we know that the function should take two arguments, a list and an integer, and it should divide the list into k sublists that are as close in size to each other possible.</p><p>Thinking about it a bit, we (hat tip to <a href="https://medium.com/u/57bf72cfb25f">Jessica Kerr</a>) came up with a list of 4 properties that make up the partiphify! function, given that there is an input list l, an input integer k, and an output value out.</p><ol><li>The length of out should be equal to k.</li><li>Every item in l should be in out.</li><li>The sum of the lengths of the items in out should equal the length of l.</li><li>The length of each list in out should be at most +-1 of any other list in out.</li></ol><p>And that’s it. If those properties match, then we know that we have divided our input list into partitions correctly. All the items are in the output, no item is in more than one sublist, we have the right number of sublists, and the sublists are balanced.</p><p>I wrote property tests, initially, using <a href="https://github.com/alfert/propcheck">PropCheck</a>, and then later rewrote those same tests using <a href="https://github.com/whatyouhide/stream_data">StreamData</a>. PropCheck is a property testing library for Elixir that provides a wrapper around <a href="https://github.com/proper-testing/proper">PropER</a>, a property based testing library for Erlang.</p><p>To set up either library, you first want to add the library to your deps(), then add the application to your extra_applications: [] or applications: [] Keyword lists. I wanted to make sure these only ran in test, so for my application the changes to my mix.exs looked like this:</p><pre><strong>def</strong> application <strong>do</strong><br>  [applications: applications(Mix.env())]<br><strong>end</strong></pre><pre><strong>defp</strong> applications(:test) <strong>do</strong><br>  applications(:default) ++ [:stream_data, :propcheck]<br>end</pre><pre><strong>defp</strong> applications(_) <strong>do</strong><br>  [:logger]<br><strong>end</strong></pre><pre><strong>defp</strong> deps <strong>do</strong><br>  [<br>    {:excoveralls, &quot;~&gt; 0.8&quot;, only: [:dev, :test]},<br>    {:ex_doc, &quot;~&gt; 0.18&quot;, only: :dev},<br>    {:credo, &quot;~&gt; 0.9.1&quot;, only: [:dev, :test]},<br>    {:stream_data, &quot;~&gt; 0.5.0&quot;, only: [:dev, :test]},<br>    {:propcheck, &quot;~&gt; 1.1&quot;, only: [:dev, :test]}<br>  ]<br><strong>end</strong></pre><p>I have the changes for adding both StreamData and PropCheck here, but really you only need one of them.</p><p>In your test itself, you’ll want to add the dependencies:</p><pre>defmodule PartiphifyPropTest do<br>  use ExUnit.Case<br><em>  use PropCheck</em></pre><p>Or:</p><pre>defmodule PartiphificationTest do<br>  use ExUnit.Case<br><em>  use ExUnitProperties</em></pre><p>So for our first property, this is the test I came up in PropCheck:</p><pre># number of partitions should be correct<br>property &quot;number of partitions is correct&quot; do<br>  forall {list, p} &lt;- {list(), integer(1, 10)} do<br>    partitioned = Morphix.partiphify!(list, p)<br>    Enum.count(partitioned) == p<br>  end<br>end</pre><p>The property block acts like a test statement. The first line is the forall statement, which names two variables, list and p, and assigns generators to those variables. The first generator is list(), which will produce lists of any valid term, and integer(1,10), which will produce integers in the range of 1 to 10. The general form of this statement is</p><pre>forall {var_1, var_2, …, var_<em>n</em>} &lt;- {generator_1, generator_2, ..., generator_<em>n</em>}<br>....<br>end</pre><p>Inside the forall statement, we want to have code that results in either a true or a false value. In this case, once we’ve partitioned the input list, we want to see that the number of partitions is equal to the input integer.</p><p>In StreamData, the test looks like this:</p><pre>property &quot;number of partitions is correct&quot; do<br>  check all list &lt;- list_of(term(), min_length: 0, max_length: 100),<br>            part &lt;- integer(1..27) do<br>    assert list<br>           |&gt; Morphix.partiphify!(part)<br>           |&gt; Enum.count()<br>           |&gt; Kernel.==(part)<br>  end<br>end</pre><p>So, again, we have a property statement, and inside that statement we have a check all statement instead of a forall statement. The check all statement adds variables one at a time rather than in tuples. So in this case, the first variable will be list, which is generated by list_of(term(), [options]), so will be lists of between 0 and 100 of type term() , and part, which is generated by integer(1..27). The general form of this statement is:</p><pre> check all var_1 &lt;- generator_1,<br>           var_2 &lt;- generator_2,<br>           ...,<br>           var_<em>n</em> &lt;- generator_<em>n do<br>    ...<br></em>end</pre><p>Inside the <em>check all</em> statement, we use assert to test the property. The code here looks a little different because I’m using a pipechain, but all the same code is being called in the same order as in the PropCheck example.</p><p>Our second invariant is that every item that is in the original list is in the partitions, somewhere. For this property, the generators are the same, but the code that checks the invariant is fairly complex and might be worth looking at:</p><pre>assert Enum.reduce(list, true, fn list_item, acc_a -&gt;<br>  in_a_list = <br>    Enum.reduce(partitioned, false, fn partition, acc_b -&gt;<br>      Enum.member?(partition, list_item) || acc_b<br>  end)</pre><pre>  in_a_list &amp;&amp; acc_a<br>end)</pre><p><strong>Warning: </strong><em>In this next section I go pretty deep in the reduction logic for this invariant. I’m doing this mostly because using </em><em>Enum.reduce/3 is not always intuitive for me, and I found it initially hard to wrap my head around it for uses other than adding up numbers. If you find the invariant code blindingly obvious, feel free to skip the next few paragraphs.</em></p><p>Let’s look at this code in detail.</p><p>Starting with the outside reduction, we’re reducing on true, and we can simplify a bit… let’s say I wanted to establish that every integer in a list of integers was greater than 10. I could write something like this:</p><pre>small_integers = Enum.filter(list, fn i -&gt; i &lt;= 10 end)<br>less_than_tens = Enum.empty?(small_integers)</pre><p>and less_than_tens would be either true or false. Or I could write this:</p><pre>less_than_tens = <br>  Enum.reduce(list, true, fn i, acc -&gt; i &lt;= 10 &amp;&amp; acc end)</pre><p>Where once some integer has flipped the accumulator from true to false, it will remain false for the rest of the list.</p><p>The outside reduction does just that, we look at each item in the input list, and check to see whether it is in a partition. If it is not, the value in_a_list &amp;&amp; acc_a will become false, and it stays false for the remainder of the list.</p><p>What about finding the value of in_a_list? In this case, I have a list of lists, and I want to see if some value is in one of those lists. There are a lot of ways to do this as well. For example, I could write:</p><pre>(i is some integer we&#39;re checking)<br>lists_of_bools =<br>  Enum.map(list_of_lists, fn list -&gt; Enum.member?(list, i))<br>in_a_list = Enum.member?(list_of_bools, true)</pre><p>If true is a member of our list of bools, than we found i in some list.</p><p>Alternately, I can use a reduction:</p><pre>in_a_list = <br>  Enum.reduce(list_of_lists, false, fn list, acc -&gt; <br>    Enum.member?(list, i) || acc<br>  end)</pre><p>In this case, because of the || statement, once the item is found in a list, the accumulator becomes true, and it will remain true even if the item is not found in any subsequent list.</p><p><strong>End of Warning Section.</strong></p><p>So our outer reduction looks at each item in the input list, and if every item is found in any partition (by the inner reduction) it will return true as it’s accumulated value. If any item is not found, we return false and the invariant will fail. Notice, this invariant would pass under a number of bad implementations. For example, if partiphify had the following implementation:</p><pre>def partiphify!(list, k), do: [[list]]</pre><p>Then this invariant would pass. Similarly, if the implementation were:</p><pre>def partiphify!(list, k) do: (1..k) |&gt; Enum.map(fn _i -&gt; [] end)</pre><p>Then the first invariant would pass.</p><pre>def partiphify!(list, k) do<br>  emptys = (2..k) |&gt; Enum.map(fn _i -&gt; [] end)<br>  [list] ++ emptys<br>end</pre><p>Would pass both of these property tests, and the third invariant as well. The only invariant it would fail would be the fourth one, because the differences in sizes between partitions would exceed 1. I could pass that invariant and the 1st and 2nd invariants with this code:</p><pre>def partiphify!(list, k), do: (1..k) |&gt; Enum.map(fn _i -&gt; [list] end)</pre><p>Now all the partitions are the same size, and I have the right number of partitions, and every item in the input list is in some partition. But I’ll fail the third invariant, because I’ll have k * Enum.count(list) items if I add up the count of the partitions, and I only want Enum.count(list) items.</p><p>Each invariant limits the implementation in different ways, and each of them limits the implementation in ways that are relevant to the function. Contrast this to our DocTest:</p><pre>iex&gt; Morphix.partiphify!([:a, :b, :c, :d, :e], 4)<br>[[:b], [:c], [:d], [:e, :a]]</pre><p>The DocTest gives an output example that meets all four invariants, but there are a lot of irrelevant factors in it as well. It tests ordering, and grouping, and the ordering inside groupings. We don’t care about any of those things, and there are many correct implementations of the method that would fail this test. Which means that rather than being a help to refactoring, this test stands in the way of refactoring!</p><p>An interesting question is whether we could write better unit tests around this code that would help us refactor. And of course, we could test these properties without using a property test library. We could even add some randomness to the test.</p><pre>test &quot;the number of partitions should be correct&quot; do<br>  list = [1,2,3,4,5,6,7,8,9,10]<br>  k = (1..27) |&gt; Enum.random()<br>  parts = Morphix.partiphify!(list, k)<br>  assert Enum.count(parts) == k<br>end</pre><p>With a little work, I could (and might) generate random lists as well. I could even change the implementation to go through every number from 1 to 27 against every one of a number of randomly generated lists, or hit some set of numbers I though was import (1, the list count / k, list count-1, stuff like that). Once I built those cases for number of partitions, it would be a simple matter to test the other invariants.</p><p>These would be better Unit Tests. They might not make great DocTests, but they would be much better unit tests than testing that the input exactly matches the output. A good DocTest should be a simple example that tells people what will happen if they use the function being documented. In Elixir, it’s there to clarify and support your documentation. It’s not there to test your code.</p><p>A good Unit Test is there to test your code. So the goals of the two tests are not the same. Something I’ve often heard and have sometimes said is that “If your tests stand in the way of refactoring, they probably aren’t very good.” You’ve probably also heard “Don’t test the implementation” when writing unit tests. But what does the second statement mean, and how do we avoid having tests stand in the way of implementation?</p><p>I think the answer is this: <strong>“Try to think in properties, not examples”</strong>. That doesn’t mean adding a property testing library to your code. It means understanding what your <em>invariants</em> are for a piece of code, and attempting to test those invariants. It means <em>not</em> testing things that are <em>not</em> invariants as well!</p><p>.If you can think in properties, with or without a property testing library, your tests will support refactoring rather than being a barrier to it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=519d6b3c63bf" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/preparing-to-refactor-with-property-tests-519d6b3c63bf">Preparing to Refactor with Property Tests</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Function of Parts]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/a-function-of-parts-a415a017fd41?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/a415a017fd41</guid>
            <category><![CDATA[recursion]]></category>
            <category><![CDATA[elixirlang]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[refactoring]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Sat, 18 Jul 2020 18:58:55 GMT</pubDate>
            <atom:updated>2020-07-18T18:58:55.618Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-nc/4.0/</cc:license>
            <content:encoded><![CDATA[<p>I recently lost my full time job. I’m working as a contractor, but one of the things about my life is that I’m legally obligated to have health insurance at all times, so I’m looking for a new full time job, which means I’m also looking at old code that I have hanging around on Github to see if I even know what it does or how it does it.</p><p>Happily, for the most part, I do.</p><p>Except for one function, which for reasons that are actually reasonable I named partifify. partifify/2 is part of my<a href="https://github.com/philosodad/morphix"> Morphix</a> library, which is a tiny library of functions to help you deal with Enumerables in Elixir. I started writing it as a port of another library that I wrote several years ago in Ruby, but it’s grown since then into a fairly nice library that a surprising (read, greater than zero) number of people download and use.</p><p>Morphix has functions for getting rid of nil values, converting string keys to atoms (with safe options to keep from creating new atoms) converting atom keys to strings, flattening, checking if two lists have all the same items even if they are in different orders, and of course partifify. Most of these functions have a recursive form which is nice if you need to convert a bunch of atom keys to strings in a nested data structure, or you want to know if a nested map has all the same elements at every level and one of those levels has a list which isn’t strictly ordered.</p><p>partifify/2 takes a list and divides it into k sublists. Here’s an example:</p><pre>iex&gt; Morphix.partiphify!([1,2,3,4,5,6], 4)  <br>[[3], [4], [5, 1], [6, 2]]</pre><p>The key thing about partifify is that the lists should be as balanced as possible. If I divide a list of 14 items into 4 sublists, I don’t want three lists of size 4 and one list of size 2. I want two lists of size 3 and two lists of size 4. For example:</p><pre>iex(70)&gt; Morphix.partiphify!([:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n], 4)</pre><pre>[[:g, :h, :i], [:j, :k, :l], [:m, :a, :b, :c], [:n, :d, :e, :f]]</pre><p>Why do I want balanced lists? That goes back to what I was doing when I wrote this function. The system I was building was drawing data from a number of sources, and comparing that data to a sink. This was an audit system, essentially, checking the correctness of one system against another. Because there were a number of sources, we wanted to distribute the work across several servers, but because the number of sources wasn’t fixed, we wanted to distribute this work flexibly and equally across all servers. Oh, and the number of servers was also not fixed.</p><p>So, given a list of sources, divide them up evenly to the number of servers in operation and let each server begin its auditing work. In a word, partifify/2.</p><p>That’s what we wanted to do. So how were we going to do it? I remember having the problem. I remember solving the problem. I don’t remember HOW I solved the problem.</p><p>Let’s start with the admission that it is entirely possible that there exists a function in Elixir that can take a list of 6 items and output four balanced sublists. I may just be very bad at reading documentation and missed something super obvious. I will say that I looked, and I looked hard, and I couldn’t find a simple way to do this with Enum.chunk*.</p><p>With that admission out of the way, let’s look at the basic algorithm.</p><p>The algorithm runs in a few steps.</p><ol><li>Find the size of the smallest sublist.</li><li>Divide the list into a number of sublists based on the smallest sublist size.</li><li>If there are extra sublists, distribute them evenly across the other sublists in a round robin fashion.</li></ol><p>Is there an easier algorithm? How about:</p><ol><li>Make <em>n </em>sublists that are empty.</li><li>Distribute the original list across the <em>n</em> sublists in a round robin fashion.</li></ol><p>We’ll take a look at the original implementation, and then at an alternate implementation.</p><h4>Find the smallest sublist</h4><p>Finding the smallest sublist is done with some Integer math:</p><pre>list |&gt; Enum.count() |&gt; Integer.floor_div(k)</pre><p>Where k is the desired number of partitions. If the result of that is greater than zero, we move on:</p><pre>with chunk_size when chunk_size &gt; 0 &lt;- <br>    list <br>    |&gt; Enum.count() <br>    |&gt; Integer.floor_div(k), <br>  true &lt;- list <br>          |&gt; Enum.count() <br>          |&gt; Integer.mod(k) <br>          |&gt; ceil_div.(chunk_size) &gt; 0</pre><p>ceil_div is a helper function that just takes the ceiling of dividing two numbers. If we look at an input of ([1,2,3,4,5,6], 4), at this point our value for chunk size is 1 and the output of the second with condition is true, because the 6 modulo 4 is 2, and the ceiling of 2/1 is 2, which is greater than zero. What we’re establishing with this code is that we’re going to have uneven sublists, and we’re going to have more than one sublist. I think the ceil_div function is unnecessary here, because the only time the value is going to be less than or equal to zero is if Integer.mod(k) <em>is</em> zero. chunk_size can’t be negative unless k is negative, and we shouldn’t allow that as an input. We don’t need to be able to divide lists into negative numbers of sublists.</p><p>With our smallest sublist in hand, we move to the next line:</p><pre>...do      <br>list      <br>|&gt; into_buckets(k, chunk_size)<br>|&gt; distribute_extra()</pre><p>These are steps two and three of the algorithm, dividing the list and distributing the extra.</p><h4>Divide the list into sublists of the smallest sublist size</h4><p>This is done using the following function:</p><pre><strong>defp</strong> into_buckets(list, k, chunk_size) <strong>do</strong><br>chunks = Enum.chunk_every(list, chunk_size, chunk_size, [])<br>extra_buckets = Enum.take(chunks, -(Enum.count(chunks) - k))<br>k_buckets = chunks -- extra_buckets<br>{extra_buckets, k_buckets}<br><strong>end</strong></pre><p>We use the Enum.chunk_every/4 method to split the original list into sublists based on the smallest chunk size, then we split the result into two lists… one, the extra_buckets are additional lists that we need to distribute, the other, the k_buckets, will be the sublists that we are distributing to. At this point, for the inputs ([1,2,3,4,5,6], 4), we have the values:</p><pre>extra_buckets = [[5],[6]]]<br>k_buckets = [[1],[2],[3],[4]]</pre><p>It took me a minute looking at this code to figure out what:</p><pre>extra_buckets = Enum.take(chunks, -(Enum.count(chunks) - k))</pre><p>Meant, but it just means: “take the additional buckets off the end of the bucket list and assign them to a variable”.</p><h4>Distribute the extra sublists</h4><p>So we’ve returned a tuple to our initial function that contains extra lists and sublists. We want to distribute the extra lists efficiently across the sublists.</p><pre><strong>defp</strong> distribute_extra({lists, buckets}) <strong>do</strong><br>  <strong>with</strong> false &lt;- Enum.empty?(lists) <strong>do</strong><br>    [current_list | rest] = lists<br>    new_buckets = distribute(current_list, buckets)<br>    distribute_extra({rest, new_buckets})<br>  <strong>else</strong><br>    _ -&gt; buckets    <br>  <strong>end</strong>  <br><strong>end</strong></pre><p>lists is an unfortunate name for the first variable in this function, because those are the <em>extra</em> lists. If there aren’t any, we return the buckets and we’re done. If there are extra lists, we take the first extra list and distribute it across all the buckets, then call this function recursively with any remaining extra lists. In our case of making 4 sublists from the list [1,2,3,4,5.6], we call this function with {[[5],[6]], [[1],[2],[3],[4]]}. So the first call to distribute will be with [5], [[1],[2],[3],[4]].</p><pre><strong>defp</strong> distribute(list, buckets) <strong>do</strong><br>  Enum.reduce(list, buckets, <strong>fn</strong> item, buckets -&gt;<br>    [current_bucket | rest_of_buckets] = buckets<br>    new_bucket = [item | current_bucket]<br>    rest_of_buckets ++ [new_bucket]<br>  <strong>end</strong>)<br><strong>end</strong></pre><p>Step one of the reduction is to grab the first bucket off of our list of buckets. In the example we’re using, the first bucket is [1]. Then we take the current item (in our example, 5) and add it to the front of the bucket. That bucket is moved to the back of the list, so on the next round of the reduction, the accumulator will be [[2], [3], [4], [5, 1]]. The function returns the accumulator when it has processed all the items in the list, and the recursive distribute_extra function calls itself back with any remaining extra lists and the new buckets. The first recursion will be called with:</p><p>{[[6]], [[2], [3], [4], [5, 1]]}</p><p>and the last call will be made with:</p><p>{[], [[3], [4], [5, 1], [6, 2]]}</p><p>That last all will terminate the recursion and return our completed buckets.</p><h4>Why do things this way?</h4><p>Looking back, we had a couple of motives for writing the code in this way. One was just that this is how it evolved and we never got around to refactoring it. We started trying to use Enum.chunk_by and a lot of decisions resulted from that initial idea. Another is that ++ is an expensive operation, and we wanted to minimize its use. In the simple example we just went through, ++ is called twice. If we naively used ++ to implement a fully round robin solution based around distribute and distribute_extra, there are always <em>n</em> calls to ++ . That probably is a premature optimization, though, because with practical numbers of lists that’s just not enough calls to matter.</p><p>It’s probably worth re-examining this code and trying to refactor it. If I were to encounter this library code as a third party, I don’t know that I’d be impressed with this implementation at all. I wrote it, and looking back, I have questions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a415a017fd41" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/a-function-of-parts-a415a017fd41">A Function of Parts</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Tale of Graphs and Darkness]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/a-tale-of-graphs-and-darkness-2b870b253fec?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/2b870b253fec</guid>
            <category><![CDATA[natalie-portman]]></category>
            <category><![CDATA[ecto]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[graph-theory]]></category>
            <category><![CDATA[postgres]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Fri, 23 Mar 2018 12:46:00 GMT</pubDate>
            <atom:updated>2018-03-23T12:46:00.981Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-sa/4.0/</cc:license>
            <content:encoded><![CDATA[<h3>TL/DR</h3><ol><li>Review of the previous two posts.</li><li>Small world networks have short distances between similarly connected nodes.</li><li>You can extract a small world of data from a small world network with a breadth-first search.</li><li>Indexes are your friend.</li><li>The Postgres COPY command exports data as well as it imports data.</li></ol><p><a href="https://github.com/philosodad/dataday/tree/3.18">Example Code</a></p><h3>Introduction</h3><p>This is part 2 of 2 in a 2 part sub-series and the third in an N part series about microservices.</p><p>In the <a href="https://medium.com/perplexinomicon-of-philosodad/the-kevin-bacon-problem-3d197983eea1">first post</a> of the series, we downloaded three tab separated files from the <a href="http://www.imdb.com/interfaces/">Internet Movie DataBase</a> and loaded them into a single Postgres Database, in three tables, title_basics, name_basics, and title_principals. We showed a little bit of how these tables related to each other, for example, we can use these three tables to find information about movies (titles) and actors (names) by way of the title_principals_table. So if we want to learn the names of the principal actors in the movie &quot;Thor: The Dark World&quot;:</p><pre>data_imdb=# <strong>select</strong> primary_name <strong>from</strong> name_basics<br>data_imdb-# <strong>where</strong> nconst <strong>in</strong>(<br>data_imdb(# <strong>select</strong> p<strong>.</strong>nconst <strong>from</strong> title_principals p,<br>data_imdb(# title_basics t<br>data_imdb(# <strong>where</strong> t<strong>.</strong>primary_title <strong>ilike</strong>(&#39;thor%dark%&#39;)<br>data_imdb(# <strong>and</strong> t<strong>.</strong>title_type=&#39;movie&#39;<br>data_imdb(# <strong>and</strong> p<strong>.</strong>tconst=t<strong>.</strong>tconst<br>data_imdb(# <strong>and</strong> p<strong>.</strong>category <strong>ilike</strong>(&#39;act%&#39;));<br>  primary_name    <br>-------------------<br> Natalie Portman<br> Stellan Skarsgård<br> Tom Hiddleston<br> Chris Hemsworth<br>(4 rows)</pre><p>We also created a Phoenix application called DataMunger with three Schemas, TitlePrincipal, TitleBasic, and NameBasic. Using these Schemas and Ecto, we can find all the movies in which <a href="https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93Bacon_number">Natalie Portman</a> played a principal role:</p><pre>iex(<strong>2</strong>)&gt; q = <strong>Ecto.Query</strong>.from(<br>...(<strong>2</strong>)&gt; n <strong>in</strong> <strong>DataMunger.NameBasic</strong>,<br>...(<strong>2</strong>)&gt; where: ilike(n.primary_name, &quot;Natalie Portman&quot;),<br>...(<strong>2</strong>)&gt; select: n.nconst)</pre><pre>iex(<strong>4</strong>)&gt; titles_from_nconst = <strong>fn</strong>(n) -&gt;  <br>...(<strong>4</strong>)&gt; <strong>Ecto.Query</strong>.from(               <br>...(<strong>4</strong>)&gt; p <strong>in</strong> <strong>DataMunger.TitlePrincipal</strong>,<br>...(<strong>4</strong>)&gt; where: p.nconst == ^n <strong>and</strong>      <br>...(<strong>4</strong>)&gt; ilike(p.category, &quot;act%&quot;),     <br>...(<strong>4</strong>)&gt; select: p.tconst) |&gt;           <br>...(<strong>4</strong>)&gt; <strong>DataMunger.ImdbRepo</strong>.all() <strong>end</strong></pre><pre>iex(<strong>8</strong>)&gt; q |&gt; <br>...(<strong>8</strong>)&gt; <strong>DataMunger.ImdbRepo</strong>.one() |&gt;<br>...(<strong>8</strong>)&gt; titles_from_nconst.() |&gt;         <br>...(<strong>8</strong>)&gt; <strong>Enum</strong>.map(<strong>fn</strong>(p) -&gt;                <br>...(<strong>8</strong>)&gt;  <strong>Ecto.Query</strong>.from(                 <br>...(<strong>8</strong>)&gt;  t <strong>in</strong> <strong>DataMunger.TitleBasic</strong>,      <br>...(<strong>8</strong>)&gt;  where: t.tconst == ^p,           <br>...(<strong>8</strong>)&gt;  select: t.primary_title) |&gt;      <br>...(<strong>8</strong>)&gt;  <strong>DataMunger.ImdbRepo</strong>.all() <strong>end</strong>)   <br>[[&quot;Leon: The Professional&quot;], [...], ...]</pre><p>In <a href="https://medium.com/perplexinomicon-of-philosodad/moving-data-from-table-to-table-with-ecto-f2d99df30dce">the second post</a>, we created three new Schemas, Actors, Movies, and ActorMovies with matching database tables, and we created changesets to convert from one repo to the other.</p><p>In a future post, we’ll be looking into the problem of finding the same relationships when the three tables are split across three microservices. To do this, we’re going to split our database into three databases, and launch three services on Heroku to act as API backends. We can’t do that as things stand, because I don’t want to pay to host several million rows in a Postgres database on Heroku. In fact, I’d like to stay on the free tier, so we’re going to need to lose almost all of the data.</p><p>We can’t just truncate the tables, because we want to maintain relationships.</p><pre>title     title        name     title      title<br>          name                  name         <br>-----     -----        ----     ----       -----<br>t1 -----&gt; t1:n1 -----&gt; n1 ----&gt; n1:t1 ---&gt; t1 <br>    \ \-&gt; t1:n4 --     n2   \-&gt; n1:t2 ---&gt; t2 <br>     \--&gt; t1:nb - \    n3   |-&gt; n1:t3 ---&gt; t3   <br>                 \ \-&gt; n4-  |-&gt; n1:t4 ---&gt; t4<br>                  |    n5 \ \-&gt; n1:t5 ---&gt; t5<br>                  |    n6  \--&gt; n4:t1      t6<br>                  |    n7   |-&gt; n4:t2      t7<br>                  |    n8   |-&gt; n4:t4      t8<br>                  |    n9   \-&gt; n4:tf --   t9<br>                  |    na   /-&gt; nb:t1  |   ta<br>                   \-&gt; nb ----&gt; nb:t4  |   tb<br>                            \-&gt; nb:t5  |   ta<br>                                       |   tb<br>                                       |   tc<br>                                       |   td<br>                                       |   te<br>                                       \-&gt; tf</pre><p>In the figure, t1 has 3 principal actors, n1, n4, and nb. Between the three actors, we have 6 movies, which would lead us to select even more actors, and so on. But if we only selected, say, 9 rows from each database, we would end up with titles in the title selection that didn&#39;t show up in the title_name selection, or title_name selections that didn&#39;t show up in names. So it&#39;s important that we select data algorithmically when trying to extract a useful subset of the data.</p><p>In the rest of this post, we’ll be using Ecto to get a subset of the data that meets these constraints.</p><h3>Getting the Data Subset</h3><p>There are a couple of approaches we could use, and probably there’s an optimal algorithm for getting just exactly the data we want, but this is a case where the intuitive approach will probably do. Looking at the figure, it seems that by just following the arrows from name -&gt; title_principal -&gt; title -&gt; title_principal -&gt; name and so on, we should be able to rapidly expand our selection of names and titles until we have a nicely connected selection across all three databases. In this post, we&#39;ll write queries to extract the records we want from the initial IMDb database, transform them, load them into our new databases, and then export those databases to CSV files.</p><h4>Extracting the Data</h4><p>Extracting the data means solving the problem of getting a sparse, connected data set of fewer than 10000 rows for the largest database. We’ll start in Postgres and explore the data. The queries we use to do this will be refined and converted into Ecto when we have a better idea what we want to do.</p><p>Just as a reminder, we’re dealing with six tables here:</p><p>The IMDb data is loaded into an ImdbRepo, into three tables, name_basics, title_basics, and title_principals. title_basics contains information about video productions: movies, documentaries, whatever. Each title_basics record has a tconst identifier and other information, such as title, release date, and title_type. name_basics has the same sort of information about people, name, nconst, birth_year. title_principals is a join table, it keeps the tconst field and the nconst field from title_basics and name_basics, as well as information about what job the person had on the film. The tables we want to load into are the actor, movie, and actor_movie tables in the Repo.</p><p><strong>Q for Query</strong></p><p>Let’s start by asking a simple question: How many movies has Natalie Portman starred in? We’ll start with getting a universal key:</p><pre>data_imdb=# <strong>select</strong> nconst <strong>from</strong> name_basics<br>data_imdb-# <strong>where</strong> primary_name <br>data_imdb-# <strong>ilike</strong>(&#39;natalie portman&#39;);<br>  nconst   <br>-----------<br> nm0000204</pre><p>Then use that universal key to answer the question:</p><pre>data_imdb=# <strong>select</strong> count(*) <strong>from</strong> title_principals p<br>data_imdb-# <strong>join</strong> title_basics t<br>data_imdb-# <strong>on</strong> (p<strong>.</strong>tconst=t<strong>.</strong>tconst)<br>data_imdb-# <strong>where</strong> nconst=&#39;nm0000204&#39; <strong>and</strong><br>data_imdb-# category <strong>ilike</strong>(&#39;act%&#39;) <strong>and</strong><br>data_imdb-# title_type=&#39;movie&#39;;<br> count <br>-------<br>    36<br>(1 row)</pre><p>That’s a fair number of movies. How many costars does that get us?</p><pre>data_imdb=# <strong>select</strong> count(<strong>distinct</strong> p<strong>.</strong>nconst) <br>data_imdb=# <strong>from</strong> title_principals p <strong>where</strong> <br>data_imdb=# p<strong>.</strong>tconst <strong>in</strong><br>data_imdb=# (<strong>select</strong> p<strong>.</strong>tconst <strong>from</strong> title_principals p<br>data_imdb=# <strong>join</strong> title_basics t<br>data_imdb=# <strong>on</strong> (p<strong>.</strong>tconst=t<strong>.</strong>tconst)<br>data_imdb=# <strong>where</strong> nconst=&#39;nm0000204&#39; <strong>and</strong><br>data_imdb=# category <strong>ilike</strong>(&#39;act%&#39;) <strong>and</strong><br>data_imdb=# title_type=&#39;movie&#39;);<br> count <br>-------<br>   276<br>(1 row)</pre><p>So, if we want to limit our extraction to 10,000 rows, we won’t be able to go 2 degrees from the source before passing our limit, because 200 squared is 40,000. If we include all the movies for all the actors, we’re going to end up with a network that has a small number of extremely tightly coupled nodes, which isn’t really the same shape as the IMDb database.</p><p>In the IMDb data, yes, there are 276 actors who are costars of Natalie Portman, but there are 1.6 million actors who are not. But for most of these 1.6 million actors, there is a fairly short path to Natalie Portman, or to any other actor. If an actor has 100 costars in a career, and each of these has a 100 costars, that’s 10,000 actors who have co-starred with a co-star of the first actor, and 1,000,000 who have co-starred with a co-star of a co-star. A network like this, where most nodes are not neighbors, but the path length between any two nodes is short, is called a <a href="https://en.wikipedia.org/wiki/Small-world_network">“small world” network</a>. This is the property that we want to maintain in our own data. One thing we could try is limiting the number of costars by limiting the number of movies:</p><pre>data_imdb=# <strong>select</strong> count(<strong>distinct</strong> p<strong>.</strong>nconst) <br>data_imdb=# <strong>from</strong> title_principals p <strong>where</strong> <br>data_imdb=# p<strong>.</strong>tconst <strong>in</strong><br>data_imdb=# (<strong>select</strong> p<strong>.</strong>tconst <strong>from</strong> title_principals p<br>data_imdb=# <strong>join</strong> title_basics t<br>data_imdb=# <strong>on</strong> (p<strong>.</strong>tconst=t<strong>.</strong>tconst)<br>data_imdb=# <strong>where</strong> nconst=&#39;nm0000204&#39; <strong>and</strong><br>data_imdb=# category <strong>ilike</strong>(&#39;act%&#39;) <strong>and</strong><br>data_imdb=# title_type=&#39;movie&#39; <strong>limit</strong> <strong>3</strong>);<br> count <br>-------<br>   22<br>(1 row)</pre><p>22 cubed is 10,648, so theoretically we won’t even be able to get 3 degrees from the source before passing our limit. However, there’s probably a good deal of overlap (that’s also a characteristic of small-world networks), so 3 seems like a reasonable starting place for limiting movies per actor.</p><p>At this point we’re passing my ability to write SQL, so lets see if we can do some exploring in Ecto. A query for finding three movies for an actor:</p><pre>iex(<strong>15</strong>)&gt; tfa = <strong>fn</strong>(a) -&gt; <strong>Ecto.Query</strong>.from(                 <br>...(<strong>15</strong>)&gt; p <strong>in</strong> <strong>DataMunger.TitlePrincipal</strong>,                 <br>...(<strong>15</strong>)&gt; join: t <strong>in</strong> <strong>DataMunger.TitleBasic</strong>,               <br>...(<strong>15</strong>)&gt; on: p.tconst == t.tconst,                       <br>...(<strong>15</strong>)&gt; where: p.nconst == ^a <strong>and</strong>                       <br>...(<strong>15</strong>)&gt; t.title_type==&quot;movie&quot; <strong>and</strong>                       <br>...(<strong>15</strong>)&gt; ilike(p.category, &quot;act%&quot;),                      <br>...(<strong>15</strong>)&gt; select: p.tconst,                             <br>...(<strong>15</strong>)&gt; limit: <strong>3</strong>) |&gt; <br>...(<strong>15</strong>)&gt; <strong>DataMunger.ImdbRepo</strong>.all() <strong>end</strong> <br>#Function&lt;6.52032458/1 in :erl_eval.expr/5&gt;<br>iex(<strong>16</strong>)&gt; tfa.(&quot;nm0000204&quot;)<br>[&quot;tt2798920&quot;, &quot;tt2180351&quot;, &quot;tt0947798&quot;]</pre><p>A query for actors in a movie:</p><pre>iex(<strong>20</strong>)&gt; aft = <strong>fn</strong>(t) -&gt; <strong>Ecto.Query</strong>.from(<br>...(<strong>20</strong>)&gt; p <strong>in</strong> <strong>DataMunger.TitlePrincipal</strong>,<br>...(<strong>20</strong>)&gt; where: p.tconst == ^t <strong>and</strong>      <br>...(<strong>20</strong>)&gt; ilike(p.category, &quot;act%&quot;),     <br>...(<strong>20</strong>)&gt; select: p.nconst) <br>...(<strong>20</strong>)&gt; |&gt; <strong>DataMunger.ImdbRepo</strong>.all() <strong>end</strong>          <br>#Function&lt;6.52032458/1 in :erl_eval.expr/5&gt;<br>iex(<strong>21</strong>)&gt; aft.(&quot;tt2798920&quot;)<br>[&quot;nm0000204&quot;, &quot;nm0000492&quot;, &quot;nm1935086&quot;, &quot;nm0938950&quot;]</pre><p>The movies for actor tfa/1 query takes about 5 seconds to return, and the aft/1 query takes half a second. We&#39;ll be happier if we add a couple of indexes.</p><pre>imdb# <strong>create</strong> <strong>index</strong> <strong>on</strong> title_principals <br>imdb# (tconst, nconst, category);<br>imdb# create index on title_principals <br>imdb# (tconst, category);<br>imdb# create index on title_basics<br>imdb# (tconst, title_type);<br>imdb# create index on name_basics<br>imdb# (nconst);</pre><p>On my machine, that drops both queries into the 1–2 millisecond range. The last index on name_basics we won&#39;t need until the end, but we might as well add it now.</p><p>Then we can write a function to chain our two queries together.</p><pre>iex(<strong>81</strong>)&gt; xs_from_ys = <strong>fn</strong>(vals, f) -&gt;<br>...(<strong>81</strong>)&gt; <strong>Enum</strong>.flat_map(vals, <strong>fn</strong>(v) -&gt;<br>...(<strong>81</strong>)&gt; f.(v) <strong>end</strong>) |&gt; <strong>Enum</strong>.uniq <strong>end</strong><br>#Function&lt;12.52032458/2 in :erl_eval.expr/5&gt;<br>iex(<strong>82</strong>)&gt; aft.(&quot;tt0110413&quot;) |&gt; xs_from_ys.(mfa)<br>[&quot;tt0085426&quot;, &quot;tt0095250&quot;, &quot;tt0099789&quot;, &quot;tt0091954&quot;,<br>&quot;tt0093776&quot;, &quot;tt0096294&quot;, &quot;tt0080605&quot;, &quot;tt0081145&quot;, <br>&quot;tt0082177&quot;, &quot;tt0110413&quot;, &quot;tt0120915&quot;, &quot;tt0121765&quot;]</pre><p>So now we can find how many movies and titles we have at various distances from our seed actor.</p><pre>iex(<strong>103</strong>)&gt; tfa.(&quot;nm0000204&quot;) |&gt; xs_from_ys.(aft) |&gt;<br>...(<strong>103</strong>)&gt; xs_from_ys.(tfa) |&gt; xs_from_ys.(aft) |&gt; <br>...(<strong>103</strong>)&gt; xs_from_ys.(tfa) |&gt; xs_from_ys.(aft) |&gt;<br>...(<strong>103</strong>)&gt; xs_from_ys.(tfa) |&gt; xs_from_ys.(aft) |&gt;<br>...(<strong>103</strong>)&gt; <strong>Enum</strong>.count()<br><strong>2927</strong><br>iex(<strong>103</strong>)&gt; tfa.(&quot;nm0000204&quot;) |&gt; xs_from_ys.(aft) |&gt;<br>...(<strong>103</strong>)&gt; xs_from_ys.(tfa) |&gt; xs_from_ys.(aft) |&gt; <br>...(<strong>103</strong>)&gt; xs_from_ys.(tfa) |&gt; xs_from_ys.(aft) |&gt;<br>...(<strong>103</strong>)&gt; xs_from_ys.(tfa) |&gt; xs_from_ys.(aft) |&gt;<br>...(<strong>103</strong>)&gt; xs_from_ys.(tfa) |&gt; xs_from_ys.(aft) |&gt;<br>...(<strong>103</strong>)&gt; <strong>Enum</strong>.count()<br><strong>12325</strong></pre><p>This pattern of moving from movie to actor to movie to actor and so on should look familiar, we’re basically recreating the logic from our ascii figure above.</p><p>After player around with this a bit, it seems like we can safely go about 4 degrees away from the seed if we limit the number of movies to three. This is imprecise, but it is a strategy that should build a sparse, connected, small worldy network.</p><p><strong>The Professional (Query)</strong></p><p>We’ll clean up our queries and add them to a GraphTraversemodule.</p><pre><strong>defmodule</strong> <strong>DataMunger.GraphTraverseTest</strong> <strong>do</strong><br>  <strong>use</strong> <strong>ExUnit.Case</strong>, async: <strong>true</strong><br>  <strong>alias</strong> <strong>DataMunger.GraphTraverse</strong><br>  <strong>alias</strong> <strong>DataMunger.ImdbRepo</strong><br>  <strong>alias</strong> <strong>DataMunger.TitleBasic</strong><br>  <strong>alias</strong> <strong>DataMunger.TitlePrincipal</strong><br>              <br>  describe &quot;movies for actor&quot; <strong>do</strong><br>    test &quot;given a name, finds 3 movie tconsts&quot; <strong>do</strong><br>      [&quot;nm0000203&quot;, &quot;nm0000702&quot;, &quot;nm0000204&quot;, &quot;nm0000102&quot;]<br>      |&gt; <strong>Enum</strong>.each(<strong>fn</strong>(n) -&gt;<br>        <strong>GraphTraverse</strong>.movies_for_actor(n)<br>        |&gt; <strong>Enum</strong>.map(<strong>fn</strong>(m) -&gt;<br>          <strong>ImdbRepo</strong>.get(<strong>TitleBasic</strong>, m)<br>        <strong>end</strong>)<br>        |&gt; <strong>Enum</strong>.map(<strong>fn</strong>(mov) -&gt;<br>          assert mov.title_type == &quot;movie&quot;<br>        <strong>end</strong>)<br>        |&gt; (&amp;(assert <strong>Enum</strong>.count(<strong>&amp;1</strong>) == <strong>3</strong>)).()<br>      <strong>end</strong>)<br>    <strong>end</strong><br>  <strong>end</strong><br><strong>end</strong></pre><p>Our testing strategy is basically to call back to TitleBasic to make sure we got the right number of movies. We should also check that the movies we got back directly related to each name as an Actor in the TitlePrincipal table. We won&#39;t show that test here, but it is in the <a href="https://github.com/philosodad/dataday/tree/3.18">example code</a>.</p><p>We can pass these tests with the following code:</p><pre><strong>defmodule</strong> <strong>DataMunger.GraphTraverse</strong> <strong>do</strong><br>  <strong>alias</strong> <strong>DataMunger.TitlePrincipal</strong><br>  <strong>alias</strong> <strong>DataMunger.TitleBasic</strong><br>  <strong>alias</strong> <strong>DataMunger.ImdbRepo</strong><br>  <strong>import</strong> <strong>Ecto.Query</strong>, only: [from: <strong>2</strong>]<br>              <br>  <strong>def</strong> movies_for_actor(actor, count \\ <strong>3</strong>) <strong>do</strong><br>    from(p <strong>in</strong> <strong>TitlePrincipal</strong>,<br>    join: t <strong>in</strong> <strong>TitleBasic</strong>,<br>    on: p.tconst == t.tconst,<br>    where: p.nconst == ^actor <strong>and</strong><br>    t.title_type==&quot;movie&quot; <strong>and</strong><br>    ilike(p.category, &quot;act%&quot;),<br>    select: p.tconst,<br>    limit: ^count)<br>    |&gt; <strong>ImdbRepo</strong>.all()<br>  <strong>end</strong><br><strong>end</strong></pre><p>The code and tests for the complementary actors_for_moviefunction and the principals_from_movies function are very similar so we won&#39;t show them here. We are interested in the code that puts these together. We&#39;ll test the functionality of finding many movies and actors from a single actor. In test/data_munger/graph_traverse_test:</p><pre>describe &quot;gets actors and movies from one actor&quot; <strong>do</strong><br>  test &quot;returns uniq collections&quot; <strong>do</strong><br>    {actors, movies} = [&quot;nm0000204&quot;]<br>                       |&gt; <strong>GraphTraverse</strong>.movies_and_actors([], <strong>2</strong>)<br>    actors<br>    |&gt; <strong>Enum</strong>.each(<strong>fn</strong>(a) -&gt;<br>         assert <strong>String</strong>.match?(a, ~r/nm[0-9]{7}/)<br>    <strong>end</strong>)<br>    assert actors <br>           |&gt; <strong>Enum</strong>.uniq()<br>           |&gt; <strong>Enum</strong>.count() == actors <br>                              |&gt; <strong>Enum</strong>.count()<br>    assert <strong>Enum</strong>.count(actors) &gt; <strong>18<br>    </strong>movies<br>    |&gt; <strong>Enum</strong>.each(<strong>fn</strong>(m) -&gt;<br>        assert <strong>String</strong>.match?(m, ~r/tt[0-9]{7}/)<br>    <strong>end</strong>)<br>    assert movies <br>           |&gt; <strong>Enum</strong>.uniq()<br>           |&gt; <strong>Enum</strong>.count() == movies <br>                              |&gt; <strong>Enum</strong>.count()<br>    assert <strong>Enum</strong>.count(movies) &gt; <strong>6</strong><br>  <strong>end</strong><br><strong>end</strong></pre><p>The API we’re assuming here is that the movies_and_actorsfunction is going to take three arguments, actors, movies, and a depth. The idea is to find all the costars of the first set of actors, then the costars of all the costars (for three movies) out to a certain search depth into the graph. In /lib/data_munger/graph_traverse.ex:</p><pre><strong>def</strong> movies_and_actors(actors, movies, <strong>0</strong>) <strong>do</strong><br>  {actors, movies}<br><strong>end</strong><br>              <br><strong>def</strong> movies_and_actors(actors, _movies, depth) <strong>do</strong><br>  new_movies = actors<br>  |&gt; <strong>Enum</strong>.flat_map(<strong>fn</strong>(a) -&gt; <br>       movies_for_actor(a) <strong>end</strong>)<br>  |&gt; <strong>Enum</strong>.uniq()<br>  new_actors = new_movies<br>               |&gt; <strong>Enum</strong>.flat_map(<strong>fn</strong>(m) -&gt; <br>                    actors_for_movie(m) <strong>end</strong>)<br>               |&gt; <strong>Enum</strong>.uniq()<br>  movies_and_actors(new_actors, new_movies, depth-<strong>1</strong>)<br><strong>end</strong></pre><p>A bit of explanation: we’re defining this as a recursive function, using the first pattern (actors, movies, 0) as the terminating condition, and the second pattern (actors, _movies, depth) is the recurrent procedure. In the recurring procedure, we use the list of actors to find 3 movies for each actor, and then we get all the actors for each of those movies. We go to the next recurrence with this new group of actors and movies. The depth variable keeps track of how deep we&#39;ve gone into the recursion, ensuring that we&#39;ll exit when that value hits zero.</p><p>This solution could be improved in a couple of ways. The most obvious is that we don’t use the results of previous searches to limit the current search, so we make a lot of repetitive calls to the database. In fact, we only carry the movies from one recursion to the next so that we can return them when we hit 0. If we were concerned with performance we’d have to use a technique such as memoization to avoid making repetitive calls, but since we aren’t the naive version of the algorithm is fine. It’s worth noting in any case, because recursion is used a lot in Elixir code and it’s good to be aware of when you are falling into a common recursion pitfall.</p><p>We can improve this slightly by adding at least one other seed actor and extract our sparse subgraph:</p><pre>iex(<strong>326</strong>)&gt; <strong>DataMunger.GraphTraverse</strong>.movies_and_actors(<br>...(<strong>326</strong>)&gt; [&quot;nm0000204&quot;, &quot;nm0000702&quot;], [], <strong>4</strong>) <br>{[&quot;nm0427136&quot;, &quot;nm0301556&quot;, &quot;nm0473218&quot;, ...],<br>[&quot;tt0084627&quot;, &quot;tt0085426&quot;, &quot;tt0275909&quot;,  ...]}<br>iex(<strong>327</strong>)&gt; {actors, movies} = v()<br>{[&quot;nm0427136&quot;, &quot;nm0301556&quot;, &quot;nm0473218&quot;, ...],<br>[&quot;tt0084627&quot;, &quot;tt0085426&quot;, &quot;tt0275909&quot;,  ...]}<br>iex(<strong>329</strong>)&gt; princs = movies |&gt;                               <br>...(<strong>329</strong>)&gt; <strong>DataMunger.GraphTraverse</strong>.principals_from_movies()<br>[<strong>714013</strong>, <strong>714015</strong>, <strong>714014</strong>, ...]<br>iex(<strong>330</strong>)&gt; <strong>Enum</strong>.count(actors)<br><strong>4787</strong><br>iex(<strong>331</strong>)&gt; <strong>Enum</strong>.count(movies)<br><strong>1966</strong><br>iex(<strong>332</strong>)&gt; <strong>Enum</strong>.count(princs)<br><strong>8137</strong></pre><p>It’s not perfect, but it’s fine.</p><h4>Extract, Transform, Load</h4><p>We’re now just about done. All that’s left is using the results from the functions in GraphTraverse to load data into the schemas we created in <a href="https://medium.com/perplexinomicon-of-philosodad/moving-data-from-table-to-table-with-ecto-f2d99df30dce">the last post</a>. We&#39;ll look in depth at just title_principal -&gt; movie_actor. Again, we start with a test (test/data_munger/etl_test.exs):</p><pre><strong>defmodule</strong> <strong>DataMunger.EtlTest</strong> <strong>do</strong><br>  <strong>use</strong> <strong>ExUnit.Case</strong>, async: <strong>true</strong><br>  <strong>require</strong> <strong>Ecto.Query</strong><br>  <strong>alias</strong> <strong>Ecto.Query</strong><br>  <strong>alias</strong> <strong>DataMunger.Etl</strong><br>  <strong>alias</strong> <strong>DataMunger.Repo</strong><br>  <strong>alias</strong> <strong>DataMunger.ImdbRepo</strong><br>  <strong>alias</strong> <strong>DataMunger.Movie</strong><br>  <strong>alias</strong> <strong>DataMunger.TitlePrincipal</strong><br>  <strong>alias</strong> <strong>DataMunger.Actor</strong><br>  <strong>alias</strong> <strong>DataMunger.MovieActor</strong><br>              <br>  setup <strong>do</strong><br>    :ok = <strong>Ecto.Adapters.SQL.Sandbox</strong>.checkout(<strong>DataMunger.Repo</strong>)<br>  <strong>end</strong><br>              <br>  test &quot;adds the actor-movies&quot; <strong>do</strong><br>    <strong>DataMunger.Etl</strong>.load_from_seed([&quot;nm0000204&quot;], <strong>1</strong>)<br>              <br>    ids = [<strong>937006</strong>, <strong>937007</strong>, <strong>937009</strong>, <strong>937008</strong>, <strong>1024551</strong>, <br>           <strong>1024552</strong>, <strong>1024554</strong>, <strong>1024553</strong>, <strong>1029911</strong>, <br>           <strong>1029913</strong>, <strong>1029914</strong>, <strong>1029912</strong>]<br>    title_principals = <strong>Query</strong>.from(<br>                       p <strong>in</strong> <strong>TitlePrincipal</strong>,<br>                       where: p.id <strong>in</strong> ^ids,<br>                       select: {p.nconst, p.tconst})<br>                       |&gt; <strong>ImdbRepo</strong>.all()<br>    movie_actors = <strong>Query</strong>.from(<br>                   ma <strong>in</strong> <strong>MovieActor</strong>, <br>                   select: {ma.nconst, ma.tconst})<br>                   |&gt; <strong>Repo</strong>.all()<br>                  <br>    assert title_principals -- movie_actors == []<br>    assert movie_actors -- title_principals == []<br>    assert <strong>Enum</strong>.count(movie_actors) == <strong>12</strong><br>  <strong>end</strong><br><strong>end</strong></pre><p>So obviously there’s some cheating going on here, I’m counting on the movies being returned from the database to always be returned in order, and I checked before hand to see which ids I would get back from a call to GraphTraverse.movies_and_actors/3. But essentially, I want to know that I&#39;m creating records that match on the relevant transformed fields (nconst and tconst). We put all of this together in data_munger/lib/data_munger/etl.ex:</p><pre><strong>defmodule</strong> <strong>DataMunger.Etl</strong> <strong>do</strong><br>  <strong>alias</strong> <strong>DataMunger.GraphTraverse</strong><br>  <strong>alias</strong> <strong>DataMunger.TitlePrincipal</strong><br>  <strong>alias</strong> <strong>DataMunger.MovieActor</strong><br>  <strong>alias</strong> <strong>DataMunger.Repo</strong><br>  <strong>alias</strong> <strong>DataMunger.ImdbRepo</strong><br>  <strong>import</strong> <strong>Ecto.Query</strong>, only: [from: <strong>2</strong>]<br>              <br>  <strong>def</strong> load_from_seed(seeds, depth) <strong>do</strong><br>    {names, movies} = <strong>GraphTraverse</strong>.movies_and_actors(seeds, [], depth)<br>    principals = <strong>GraphTraverse</strong>.principals_from_movies(movies)<br>    from(p <strong>in</strong> <strong>TitlePrincipal</strong>,<br>    where: p.id <strong>in</strong> ^principals,<br>    select: p)<br>    |&gt; <strong>ImdbRepo</strong>.all()<br>    |&gt; <strong>Enum</strong>.each(<strong>fn</strong>(p) -&gt;<br>         movie_actor_from_title_principal(p)<br>    <strong>end</strong>)<br>  <strong>end</strong><br>              <br>  <strong>defp</strong> movie_actor_from_title_principal(principal) <strong>do</strong><br>    %<strong>MovieActor</strong>{}<br>    |&gt; <strong>MovieActor</strong>.changeset(principal)<br>    |&gt; <strong>Repo</strong>.insert()<br>  <strong>end</strong><br><strong>end</strong></pre><p>Similar strategies will load the names and movies tables. With this code written, we&#39;re ready to actually execute the ETL in development.</p><pre>data_imdb=# <strong>\c</strong> data_munger_dev<br>You are now connected <strong>to</strong> <strong>database</strong> &quot;data_munger_dev&quot; <strong>as</strong><br>data_munger_dev=# <strong>select</strong> count(*) <strong>from</strong> actors;<br> count <br>-------<br>     0<br>(1 row)<br>              <br>data_munger_dev=# select count(*) from movies;<br> count <br>-------<br>     0<br>(1 row)<br>              <br>data_munger_dev=# select count(*) from movie_actors;<br> count <br>-------<br>     0<br>(1 row)</pre><p>So at this point, if you haven’t created the indexes we created earlier and you want to run this step, definitely create the indexes! Otherwise this will absolutely not run in a reasonable period of time:</p><pre>iex(1)&gt; DataMunger.Etl.load_from_seed([&quot;nm0000204&quot;, &quot;nm0000702&quot;], 4)</pre><p>If we check our work in psql:</p><pre>data_munger_dev=# select count(*) from actors;<br> count <br>-------<br>  4787<br>(1 row)<br>              <br>data_munger_dev=# select count(*) from movies;<br> count <br>-------<br>  1966<br>(1 row)<br>              <br>data_munger_dev=# select count(*) from movie_actors;<br> count <br>-------<br>  8137<br>(1 row)</pre><p>Now that we have the data loaded into separate databases and the data set shrunk down to the size we wanted, we can export each database. The &lt;filepath&gt; here has to be an absolute filepath.</p><pre>data_munger_dev=# <strong>copy</strong> actors <strong>to</strong> &lt;filepath&gt;;<br>COPY 4787<br>data_munger_dev=# copy movies to &lt;filepath&gt;;                    <br>COPY 1966<br>data_munger_dev=# copy movie_actors to &lt;filepath&gt;;        <br>COPY 8137</pre><p>And finally, we’re done! Next time, we’ll look at creating our three phoenix services and launching them to heroku. <a href="https://github.com/philosodad/dataday/tree/3.18">Code from this post here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2b870b253fec" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/a-tale-of-graphs-and-darkness-2b870b253fec">A Tale of Graphs and Darkness</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Just the ‘T’ from ‘ETL’]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/moving-data-from-table-to-table-with-ecto-f2d99df30dce?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/f2d99df30dce</guid>
            <category><![CDATA[ecto]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[etl]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Tue, 20 Mar 2018 21:53:16 GMT</pubDate>
            <atom:updated>2018-03-20T21:56:49.272Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-sa/4.0/</cc:license>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>This is part 1 of a 2 part series in an N part series about about microservices. Our overall goal is to create three microservices with related data, host them on Heroku, and then look at strategies for coordinating those services.</p><p>In the <a href="https://medium.com/perplexinomicon-of-philosodad/the-kevin-bacon-problem-3d197983eea1">last post</a>, we downloaded three tab separated files from the <a href="http://www.imdb.com/interfaces/">Internet Movie DataBase</a> and loaded them into a single Postgres Database, in three tables, title_basics, name_basics, and title_principals. We created a Phoenix application called DataMunger with three Schemas, TitlePrincipal, TitleBasic, and NameBasic. We can use these three tables to find information about movies (titles) and actors (names) by way of the title_principals_table. For example, in SQL, we can learn the names of the principal actors in the movie &quot;Thor: The Dark World&quot;</p><pre>data_imdb=# <strong>select</strong> primary_name <strong>from</strong> name_basics<br>data_imdb-# <strong>where</strong> nconst <strong>in</strong>(<br>data_imdb(# <strong>select</strong> p<strong>.</strong>nconst <strong>from</strong> title_principals p,<br>data_imdb(# title_basics t<br>data_imdb(# <strong>where</strong> t<strong>.</strong>primary_title <strong>ilike</strong>(&#39;thor%dark%&#39;)<br>data_imdb(# <strong>and</strong> t<strong>.</strong>title_type=&#39;movie&#39;<br>data_imdb(# <strong>and</strong> p<strong>.</strong>tconst=t<strong>.</strong>tconst<br>data_imdb(# <strong>and</strong> p<strong>.</strong>category <strong>ilike</strong>(&#39;act%&#39;));<br>  primary_name    <br>-------------------<br> Natalie Portman<br> Stellan Skarsgård<br> Tom Hiddleston<br> Chris Hemsworth<br>(4 rows)</pre><p>Or, we can find the titles of movies where Kirsten Dunst plays a principal role with Ecto:</p><pre>iex(<strong>2</strong>)&gt; q = <strong>Ecto.Query</strong>.from(<br>...(<strong>2</strong>)&gt; n <strong>in</strong> <strong>DataMunger.NameBasic</strong>,<br>...(<strong>2</strong>)&gt; where: ilike(n.primary_name, &quot;Kirsten Dunst&quot;),<br>...(<strong>2</strong>)&gt; select: n.nconst)</pre><pre>iex(<strong>4</strong>)&gt; titles_from_nconst = <strong>fn</strong>(n) -&gt;  <br>...(<strong>4</strong>)&gt; <strong>Ecto.Query</strong>.from(               <br>...(<strong>4</strong>)&gt; p <strong>in</strong> <strong>DataMunger.TitlePrincipal</strong>,<br>...(<strong>4</strong>)&gt; where: p.nconst == ^n <strong>and</strong>      <br>...(<strong>4</strong>)&gt; ilike(p.category, &quot;act%&quot;),     <br>...(<strong>4</strong>)&gt; select: p.tconst) |&gt;           <br>...(<strong>4</strong>)&gt; <strong>DataMunger.ImdbRepo</strong>.all() <strong>end</strong> </pre><pre>iex(<strong>8</strong>)&gt; q |&gt; <br>...(<strong>8</strong>)&gt; <strong>DataMunger.ImdbRepo</strong>.one() |&gt;<br>...(<strong>8</strong>)&gt; titles_from_nconst.() |&gt;         <br>...(<strong>8</strong>)&gt; <strong>Enum</strong>.map(<strong>fn</strong>(p) -&gt;                <br>...(<strong>8</strong>)&gt;  <strong>Ecto.Query</strong>.from(                 <br>...(<strong>8</strong>)&gt;  t <strong>in</strong> <strong>DataMunger.TitleBasic</strong>,      <br>...(<strong>8</strong>)&gt;  where: t.tconst == ^p,           <br>...(<strong>8</strong>)&gt;  select: t.primary_title) |&gt;      <br>...(<strong>8</strong>)&gt;  <strong>DataMunger.ImdbRepo</strong>.all() <strong>end</strong>)   <br>[[&quot;The Virgin Suicides&quot;], [&quot;Get Over It&quot;],<br>[&quot;Luckytown&quot;], [&quot;The Devil&#39;s Arithmetic&quot;],<br>[&quot;The Animated Adventures of Tom Sawyer&quot;],<br>[&quot;Woodshock&quot;], [&quot;The Beguiled&quot;], [...], ...]<br>iex(<strong>9</strong>)&gt; v() |&gt; <strong>Enum</strong>.count()<br><strong>71</strong></pre><p>What we’d like to do in this post is to take the data that we have in the three tables of the imdb_data database and prepare to move a subselection of that data into a new database. In a future post we’ll extract the data we want, load it into the new tables, and export it from the new tables.</p><p>In the rest of this post, we’ll be primarily using Ecto to transform data from one table into another table.</p><h3>Create new tables</h3><p>So in our final service, we’re going to use a severely truncated subset of the data. Therefore, the database we have has some fields and some data that we know we aren’t interested in.</p><p>For example, there are a number of title_types in the title_basics table:</p><pre>data_imdb=# <strong>select</strong> <strong>distinct</strong>(title_type) <strong>from </strong>title_basics <strong>limit</strong> <strong>5</strong>;<br> title_type  <br>--------------<br> short<br> tvMiniSeries<br> movie<br> tvEpisode<br> video<br>(5 rows)</pre><p>But we’re ultimately only interested in one title_type, movie. Similarly, in title_principals, there are a number of categories:</p><pre>data_imdb=# <strong>select</strong> <strong>distinct</strong>(category) <strong>from</strong> title_principals <strong>limit</strong> <strong>5</strong>;<br>    category     <br>-----------------<br> writer<br> archive_footage<br> composer<br> archive_sound<br> cinematographer<br>(5 rows)</pre><p>But we’re only interested in actor or actress. So we&#39;ll create three new schemas that reflect our use case:</p><p>Create the migrations:</p><pre>$ mix ecto.gen.migration add_movies_table<br>$ mix ecto.gen.migration add_actors_table<br>$ mix ecto.gen.migration add_movies_actors_table</pre><p>Define the migrations for movies:</p><pre><strong>defmodule</strong> <strong>DataMunger.Repo.Migrations.AddMoviesTable</strong> <strong>do</strong><br>  <strong>use</strong> <strong>Ecto.Migration</strong><br>              <br>  <strong>def</strong> change <strong>do</strong><br>    create table(&quot;movies&quot;) <strong>do</strong><br>      add :title, :string, size: <strong>480</strong><br>      add :tconst, :string, size: <strong>9</strong><br>      add :year, :integer<br>    <strong>end</strong><br>  <strong>end</strong><br><strong>end</strong></pre><p>Actors:</p><pre><strong>defmodule</strong> <strong>DataMunger.Repo.Migrations.AddActorsTable</strong> <strong>do</strong><br>  <strong>use</strong> <strong>Ecto.Migration</strong><br>              <br>  <strong>def</strong> change <strong>do</strong><br>    create table(&quot;actors&quot;) <strong>do</strong><br>      add :name, :string, size: <strong>480</strong><br>      add :nconst, :string, size: <strong>9</strong><br>      add :birth_year, :integer<br>      add :death_year, :integer<br>    <strong>end</strong><br>  <strong>end</strong><br><strong>end</strong></pre><p>MovieActors:</p><pre><strong>defmodule</strong> <strong>DataMunger.Repo.Migrations.AddMoviesActorsTable</strong> <strong>do</strong><br>  <strong>use</strong> <strong>Ecto.Migration</strong><br>              <br>    <strong>def</strong> change <strong>do</strong><br>      create table(&quot;movie_actors&quot;) <strong>do</strong><br>      add :nconst, :string, size: <strong>9</strong><br>      add :tconst, :string, size: <strong>9</strong><br>      add :characters, {:array, :string}<br>    <strong>end</strong><br>  <strong>end<br>end</strong></pre><p>Run the migrations:</p><pre>$ mix ecto.migrate</pre><h4>Transform a single record</h4><p>We’re going to want to transform a lot of different records from one type to another, TitleBasic to Movie, NameBasic to Actor, and TitlePrincipal to MovieActor, which means we&#39;re going to write some custom changesets. We can use tests to help us here, by defining what we want to have happen before we try to make it happen. The first two transformations are fairly straightforward:</p><pre><strong>defmodule</strong> <strong>DataMunger.ActorTest</strong> <strong>do</strong><br>  <strong>use</strong> <strong>ExUnit.Case</strong>, async: <strong>true</strong><br>  <strong>alias</strong> <strong>DataMunger.Actor</strong><br>  <strong>import</strong> <strong>Ecto.Query</strong><br>              <br>  setup <strong>do</strong><br>    :ok = <strong>Ecto.Adapters.SQL.Sandbox</strong>.checkout(<strong>DataMunger.Repo</strong>)<br>  <strong>end</strong><br>              <br>  test &quot;can convert a name_basic record to an actor record&quot; <strong>do</strong><br>    [&quot;Humphrey Bogart&quot;, &quot;Natalie Portman&quot;]<br>    |&gt; <strong>Enum</strong>.each(<strong>fn</strong>(who) -&gt; <br>      [name_basic] = from(n <strong>in</strong> <strong>DataMunger.NameBasic</strong>,<br>                     where: ilike(n.primary_name, ^who),<br>                     select: n) <br>                     |&gt; <strong>DataMunger.ImdbRepo</strong>.all() <br>      change = <strong>Actor</strong>.changeset(%<strong>Actor</strong>{}, name_basic)<br>              <br>      {:ok, actor} = <strong>DataMunger.Repo</strong>.insert(change)<br>      assert actor.name == name_basic.primary_name<br>      assert actor.nconst == name_basic.nconst<br>      assert actor.birth_year == name_basic.birth_year<br>      assert actor.death_year == name_basic.death_year<br>    <strong>end</strong>)<br>  <strong>end</strong><br><strong>end</strong></pre><p>Here, we are looking up 2 actors by name to get their NameBasic structs, creating an Actor changeset for each one, inserting that changeset into the database, and comparing the relevant keys between NameBasic and Actor.</p><p>In data_munger/lib/data_munger/actor.ex:</p><pre><strong>def</strong> changeset(%<strong>Actor</strong>{} = actor, <br>  attrs = %<strong>DataMunger.NameBasic</strong>{}) <strong>do</strong><br>  %{}<br>  |&gt; <strong>Map</strong>.put(:nconst, attrs.nconst)<br>  |&gt; <strong>Map</strong>.put(:name, attrs.primary_name)<br>  |&gt; <strong>Map</strong>.put(:birth_year, attrs.birth_year)<br>  |&gt; <strong>Map</strong>.put(:death_year, attrs.death_year)<br>  |&gt; (&amp;(changeset(actor, <strong>&amp;1</strong>))).()<br><strong>end</strong><br>              <br><strong>def</strong> changeset(%<strong>Actor</strong>{} = actor, attrs) <strong>do</strong><br>  actor<br>  |&gt; cast(attrs, [:nconst, :name, :birth_year, :death_year])<br>  |&gt; validate_required([:nconst, :name])<br><strong>end</strong></pre><p>We can take a similar approach to movies, but the MovieActortransformation is a little different. When we defined this schema, we made the characters field an array type. Most of our records, however, will probably involve actors who only played one character in a particular movie. To check that we’re handling the array properly, we’ll add a constraint on char_length to attempt to get responses in our test for actors who played multiple characters in a movie.</p><pre>test &quot;can convert a name_basic record to an actor record&quot; <strong>do</strong><br>  from(p <strong>in</strong> <strong>DataMunger.TitlePrincipal</strong>, <br>  where: ilike(p.category, &quot;act%&quot;) <strong>and</strong> <br>  fragment(&quot;char_length(?)&quot;, p.characters) &gt; <strong>100</strong>, <br>  select: p, limit: <strong>3</strong>)<br>  |&gt; <strong>DataMunger.ImdbRepo</strong>.all()<br>  |&gt; <strong>Enum</strong>.each(<strong>fn</strong>(principal) -&gt; <br>    change = <strong>MovieActor</strong>.changeset(%<strong>MovieActor</strong>{}, principal)<br>              <br>    {:ok, movie_actor} = <strong>DataMunger.Repo</strong>.insert(change)<br>    assert movie_actor.nconst == principal.nconst<br>    assert movie_actor.tconst == principal.tconst<br>    assert <strong>Enum</strong>.count(movie_actor.characters) &gt; <strong>1</strong><br>  <strong>end</strong>)<br><strong>end</strong></pre><p>But when we run this query, we’re going to see a problem in our results:</p><pre>iex(309)&gt; Ecto.Query.from(p in DataMunger.TitlePrincipal, <br>...(309)&gt; select: p.characters,                          <br>...(309)&gt; where: ilike(p.category, &quot;act%&quot;) and           <br>...(309)&gt; fragment(&quot;char_length(?)&quot;, p.characters) &gt; 100, <br>...(309)&gt; limit: 3) |&gt;                                   <br>...(309)&gt; DataMunger.ImdbRepo.all()                       <br>[&quot;[\&quot;Housekeeper (segment \&quot;The Ghost of Murder Hill\&quot;)\&quot;,<br>\&quot;Susan Castaneda (segment \&quot;The Sailor&#39;s Secret\&quot;)\&quot;]&quot;,<br>.....</pre><p>Specifically, the first result contains nested quotes. Now, we could probably parse the results in such a way as to keep that data, but in the interests of time, we’ll just drop any sort of segment or episode information, by removing anything that is between ( and ).</p><p>In lib/data_munger/movie_actor.ex:</p><pre><strong>def</strong> changeset(%<strong>MovieActor</strong>{} = movie_actor, <br>              %<strong>DataMunger.TitlePrincipal</strong>{} = attrs) <strong>do</strong><br>  attrs = <strong>Map</strong>.from_struct(attrs)<br>  changeset(movie_actor, attrs)<br><strong>end</strong><br>              <br><strong>def</strong> changeset(%<strong>MovieActor</strong>{} = movie_actor, attrs) <strong>do</strong><br>  movie_actor<br>  |&gt; cast(attrs, [:tconst, :nconst])<br>  |&gt; add_category(attrs)<br>  |&gt; validate_required([:nconst, :tconst])<br><strong>end</strong><br>              <br>@doc <strong>false</strong> <br><strong>def</strong> add_category(changeset, attrs) <strong>do</strong><br>  <strong>case</strong> attrs<br>       |&gt; <strong>Map</strong>.get(:characters) <strong>do</strong>              <br>    <strong>nil</strong> -&gt; changeset<br>    character_string -&gt; <br>      characters = character_string<br>                   |&gt; <strong>String</strong>.replace(~r/\ \(.*\)/, &quot;&quot;)<br>                   |&gt; <strong>Poison</strong>.decode!()<br>      changeset <br>      |&gt; <strong>Ecto.Changeset</strong>.put_change(:characters, characters)<br>  <strong>end</strong><br><strong>rescue</strong><br>  _ -&gt; changeset<br>       |&gt; <strong>Ecto.Changeset</strong>.put_change(:characters, [])<br><strong>end</strong></pre><p>Now we can convert one single record of any kind to its corresponding record in our new tables. See the <a href="https://github.com/philosodad/dataday/tree/3.18">github repository</a> for details on how the Movie schema is handled.</p><p>In the next post we’ll look at how to extract, load, and export the data.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f2d99df30dce" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/moving-data-from-table-to-table-with-ecto-f2d99df30dce">Just the ‘T’ from ‘ETL’</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Kevin Bacon Problem]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/the-kevin-bacon-problem-3d197983eea1?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/3d197983eea1</guid>
            <category><![CDATA[phoenix-framework]]></category>
            <category><![CDATA[ecto]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[postgres]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Sat, 24 Feb 2018 14:37:01 GMT</pubDate>
            <atom:updated>2018-02-24T14:37:01.674Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-nc/4.0/</cc:license>
            <content:encoded><![CDATA[<h3>TL/DR</h3><ol><li>Download compressed .tsv files</li><li>Import tsv files into postgres database</li><li>Create phoenix.ecto project</li><li>Connect project to tables from import step</li><li><a href="https://github.com/philosodad/dataday/tree/2.18">Code Here</a></li></ol><h3>Tech Stack</h3><p>Postgres 10.x, Elixir 1.4+, Erlang 19+</p><h3>Motivation</h3><p>This is a post about microservices. Actually, it’s a series of posts about microservices, because if I could put everything into one POST, it wouldn’t be a microservice.</p><p>Where we’re going in the future is to put three microservices up on Heroku, with Postgres databases, each one serving a small subset of the Internet Movie DataBase. Then we’re going to look at the problem of querying data across them, possibly creating a very small <a href="https://oracleofbacon.org/help.php">oracle of bacon</a> along the way.</p><p>The three datasets are title.basics, which contains the names of movies and a unique identifier for each; name.basics, which contains the names of actors and crew and a unique identifier for each, and title.principals, which contains all the unique name/title combinations as well as some other data. Side note: On the imdb.com/datasets website, title.principals is not described this way, and I&#39;ve downloaded that data three times and gotten two different schemas, so YMMV.</p><p>These datasets are big enough that, if we want to do this project on Heroku’s free tier (which I do), we’re going to need to filter out a lot of data. But we want to filter out that data such that we have connections (via the title.principals dataset) between all the actors and movies that we select. Given that we’re taking a dataset with twenty-three million or so rows and breaking it down to a dataset of ten thousand, we could easily end up with disjoint actors and movies.</p><p>We’ll get into that requirement in more detail in a future post, in this post, we’ll set up to filter that data with Elixir. We’ll download the datasets, import them into a postgres database, create a phoenix ecto project, and write the schemas and configuration we need to connect to the existing tables.</p><h3>Let us cut Footloose</h3><h4>Get The Data into Postgres:</h4><p>The first step is to get the data and prepare it for import. This involves using head and tail to split the .tsv file in two:</p><pre>$ wget <a href="https://datasets.imdbws.com/title.basics.tsv.gz">https://datasets.imdbws.com/title.basics.tsv.gz</a><br>$ gunzip title.basics.tsv.gz <br>$ head -n <strong>1</strong> title.basics.tsv &gt; title.basics.headers.tsv<br>$ tail -n +2 title.basics.tsv &gt; trunc.title.basics.tsv<br>$ rm title.basics.tsv</pre><p>Repeat with title.principals.tsv.gz and name.basics.tsv.gz. We should end up with six files, three containing the column headers, and three containing the column data. This will make importing into Postgres easier.</p><p>Now we want to create a postgres database. I created one owned by the postgres user, although I’m not sure that it matters. You have to execute the copy command as a root user, so we end up altering the grants on the tables anyway.</p><pre>$ createdb -O postgres imdb_data<br>$ psql imdb_data</pre><p>Create a table for the title.basics data. We can refer to the title.basics.headers.tsv file we created earlier for the column names.</p><pre>imdb=# <strong>create</strong> <strong>table</strong> title_basics(<br>imdb(# tconst varchar(<strong>9</strong>),<br>imdb(# title_type varchar(<strong>80</strong>),<br>imdb(# primary_title varchar(<strong>512</strong>),<br>imdb(# original_title varchar(<strong>512</strong>),<br>imdb(# is_adult boolean,<br>imdb(# start_year smallint,<br>imdb(# end_year smallint,<br>imdb(# runtime_minutes int,<br>imdb(# genres varchar(<strong>80</strong>)<br>imdb(# )<br><strong>CREATE</strong> TAB,</pre><p>Now that we’ve created a table for the title.basics data, we can import the trunc.title.basics.csv file that we created. Remember, this is the data without the header columns.</p><pre>imdb=# <strong>COPY</strong> title_basics <strong>FROM</strong> &#39;/path/to/trunc.title.basics.tsv&#39;;<br>COPY 4817652</pre><p>Now update the privileges on the title_basics table so that the postgres user can access it.</p><pre>imdb=# <strong>grant</strong> <strong>all</strong> <strong>on</strong> title_basics <strong>to</strong> postgres;<br>GRANT</pre><p>Now we repeat this process for trunc.title.principals.tsv and trunc.name.basics.tsv. When I was doing this, I ran into some errors, either because the ordering of my columns didn’t match the tsv column ordering, or because I was using smallint where I should have used int, or I wasn’t allowing the varchar size to be large enough. Eventually, my final schemas looked like this:</p><pre>data_imdb=# <strong>\d</strong> title_principals<br>                <strong>Table</strong> &quot;public.title_principals&quot;<br>  <strong>Column</strong>    |          <strong>Type</strong>          | Collation | <strong>Nullable </strong>| Defau <br>------------+------------------------+-----------+----------+----<br> tconst     | character <strong>varying</strong>(<strong>9</strong>)   |           |          | <br> ordering   | smallint               |           |          | <br> nconst     | character <strong>varying</strong>(<strong>9</strong>)   |           |          | <br> category   | character <strong>varying</strong>(<strong>80</strong>)  |           |          | <br> job        | character <strong>varying</strong>(<strong>480</strong>) |           |          | <br> characters | character <strong>varying</strong>(<strong>480</strong>) |           |          | <br>              <br>data_imdb=# <strong>\d</strong> name_basics <br>                 <strong>Table</strong> &quot;public.name_basics&quot;<br>       <strong>Column</strong>       |          <strong>Type</strong>          | Collati<br>--------------------+------------------------+--------<br> nconst             | character <strong>varying</strong>(<strong>9</strong>)   |       <br> primary_name       | character <strong>varying</strong>(<strong>240</strong>) |       <br> birth_year         | smallint               |       <br> death_year         | smallint               |       <br> primary_profession | character <strong>varying</strong>(<strong>180</strong>) |       <br> known_for_titles   | character <strong>varying</strong>(<strong>180</strong>) |       <br>              <br>data_imdb=# <strong>\d</strong> title_basics<br>                   <strong>Table</strong> &quot;public.title_basics&quot;<br>     <strong>Column</strong>      |          <strong>Type</strong>          | <strong>Collation</strong> |<br>-----------------+------------------------+-----------+<br> tconst          | character <strong>varying</strong>(<strong>9</strong>)   |           |<br> title_type      | character <strong>varying</strong>(<strong>80</strong>)  |           |<br> primary_title   | character <strong>varying</strong>(<strong>512</strong>) |           |<br> original_title  | character <strong>varying</strong>(<strong>512</strong>) |           |<br> is_adult        | boolean                |           |<br> start_year      | smallint               |           |<br> end_year        | smallint               |           |</pre><p>Make sure you have all your data loaded and your privileges set:</p><pre>data_imdb=# <strong>select</strong> count(*) <strong>from</strong> title_basics;<br>  count  <br>---------<br> 4817652<br>(1 row)<br>              <br>data_imdb=# select count(*) from name_basics;<br>  count  <br>---------<br> 8435455<br>(1 row)<br>              <br>data_imdb=# select count(*) from title_principals;<br>  count   <br>----------<br> 27109331<br>(1 row)<br>              <br>data_imdb=# \dp<br>                 Access privile<br> Schema |       Name       |   Type   |   Access privil<br>--------+-------------------------+----------+---------<br> public | name_basics      | table    | paul=arwdDxt/pa<br>        |                  |          | postgres=arwdDx<br> public | title_basics     | table    | paul=arwdDxt/pa<br>        |                  |          | postgres=arwdDx<br> public | title_principals | table    | paul=arwdDxt/pa<br>        |                  |          | postgres=arwdDx<br>              (3 rows)</pre><p>If that looks more or less like what you’re looking at, you’re ready to move on.</p><h4>Connect the database to a Phoenix project</h4><p>We’re going to use Ecto for the next part, and for convenience, we’re going to create an Phoenix ecto project. We’ll probably put the microservices we’ll be building in a later post into this same umbrella project.</p><pre>$ mix new --umbrella dataday<br>$ cd dataday/apps/<br>$ mix phx.new.ecto data_munger</pre><p>This project assumes that there is a data_munger database, which we will create next time. The config for that database is already set up. To connect to our imdb_data database, we need to add configuration to /data_munger/config/dev.exs.</p><pre>config :name_basics, <strong>DataMunger.ImdbRepo</strong>,<br>  adapter: <strong>Ecto.Adapters.Postgres</strong>,<br>  username: &quot;postgres&quot;,<br>  password: &quot;postgres&quot;,<br>  database: &quot;imdb_data&quot;,<br>  hostname: &quot;localhost&quot;,<br>  pool_size: <strong>10</strong></pre><p>We also need to add the new DataMunger.ImdbRepo to our supervision tree. In data_munger/lib/data_munger/application.ex</p><pre><strong>def</strong> start(_type, _args) <strong>do</strong><br>  <strong>import</strong> <strong>Supervisor.Spec</strong>, warn: <strong>false</strong><br>              <br>  <strong>Supervisor</strong>.start_link([<br>    supervisor(<strong>DataMunger.Repo</strong>, []),<br>    supervisor(<strong>DataMunger.ImdbRepo</strong>, []),<br>  ], strategy: :one_for_one, name: <strong>DataMunger.Supervisor</strong>)<br><strong>end</strong></pre><p>Now if we write a schema for one of the tables, we should be able to access it. Create a file data_munger/lib/data_munger/name_basic.ex:</p><pre><strong>defmodule</strong> <strong>DataMunger.NameBasic</strong> <strong>do</strong><br>  <strong>use</strong> <strong>Ecto.Schema</strong><br>  <strong>import</strong> <strong>Ecto.Changeset</strong><br>  <strong>alias</strong> <strong>DataMunger.NameBasic</strong>           <br>              <br>  @primary_key{:nconst, :string, []}<br>  schema &quot;name_basics&quot; <strong>do</strong><br>    field :birth_year, :integer<br>    field :death_year, :integer<br>    field :primary_name, :string <br>      <br>  <strong>end</strong><br>              <br>  @doc <strong>false</strong><br>  <strong>def</strong> changeset(%<strong>NameBasic</strong>{} = name, attrs) <strong>do</strong><br>    name<br>    |&gt; cast(attrs, [:nconst, <br>                    :primary_name,<br>                    :birth_year,<br>                    :death_year])<br>    |&gt; validate_required([:nconst, :primary_name])<br>  <strong>end</strong><br><strong>end</strong></pre><p>You might notice the line @primary_key{:nconst, :string, []} above the schema definition. When we created this table, we didn&#39;t give it an autoincrement id, but there is a good natural key we can use in the dataset, the nconst value, so we can instruct Ecto that we&#39;ll use that as the primary key.</p><p>Let’s test our new schema in iex:</p><pre>iex(<strong>1</strong>)&gt; <strong>require</strong> <strong>Ecto.Query</strong><br><strong>Ecto.Query</strong><br>iex(<strong>2</strong>)&gt; <strong>Ecto.Query</strong>.from(                                 <br>...(<strong>2</strong>)&gt; n <strong>in</strong> <strong>DataMunger.NameBasic</strong>,                      <br>...(<strong>2</strong>)&gt; where: ilike(n.primary_name, &quot;Natalie Portman&quot;),<br>...(<strong>2</strong>)&gt; select: n) |&gt;<br>...(<strong>2</strong>)&gt; <strong>DataMunger.ImdbRepo</strong>.all()<br>              <br><strong>00</strong>:<strong>37</strong>:<strong>47.847</strong> [debug] <strong>QUERY</strong> <strong>OK</strong> source=&quot;name_basics&quot; db=<strong>1</strong><br><strong>722.7</strong>ms<br><strong>SELECT</strong> n0.&quot;nconst&quot;, n0.&quot;birth_year&quot;, n0.&quot;death_year&quot;, n<br><strong>0</strong>.&quot;primary_name&quot; <strong>FROM</strong> &quot;name_basics&quot; <strong>AS</strong> n0 <strong>WHERE</strong> (n0.&quot;pr<br>imary_name&quot; <strong>ILIKE</strong> &#39;Natalie Portman&#39;) []<br>%<strong>DataMunger.NameBasic</strong>{__meta__: #Ecto.Schema.Metadata <br>&lt;:loaded, &quot;name_basics&quot;&gt;,<br>birth_year: <strong>1981</strong>, death_year: <strong>nil</strong>, nconst: &quot;nm0000204&quot;<br>, primary_name: &quot;Natalie Portman&quot;}</pre><p>Fun fact, Natalie Portman’s <a href="https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93Bacon_number">Bacon/Erdős</a> number is lower than Kevin Bacon’s, but not lower than Paul Erdős’s.</p><p>We can repeat this with the other two tables. One thing is, we don’t have a natural key for the title_principals table, so we should go ahead and add an automatic autoincrement column to that table, if we don&#39;t, Postgrex will not be happy and we won&#39;t be able to search the table.</p><pre>data_imdb=# <strong>alter</strong> <strong>table</strong> title_principals<br>data_imdb-# <strong>add</strong> <strong>column</strong> id<br>data_imdb-# serial <strong>primary</strong> <strong>key</strong>;<br>ALTER TABLE</pre><p>Once we’ve added an id column to the table, we can add the schema file /data_munger/lib/data_munger/title_principal.ex with the contents:</p><pre><strong>defmodule</strong> <strong>DataMunger.TitlePrincipal</strong> <strong>do</strong><br>  <strong>use</strong> <strong>Ecto.Schema</strong><br>  <strong>import</strong> <strong>Ecto.Changeset</strong><br>  <strong>alias</strong> <strong>DataMunger.TitlePrincipal</strong><br>              <br>  schema &quot;title_principals&quot; <strong>do</strong><br>    field :nconst, :string<br>    field :tconst, :string<br>    field :category, :string<br>    field :characters, :string<br>    field :ordering, :integer</pre><pre>  <strong>end</strong><br>              <br>  @doc <strong>false</strong><br>  <strong>def</strong> changeset(%<strong>TitlePrincipal</strong>{} = title, attrs) <strong>do</strong><br>    title<br>    |&gt; cast(attrs, [:nconst,<br>                    :category,<br>                    :tconst,<br>                    :characters,<br>                    :ordering])<br>    |&gt; validate_required([:nconst, :tconst])<br>  <strong>end</strong><br><strong>end</strong></pre><p>And test our new schema in IEx:</p><pre>iex(6)&gt; Ecto.Query.from(                <br>...(6)&gt; n in DataMunger.TitlePrincipal, <br>...(6)&gt; where: n.nconst == &quot;nm0000204&quot;,<br>...(6)&gt; select: n,<br>...(6)&gt; limit: 1) |&gt;<br>...(6)&gt; DataMunger.ImdbRepo.all()<br>              <br>01:22:47.033 [debug] QUERY OK source=&quot;title_princip....<br>SELECT t0.&quot;id&quot;, t0.&quot;nconst&quot;, t0.&quot;tconst&quot;, t0.&quot;categ....<br>[%DataMunger.TitlePrincipal{__meta__: #Ecto.Schema.....<br>category: &quot;actress&quot;, characters: &quot;[\&quot;Nina\&quot;]&quot;,<br>id: 1437846, nconst: &quot;nm0000204&quot;, ordering: 9,<br>tconst: &quot;tt0185275&quot;}]</pre><p>I’ve truncated some of the output, but it should look something like that.</p><p>Finally, we add the title_basics schema (/data_munger/lib/data_munger/title_basic.ex), this time using the tconst field as a natural key:</p><pre><strong>defmodule</strong> <strong>DataMunger.TitleBasic</strong> <strong>do</strong><br>  <strong>use</strong> <strong>Ecto.Schema</strong><br>  <strong>import</strong> <strong>Ecto.Changeset</strong><br>  <strong>alias</strong> <strong>DataMunger.TitleBasic</strong><br>              <br>              <br>  @primary_key{:tconst, :string, []}<br>  schema &quot;title_basics&quot; <strong>do</strong><br>    field :primary_title, :string<br>    field :original_title, :string<br>    field :start_year, :integer<br>    field :end_year, :integer           </pre><pre>  <strong>end</strong><br>              <br>  @doc <strong>false</strong><br>  <strong>def</strong> changeset(%<strong>TitleBasic</strong>{} = name, attrs) <strong>do</strong><br>    name<br>    |&gt; cast(attrs, [:nconst,<br>                    :primary_name,<br>                    :birth_year,<br>                    :death_year])<br>    |&gt; validate_required([:nconst, :primary_name])<br>  <strong>end</strong><br><strong>end</strong></pre><p>And verify that we have access:</p><pre>iex(9)&gt; Ecto.Query.from(               <br>...(9)&gt; t in DataMunger.TitleBasic,    <br>...(9)&gt; where: t.tconst == &quot;tt0185275&quot;,<br>...(9)&gt; select: t) |&gt;                  <br>...(9)&gt; DataMunger.ImdbRepo.all()      <br>              <br>03:28:42.722 [debug] QUERY OK source=&quot;title_basic.....<br>SELECT t0.&quot;tconst&quot;, t0.&quot;primary_title&quot;, t0.&quot;origi.....<br>[%DataMunger.TitleBasic{__meta__: #Ecto.Schema.Me.....<br>end_year: nil, original_title: &quot;Developing&quot;,<br>primary_title: &quot;Developing&quot;, start_year: 1994,<br>tconst: &quot;tt0185275&quot;}]</pre><p>Next, we’re going to work on how to start filtering through this data. The code up to this point is <a href="https://github.com/philosodad/dataday/tree/2.18">available here</a>, and my first pass at solving all these problems is <a href="https://github.com/philosodad/tiny_bacon_oracle">available here</a> if you want to read ahead.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3d197983eea1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/the-kevin-bacon-problem-3d197983eea1">The Kevin Bacon Problem</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[It’s basically “Kung Fu Panda”.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-snippet">First off, if you have not yet seen The Last Jedi and you don&#x2019;t want to know anything about it, stop reading.</p><p class="medium-feed-link"><a href="https://medium.com/@philosodad/its-basically-kung-fu-panda-495193f35872?source=rss-189cad5ec980------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@philosodad/its-basically-kung-fu-panda-495193f35872?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/495193f35872</guid>
            <category><![CDATA[social-justice]]></category>
            <category><![CDATA[the-last-jedi]]></category>
            <category><![CDATA[movies]]></category>
            <category><![CDATA[star-wars]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Thu, 28 Dec 2017 15:00:57 GMT</pubDate>
            <atom:updated>2017-12-28T15:00:57.936Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-sa/4.0/</cc:license>
        </item>
        <item>
            <title><![CDATA[Mock Responses with Stash]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/mock-responses-with-stash-9f1d7f01a844?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/9f1d7f01a844</guid>
            <category><![CDATA[mock]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[test-driven-development]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Tue, 10 Oct 2017 12:34:47 GMT</pubDate>
            <atom:updated>2017-10-10T12:34:47.471Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-sa/4.0/</cc:license>
            <content:encoded><![CDATA[<p>So a few posts ago, we looked at how to <a href="http://www.johnpdaigle.com/complexable/2016/10/23/mock-modules-and-where-to-find-them.html">abstract dependent modules</a> in Elixir to make testing easier. That post and its <a href="http://www.johnpdaigle.com/complexable/2016/11/09/real-responses-from-mock-modules.html">follow up</a> attempt to implement a pattern suggested by <a href="http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/">Jose Valim</a>, which he called <em>explicit contract</em>. This pattern has the following characteristics:</p><ol><li>An interface module is created to wrap around the dependency.</li><li>A test version of the interface module is created.</li><li>Test responses are written in the test interface</li></ol><p>Our implementation of this in the <a href="https://github.com/philosodad/near_earth/tree/mock404">near earth</a> wraps the HTTPoison library. There are some issues with this, but before we get to those I want to look at how we’re building and retrieving mock responses. Right now those responses are built into the test module.</p><p>This post is concerned with moving our test responses out of the test interface module and into the tests themselves. This is useful because we don’t have to move from file to file while we’re working on the tests. It should simplify our test code somewhat.</p><p>To do this, we’re going to use a library called <a href="https://hexdocs.pm/stash/api-reference.html">Stash</a>, which is a wrapper around ETS, Elixir’s build in key-value store. Adding Stash is simple, of course we add stash to our mix.exs file:</p><pre><strong>defp</strong> deps <strong>do</strong><br>  [<br>    {:env_helper, &quot;~&gt; 0.0.4&quot;},<br>    {:httpoison, &quot;~&gt; 0.10.0&quot;},<br>    {:poison, &quot;~&gt; 3.0&quot;},<br>    {:stash, &quot;~&gt; 1.0&quot;}<br>  ]<br><strong>end</strong></pre><p>And then change the implementation of test/support/http.ex. Previously, we only had a mock response for the 404 case:</p><pre><strong>defmodule</strong> <strong>NearEarth.TestHttp</strong> <strong>do</strong><br>  <strong>use</strong> <strong>HTTPoison.Base</strong><br>              <br>  <strong>def</strong> get(&quot;https://api.nasa....?api_key=DEMO_KEY&quot;) <strong>do</strong><br>    {:ok, %<strong>HTTPoison.Response</strong>{headers: [], status_code: <strong>404</strong>}}<br>  <strong>end</strong><br>              <br> <strong>def</strong> get(args) <strong>do</strong><br>   <strong>HTTPoison</strong>.get(args)<br> <strong>end</strong><br><strong>end</strong></pre><p>But now we’re going to redefine the get method to use stash:</p><pre><strong>def</strong> get(route) <strong>do</strong><br>  <strong>Stash</strong>.get(:near_earth, route)<br><strong>end</strong></pre><p>So whatever value is stored in the ETS table :near_earth for the key route, that&#39;s what the get method will return.</p><p>Obviously, to get our current tests working we’re going to need to do some work. Right now, we have two tests. The first tests our response to a 404, the second actually makes a real call out to the near earth api to get an asteroid and then tests the parsing of that JSON file. To get the 404 test working, we add a response to the setup:</p><pre>setup <strong>do</strong><br>  not_found = %<strong>HTTPoison.Response</strong>{status_code: <strong>404</strong>}<br>  {:ok, %{not_found: not_found}}<br><strong>end</strong></pre><p>And then add that response to the :near_earth stash table in the test:</p><pre>test &quot;will return not found if 404&quot;, context <strong>do</strong><br>  <strong>Stash</strong>.set(:near_earth, &quot;#{<strong>Sets</strong>.neo_url}fakevalue?api_key=#{<strong>Sets</strong>.api_key}&quot;,<br>           {:ok, context.not_found})<br>  assert {:error, :not_found} == <strong>NearEarth</strong>.get_asteroid(&quot;fakevalue&quot;)<br><strong>end</strong></pre><p>So we’ve set the value of &quot;#{Sets.neo_url}fakevalue?api_key=#{Sets.api_key}&quot; to be an HTTPoison response with a status of 404. The Sets module is where I&#39;m <a href="https://medium.com/perplexinomicon-of-philosodad/using-macros-to-handle-environment-variables-in-elixir-87bc81ea83dd">defining helpers</a> to application and environment variables using the <a href="https://hex.pm/packages/env_helper">env_helper</a> library.</p><p>The get_asteroid call uses the HTTP module:</p><pre><strong>def</strong> get_asteroid asteroid <strong>do</strong><br>  url = &quot;#{<strong>Sets</strong>.neo_url}#{asteroid}?api_key=#{<strong>Sets</strong>.api_key}&quot;<br>  <strong>case</strong> <strong>Sets</strong>.http.get(url) <strong>do</strong><br>    {:ok, response = %{status_code: <strong>200</strong>}} -&gt; <br>      <strong>Poison</strong>.decode(response.body)<br>    {:ok, %{status_code: <strong>404</strong>}} -&gt;<br>      {:error, :not_found}<br>  <strong>end</strong><br><strong>end</strong></pre><p>And because we’re using the test http module in our test environment, we get back the fake 404 response and the test passes.</p><p>A similar strategy will work for getting the asteroid, although in that case we will need a fake asteroid JSON response to work with. My solution to this, for the moment, was to add that as a json file in the test/support folder and then add a module stubs:</p><pre><strong>defmodule</strong> <strong>NearEarthTest.Stubs</strong> <strong>do</strong><br>  <strong>def</strong> asteroid <strong>do</strong><br>    {:ok, asteroid} = <strong>File</strong>.read(&quot;test/support/asteroid.json&quot;)<br>    asteroid<br>  <strong>end</strong><br><strong>end</strong></pre><p>Add the stub to the test setup:</p><pre>asteroid_response = %<strong>HTTPoison.Response</strong>{status_code: <strong>200</strong>, <br>                                        body: <strong>Stubs</strong>.asteroid}<br>              <br>{:ok, %{asteroid: asteroid_response,<br>        not_found: not_found}}</pre><p>And then put that in the test:</p><pre>test &quot;retrieves asteroid by designation&quot;, context <strong>do</strong><br>  id = &quot;3542519&quot;<br>  <strong>Stash</strong>.set(:near_earth, &quot;#{<strong>Sets</strong>.neo_url}#{id}?api_key=#{<strong>Sets</strong>.api_key}&quot;,<br>           {:ok, context.asteroid})<br>  {:ok, asteroid_response} = <strong>NearEarth</strong>.get_asteroid(id)<br>  assert asteroid_response[&quot;name&quot;] == &quot;465633 (2009 JR5)&quot;<br><strong>end</strong></pre><p>And again, we should have a passing test.</p><p>One obvious problem with this implementation is that I’m making everything very tightly coupled to the HTTPoison library. In a future post, we’ll change the http module so that this coupling disappears, but for now we&#39;ll live with it.</p><p>We’ve refactored, and now we want to add an endpoint. In particular, I’d like to get all the asteroids that will make their closest approach today. Fortunately, NASA has a useful /feedendpoint that takes a start and end date as arguments. To write the failing test, we&#39;ll add another library to our codebase. <a href="https://hex.pm/packages/faker">Faker</a> is a good library for generating fake data of various kinds, such as dates, addresses, and of course styles of beer. We use Faker at work mostly for dates, addresses and to generate fake car make and model names. Here, we&#39;re interested in the date functionality.</p><p>We’ll also want to add another stub, this one with a real JSON response from the feed api. We’ll again use the Stubsmodule to import the file, and add the stub to the test setup:</p><pre>asteroids_response = %<strong>HTTPoison.Response</strong>{status_code: <strong>200</strong>, <br>                                         body: <strong>Stubs</strong>.asteroids}<br>{:ok, %{asteroids: asteroids_response,<br>        asteroid: asteroid_response,<br>        not_found: not_found}}</pre><p>And then test against the stub by adding the fake response to stash:</p><pre>test &quot;retrieves today&#39;s asteroids&quot;, context <strong>do</strong><br>  today = <strong>Date</strong>.utc_today<br>          |&gt; <strong>Date</strong>.to_string<br>  tomorrow = <strong>Faker.Date</strong>.forward(<strong>1</strong>)<br>             |&gt; <strong>Date</strong>.to_string<br>  <strong>Stash</strong>.set(:near_earth, <br>            &quot;#{<strong>Sets</strong>.neo_url}feed?start_date=#{today}&amp;end_date=#{tomorrow}&amp;api_key=#{<strong>Sets</strong>.api_key}&quot;, <br>            {:ok, context.asteroids})<br>  {:ok, asteroids} = <strong>NearEarth</strong>.get_today()<br>  assert asteroids == context.asteroids.body <br>                      |&gt; <strong>Poison</strong>.decode!<br><strong>end</strong></pre><p>Since we haven’t implemented get_today at all, it&#39;s pretty clear we&#39;re going to get a failing test here. So, we&#39;ll get it passing:</p><pre><strong>def</strong> get_today <strong>do</strong><br>  today = <strong>Date</strong>.utc_today()<br>          |&gt; <strong>Date</strong>.to_string()<br>  tomorrow = :calendar.universal_time<br>             |&gt; :calendar.datetime_to_gregorian_seconds<br>             |&gt; (&amp;(<strong>&amp;1</strong> + <strong>86400</strong>)).()<br>             |&gt; :calendar.gregorian_seconds_to_datetime<br>             |&gt; elem(<strong>0</strong>)<br>             |&gt; <strong>Date</strong>.from_erl<br>             |&gt; elem(<strong>1</strong>)<br>             |&gt; <strong>Date</strong>.to_string <br> <strong>case</strong> <strong>Sets</strong>.http.get(&quot;#{<strong>Sets</strong>.neo_url}feed?start_date=#{today}&amp;end_date=#{tomorrow}&amp;api_key=#{<strong>Sets</strong>.api_key}&quot;) <strong>do</strong><br>   {:ok, response = %{status_code: <strong>200</strong>}} -&gt;  <br>      <strong>Poison</strong>.decode(response.body)<br> <strong>end</strong></pre><p>Granted, that could be the most convoluted method ever of getting a date string for tomorrow, but I think that this one will at least work. As always, <a href="https://github.com/philosodad/near_earth/tree/mock-with-stash">a working version of this code is available on github</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9f1d7f01a844" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/mock-responses-with-stash-9f1d7f01a844">Mock Responses with Stash</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elixir for the Little Things]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/elixir-for-the-little-things-6b2d761417b0?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/6b2d761417b0</guid>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[utility-scripting]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Mon, 07 Aug 2017 13:01:01 GMT</pubDate>
            <atom:updated>2017-08-15T02:38:35.198Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-sa/4.0/</cc:license>
            <content:encoded><![CDATA[<p>A lot of blog posts, videos, books, and tutorials about programming Elixir tend to focus on the big things: web apps, web apps with Plug, web apps with Phoenix, web apps that need to handle 80 million concurrent requests, web apps that need to be web scale, big things. Big things are important.</p><p>But we don’t exclusively write big things, sometimes we write smaller things. Sometimes, you’re at work, and you’re faced with a problem like “convert these 20 basically identical yaml files into 20 basically identical xml files”. The question is, what tool do you reach for when that happens?</p><p>Because even if you’re working in a PhP shop out in Bakersfield, you can use Elixir for scripting stuff. And it can make your life pretty good if you do. The <a href="https://github.com/atlantaelixir/sendalot">Sendalot</a> tool I’ve talked about before is an example of this, a little script that I can open up, mess around with, and start sending hundreds of thousands of simultaneous requests from my laptop to a server. Is it pretty? No. But it gets that specific job done and we break it out from time to time.</p><p>A week or so ago, I was faced with a little scripting job where we had to take about 20 basically identical YAML files, and convert them to 20 basically identical XML files. My pair that day and I are both big fans of Elixir, so naturally we solved this with a little Elixir script.</p><p>So the first thing we needed was a library that could turn Elixir data structures into XML, or YAML into Elixir data structures, or possibly both. A quick check on Hex led to the <a href="https://hex.pm/packages/yaml_elixir">yaml_elixir</a> library, which is pretty effective at reading YAML files and turning them into Elixir maps.</p><pre><strong>def</strong> file_to_map(path) <strong>do</strong><br>  <strong>File</strong>.cwd!<br>  |&gt; <strong>Path</strong>.join(path)<br>  |&gt; <strong>YamlElixir</strong>.read_from_file<br><strong>end</strong></pre><p>This takes a relative path (like “assets/test.yml”) to a .yml file and returns a map, when run from the top level of the project. So that’s the first half of the problem solved.</p><p>Unfortunately, writing the XML was not going to be quite so simple, because we needed to join multiple YAML files into a single XML file, and because we only need a part of each, and the nesting of the eventual XML file is going to be slightly different than the nesting of the YAML file.</p><p>All of which leads to this code:</p><pre><strong>def</strong> get_env_entries(map) <strong>do</strong><br>  get_env_entries({}, <br>  <strong>Map</strong>.get(map, &quot;env_entries&quot;))<br><strong>end</strong><br>              <br><strong>defp</strong> get_env_entries(acc, env_entries) <strong>do</strong><br>  with <strong>false</strong> &lt;- <strong>Enum</strong>.empty?(<strong>Map</strong>.keys(env_entries)),<br>       entry_name &lt;- env_entries<br>                     |&gt; <strong>Map</strong>.keys()<br>                     |&gt; <strong>List</strong>.first(),<br>       entry_map &lt;- create_entry(env_entries, entry_name) <strong>do</strong><br>    acc<br>    |&gt; <strong>Tuple</strong>.append(entry_map)<br>    |&gt; get_env_entries(<strong>Map</strong>.delete(env_entries, entry_name))<br>  <strong>else</strong><br>    _ -&gt; acc<br>         |&gt; <strong>Tuple</strong>.to_list<br>  <strong>end</strong><br><strong>end</strong></pre><p>Where we are using the with statement in a recursive method, with the exit condition being the terminal condition of the recursion. To be honest, I didn&#39;t actually see this as a possibility until my pair pointed it out, but it makes perfect sense, the with/do/else control structure is just that, a control structure, so clearly we can use it as one.</p><p>In this case, get_env_entries/1 acts as the entry point to the recursion, taking as its argument the map produced from the original yaml file. It passes on an empty tuple (the initial accumulator), and the sub map under the &quot;env_entries&quot; key.</p><p>This sub map is actually composed of several sub maps, and the goal of the code is to take each key and generate a flattened and transformed version of that nested map. That is what the create_entry/2 method will do, but that code isn&#39;t terribly important. It takes a map and a key and returns a map. So, we get the first key from the &quot;env_entries&quot; sub map, and send that key and the “env_entries” map to the create_entry/2transformer, which returns entry_map. entry_map gets appended onto the accumulator, and then we make the recursive call back to get_env_entries/2, calling Map.delete/2 to remove the key we just transformed. When we run out of keys, the Enum.empty?/1 call that we make at the start of the with statement returns true, and we convert the accumulator from a tuple of maps into a list of maps and return it.</p><p>When I write it up like that, it feels sort of complicated. But the funny thing is this was developed mostly using iex, so once we had the recursive call with the delete, the empty check, and the return taken care of we were able to sort of REPL-drive the rest very quickly and easily, just filling in transforms in the middle until the correct accumulator was returned. It was fast, and it was extremely fun.</p><p>In any case, there was obviously more to be done once we had these property lists (remember, we had two files, so there was another get_entries type of method) we had to turn them into XML. The tool we used for this was <a href="https://hex.pm/packages/xml_builder">xml_builder</a>.</p><pre><strong>defp</strong> generate_env_elements(env) <strong>do</strong><br>  env<br>  |&gt; <strong>Enum</strong>.reduce({}, <br>                 <strong>fn</strong>(lst, acc) -&gt; <strong>XmlBuilder</strong>.element(:Env,lst) <br>                                 |&gt; (&amp;(<strong>Tuple</strong>.append(acc, <strong>&amp;1</strong>))).()<br>                 <strong>end</strong>)<br>  |&gt; <strong>Tuple</strong>.to_list<br><strong>end</strong></pre><p>This code is more my style, for better or worse. I’m a fan of the reduce/3 function and of anonymous functions. I don&#39;t know if this is more readable than a recursive approach to this same problem, but in any case, we take the list we built in the last stage, and for each entry in the list we create an XML element tuple. The reduce returns a tuple of tuples which we convert back into a list, because for whatever reason, it seems like the XmlBuilder API takes in lists and returns tuples, except when it takes tuples and returns an XML string.</p><p>So, I think that in the first method we could have used Enum.reduce/3, but I&#39;m not 100% positive that the internal function would have been readable, and we might have ended up with a nested with statement anyway. The second method was much more straightforward and the with statement would have been overkill. But in any case, a few hours of hacking along, 100 lines of code or so later, and we had that yak shaved and the YAML converted to XML. It definitely wasn&#39;t harder than it would have been in any other language, and I think that the control structures and functional nature of Elixir made it an excellent choice for this problem… after all, we were only transforming some data from one form into another, and that&#39;s an area where Elixir really shines.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6b2d761417b0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/elixir-for-the-little-things-6b2d761417b0">Elixir for the Little Things</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[In Defense (sort of) of Side Projects]]></title>
            <link>https://medium.com/perplexinomicon-of-philosodad/in-defense-sort-of-of-side-projects-d25f705804fa?source=rss-189cad5ec980------2</link>
            <guid isPermaLink="false">https://medium.com/p/d25f705804fa</guid>
            <category><![CDATA[entrepreneurship]]></category>
            <category><![CDATA[life-lessons]]></category>
            <dc:creator><![CDATA[J Paul Daigle]]></dc:creator>
            <pubDate>Mon, 30 Jan 2017 22:34:04 GMT</pubDate>
            <atom:updated>2017-01-30T22:34:04.665Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>About two months ago, I got into a minor twitter war with a coding celebrity of some stature over the nature and neccessity of side projects. At issue was the question of whether developers should have some sort of “Side Projects” that they work on, some extra-curricular activity associated with programming that they do in their free time. My answer to this was an emphatic yes, the internet weighed in on both sides, and the whole thing was a useless distraction.</p><p>But in the process, I was forced to ask myself what good all my side work, the blogging, the meetups, the presentations, etc, were doing. Was this time well spent? I could be spending more time with my family. I could be writing a novel. I could be exercising more, or learning to cook French cuisine, or getting more sleep.</p><p>So I stopped. Not on purpose, but out of a just general sense of ennui. I lost my motivation to side job. And for two months, I haven’t written a blog post or worked on my coding chops or generally done anything like that.</p><p>And you know what? I didn’t spend more time with my family. I didn’t work on my novel. I didn’t exercise more, I didn’t learn French cooking, I didn’t get more sleep. I did watch more TV. I did read a lot of fiction. I did gain ten pounds. I’m not happier, and I’m probably actually less useful at work.</p><p>I don’t think that this is a general lesson for everyone. It just is for me, which is this: I like puzzles. I like typing. I like writing code, and I like sharing it with other people. There’s a certain kind of side project that appeals to someone like me, and if I’m not working on that I stop working on other stuff too. For me, personally, not working on coding projects means that parts of my creative brain aren’t running, and I’m not working on anything else, either.</p><p>Of course, I’m speaking of myself here, and not in the general sense. In the general sense, the question is “Should coders, in general, have some sort of side projects going on?” And you know what? I’ll say no. It’s fine if you aren’t involved in the community, or don’t have anything to show on GitHub, or aren’t going to meetups or presenting at conferences or whatever. Especially if you have a lot of experience, and there are plenty of people to recommend you for the job I’m interviewing you for.</p><p>But if you don’t have that experience, ask yourself what seperates you from the person I’m interviewing next. If it is that they met me at a meetup and impressed me into going to my boss and saying “HIRE THIS PERSON”, than that might not be great for you. If it’s that the person I’m interviewing next ran a startup in college and can show me how they learned from that experience, and you didn’t, that might not be great for you. If it’s that the person I’m interviewing next got some documentation pull requests into a project I’m familiar with and you didn’t, that might not be great for you.</p><p>After you’ve been working for a couple of years, this stuff is less important. Because honestly, the industry is savagely hungry for talent and once you’ve shown you can do the job, you’ll probably get another job. But hiring is hard, and expensive, and people are pretty reticent about hiring unknown quantities.</p><p>I think that this is the core of this whole debate. If you have been working as a coder for a decade, you don’t need my advice. You’ve got this. But if you’re still trying to move into or from your first junior job, it can’t hurt to put in some extra effort.</p><p>As for me, I’m just happier when I’m working on my own stuff in my spare time. It’s become a habit, and it’s not a bad habit to have.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d25f705804fa" width="1" height="1" alt=""><hr><p><a href="https://medium.com/perplexinomicon-of-philosodad/in-defense-sort-of-of-side-projects-d25f705804fa">In Defense (sort of) of Side Projects</a> was originally published in <a href="https://medium.com/perplexinomicon-of-philosodad">Perplexinomicon</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>