Open SourceryPerforming regular wizardry through open-source softwarehttps://opensourcery.blog2018-04-22T15:00:00-05:00Kenneth KalmerPassing around components with reagent and Semantic UIhttps://opensourcery.blog/2018/04/22/passing-around-components-with-reagent-and-semantic-ui/2018-04-22T15:00:00-05:002023-02-25T12:01:35-06:00<p>I've been happily using Semantic UI React since I <a href="/2017/02/12/using-semantic-ui-react-with-re-frame/">first wrote about it</a> more than a year ago. Everything in the previous post still holds true, the only thing that has changed is the version number of the semantic-ui-react package.</p>
<p>The CLJSJS community has been great at keeping things up to date, and I can't recall any breaking changes in the components that I've been using.</p>
<p>One thing I unknowingly skipped in the previous article was passing around React components as arguments for other components. I don't think I realized at the time it was possible. I was learning the minimum viable React through re-frame, which got me very far (and continues to do).</p>
<p>This oversight was noticed by others though. A few people reached out for advice in private, and on <a href="https://stackoverflow.com/questions/49327122/clojurescript-semantic-ui-react-search-custom-renderer/49327457#49327457">StackOverflow</a>. Where I fell short other community members helped out in the Clojurians Slack.</p>
<h2>The Problem</h2>
<p>Looking at the <a href="https://react.semantic-ui.com/modules/tab#tab-example-basic">tabs example</a> in the Semantic UI React docs, you encounter this:</p>
<div class="highlight"><pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Tab</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">semantic-ui-react</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">panes</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">menuItem</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Tab 1</span><span class="dl">'</span><span class="p">,</span> <span class="na">render</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p"><</span><span class="nc">Tab</span><span class="p">.</span><span class="nc">Pane</span><span class="p">></span>Tab 1 Content<span class="p"></</span><span class="nc">Tab</span><span class="p">.</span><span class="nc">Pane</span><span class="p">></span> <span class="p">},</span>
<span class="p">{</span> <span class="na">menuItem</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Tab 2</span><span class="dl">'</span><span class="p">,</span> <span class="na">render</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p"><</span><span class="nc">Tab</span><span class="p">.</span><span class="nc">Pane</span><span class="p">></span>Tab 2 Content<span class="p"></</span><span class="nc">Tab</span><span class="p">.</span><span class="nc">Pane</span><span class="p">></span> <span class="p">},</span>
<span class="p">{</span> <span class="na">menuItem</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Tab 3</span><span class="dl">'</span><span class="p">,</span> <span class="na">render</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p"><</span><span class="nc">Tab</span><span class="p">.</span><span class="nc">Pane</span><span class="p">></span>Tab 3 Content<span class="p"></</span><span class="nc">Tab</span><span class="p">.</span><span class="nc">Pane</span><span class="p">></span> <span class="p">},</span>
<span class="p">]</span>
<span class="kd">const</span> <span class="nx">TabExampleBasic</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><</span><span class="nc">Tab</span> <span class="na">panes</span><span class="p">=</span><span class="si">{</span><span class="nx">panes</span><span class="si">}</span> <span class="p">/></span>
<span class="p">)</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">TabExampleBasic</span>
</code></pre></div>
<p>The same thing occurs in several places, including <a href="https://react.semantic-ui.com/modules/popup">popups</a>.</p>
<p>So the question really is how do we pass along our reagent component along as a React component?</p>
<h2>The Widget</h2>
<p>Sticking with my tradition of building over engineered GitHub widgets, here are some tabs in action:</p>
<video width="100%" controls src="/2018/04/22/passing-around-components-with-reagent-and-semantic-ui/github-tabs.mp4"></video>
<p>The source code is available on GitHub at <a href="https://github.com/kennethkalmer/re-frame-semantic-ui-react-github-tabs">kennethkalmer/re-frame-semantic-ui-react-github-tabs</a>.</p>
<p>Here is a truncated version of the tabs in the above video:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nf">ns</span><span class="w"> </span><span class="n">github-repo-widget.views</span><span class="w">
</span><span class="p">(</span><span class="no">:require</span><span class="w"> </span><span class="p">[</span><span class="n">reagent.core</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">reagent</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">re-frame.core</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">re-frame</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">github-repo-widget.events</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">events</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">github-repo-widget.subs</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="nb">subs</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">github-repo-widget.ui</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">ui</span><span class="p">]))</span><span class="w">
</span><span class="p">(</span><span class="k">defn-</span><span class="w"> </span><span class="n">readme-tab</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">loading?</span><span class="w"> </span><span class="o">@</span><span class="p">(</span><span class="nf">re-frame/subscribe</span><span class="w"> </span><span class="p">[</span><span class="no">::subs/repo-readme-loading?</span><span class="p">])</span><span class="w">
</span><span class="n">readme</span><span class="w"> </span><span class="o">@</span><span class="p">(</span><span class="nf">re-frame/subscribe</span><span class="w"> </span><span class="p">[</span><span class="no">::subs/repo-readme</span><span class="p">])</span><span class="w">
</span><span class="n">pane</span><span class="w"> </span><span class="p">(</span><span class="nf">ui/component</span><span class="w"> </span><span class="s">"Tab"</span><span class="w"> </span><span class="s">"Pane"</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">pane</span><span class="w"> </span><span class="p">{</span><span class="no">:loading</span><span class="w"> </span><span class="n">loading?</span><span class="p">}</span><span class="w">
</span><span class="p">[</span><span class="no">:div</span><span class="w"> </span><span class="p">{</span><span class="no">:dangerouslySetInnerHTML</span><span class="w"> </span><span class="p">{</span><span class="no">:__html</span><span class="w"> </span><span class="n">readme</span><span class="p">}}]]))</span><span class="w">
</span><span class="p">(</span><span class="k">defn-</span><span class="w"> </span><span class="n">stats-tab</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">loading?</span><span class="w"> </span><span class="o">@</span><span class="p">(</span><span class="nf">re-frame/subscribe</span><span class="w"> </span><span class="p">[</span><span class="no">::subs/repo-info-loading?</span><span class="p">])</span><span class="w">
</span><span class="n">pane</span><span class="w"> </span><span class="p">(</span><span class="nf">ui/component</span><span class="w"> </span><span class="s">"Tab"</span><span class="w"> </span><span class="s">"Pane"</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">pane</span><span class="w"> </span><span class="p">{</span><span class="no">:loading</span><span class="w"> </span><span class="n">loading?</span><span class="p">}</span><span class="w">
</span><span class="c1">;; ...</span><span class="w">
</span><span class="p">]))</span><span class="w">
</span><span class="p">(</span><span class="k">defn-</span><span class="w"> </span><span class="n">repo-tabs</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">panes</span><span class="w"> </span><span class="p">[{</span><span class="no">:menuItem</span><span class="w"> </span><span class="s">"Readme"</span><span class="w">
</span><span class="no">:render</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">reagent/as-component</span><span class="w"> </span><span class="p">[</span><span class="n">readme-tab</span><span class="p">])}</span><span class="w">
</span><span class="p">{</span><span class="no">:menuItem</span><span class="w"> </span><span class="s">"Stats"</span><span class="w">
</span><span class="no">:render</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">reagent/as-component</span><span class="w"> </span><span class="p">[</span><span class="n">stats-tab</span><span class="p">])}]</span><span class="w">
</span><span class="n">tab</span><span class="w"> </span><span class="p">(</span><span class="nf">ui/component</span><span class="w"> </span><span class="s">"Tab"</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">tab</span><span class="w"> </span><span class="p">{</span><span class="no">:panes</span><span class="w"> </span><span class="n">panes</span><span class="p">}]))</span><span class="w">
</span></code></pre></div>
<h2>The solution</h2>
<p>Reagent gives us <code>reagent.core/as-component</code>, which is exactly the interop we need to turn our Reagent component into a React component for these cases.</p>
<p>In the case of the tab panes, Semantic UI React expects a function that returns a component as a value for the <code>render</code> property. In other places it expects the component directly, as seen with popups:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">info-icon</span><span class="w">
</span><span class="p">([</span><span class="n">message</span><span class="p">]</span><span class="w">
</span><span class="p">(</span><span class="nf">info-icon</span><span class="w"> </span><span class="p">{}</span><span class="w"> </span><span class="n">message</span><span class="p">))</span><span class="w">
</span><span class="p">([</span><span class="n">options</span><span class="w"> </span><span class="n">message</span><span class="p">]</span><span class="w">
</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">popup</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Popup"</span><span class="p">)</span><span class="w">
</span><span class="n">icon</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Icon"</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">popup</span><span class="w">
</span><span class="p">{</span><span class="no">:trigger</span><span class="w"> </span><span class="p">(</span><span class="nf">reagent/as-component</span><span class="w"> </span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">icon</span><span class="w">
</span><span class="p">(</span><span class="nb">merge</span><span class="w"> </span><span class="p">{</span><span class="no">:name</span><span class="w"> </span><span class="s">"info"</span><span class="p">}</span><span class="w">
</span><span class="n">options</span><span class="p">)])}</span><span class="w">
</span><span class="s">" "</span><span class="w">
</span><span class="n">message</span><span class="p">])))</span><span class="w">
</span></code></pre></div>
<p>Here the <code>trigger</code> property expects another component, not a function that returns the component.</p>
<h2>In close</h2>
<p>Being able to use Semantic UI React directly in ClojureScript without going through some insane incantations or rituals is a testament to the amazing work done by the Reagent contributors.</p>
<p>It is also a testament to Clojures pragmatic approach of embracing the language/environment that hosts it.</p>
Demystifying Datomic 'Two datoms in the same transaction conflict' errorshttps://opensourcery.blog/2017/05/29/two-datoms-in-the-same-transaction-conflict/2017-05-29T01:00:00-05:002023-02-25T12:01:35-06:00<p>I love Datomic, absolutely love it. I simply can't imagine a world without it anymore. I can now relate to how the NoSQL crowd felt a decade ago when they were unshackled from relational databases, except this time I feel the technology has really nailed it. Enough with the anecdotes!</p>
<p>When Datomic transactions fail, the errors can sometimes be very cryptic. Poor error messages are a recurring theme in Clojureland, and my hope is this post helps clarify an error that took me some time to solve. It took some time because my head was somewhere else when it occurred, I was thinking about my system and not Datomic. I was simply trying to transact some data, and it blew up spectacularly. This is what I learned in anger.</p>
<h2>Welcome to the Unseen Gym</h2>
<p>Discworld has a new entrant in the personal fitness space, the Unseen Gym. You're tasked with building the membership system. Datomic's treatment of datoms is respectfully similar to your understanding of <a href="https://wiki.lspace.org/mediawiki/Resons">Resons</a>, so it will be a good fit. Your team agrees and you're off to the races.</p>
<p>You start with a basic schema to capture member details:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">{</span><span class="no">:db/ident</span><span class="w"> </span><span class="no">:being/given-name</span><span class="w">
</span><span class="no">:db/valueType</span><span class="w"> </span><span class="no">:db.type/string</span><span class="w">
</span><span class="no">:db/doc</span><span class="w"> </span><span class="s">"Given name of a being"</span><span class="w">
</span><span class="no">:db/cardinality</span><span class="w"> </span><span class="no">:db.cardinality/one</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="no">:db/ident</span><span class="w"> </span><span class="no">:being/family-name</span><span class="w">
</span><span class="no">:db/valueType</span><span class="w"> </span><span class="no">:db.type/string</span><span class="w">
</span><span class="no">:db/doc</span><span class="w"> </span><span class="s">"Family name of a being"</span><span class="w">
</span><span class="no">:db/cardinality</span><span class="w"> </span><span class="no">:db.cardinality/one</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="no">:db/ident</span><span class="w"> </span><span class="no">:membership/number</span><span class="w">
</span><span class="no">:db/valueType</span><span class="w"> </span><span class="no">:db.type/long</span><span class="w">
</span><span class="no">:db/doc</span><span class="w"> </span><span class="s">"Membership number"</span><span class="w">
</span><span class="no">:db/unique</span><span class="w"> </span><span class="no">:db.unique/identity</span><span class="w">
</span><span class="no">:db/index</span><span class="w"> </span><span class="n">true</span><span class="w">
</span><span class="no">:db/cardinality</span><span class="w"> </span><span class="no">:db.cardinality/one</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<p>It took a long time just to model those attributes, and you're happy with the start. Given the variety of Discworld occupants, it is really hard getting specific with namespaces like <code>:person/</code>, since <strong>DEATH</strong> might join too. At least now you can identify a member by a number and some (hopefully pronounceable) name.</p>
<p>There is a lot more to your schema, you might even have partitions in other dimensions. Datomic is keeping it all together.</p>
<h2>First deviation</h2>
<p>One of the sales folk decide that Cohen deserves a special rate at the gym. His membership attracts other heros-to-be, but also requires extra ointment to be purchased and kept on a dedicated shelf. You're not sure if he'll end up paying more, or less, but that doesn't matter. You need to be able to set a custom membership fee! And like most changes, it needs to happen right now since word is that Cohen is on his way to the city.</p>
<p>You decide having an attribute that would overwrite the basic membership price is the way to go, and in haste you copy & paste the following forms into your schema and make some small adjustments:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">{</span><span class="no">:db/ident</span><span class="w"> </span><span class="no">:membership/set-fee</span><span class="w">
</span><span class="no">:db/valueType</span><span class="w"> </span><span class="no">:db.type/long</span><span class="w">
</span><span class="no">:db/doc</span><span class="w"> </span><span class="s">"Set fee for this member"</span><span class="w">
</span><span class="no">:db/unique</span><span class="w"> </span><span class="no">:db.unique/identity</span><span class="w">
</span><span class="no">:db/cardinality</span><span class="w"> </span><span class="no">:db.cardinality/one</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="no">:db/ident</span><span class="w"> </span><span class="no">:membership/set-fee-justification</span><span class="w">
</span><span class="no">:db/valueType</span><span class="w"> </span><span class="no">:db.type/string</span><span class="w">
</span><span class="no">:db/doc</span><span class="w"> </span><span class="s">"Justification for the set fee of this member"</span><span class="w">
</span><span class="no">:db/cardinality</span><span class="w"> </span><span class="no">:db.cardinality/one</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<p>And you set off to test:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="n">user=></span><span class="w"> </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">heros</span><span class="w"> </span><span class="p">[{</span><span class="no">:being/given-name</span><span class="w"> </span><span class="s">"Cohen"</span><span class="n">,</span><span class="w">
</span><span class="no">:membership/number</span><span class="w"> </span><span class="mi">6</span><span class="w">
</span><span class="no">:membership/set-fee</span><span class="w"> </span><span class="mi">20</span><span class="w">
</span><span class="no">:membership/set-fee-justification</span><span class="w">
</span><span class="s">"Marketing & ointment"</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="no">:being/given-name</span><span class="w"> </span><span class="s">"Havelock"</span><span class="w">
</span><span class="no">:being/family-name</span><span class="w"> </span><span class="s">"Vetinari"</span><span class="w">
</span><span class="no">:membership/number</span><span class="w"> </span><span class="mi">7</span><span class="w">
</span><span class="no">:membership/set-fee</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="no">:membership/set-fee-justification</span><span class="w">
</span><span class="s">"Not visibly present, ever"</span><span class="p">}])</span><span class="w">
</span><span class="ss">'user/heros</span><span class="w">
</span><span class="n">user=></span><span class="w"> </span><span class="p">(</span><span class="nf"><!!</span><span class="w"> </span><span class="p">(</span><span class="nf">client/transact</span><span class="w"> </span><span class="n">conn</span><span class="w"> </span><span class="p">{</span><span class="no">:tx-data</span><span class="w"> </span><span class="n">heros</span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="no">:db-before</span><span class="w"> </span><span class="p">{</span><span class="no">:database-id</span><span class="w"> </span><span class="s">"59293380-3347-4675-be0a-7daa6286b41c"</span><span class="n">,</span><span class="w">
</span><span class="no">:t</span><span class="w"> </span><span class="mi">1002</span><span class="n">,</span><span class="w">
</span><span class="no">:next-t</span><span class="w"> </span><span class="mi">1003</span><span class="n">,</span><span class="w">
</span><span class="no">:history</span><span class="w"> </span><span class="n">false</span><span class="p">}</span><span class="n">,</span><span class="w">
</span><span class="no">:db-after</span><span class="w"> </span><span class="p">{</span><span class="no">:database-id</span><span class="w"> </span><span class="s">"59293380-3347-4675-be0a-7daa6286b41c"</span><span class="n">,</span><span class="w">
</span><span class="no">:t</span><span class="w"> </span><span class="mi">1003</span><span class="n">,</span><span class="w">
</span><span class="no">:next-t</span><span class="w"> </span><span class="mi">1006</span><span class="n">,</span><span class="w">
</span><span class="no">:history</span><span class="w"> </span><span class="n">false</span><span class="p">}</span><span class="n">,</span><span class="w">
</span><span class="no">:tx-data</span><span class="w"> </span><span class="p">[</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">13194139534315</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="o">#</span><span class="n">inst</span><span class="w"> </span><span class="s">"2017-05-27T08:12:08.248-00:00"</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">63</span><span class="w"> </span><span class="s">"Cohen"</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">65</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w"> </span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">66</span><span class="w"> </span><span class="mi">20</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">68</span><span class="w"> </span><span class="s">"Marketing & ointment"</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045421</span><span class="w"> </span><span class="mi">63</span><span class="w"> </span><span class="s">"Havelock"</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045421</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="s">"Vetinari"</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w"> </span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045421</span><span class="w"> </span><span class="mi">66</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045421</span><span class="w"> </span><span class="mi">68</span><span class="w"> </span><span class="s">"Not visibly present, ever"</span><span class="w"> </span><span class="mi">13194139534315</span><span class="w"> </span><span class="n">true</span><span class="p">]]</span><span class="n">,</span><span class="w">
</span><span class="no">:tempids</span><span class="w"> </span><span class="p">{</span><span class="mi">-9223301668109598136</span><span class="w"> </span><span class="mi">17592186045420</span><span class="n">,</span><span class="w"> </span><span class="mi">-9223301668109598135</span><span class="w"> </span><span class="mi">17592186045421</span><span class="p">}}</span><span class="w">
</span></code></pre></div>
<p>It worked as expected. But just like Lord Vetinari, there is a stealthy bug that will bite us later...</p>
<p>An enthusiastic sales person signs up all the wizards of the Unseen University and a fixed rate of 10. It is almost a given they'll never show up, but you need to import the data quickly:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="n">user=></span><span class="w"> </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">wizards</span><span class="w"> </span><span class="p">[{</span><span class="no">:being/given-name</span><span class="w"> </span><span class="s">"Alberto"</span><span class="w">
</span><span class="no">:being/family-name</span><span class="w"> </span><span class="s">"Malich"</span><span class="w">
</span><span class="no">:membership/number</span><span class="w"> </span><span class="mi">10</span><span class="w">
</span><span class="no">:membership/set-fee</span><span class="w"> </span><span class="mi">10</span><span class="w">
</span><span class="no">:membership/set-fee-justification</span><span class="w">
</span><span class="s">"Wizard discount"</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="no">:being/given-name</span><span class="w"> </span><span class="s">"Galder"</span><span class="w">
</span><span class="no">:being/family-name</span><span class="w"> </span><span class="s">"Weatherwax"</span><span class="w">
</span><span class="no">:membership/number</span><span class="w"> </span><span class="mi">11</span><span class="w">
</span><span class="no">:membership/set-fee</span><span class="w"> </span><span class="mi">10</span><span class="w">
</span><span class="no">:membership/set-fee-justification</span><span class="w">
</span><span class="s">"Wizard discount"</span><span class="p">}])</span><span class="w">
</span><span class="ss">'user/wizards</span><span class="w">
</span><span class="n">user=></span><span class="w"> </span><span class="p">(</span><span class="nf"><!!</span><span class="w"> </span><span class="p">(</span><span class="nf">client/transact</span><span class="w"> </span><span class="n">conn</span><span class="w"> </span><span class="p">{</span><span class="no">:tx-data</span><span class="w"> </span><span class="n">wizards</span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="no">:datomic.client-spi/request-id</span><span class="w"> </span><span class="s">"4c69f404-07a4-4c99-bdbb-4596c97ec1f1"</span><span class="n">,</span><span class="w">
</span><span class="no">:cognitect.anomalies/category</span><span class="w"> </span><span class="no">:cognitect.anomalies/incorrect,</span><span class="w">
</span><span class="no">:cognitect.anomalies/message</span><span class="w"> </span><span class="s">":db.error/datoms-conflict Two datoms in the same transaction conflict\n{:d1 [17592186045418 :being/given-name \"Alberto\" 13194139534313 true],\n :d2 [17592186045418 :being/given-name \"Galder\" 13194139534313 true]}\n"</span><span class="n">,</span><span class="w">
</span><span class="no">:dbs</span><span class="w"> </span><span class="p">[{</span><span class="no">:database-id</span><span class="w"> </span><span class="s">"59293380-3347-4675-be0a-7daa6286b41c"</span><span class="n">,</span><span class="w">
</span><span class="no">:t</span><span class="w"> </span><span class="mi">1006</span><span class="n">,</span><span class="w">
</span><span class="no">:next-t</span><span class="w"> </span><span class="mi">1007</span><span class="n">,</span><span class="w">
</span><span class="no">:history</span><span class="w"> </span><span class="n">false</span><span class="p">}]}</span><span class="w">
</span></code></pre></div>
<p>Hidden in there is the following exception:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="no">:db.error/datoms-conflict</span><span class="w"> </span><span class="n">Two</span><span class="w"> </span><span class="n">datoms</span><span class="w"> </span><span class="n">in</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">same</span><span class="w"> </span><span class="n">transaction</span><span class="w"> </span><span class="n">conflict</span><span class="w">
</span><span class="p">{</span><span class="no">:d1</span><span class="w"> </span><span class="p">[</span><span class="mi">17592186045418</span><span class="w"> </span><span class="no">:being/given-name</span><span class="w"> </span><span class="sc">\"</span><span class="n">Alberto</span><span class="sc">\"</span><span class="w"> </span><span class="mi">13194139534313</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="n">,</span><span class="w">
</span><span class="no">:d2</span><span class="w"> </span><span class="p">[</span><span class="mi">17592186045418</span><span class="w"> </span><span class="no">:being/given-name</span><span class="w"> </span><span class="sc">\"</span><span class="n">Galder</span><span class="sc">\"</span><span class="w"> </span><span class="mi">13194139534313</span><span class="w"> </span><span class="n">true</span><span class="p">]}</span><span class="w">
</span></code></pre></div>
<p>Definitely not what we expected, we have no constraints on <code>:being/given-name</code>, so what is actually going here? We're not wizards, so we wouldn't notice if there is any sparks of octarine in the air...</p>
<p>Turns out in our earlier haste that we accidentally left the <code>:db.unique/identity</code> constraint on <code>:membership/set-fee</code>. So what is actually happening here?</p>
<p>It is the first time Datomic is seeing a <code>:membership/set-fee</code> value of <code>10</code>, so it rightfully prepares to create a new entity using this identifier. But we're giving it two different values for the <code>:being/given-name</code> attribute in the same transaction, and that is the conflict it is complaining about.</p>
<p>It is completely non-obvious, especially when encountered in anger. The attribute responsible for the conflict is simply not there, the only thing that hints to it is the duplicate entity id in the datoms.</p>
<p>Luckily this is easy enough to fix, we'll just drop the incorrect unique constraint from the schema:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="n">user=></span><span class="w"> </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">alter-schema</span><span class="w"> </span><span class="p">[[</span><span class="no">:db/retract</span><span class="w"> </span><span class="no">:membership/set-fee</span><span class="w"> </span><span class="no">:db/unique</span><span class="w"> </span><span class="no">:db.unique/identity</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="no">:db/add</span><span class="w"> </span><span class="no">:db.part/db</span><span class="w"> </span><span class="no">:db.alter/attribute</span><span class="w"> </span><span class="no">:membership/set-fee</span><span class="p">]])</span><span class="w">
</span><span class="ss">'user/alter-schema</span><span class="w">
</span><span class="n">user=></span><span class="w"> </span><span class="p">(</span><span class="nf"><!!</span><span class="w"> </span><span class="p">(</span><span class="nf">client/transact</span><span class="w"> </span><span class="p">{</span><span class="no">:tx-data</span><span class="w"> </span><span class="n">alter-schema</span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="no">:db-before</span><span class="w"> </span><span class="p">{</span><span class="n">...</span><span class="p">}</span><span class="n">,</span><span class="w">
</span><span class="no">:db-after</span><span class="w"> </span><span class="p">{</span><span class="n">...</span><span class="p">}</span><span class="n">,</span><span class="w">
</span><span class="no">:tx-data</span><span class="w"> </span><span class="p">[</span><span class="n">...</span><span class="p">]</span><span class="n">,</span><span class="w">
</span><span class="no">:tempids</span><span class="w"> </span><span class="p">{}}</span><span class="w">
</span></code></pre></div>
<p>And transact again:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="n">user=></span><span class="w"> </span><span class="p">(</span><span class="nf"><!!</span><span class="w"> </span><span class="p">(</span><span class="nf">client/transact</span><span class="w"> </span><span class="n">conn</span><span class="w"> </span><span class="p">{</span><span class="no">:tx-data</span><span class="w"> </span><span class="n">wizards</span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="no">:db-before</span><span class="w"> </span><span class="p">{</span><span class="no">:database-id</span><span class="w"> </span><span class="s">"59294074-9a48-4604-9014-c0c615d32699"</span><span class="n">,</span><span class="w">
</span><span class="no">:t</span><span class="w"> </span><span class="mi">1001</span><span class="n">,</span><span class="w">
</span><span class="no">:next-t</span><span class="w"> </span><span class="mi">1002</span><span class="n">,</span><span class="w">
</span><span class="no">:history</span><span class="w"> </span><span class="n">false</span><span class="p">}</span><span class="n">,</span><span class="w">
</span><span class="no">:db-after</span><span class="w"> </span><span class="p">{</span><span class="no">:database-id</span><span class="w"> </span><span class="s">"59294074-9a48-4604-9014-c0c615d32699"</span><span class="n">,</span><span class="w">
</span><span class="no">:t</span><span class="w"> </span><span class="mi">1002</span><span class="n">,</span><span class="w">
</span><span class="no">:next-t</span><span class="w"> </span><span class="mi">1005</span><span class="n">,</span><span class="w">
</span><span class="no">:history</span><span class="w"> </span><span class="n">false</span><span class="p">}</span><span class="n">,</span><span class="w">
</span><span class="no">:tx-data</span><span class="w"> </span><span class="p">[</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">13194139534314</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="o">#</span><span class="n">inst</span><span class="w"> </span><span class="s">"2017-05-27T10:51:49.395-00:00"</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045419</span><span class="w"> </span><span class="mi">63</span><span class="w"> </span><span class="s">"Alberto"</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045419</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="s">"Malich"</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045419</span><span class="w"> </span><span class="mi">65</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045419</span><span class="w"> </span><span class="mi">66</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045419</span><span class="w"> </span><span class="mi">67</span><span class="w"> </span><span class="s">"Wizard discount"</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">63</span><span class="w"> </span><span class="s">"Galder"</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="s">"Weatherwax"</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">65</span><span class="w"> </span><span class="mi">11</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">66</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
</span><span class="o">#</span><span class="n">datom</span><span class="p">[</span><span class="mi">17592186045420</span><span class="w"> </span><span class="mi">67</span><span class="w"> </span><span class="s">"Wizard discount"</span><span class="w"> </span><span class="mi">13194139534314</span><span class="w"> </span><span class="n">true</span><span class="p">]]</span><span class="n">,</span><span class="w">
</span><span class="no">:tempids</span><span class="w"> </span><span class="p">{</span><span class="mi">-9223301668109598135</span><span class="w"> </span><span class="mi">17592186045419</span><span class="n">,</span><span class="w"> </span><span class="mi">-9223301668109598134</span><span class="w"> </span><span class="mi">17592186045420</span><span class="p">}}</span><span class="w">
</span></code></pre></div>
<p>No octarine sparks, and no signs of the wizards. I doubt they'll ever come to the Unseen Gym...</p>
<h3>Some irony</h3>
<p>An aside, if you had a <code>:db.unique/value</code> constraint set, instead of <code>:db.unique/identity</code>, you would have received the following helpful exception indicating the problem more clearly:</p>
<div class="highlight"><pre class="highlight plaintext"><code>:db.error/datoms-conflict Two datoms in the same transaction conflict
{:d1 [17592186045424 :membership/set-fee 10 13194139534319 true],
:d2 [17592186045425 :membership/set-fee 10 13194139534319 true]}
</code></pre></div>
<p>Trust me while writing this post I got the above exception and had to spend some time to figure out why it wasn't the original exception I noted for this post. Things are seldom as they appear here on the Disc...</p>
<h2>References</h2>
<p>This was all done with Datomic Pro 0.9.5561 using the bundled repl. Following the official quick start docs means you could run all of the code snippets in the repl and see the effects for yourself.</p>
<ul>
<li>Datomic <a href="http://docs.datomic.com/getting-started/brief-overview.html">getting started docs</a></li>
<li><a href="http://docs.datomic.com/identity.html">Identity and uniqueness</a> in Datomic</li>
<li><a href="https://en.wikipedia.org/wiki/Discworld">Discworld</a> by Terry Pratchett</li>
</ul>
<h3>Cover image</h3>
<p><a href="http://shinigamisama19.deviantart.com/art/Colliding-Atoms-1-0-360830969">Colliding Atoms 1.0</a> by Shinigamisama19</p>
Using semantic-ui-react with re-framehttps://opensourcery.blog/2017/02/12/using-semantic-ui-react-with-re-frame/2017-02-12T09:27:00-06:002023-02-25T12:01:35-06:00<p>One of the great things about ClojureScript is the fantastic interop story with the JavaScript ecosystem. That said, taming JavaScript is a bit of an art that takes some practice.</p>
<p>I've integrated plain <a href="http://semantic-ui.com">Semantic-UI</a> with a <a href="https://github.com/Day8/re-frame">re-frame</a> project before and there was a lot of state to tame, and plenty of calls to <a href="https://github.com/Day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md"><code>(reagent/create-class)</code></a> which I would have liked to avoid.</p>
<p>In starting up a fresh re-frame app I decided to see if I can't make use of the official <a href="http://react.semantic-ui.com">Semantic-UI-React</a> components. Turned out to be fairly simple, and here I'll show you how.</p>
<h2>The Widget</h2>
<p>I decided to build a little widget to show off the code in real life. <a href="/github-tree-widget">Have a look</a> for yourself.</p>
<video width="100%" controls src="/2017/02/12/using-semantic-ui-react-with-re-frame/github-tree-widget.mp4"></video>
<p>The source code is available on GitHub at <a href="https://github.com/kennethkalmer/re-frame-semantic-ui-react-github-widget">kennethkalmer/re-frame-semantic-ui-react-github-widget</a>.</p>
<h2>Piecing it together</h2>
<p>Piecing it together was relatively simple with only one dependency: <code>[cljsjs/semantic-ui-react "0.64.0-0"]</code> and a few functions.</p>
<h3>cljsjs?</h3>
<p>From the CLJSJS website:</p>
<blockquote>
<p>CLJSJS provides an easy way for ClojureScript developers to depend on Javascript libraries.</p>
</blockquote>
<p>It is a crowd sourced list of JavaScript packages bundled up with the appropriate extern files that allow you to use the <a href="https://github.com/clojure/clojurescript/wiki/Compiler-Options#optimizations">advanced optimizations</a> offered by the <a href="https://developers.google.com/closure/compiler/">Google Closure compiler</a>. By having one place with a lot of packages and their extern files, it makes all of our lives a little simpler.</p>
<p>This is a little different from <a href="http://www.webjars.org">WebJars</a> though, which seem to package up various NPM & Bower packages verbatim. WebJars is also focused on the broader Java ecosystem and doesn't cater for Clojure specifically.</p>
<h3>The functions</h3>
<p>Based on the cljsjs/semantic-ui-react README I came up with this:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nf">ns</span><span class="w"> </span><span class="n">re-frame-semantic-ui-react-github-widget.views</span><span class="w">
</span><span class="c1">;; Other requires omitted...</span><span class="w">
</span><span class="p">(</span><span class="no">:require</span><span class="w"> </span><span class="p">[</span><span class="n">cljsjs.semantic-ui-react</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">goog.object</span><span class="p">]))</span><span class="w">
</span><span class="c1">;; Easy handle to the top-level extern for semantic-ui-react</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">semantic-ui</span><span class="w"> </span><span class="n">js/semanticUIReact</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">component</span><span class="w">
</span><span class="s">"Get a component from sematic-ui-react:
(component \"Button\")
(component \"Menu\" \"Item\")"</span><span class="w">
</span><span class="p">[</span><span class="n">k</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="n">ks</span><span class="p">]</span><span class="w">
</span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="n">ks</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">goog.object/getValueByKeys</span><span class="w"> </span><span class="n">semantic-ui</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="n">ks</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">goog.object/get</span><span class="w"> </span><span class="n">semantic-ui</span><span class="w"> </span><span class="n">k</span><span class="p">)))</span><span class="w">
</span></code></pre></div>
<p>This gives us a single <code>(component)</code> function to use for getting any component (nested or not) from Semantic-UI-React.</p>
<h3>Common components</h3>
<p>It then setup some useful common components:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">container</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Container"</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Segment"</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">dimmer</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Dimmer"</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">loader</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Loader"</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">message</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Message"</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">message-header</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Message"</span><span class="w"> </span><span class="s">"Header"</span><span class="p">))</span><span class="w">
</span><span class="c1">;; etc...</span><span class="w">
</span></code></pre></div>
<h3>Rendering components</h3>
<p>Since <a href="http://reagent-project.github.io/news/news060-alpha.html">reagent 0.6-alpha</a>, "native react components" can be used directly in the Reagent's Hiccup forms like this:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">container</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="p">{</span><span class="no">:basic</span><span class="w"> </span><span class="n">true</span><span class="p">}</span><span class="w">
</span><span class="p">[</span><span class="no">:p</span><span class="w"> </span><span class="s">"Hello from this segment"</span><span class="p">]]]</span><span class="w">
</span></code></pre></div>
<p>So piecing together your interface with the Semantic-UI components becomes very simple.</p>
<h3>Going shorthand</h3>
<p>Semantic-UI-React offers some nice shorthand props where possible, allowing things to become very compact:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">header</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Header"</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">header</span><span class="w"> </span><span class="p">{</span><span class="no">:as</span><span class="w"> </span><span class="s">"h1"</span><span class="w">
</span><span class="no">:subheader</span><span class="w"> </span><span class="s">"A sample widget using semantic-ui-react with re-frame"</span><span class="w">
</span><span class="no">:content</span><span class="w"> </span><span class="s">"GitHub Tree Widget"</span><span class="w">
</span><span class="no">:icon</span><span class="w"> </span><span class="s">"github"</span><span class="p">}]))</span><span class="w">
</span><span class="c1">;; vs</span><span class="w">
</span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">header</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Header"</span><span class="p">)</span><span class="w">
</span><span class="n">content</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Header"</span><span class="w"> </span><span class="s">"Content"</span><span class="p">)</span><span class="w">
</span><span class="n">subheader</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Header"</span><span class="w"> </span><span class="s">"Subheader"</span><span class="p">)</span><span class="w">
</span><span class="n">icon</span><span class="w"> </span><span class="p">(</span><span class="nf">component</span><span class="w"> </span><span class="s">"Icon"</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">header</span><span class="w"> </span><span class="p">{</span><span class="no">:as</span><span class="w"> </span><span class="s">"h1"</span><span class="p">}</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">icon</span><span class="w"> </span><span class="p">{</span><span class="no">:name</span><span class="w"> </span><span class="s">"github"</span><span class="p">}]</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">content</span><span class="w">
</span><span class="s">"GitHub Tree Widget"</span><span class="w">
</span><span class="p">[</span><span class="no">:></span><span class="w"> </span><span class="n">subheader</span><span class="w">
</span><span class="s">"A sample widget using semantic-ui-react with re-frame"</span><span class="p">]]]))</span><span class="w">
</span></code></pre></div>
<h3>Bonus content</h3>
<p>I used the opportunity in the widget to really come to terms with "Layer 3 computations" in the re-frame <a href="https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md">signal graph</a>. If you're interested have a look at <code>src/cljs/subs.cljs</code>.</p>
<p>re-frame's <a href="https://github.com/Day8/re-frame-http-fx">http-fx</a> handler also came in useful for the ajax calls, although I could do better by having a function build up the request from a common base. The details are in <code>src/cljs/events.cljs</code>.</p>
<h3>More bonus content</h3>
<p><em>Updated April 2018:</em> I've added a new post on <a href="/2018/04/22/passing-around-components-with-reagent-and-semantic-ui/">passing around reagent components as values</a> to Semantic UI React components. Please be sure to read that post as well.</p>
<h2>Looking forward</h2>
<p>The demo is a bit contrived, but is a different take on the boring TodoMVC-style demos. I got to articulate the process, and hopefully, help you dear reader.</p>
<p>Having only used react through re-frame, this process definitely helped me gain a better understanding of react itself. Spending the time looking at the extensive demos in the the Semantic-UI-React docs lead to a few "aha" moments. It also made me happy knowing I odn't have to write all that verbose syntax :).</p>
<p>ClojureScript is fantastic, and if you haven't yet experienced it try and get this project running locally and dabble with. Figwheel will surely blow your mind.</p>
<h3>Final caveat</h3>
<p>As with all proxies for upstream projects, CLJSJS might be out of date or missing the project you want. If things are out of date, quickly <a href="https://github.com/cljsjs/packages/pull/991">file a pull request</a> to get things updated. If missing, open an issue and hear if they'll accept the new package.</p>
Bringing order with Clojure's sort-byhttps://opensourcery.blog/2017/01/24/bringing-order-with-clojure-s-sort-by/2017-01-24T11:04:00-06:002023-02-25T12:01:35-06:00<p>It is unavoidable, really.</p>
<p>Any data eventually needs to be sorted for presentation. Most of the times we’re very lucky and we could lean on the implicit order of data returned from the database, or we can decorate that HTML table with DataTables and get sorting for free.</p>
<p>Implicit sorting is a crutch that I’ve relied on many times, and I’m sure you have too.</p>
<h2>Implicit sorting in RDBMS systems</h2>
<p>What many of us observe when using, say MySQL, is that the rows get returned to us in an order we're familiar with. The insertion order. Using InnoDB tables this generally means ascending by numeric auto-incrementing primary keys. For MyISAM it is strictly insertion order. Other storage engines might have different properties.</p>
<p>Already it is clear that within one RDBMS system we can have multiple behaviours depending on the underlying storage engine used by the table.</p>
<p>Long story short, we have no gaurantees and this can easily nip us in the behind if we're not careful.</p>
<h2>Leaving it to the caller</h2>
<p>Knowing now that we cannot depend on the source to sort the data for us, it is almost certainly up to the caller to sort the data. This often comes in the form of a query parameter, be it an <code>ORDER BY</code> clause in a SQL statement or a query parameter to an API.</p>
<p>More bespoke sorting tends to happen in the consuming code, not at the source, and this is where we'll look at what Clojure offers us.</p>
<h1>Sorting data with Clojure</h1>
<p>Clojure is great at taming data of all kinds, here I just want to explore a few ways to get some order to your data using simple functions.</p>
<h2>Example data</h2>
<p>For the examples below I’m going to be working with some invoice data. Each invoice looks something like this:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">{</span><span class="no">:invoice/number</span><span class="w"> </span><span class="s">"ACMEINV00001"</span><span class="w">
</span><span class="no">:invoice/date</span><span class="w"> </span><span class="s">"2016-11-25"</span><span class="w">
</span><span class="no">:invoice/total-before-tax</span><span class="w"> </span><span class="mi">100</span><span class="w">
</span><span class="no">:invoice/tax</span><span class="w"> </span><span class="mi">10</span><span class="w">
</span><span class="no">:invoice/items</span><span class="w"> </span><span class="p">[</span><span class="n">...</span><span class="p">]}</span><span class="w">
</span></code></pre></div>
<p>I’ll leave it up to you to imagine how rich these data structures can become in a real invoicing system.</p>
<h2>Getting started with <code>sort-by</code></h2>
<p>The first requirement could be to sort a vector of invoices by total. These is relatively simple with <code>sort-by</code>:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nb">sort-by</span><span class="w"> </span><span class="no">:invoice/total-before-tax</span><span class="w"> </span><span class="n">invoices</span><span class="p">)</span><span class="w">
</span></code></pre></div>
<p>The first argument to <code>sort-by</code> should be function, and since keywords are functions of maps you can just specify the key in the map to be used. One caveat, the value of the entry in the map must be comparable.</p>
<p>That gets you a new vector, with the smallest invoices first and the valuable ones at the end. Hardly useful for business, so lets flip it around by supplying a comparator function too:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nb">sort-by</span><span class="w"> </span><span class="no">:invoice/total-before-tax</span><span class="w"> </span><span class="nb">></span><span class="w"> </span><span class="n">invoices</span><span class="p">)</span><span class="w">
</span></code></pre></div>
<p>Now we’re cooking with gas! The most valuable invoices are now at the head of the list! Need the top 10? Just <code>take</code> what you need:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nb">take</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="p">(</span><span class="nb">sort-by</span><span class="w"> </span><span class="no">:invoice/total-before-tax</span><span class="w"> </span><span class="nb">></span><span class="w"> </span><span class="n">invoices</span><span class="p">))</span><span class="w">
</span></code></pre></div>
<p>How did this happen? Clojure compared the values returned by <code>:invoice/total-before-tax</code> using the <code>></code> function.</p>
<h2>Sorting by composite keys</h2>
<p>The next requirement might be to sort by <code>:invoice/number</code> and <code>:invoice/total-before-tax</code>. Imagine the idea is that when two invoices have the same total that they are then sorted by their invoice number to show some kind of implied order.</p>
<p>This is where our friend <code>juxt</code> comes in. <code>juxt</code> accepts a list of functions and returns a new function, that when called, returns the results of all the original functions in a vector.</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">head-and-tail</span><span class="w"> </span><span class="p">(</span><span class="nf">juxt</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="nb">last</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">head-and-tail</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">5</span><span class="p">])</span><span class="w"> </span><span class="o">#</span><span class="n">=></span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">5</span><span class="p">]</span><span class="w">
</span></code></pre></div>
<p>Here you can see that juxt applied <code>first</code>, and <code>last</code>, to the supplied list of numbers and gave us the head and the tail of the list. Keen readers might have just figured out where I’m going with this.</p>
<p><code>sort-by</code> can compare these vectors too, so we can sort our invoices like this:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">total-before-tax-and-number</span><span class="w"> </span><span class="p">(</span><span class="nf">juxt</span><span class="w"> </span><span class="no">:invoice/total-before-tax</span><span class="w"> </span><span class="no">:invoice/number</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nb">sort-by</span><span class="w"> </span><span class="n">total-before-tax-and-number</span><span class="w"> </span><span class="n">invoices</span><span class="p">)</span><span class="w">
</span></code></pre></div>
<p>Now the results will be sorted from lowest value invoice to the highest, with the invoice numbers in order too. So we're halfway there.</p>
<h2>Mixing the order (or composing functions)</h2>
<p>The results of the previous example doesn't make much sense. How can we combine sorting the invoice amounts in descending order <em>and</em> have the invoice numbers run sequentially when there is an overlap?</p>
<p>One possible solution is to use <code>comp</code>, and make a new function that will return the value of <code>:invoice/total-before-tax</code> as a negative number. <code>comp</code> works by accepting a list of functions and returning a new function, which when called, calls the arguments from right to left and passing the result of the previous call to the next one, starting with the parameter when called.</p>
<p>An example will be worth a thousand words:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.string</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="nb">str</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">up-and-reverse</span><span class="w"> </span><span class="p">(</span><span class="nb">comp</span><span class="w"> </span><span class="n">str/reverse</span><span class="w"> </span><span class="n">str/upper-case</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">up-and-reverse</span><span class="w"> </span><span class="s">"elloh"</span><span class="p">)</span><span class="w"> </span><span class="o">#</span><span class="n">=></span><span class="w"> </span><span class="s">"HELLO"</span><span class="w">
</span><span class="c1">;; or</span><span class="w">
</span><span class="p">(</span><span class="nf">str/reverse</span><span class="w"> </span><span class="p">(</span><span class="nf">str/upper-case</span><span class="w"> </span><span class="s">"elloh"</span><span class="p">))</span><span class="w"> </span><span class="n">=></span><span class="w"> </span><span class="s">"HELLO"</span><span class="w">
</span></code></pre></div>
<p>In order to get the negative total we can just <code>comp</code> together <code>-</code> and <code>:invoice/total-before-tax</code> like this:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">negative-total</span><span class="w"> </span><span class="p">(</span><span class="nb">comp</span><span class="w"> </span><span class="nb">-</span><span class="w"> </span><span class="no">:invoice/total-before-tax</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">negative-total</span><span class="w"> </span><span class="n">invoice</span><span class="p">)</span><span class="w"> </span><span class="o">#</span><span class="n">=></span><span class="w"> </span><span class="mi">-100</span><span class="w">
</span></code></pre></div>
<p>If this right-to-leftness of <code>comp</code> bothers you, you could also simply declare it as an anonymous function which wraps a thread-first functional pipeline: <code>#(-> % :invoice/total-before-tax -)</code>.</p>
<p>And using our new friend <code>juxt</code> we can simply roll it up like this:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">negative-total-and-number</span><span class="w"> </span><span class="p">(</span><span class="nf">juxt</span><span class="w"> </span><span class="n">negative-total</span><span class="w"> </span><span class="no">:invoice/number</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nb">sort-by</span><span class="w"> </span><span class="n">negative-total-and-number</span><span class="w"> </span><span class="n">invoices</span><span class="p">)</span><span class="w">
</span></code></pre></div>
<p>And now we have a list of invoices sorted by total from most to least valuable, and where the totals match the invoice numbers follow a progression.</p>
<p>Another variation would be to sort by number of items on an invoice. This can be achieved by composing <code>count</code> and <code>:invoice/items</code> together:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nb">sort-by</span><span class="w"> </span><span class="p">(</span><span class="nb">comp</span><span class="w"> </span><span class="nb">count</span><span class="w"> </span><span class="no">:invoice/items</span><span class="p">)</span><span class="w"> </span><span class="nb">></span><span class="w"> </span><span class="n">invoices</span><span class="p">)</span><span class="w">
</span></code></pre></div>
<p>And you'll have the invoice with the most items in at the head of the list.</p>
<h2>Wrapping up</h2>
<p>Although my examples are a bit contrived, the power that comes from composing functions in these intuitive ways are nearly endless. This works equally well for predicate functions used by <code>filter</code>, <code>remove</code> and many others.</p>
<p>It seems many small and composable functions will end up serving you better in the long run!</p>
<h2>Thanks</h2>
<p>A big thanks for <a href="http://www.stuttaford.me/">Robert Stuttaford</a> for helping to <a href="https://github.com/kennethkalmer/opensourcery.co.za/pull/2">review this post</a></p>
<h1>References & further reading</h1>
<ul>
<li>Clojure Docs for <a href="http://clojuredocs.org/clojure.core/sort-by">sort-by</a>, <a href="http://clojuredocs.org/clojure.core/sort">sort</a>, <a href="http://clojuredocs.org/clojure.core/compare">compare</a>, <a href="http://clojuredocs.org/clojure.core/juxt">juxt</a> & <a href="http://clojuredocs.org/clojure.core/comp">comp</a>.</li>
<li><a href="https://camdez.com/blog/2012/03/21/callability-in-clojure/">Callability in Clojure</a></li>
<li><a href="http://dba.stackexchange.com/questions/6051/what-is-the-default-order-of-records-for-a-select-statement-in-mysql">What is the default order of records for a SELECT statement in MySQL?</a></li>
</ul>
<p>Cover image by <a href="https://en.wikipedia.org/wiki/Sorting#/media/File:Metal_movable_type.jpg">Willi Heidelbach</a> — Creative Commons Attribution-Share Alike 3.0 Unported — Wikipedia</p>
The JVM is not that heavyhttps://opensourcery.blog/2017/01/05/the-jvm-is-not-that-heavy/2017-01-05T15:19:00-06:002023-02-25T12:01:35-06:00<blockquote>
<p>Mostly my opposition to Clojure is the JVM. That sh*t is heavy.</p>
</blockquote>
<p>This came up in the <a href="http://zatech.co.za">ZA Tech</a> Slack team several weeks ago. While watching some Clojure talks over the holidays the speakers also noted this objection over and over again.</p>
<p>I had a bit of a <a href="https://zatech.slack.com/archives/developers/p1481966328001513">monologue in Slack</a> about this. Now I'm penning it down for broader consumption and discussion.</p>
<h2>Background</h2>
<p>I used to think the JVM was heavy too. This was way back in the early 2000's when I compared it to PHP. There were other heavy alternatives too, like .NET and ColdFusion. There were lighter alternatives like Perl & Python, but I was on Windows back then so ActivePerl and ActivePython were also a bit heavy.</p>
<p>I first got over my "fear" of the JVM when I deployed a little production app with JRuby to Heroku. This little beast only had to perform a single daily task. It generated a bunch of PDF's and then uploaded them to iSign (now defunct) for storage and sharing. iSign itself was a classic Rails app, hosted on 3 AMI's. This little dyno running a stock JVM (except for <code>-server -Xmx=512M</code>) produced PDF's so fast that it basically killed the 3 node cluster on every run.</p>
<p>Still I thought it was a bit heavy to use, but I was in love with the ugly duckling.</p>
<p>I've more or less followed JRuby development and success stories, and had a fantastic time at <a href="http://rubyfuza.org">Rubyfuza 2015</a> with <a href="https://www.youtube.com/watch?v=aZh9e7aFE4E">Charles Nutter</a>. I felt so inspired afterwards that I went on a mission to <a href="https://github.com/activeadmin/activeadmin/pull/3792">open pull requests</a> to Ruby projects that simply runs their tests with JRuby. It stopped with that one, my bad.</p>
<h2>Fast forward to 2016</h2>
<p>I tried building a Rails app from scratch in November 2016. It was the first time in months that I attempted any Ruby programming on my machine. A <code>brew upgrade</code> bumped rbenv and thus threw away all my Ruby installs and I didn't even notice.</p>
<p>I was going to present on websockets at <a href="https://www.meetup.com/joziruby/events/235962465/">Jozi.rb</a>.</p>
<p>My starting point was to play with the <a href="https://www.reactrails.com">React on Rails</a> repo just to get a feel for using React with Rails. I've been using <a href="https://github.com/Day8/re-frame">re-frame</a> for a few months and was confident I can pull it off with raw React.</p>
<p>The wheels came off, spectacularly.</p>
<p>To clone and run one sample app I needed to upgrade XCode, upgrade the command line tools for XCode (>6GB in total), install a new Ruby version and bundler and then bundle install in the sample app... Simple right? The sample app, like the majority of Rails apps, depends on libv8 somewhere in the dependency graph and that alone is more than 1GB in size.</p>
<p>That whole exercise took hours.</p>
<p>Playing with the very impressive demo I realized it was bringing an HCMB to a game of rock-paper-scissors. I decided to build the frontend with Ember instead, since I know Ember and was running out of time.</p>
<p>Same thing again, need to update nvm, install a respectable node version, install ember-cli, generate the app and install the dependencies via npm <em>and</em> bower.</p>
<p>I played a little and gave up, and instead shared this experience with the handful of folks that came. It was humbling, truly humbling. I felt like a stranger in a world I've been part of for so long.</p>
<p>Back to the statement of the JVM being <em>heavy</em>.</p>
<h2>How do you weigh it?</h2>
<ul>
<li>Is it the size of the JDK when you download it?</li>
<li>Does it use a lot of resources when you run it?</li>
<li>Do the libraries consume a lot of disk space?</li>
<li>Is it a ceremonial affair to deploy?</li>
<li>Does it slow you down in your day to day?</li>
</ul>
<p>These are some questions that can help cut through our emotional barriers when thinking about the JVM. These emotions, these biases are costly and can hurt us in the long run.</p>
<p>So lets tackle them to see what lies beneath.</p>
<h2>Is the upfront cost really that high?</h2>
<p>This <em>"heaviness"</em> of the JVM is pure FUD, and starts with this large-ish upfront cost to installing it. You compare the ~200MB download of the JDK to the ~15MB download of Node or Ruby. That is only the baseline. For both Node and Ruby you need a C compiler on the system which is hundreds of megabytes alone. Even worse, you probably need a compiler in production!</p>
<p>The amount of bloat required with Node and Ruby is hidden away from you with these small incremental steps. If you stop and take stock of it all, not to mention the time you've spent, you'll see the 200MB is way more efficient.</p>
<p>I don't even want to go down the path of assembling your own webpack config, just to have it overthrown when the next new tool arrives.</p>
<p><img src="/2017/01/05/the-jvm-is-not-that-heavy/iamdevloper-451e2bd8.png" alt="https://twitter.com/iamdevloper/status/517616294909464576" /></p>
<h2>Is running the JVM that heavy?</h2>
<p>The JVM is fast, it is probably one of the <a href="https://www.techempower.com/benchmarks/#section=data-r13&hw=cl&test=fortune">fastest runtimes</a> out there. It just keeps getting faster and leaner with time. Thousands of the smartest engineers are working to make it better, and even more have contributed in the last 21 years.</p>
<p>It has real threads, supports multiple cores, and can be configured to hell and back, or just left alone. The only useful thing you probably need to know is how to set the memory for the JVM so it can works its magic within the constraints of your environment.</p>
<p>Deploying to Heroku? <code>java -server -Xmx512m beast.jar</code>. If that doesn't suffice you probably have income and can pay someone for advice... Oh, or StackOverflow.</p>
<p>This is a key thing that Charles and other people in the JRuby community keep pushing. Without you doing anything, your apps will surely get faster and faster with each JVM release (independent of JRuby progress).</p>
<h2>Is disk usage that heavy?</h2>
<p>I was curious and looked at my <code>~/.m2</code> folder, turns out in 9 months of Clojure development I've only accumulated <strong>1010MB</strong> of dependencies. Not even a gigabyte yet.</p>
<div class="highlight"><pre class="highlight plaintext"><code>$ du -sh /usr/local/opt/rbenv/versions/2.3.3 ~/.nvm/versions/node/v6.9.1 ~/.m2
690M /usr/local/opt/rbenv/versions/2.3.3
232M /Users/kenneth/.nvm/versions/node/v6.9.1
1010M /Users/kenneth/.m2
</code></pre></div>
<p>The Ruby install is fresh again, and basically has the requirements for this blog and Middleman (I've been working on a fix there). Yep, to run this static blog and contribute to the tools that power it requires nearly 700MB of storage.</p>
<p>Node only has ember, docpad and bower installed and we're over 200MB.</p>
<h2>Is deploying it that heavy?</h2>
<p>You can probably predict where I'm going with this...</p>
<p>Your build step produces a single JAR file. It has everything you need to your app running somewhere else. You simply place the JAR where you need it and let a JVM loose on it.</p>
<p>It is not a requirement that you deploy your applications into some massive application server, you can very easily bundle up a performant HTTP server right in your JAR file. Node folks do, Ruby folks do it, yet somehow JAR files can't stand on their own? I used to think so too...</p>
<p>I, for one, am relieved not to have run <code>apt-get install build-essentials</code> on a production box.</p>
<h2>Day to day with the JVM</h2>
<p>I run at least <strong>5</strong> JVM processes on my 2012 MacBook Pro with 8GB of memory. This is all day, every day. I would never have tried to start 5 Rails apps at the same time.</p>
<p>Why 5? Two for <a href="http://www.datomic.com">Datomic</a> (transactor & console), one for our backend API and one for whichever frontend I'm working on. Sometimes I have automated tests running the background too. I'm sure that macOS's memory compression definitely helps here since a good chunk of those JVM processes should have all the same bytes loaded into memory.</p>
<p><img src="/2017/01/05/the-jvm-is-not-that-heavy/activity-monitor-main-d3c4f963.png" alt="" />
<img src="/2017/01/05/the-jvm-is-not-that-heavy/activity-monitor-java-87ee7ebc.png" alt="" /></p>
<p>But if you told me just 10 months ago that I would be doing this I would have laughed you off. Who in their right mind runs <strong>5 or more</strong> JVM processes? I can go on a limb and say I'm definitely not the only one.</p>
<p>Oh, but what about class paths and all these other crazy things? Haven't needed to touch any of those thanks to the great tooling provided by Clojure. It is the same reason you use npm or bundler, so you don't have to fiddle with include paths. You could, but then you probably have a different problem you're not seeing.</p>
<h3>Joys of the REPL</h3>
<p>If I had to stop and start the JVM instances continuously I would definitely loose my mind. That bothered me with JRuby a lot back in the day. Luckily with Clojure and the amazing REPL I only need to restart a JVM instance in the rare case that I borked something up badly. It is rather idiot proof. <a href="https://github.com/bhauman/lein-figwheel">Figwheel</a> runs for days without issues.</p>
<h2>Conclusion</h2>
<p>Be very careful before judging the JVM as a target. Judge Java as a language by all means, but keep it separate from the virtual machine.</p>
<p>I used to believe the same things you did. I used to think of the JVM as this behemoth. Now I'm thankful to be able to throw my parentheses at it and have the work of thousands of giants support it.</p>
<p>By no means take this post as a sign of "the end of Node" or "the end of Ruby". Let it bring a fresh perspective. If you can't switch to the JVM, at least think about what you could do to help eliminate the bloat from your own world.</p>
<p>Thanks for giving me the privilege of your time. Now <a href="http://www.braveclojure.com/">go learn</a> some Clojure, and experience <a href="https://www.infoq.com/presentations/Simple-Made-Easy">Simple Made Easy</a>.</p>
Smooth client-side routing in a figwheel-only projecthttps://opensourcery.blog/2016/05/27/smooth-client-side-routing-in-a-figwheel-only-project/2016-05-27T11:59:00-05:002023-02-25T12:01:35-06:00<p>One of the few things I missed when starting with <a href="https://github.com/Day8/re-frame">re-frame</a> was the excellent client-side routing setup you get with ember-cli, especially the development server part of that.</p>
<p>After some digging I found this fantastic article, <a href="https://pupeno.com/2015/08/18/no-hashes-bidirectional-routing-in-re-frame-with-silk-and-pushy/">No-hashes bidirectional routing in re-frame with silk and pushy</a>, in which Pablo lays out all the Clojurescript required for nice push state based routing.</p>
<p>In the article though Pablo does note that he hasn't tried setting this up with a figwheel only project. I needed to figure this out for myself, and it turned out that my first journey into the wilderness was not nearly as daunting as I thought.</p>
<p>Browsing through the lein-figwheel docs I found a reference to providing your own <a href="https://github.com/bhauman/lein-figwheel#static-file-server"><code>ring-handler</code></a>. Looking around further I noticed that Arne Brasseur's <a href="https://github.com/plexus/chestnut">chestnut</a> provides a custom <code>ring-handler</code> for a smoother development experience.</p>
<p>Using these hints I got it working fairly easily!</p>
<h2>Project updates</h2>
<p>First off, add the following dependencies to your <code>project.clj</code></p>
<div class="highlight"><pre class="highlight clojure"><code><span class="w"> </span><span class="p">[</span><span class="n">ring</span><span class="w"> </span><span class="s">"1.4.0"</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">ring/ring-defaults</span><span class="w"> </span><span class="s">"0.2.0"</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">compojure</span><span class="w"> </span><span class="s">"1.5.0"</span><span class="p">]</span><span class="w">
</span></code></pre></div>
<p>Still in your <code>project.clj</code>, add a <code>:ring-handler</code> option to the fighweel settings like so:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="w"> </span><span class="no">:figwheel</span><span class="w"> </span><span class="p">{</span><span class="no">:css-dirs</span><span class="w"> </span><span class="p">[</span><span class="s">"resources/public/css"</span><span class="p">]</span><span class="w">
</span><span class="no">:ring-handler</span><span class="w"> </span><span class="n">example.dev-server/http-handler</span><span class="p">}</span><span class="w">
</span><span class="c1">;; ^-- THIS</span><span class="w">
</span></code></pre></div>
<p>Now it is time to create the dev-server, simply open up <code>src/clj/example/dev_server.clj</code> (making the parent directories and setting the right namespace) and drop in the following:</p>
<div class="highlight"><pre class="highlight clojure"><code><span class="p">(</span><span class="nf">ns</span><span class="w"> </span><span class="n">example.dev-server</span><span class="w">
</span><span class="p">(</span><span class="no">:require</span><span class="w"> </span><span class="p">[</span><span class="n">clojure.java.io</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">io</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="n">compojure.core</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">[</span><span class="n">ANY</span><span class="w"> </span><span class="n">defroutes</span><span class="p">]]</span><span class="w">
</span><span class="p">[</span><span class="n">ring.middleware.defaults</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">[</span><span class="n">wrap-defaults</span><span class="w"> </span><span class="n">site-defaults</span><span class="p">]]))</span><span class="w">
</span><span class="p">(</span><span class="nf">defroutes</span><span class="w"> </span><span class="n">routes</span><span class="w">
</span><span class="p">(</span><span class="nf">ANY</span><span class="w"> </span><span class="s">"*"</span><span class="w"> </span><span class="n">_</span><span class="w">
</span><span class="p">{</span><span class="no">:status</span><span class="w"> </span><span class="mi">200</span><span class="w">
</span><span class="no">:headers</span><span class="w"> </span><span class="p">{</span><span class="s">"Content-Type"</span><span class="w"> </span><span class="s">"text/html; charset=utf-8"</span><span class="p">}</span><span class="w">
</span><span class="no">:body</span><span class="w"> </span><span class="p">(</span><span class="nf">io/input-stream</span><span class="w"> </span><span class="p">(</span><span class="nf">io/resource</span><span class="w"> </span><span class="s">"public/index.html"</span><span class="p">))}))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">http-handler</span><span class="w">
</span><span class="p">(</span><span class="nb">-></span><span class="w"> </span><span class="n">routes</span><span class="w">
</span><span class="p">(</span><span class="nf">wrap-defaults</span><span class="w"> </span><span class="n">site-defaults</span><span class="p">)))</span><span class="w">
</span></code></pre></div>
<p>Basically what this does is turn your <code>index.html</code> file into a 404 handler. The dev-server will serve up this file when figwheel can't find static assets in your project.</p>
<h2>Deployment</h2>
<p>Just for completeness sake, when deploying your figwheel build you'll need to add something like this to <code>server</code> directive in nginx:</p>
<div class="highlight"><pre class="highlight nginx"><code><span class="k">server</span> <span class="p">{</span>
<span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
<span class="kn">expires</span> <span class="s">-1</span><span class="p">;</span>
<span class="kn">try_files</span> <span class="nv">$uri</span> <span class="nv">$uri</span><span class="n">/</span> <span class="n">/index.html</span> <span class="p">=</span><span class="mi">404</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>That <a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files"><code>try_files</code></a> directive does the same as our ring middleware. It turns your index.html into a glorified 404 handler.</p>
<p>Beware though to properly do 404 handling in your client-side application when you don't recognize the URL, or if a backend API returns a 404. You don't have nice status codes now, which may or may not matter to your app.</p>
<h2>In closing</h2>
<p>I'm sure there are many more ways to get this done, so do some research before just pasting my code in :). For one, maybe setting up the dependencies in the development profile in leiningen instead of a hard dependency for the entire project.</p>
<p>There are many small things like this that makes ember-cli shine. But we can bring those refinements across bit by bit. Then again, figwheel offers an amazing developer experience on its own.</p>
<p>At the moment though I can say I'm very happy with learning Clojure & ClojureScript. There is still so much to learn!</p>
<p><em>Image from <a href="http://www.remonline.com/when-do-the-training-wheels-come-off/">REM online</a></em>.</p>