<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jeremy Woertink</title>
    <description>The latest articles on DEV Community by Jeremy Woertink (@jwoertink).</description>
    <link>https://dev.to/jwoertink</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F32485%2F77854424-7b22-4ee7-a22c-16110b7d4dca.jpeg</url>
      <title>DEV Community: Jeremy Woertink</title>
      <link>https://dev.to/jwoertink</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jwoertink"/>
    <language>en</language>
    <item>
      <title>Getting Lucky with HTMX</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Sat, 06 May 2023 16:11:06 +0000</pubDate>
      <link>https://dev.to/jwoertink/getting-lucky-with-htmx-57b3</link>
      <guid>https://dev.to/jwoertink/getting-lucky-with-htmx-57b3</guid>
      <description>&lt;h2&gt;
  
  
  A quick intro to HTMX
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://htmx.org/"&gt;HTMX&lt;/a&gt; is a small, dependency-less javascript library that sprinkles some fancy bindings on to your normal markup without you needing to write javascript. It gives you the ability to have things like asynchronous data fetching and dynamic content updates.&lt;/p&gt;

&lt;p&gt;I came across this post by &lt;a href="https://quii.dev/HTMX_is_the_Future"&gt;Chris James&lt;/a&gt; that sounded right up my alley.&lt;/p&gt;

&lt;p&gt;I was following right along, and things were sounding great, but then I read this line&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I did not write any JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and I was sold!&lt;/p&gt;

&lt;p&gt;For me, many of my applications just need minimal javascript for things like search autocomplete, toggling the view of something, or just delay loading hefty content.&lt;/p&gt;

&lt;p&gt;I decided to try this out in a Lucky app to see how quick can I get &lt;em&gt;something&lt;/em&gt; to show... (hint: it was really quick)&lt;/p&gt;

&lt;h3&gt;
  
  
  Example code
&lt;/h3&gt;

&lt;p&gt;To get started, you just need to include their package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/htmx.org@1.3.3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then somewhere on your page, you just use HTML attribute designated by their API to augment your markup.&lt;/p&gt;

&lt;p&gt;This example taken from the docs site &lt;a href="https://htmx.org/examples/click-to-load/"&gt;https://htmx.org/examples/click-to-load/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"replaceMe"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'btn'&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"/contacts/?page=2"&lt;/span&gt; 
                        &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#replaceMe"&lt;/span&gt; 
                        &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
         Load More Agents... &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"htmx-indicator"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/img/bars.svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will call back to your server hitting the &lt;code&gt;/contacts/?page=2&lt;/code&gt; path which will return some HTML. Then it takes that markup and replaces the &lt;code&gt;&amp;lt;tr id="replaceMe"&amp;gt;&lt;/code&gt; with the content that was fetched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Lucky for the server
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://luckyframework.org/"&gt;Lucky&lt;/a&gt; is a full-stack framework written in the &lt;a href="https://crystal-lang.org/"&gt;Crystal&lt;/a&gt; programming language. One of the neat benefits of using Lucky with Crystal is the typesafety you get.&lt;/p&gt;

&lt;p&gt;Lucky doesn't use standard HTML, but instead uses Crystal methods that generate HTML. This ensures that your markup is always rendered properly. No misspelling a tag name, or forgetting to close a tag. No passing in the wrong type and accidentally rendering something that looks like &lt;code&gt;[object Object]&lt;/code&gt;, or worse, rendering an empty string to the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example code
&lt;/h3&gt;

&lt;p&gt;This is an example of a simple HTML page in Lucky.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ShowPage&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MainLayout&lt;/span&gt;
  &lt;span class="n"&gt;needs&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;content&lt;/span&gt;
    &lt;span class="c1"&gt;# recognizable HTML tags&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"flex flex-col p-4"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"text-xl"&lt;/span&gt;
      &lt;span class="c1"&gt;# use of sharable components&lt;/span&gt;
      &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
      &lt;span class="c1"&gt;# built-in methods for displaying text&lt;/span&gt;
      &lt;span class="n"&gt;simple_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lucky meets HTMX
&lt;/h2&gt;

&lt;p&gt;Getting these two things together doesn't take too long. I will do a quick demonstration of an app where a user logs in, and on their "dashboard", some numbers are displayed from a timer. You can imagine that these numbers would be fed in to a chart or report that could update without the need of refreshing the browser.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At this point, if you're not familiar with Crystal or Lucky, just do your best to follow along. I'm skipping the setup/install process for brevity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check out the &lt;a href="https://luckyframework.org/guides/tutorial/overview"&gt;Lucky tutorial&lt;/a&gt; for later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating the app
&lt;/h3&gt;

&lt;p&gt;From the CLI, we will bootstrap the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lucky init
Project name?: htmx_demo
API only or full support for HTML and Webpack? (api/full): full
Generate authentication? (y/n): y
&amp;gt; cd htmx_demo
&amp;gt; ./script/setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Report
&lt;/h3&gt;

&lt;p&gt;This is a &lt;code&gt;Report&lt;/code&gt; model that just has a &lt;code&gt;count&lt;/code&gt; property. It'll be associated to a user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lucky gen.model Report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we will edit that new migration in &lt;code&gt;db/migrations/&lt;/code&gt; to add our new fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;migrate&lt;/span&gt;
  &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;table_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;primary_key&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int64&lt;/span&gt;
    &lt;span class="n"&gt;add_timestamps&lt;/span&gt;
    &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int32&lt;/span&gt;
    &lt;span class="n"&gt;add_belongs_to&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete: :cascade&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now update the model in &lt;code&gt;src/models/report.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Report&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseModel&lt;/span&gt;
  &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int32&lt;/span&gt;
    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And lastly, update the &lt;code&gt;User&lt;/code&gt; model in &lt;code&gt;src/models/user.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;
  &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;encrypted_password&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;

  &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="n"&gt;reports&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Report&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrate it the DB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lucky db.migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting up views
&lt;/h3&gt;

&lt;p&gt;First we will add HTMX to our layout in &lt;code&gt;src/components/shared/layout_head.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# add this line to the `head` block&lt;/span&gt;
&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="ss"&gt;src: &lt;/span&gt;&lt;span class="s2"&gt;"https://unpkg.com/htmx.org@1.3.3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the next part, we will be setting up the user's "dashboard" to render this "report". We will first generate a new component to use for this report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lucky gen.component Reports::List
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update that component in &lt;code&gt;src/components/reports/list.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;List&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseComponent&lt;/span&gt;
  &lt;span class="n"&gt;needs&lt;/span&gt; &lt;span class="n"&gt;reports&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;reports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;rep&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;rep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can update the user's dashboard on &lt;code&gt;src/pages/me/show_page.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;content&lt;/span&gt;
  &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="s2"&gt;"This is your profile"&lt;/span&gt;
  &lt;span class="n"&gt;h3&lt;/span&gt; &lt;span class="s2"&gt;"Email:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c1"&gt;# the actual HTMX magic&lt;/span&gt;
  &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;hx_get: &lt;/span&gt;&lt;span class="no"&gt;Stats&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;hx_trigger: &lt;/span&gt;&lt;span class="s2"&gt;"every 10s"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create a normal div that tells HTMX to do &lt;code&gt;HTTP GET&lt;/code&gt; to the &lt;code&gt;Stats::Index.path&lt;/code&gt; (which doesn't exist yet), and we tell it to do that "every 10s".&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the actions
&lt;/h3&gt;

&lt;p&gt;Now we need to define that &lt;code&gt;Stats::Index&lt;/code&gt; so it knows where to fetch the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lucky gen.action.browser Stats::Index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can update that action in &lt;code&gt;src/actions/stats/index.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stats&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BrowserAction&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/stats"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;reports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ReportQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;component&lt;/span&gt; &lt;span class="no"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reports: &lt;/span&gt;&lt;span class="n"&gt;reports&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wrap it up
&lt;/h3&gt;

&lt;p&gt;That's actually all it takes for the MVP just to see something in action. We can now boot up the app, create an account, and check it out.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Boot the app &lt;code&gt;lucky dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Visit the app in browser &lt;code&gt;http://localhost:3000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click the "go to app" button&lt;/li&gt;
&lt;li&gt;Create a new account and sign in&lt;/li&gt;
&lt;li&gt;See that there's nothing to display&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While you're on that page, go back to your terminal, and open a new terminal tab/window. We can jump in to the sql console and add some records manually to watch  them show up on the page!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lucky db.console

# insert into reports (user_id, count) values (1, 1);
# insert into reports (user_id, count) values (1, 9);
# insert into reports (user_id, count) values (1, 4);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think you get the idea :D &lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Every 10 seconds, a call will be made back to the &lt;code&gt;Stats::Index&lt;/code&gt; action which will return the &lt;code&gt;Reports::List&lt;/code&gt; component markup. We do have an issue here where if someone tries to visit &lt;code&gt;/stats&lt;/code&gt;, they'll get some broken markup. We can fix that by detecting HTMX calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/stats"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;reports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ReportQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HX-Request"&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
    &lt;span class="n"&gt;component&lt;/span&gt; &lt;span class="no"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reports: &lt;/span&gt;&lt;span class="n"&gt;reports&lt;/span&gt;
   &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="no"&gt;Stats&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IndexPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reports: &lt;/span&gt;&lt;span class="n"&gt;reports&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we'd create that &lt;code&gt;Stats::IndexPage&lt;/code&gt; to render the component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pages/stats/index_page.cr&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stats&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IndexPage&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MainLayout&lt;/span&gt;
  &lt;span class="n"&gt;needs&lt;/span&gt; &lt;span class="n"&gt;reports&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;content&lt;/span&gt;
    &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reports: &lt;/span&gt;&lt;span class="n"&gt;reports&lt;/span&gt;    
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you'll notice that when the &lt;code&gt;/me&lt;/code&gt; page first loads, there's no data... It takes 10 seconds before we see something. We can make some default data by telling the &lt;code&gt;Me::ShowPage&lt;/code&gt; that it &lt;code&gt;needs reports : Array(Report)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;hx_get: &lt;/span&gt;&lt;span class="no"&gt;Stats&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;hx_trigger: &lt;/span&gt;&lt;span class="s2"&gt;"every 10s"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reports: &lt;/span&gt;&lt;span class="n"&gt;reports&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's still a lot left to explore with this, and obviously HTMX isn't going to cover every case when it comes to building an interactive site, but if you don't need a ton of interactivity, this is a beautiful solution!&lt;/p&gt;

</description>
      <category>lucky</category>
      <category>crystal</category>
      <category>webdev</category>
      <category>htmx</category>
    </item>
    <item>
      <title>Programming in the Adult Entertainment Industry III</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Thu, 15 Sep 2022 15:53:43 +0000</pubDate>
      <link>https://dev.to/jwoertink/programming-in-the-adult-entertainment-industry-iii-1io7</link>
      <guid>https://dev.to/jwoertink/programming-in-the-adult-entertainment-industry-iii-1io7</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTE&lt;/em&gt; The following post talks about the use of adult toys, and though the post only pertains to code, I figured I'd give you a heads up anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Recently I was asked to add a feature to our site which integrates with the &lt;a href="https://developer.lovense.com/#introduction"&gt;Lovense (adult-toy) API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How this integrates with the site is through a live chat with a streamer. You as the viewer have the ability to send token tips. When you tip tokens, this makes the streamer's toy activate. It can be activated by strength of vibrations, length of time to vibrate, or in some cases, even control things like rotation. For example, 1 token may vibrate the toy at a low strength for 2 seconds, but 100 tokens may vibrate a high strength for 30 seconds.&lt;/p&gt;

&lt;p&gt;A streamer asked if anytime a token was tipped, if the chat could display the strength and length of time. We were already displaying when a tip was made, and we knew what settings the toy had because the API sends this to us. It should be easy!&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Testing the toy isn't that easy. There's quite a bit of setup that goes in to getting this working. You have two primary modes of setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The "dongle"&lt;/li&gt;
&lt;li&gt;Through mobile&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The dongle method requires an additional purchase of a USB-A key. Once you have this, you have to download and install an entire app pack from Lovense. This includes several apps and extensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Lovense Browser. It's a Chromium browser with their extension already installed&lt;/li&gt;
&lt;li&gt;Lovense connect. A desktop app for Windows and macOS to connect to the toy through bluetooth.&lt;/li&gt;
&lt;li&gt;OBS extensions for displaying integrated animations on your live streams (optional extras)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the Lovense connect open, you plug in the USB key, and it finds the toy and connects to it.&lt;/p&gt;

&lt;p&gt;When using the mobile setup, you open up the browser extension (Chrome, Firefox, or Chromium based), then scan a QR code through the mobile app, and it sets everything up for you.&lt;/p&gt;

&lt;p&gt;As a developer, when a streamer says "My toy won't connect", there's a lot that has to be considered.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are you connected through dongle, or mobile?&lt;/li&gt;
&lt;li&gt;If mobile, iOS or Android, and what version?&lt;/li&gt;
&lt;li&gt;If dongle, what version of Lovense Connect desktop?&lt;/li&gt;
&lt;li&gt;Is the toy charged?&lt;/li&gt;
&lt;li&gt;What OS are you on?&lt;/li&gt;
&lt;li&gt;What browser are you using?&lt;/li&gt;
&lt;li&gt;What version is the browser extension?&lt;/li&gt;
&lt;li&gt;Could this be an issue on our side from a recent update?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, it can be quite a complicated setup making it somewhat hard to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some code
&lt;/h2&gt;

&lt;p&gt;I'm using &lt;a href="https://crystal-lang.org/"&gt;Crystal Lang&lt;/a&gt; on the back-end. One thing about Crystal is that it's statically typed, even for the JSON. (This is important to the story).&lt;/p&gt;

&lt;p&gt;Setting up the front-end is fairly straight forward. Here's some javascript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;camScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;camScript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.lovense.com/cam-extension/static/js-sdk/broadcast.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;camScript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camExtension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CamExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;streamID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateDeviceSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ....        &lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toyStatusChange&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toys&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;toys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mainToy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mainToy&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;mainToy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateDeviceStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;settingsChange&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateDeviceSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;NOTE:&lt;/em&gt; I'm using VueJS, and there's a lot of stuff going on, so this isn't a copy/paste example.&lt;/p&gt;

&lt;p&gt;Basically how this works is, it adds a script that connects to the Lovense API. This assumes you have the Lovense browser extension installed on the browser running this code (i.e. the streamer). When the streamer updates their toy, a callback comes in and fires off an update to store those settings in the DB. &lt;/p&gt;

&lt;p&gt;Here's where it gets tricky... The settings are a JSON object that has a &lt;code&gt;levels&lt;/code&gt; key, and a &lt;code&gt;special&lt;/code&gt; key. Here's an example of a default level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"levels"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"level1"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"rLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"vLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when parsing this with Crystal, it's really easy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# parse the JSON&lt;/span&gt;
&lt;span class="n"&gt;device_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lovense_settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# iterate over the levels&lt;/span&gt;
&lt;span class="n"&gt;device_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"levels"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; "level1"&lt;/span&gt;
  &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 1&lt;/span&gt;
  &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 9&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With access to the data, I just needed a bit more info. Reviewing the original request, what I want is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;userXYZ tipped 1 token. Low vibrations for 5 seconds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I can see from the JSON that I have a &lt;code&gt;time&lt;/code&gt; key which gives me the number of seconds. I can also see the &lt;code&gt;vLevel&lt;/code&gt; (vibrate level) is set to 3 which is a "low" number. I was going to need a helper method for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;toy_strength_level_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="s2"&gt;"Low"&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="s2"&gt;"Medium"&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
    &lt;span class="s2"&gt;"High"&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="s2"&gt;"Ultra"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="s2"&gt;"Special"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finding the specific level looked a bit like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;selected_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;device_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"levels"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;token_count&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;token_count&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting this together, I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; tipped &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pluralize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;toy_strength_level_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vLevel"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; vibrations for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pluralize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"second"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Problem 1
&lt;/h2&gt;

&lt;p&gt;It turns out that when a streamer changes a setting, their extension saves the value as a &lt;code&gt;String&lt;/code&gt;. This isn't an issue for dynamic languages like javascript, but with static languages (especially Crystal), this doesn't quite work. Basically, the settings now looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"levels"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"level1"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"rLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"vLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;min&lt;/code&gt; is &lt;code&gt;"3"&lt;/code&gt; instead of &lt;code&gt;3&lt;/code&gt;, and &lt;code&gt;vLevel&lt;/code&gt; is &lt;code&gt;"5"&lt;/code&gt; instead of &lt;code&gt;5&lt;/code&gt;. How this affects Crystal is like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# runtime exception can't cast String to Int32&lt;/span&gt;
&lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i&lt;/span&gt;

&lt;span class="c1"&gt;# we can't cast right to integer if it's a string&lt;/span&gt;
&lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but now min is nilable... we can't assume it'll always be an integer, and we can't assume that casting it to a string and converting to an integer will always work. So for now, I just default to &lt;code&gt;0&lt;/code&gt;. This also means we need to do it for &lt;code&gt;max&lt;/code&gt;, and &lt;code&gt;vLevel&lt;/code&gt;, and &lt;code&gt;time&lt;/code&gt;, and &lt;code&gt;rLevel&lt;/code&gt;...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;yeah, you see where I'm going. It's not pretty. (and yes, I did abstract to a helper method... but still...)&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2
&lt;/h2&gt;

&lt;p&gt;I didn't notice this the first time (or second time) through the API, but &lt;code&gt;max&lt;/code&gt; on the last level actually returns the value &lt;code&gt;"infinity"&lt;/code&gt; lol. It took deploying to production before I realized that one.. oops!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# it's a string, so it casts, but it's not&lt;/span&gt;
&lt;span class="c1"&gt;# a number, so it doesn't convert.&lt;/span&gt;
&lt;span class="c1"&gt;# runtime exception&lt;/span&gt;
&lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_i?&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;str_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selected_level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s?&lt;/span&gt;
    &lt;span class="c1"&gt;# downcase just in case it comes through as Infinity&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;str_val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"infinity"&lt;/span&gt;
      &lt;span class="c1"&gt;# if I'm expecting a number, what do I default to?&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
       &lt;span class="n"&gt;str_val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Problem 3
&lt;/h2&gt;

&lt;p&gt;Remember I mentioned there's 2 main keys, &lt;code&gt;levels&lt;/code&gt;, and &lt;code&gt;special&lt;/code&gt;? The levels are standard for the toy; buzz this hard for this long. The specials, however, are more like "Earthquake" which will buzz in a square wave or sine wave pattern, or "RandomTime" which could be between 1 and 40 seconds. There's several others including "Clear" which will clear out the entire queue blowing out any tips previous users have sent. Devious.... But here's the kicker... None of the specials have &lt;code&gt;min&lt;/code&gt; or &lt;code&gt;max&lt;/code&gt;. Their structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"wave"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"160"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"clear"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"twowaves"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a whole new structure. I have to find the special that matches the token count exactly. Also notice that the &lt;code&gt;token&lt;/code&gt; key can be a String, Integer, or an empty String. &lt;code&gt;time&lt;/code&gt; can also be 0 which doesn't  necessarily mean "0 seconds" because &lt;code&gt;randomTime&lt;/code&gt; comes through as a time of 0. This also means I need to change how the message displays.&lt;/p&gt;

&lt;p&gt;It'll look more like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;userXYZ tipped 160 tokens. Wave vibration for 30 seconds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;userXYZ tipped 1000 tokens. Clear vibration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problem 4
&lt;/h2&gt;

&lt;p&gt;At this point, like most devs, we see a time to refactor. This code has become way too messy (and has tanked several production deploys due to not catching runtime edgecases...), so we can make it a lot cleaner.&lt;/p&gt;

&lt;p&gt;Crystal comes with a &lt;a href="https://crystal-lang.org/api/1.5.1/JSON/Serializable.html"&gt;Serializable&lt;/a&gt; module that allows you to create an object that receives some JSON, and normalizes all of the data in to easily callable objects.&lt;/p&gt;

&lt;p&gt;I took the time, refactored, wrote specs, and smiled at how much nicer all of the code looked. My model had this change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- column device_settings : JSON::Any = JSON::Any.new({} of String =&amp;gt; JSON::Any)
&lt;/span&gt;&lt;span class="gi"&gt;+ column device_settings : LovenseSettingsSerializer = LovenseSettingsSerializer.new, serialize: true
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now with that field being an object I can throw methods on to, it looked more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# so clean!&lt;/span&gt;
&lt;span class="n"&gt;selected_action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;device_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;selected_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; "Wave"&lt;/span&gt;
&lt;span class="n"&gt;selected_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But why is this a problem? Well.... We're using Apollo GraphQL on the front-end. Apollo likes to fire off the mutations/queries (&lt;code&gt;updateDeviceSettings&lt;/code&gt; in this case) when it sees that some value/variable has changed. We were checking to see if the settings we got back from Lovense were different from the settings we stored. If they were, then fire off this mutation to store the updated settings.&lt;/p&gt;

&lt;p&gt;Guess what? Since we normalized the data, we stored all Integer values, but the data coming from Lovense was a mix.... This meant the values were &lt;em&gt;always&lt;/em&gt; different. I'm sure you can see what this problem was.  We were running this update basically as fast as the machine could process it in an infinite loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 5
&lt;/h2&gt;

&lt;p&gt;As it turns out, if the streamer goes in to their Lovense toy settings, there's a section called "Chat notifications". They have an option to turn on and customize. In the javascript API, there's a &lt;code&gt;postMessage&lt;/code&gt; callback which receives a text message from Lovense with these events. When you receive these, you can send them in to your websocket channel or whatever you need.&lt;/p&gt;

&lt;p&gt;Once that was figured out, we noticed we had 2 toy notifications. The one solved by all of this complicated code, and the one that Lovense actually does automatically for you with all of (and more) the same information.... &lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;Rip out all the code, undo all the commits, and tell the streamers to just enable it if they want through their settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;Read docs, explore, test test test, and remember that programming is hard.&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>javascript</category>
      <category>nsfw</category>
    </item>
    <item>
      <title>Using GraphQL with Lucky</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Wed, 07 Apr 2021 01:22:08 +0000</pubDate>
      <link>https://dev.to/jwoertink/using-graphql-with-lucky-4fcb</link>
      <guid>https://dev.to/jwoertink/using-graphql-with-lucky-4fcb</guid>
      <description>&lt;p&gt;This is how to get &lt;a href="https://graphql.org/"&gt;GraphQL&lt;/a&gt; running with &lt;a href="https://luckyframework.org/"&gt;Lucky Framework&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preface
&lt;/h3&gt;

&lt;p&gt;I have a total of just 1 app that uses GraphQL under my belt, so I'm by no means an expert. Chances are, this setup is "bad" in terms of using GraphQL; however, it's working... so with that said, here's how I got it running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;We need to get our Lucky app setup first. We can use a quick shortcut and skip the wizard 😬&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lucky init.custom lucky_graph
&lt;span class="nb"&gt;cd &lt;/span&gt;lucky_graph
&lt;span class="c"&gt;# Edit your config/database.cr if you need&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we run the &lt;code&gt;setup&lt;/code&gt; script, we need to add our dependencies. We will add the &lt;a href="https://github.com/graphql-crystal/graphql"&gt;GraphQL shard&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# shard.yml&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;graphql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;graphql-crystal/graphql&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, now we can run our &lt;code&gt;./script/setup&lt;/code&gt; to install our shards, setup the DB, and all that fun stuff. Do that now....&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./script/setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then require the GraphQL shard require to your &lt;code&gt;./src/shards.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"avram"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"lucky"&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"graphql"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, before we go writing some code, let's generate our graph action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lucky gen.action.api Api::Graphql::Index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a new action in your &lt;code&gt;./src/actions/api/graphql/index.cr&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graph Action
&lt;/h2&gt;

&lt;p&gt;We generated an "index" file, but GraphQL does &lt;code&gt;POST&lt;/code&gt; requests... it's not quite "REST", but that's the whole point, right? 😅&lt;/p&gt;

&lt;p&gt;Let's open up that new action file, and update to work our GraphQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/actions/api/graphql/index.cr&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Graphql&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApiAction&lt;/span&gt;
  &lt;span class="c1"&gt;# NOTE: This is only for a test. I'll come back to it later&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SkipRequireAuthToken&lt;/span&gt;
  &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;

  &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s2"&gt;"/api/graphql"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;send_text_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_user?&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;
    &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;operation_name&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"operationName"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;variables&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_h&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a few things going on here, so I'll break them down.&lt;/p&gt;

&lt;h3&gt;
  
  
  send_text_response
&lt;/h3&gt;

&lt;p&gt;It's true Lucky has a &lt;code&gt;json()&lt;/code&gt; response method, but that method takes an object and calls &lt;code&gt;to_json&lt;/code&gt; on it. In our case, the &lt;code&gt;schema.execute()&lt;/code&gt; will return a json string. So passing that in to &lt;code&gt;json()&lt;/code&gt; would result in a super escaped json object string &lt;code&gt;"{\"key\":\"val\"}"&lt;/code&gt;. We can use &lt;code&gt;send_text_response&lt;/code&gt;, and tell it to return a json content-type.&lt;/p&gt;

&lt;h3&gt;
  
  
  param query
&lt;/h3&gt;

&lt;p&gt;When we make our GraphQL call from the front-end, our query will be the full formatted query (or mutation).&lt;/p&gt;

&lt;h3&gt;
  
  
  operation_name and variables
&lt;/h3&gt;

&lt;p&gt;When you send the GraphQL POST from your client, it might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"operationName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"FeaturedPosts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"query FeaturedPosts($limit: Integer!) {
  posts(featured: true, limit: $limit) {
    title
    slug
    content
    publishedAt
  }
 }"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can pull out the &lt;code&gt;operationName&lt;/code&gt;, and the &lt;code&gt;variables&lt;/code&gt; allowing the GraphQL shard to do some magic behind the scenes.&lt;/p&gt;

&lt;h3&gt;
  
  
  A few extra classes
&lt;/h3&gt;

&lt;p&gt;We have a few calls to some classes that don't exist, yet. We will need to add these next.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Graph::Context&lt;/code&gt; - A class that will contain access to our &lt;code&gt;current_user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Graph::Queries&lt;/code&gt; - A class where we will define what our graphql queries will do&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Graph::Mutations&lt;/code&gt; - A class where we will define what our graphql mutations will do&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Graph objects
&lt;/h2&gt;

&lt;p&gt;In GraphQL, you'll have all kinds of different objects to interact with. It's really its own mini little framework. You might have input objects, outcome objects, or possibly breaking your logic out in to mini bits. We can put all of this in to a new &lt;code&gt;src/graph/&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir ./src/graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make sure to require the new &lt;code&gt;graph/&lt;/code&gt; directory in &lt;code&gt;./src/app.cr&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ./src/app.cr&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./shards"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./app_database"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./models/base_model"&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./serializers/base_serializer"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./serializers/**"&lt;/span&gt;

&lt;span class="c1"&gt;# This should go here&lt;/span&gt;
&lt;span class="c1"&gt;# After your Models, Operations, Queries, Serializers&lt;/span&gt;
&lt;span class="c1"&gt;# but before Actions, Pages, Components, etc...&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./graph/*"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./actions/**"&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./app_server"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we will create all of the new Graph objects we will be using.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph::Context
&lt;/h3&gt;

&lt;p&gt;Create a new file in &lt;code&gt;./src/graph/context.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/graph/context.cr&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Context&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Context&lt;/span&gt;
  &lt;span class="kp"&gt;property&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@current_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Graph::Queries
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Graph::Queries&lt;/code&gt; object should contain methods that fetch data from the database. Generally these will use a Query object from your &lt;code&gt;./src/queries/&lt;/code&gt; directory, or just piggy back off the &lt;code&gt;current_user&lt;/code&gt; object as needed.&lt;/p&gt;

&lt;p&gt;Create a new file in &lt;code&gt;./src/graph/queries.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/graph/queries.cr&lt;/span&gt;
&lt;span class="nd"&gt;@[GraphQL::Object]&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Queries&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ObjectType&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueryType&lt;/span&gt;

  &lt;span class="nd"&gt;@[GraphQL::Field]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;me&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;UserSerializer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_user&lt;/span&gt;
      &lt;span class="no"&gt;UserSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query object starts with a single method &lt;code&gt;me&lt;/code&gt; which will return a serialized version of the &lt;code&gt;current_user&lt;/code&gt; if there is a &lt;code&gt;current_user&lt;/code&gt;. You'll notice all of the annotations. This GraphQL shard LOVES the annotations 😂&lt;/p&gt;

&lt;p&gt;For our queries to return a &lt;code&gt;Lucky::Serializer&lt;/code&gt; object like &lt;code&gt;UserSerializer&lt;/code&gt;, we'll need to update it and tell it that it's a GraphQL object.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;./src/serializers/user_serializer.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; src/serializers/user_serializer.cr
&lt;span class="gi"&gt;+ @[GraphQL::Object]
&lt;/span&gt;  class UserSerializer &amp;lt; BaseSerializer
&lt;span class="gi"&gt;+   include GraphQL::ObjectType
&lt;/span&gt;
    def initialize(@user : User)
    end

    def render
&lt;span class="gd"&gt;-     {email: @user.email}
&lt;/span&gt;    end

+   @[GraphQL::Field]
&lt;span class="gi"&gt;+   def email : String
+     @user.email
+   end
&lt;/span&gt;  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;That include could probably go in your &lt;code&gt;BaseSerializer&lt;/code&gt; if you wanted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Graph::Mutations
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Graph::Mutations&lt;/code&gt; object should contain methods that mutate the data (i.e. create, update, destroy). Generally these will call to your Operation objects from your &lt;code&gt;./src/operations/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Create a new file in &lt;code&gt;./src/graph/mutations.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/graph/mutations.cr&lt;/span&gt;
&lt;span class="nd"&gt;@[GraphQL::Object]&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mutations&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ObjectType&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MutationType&lt;/span&gt;

  &lt;span class="nd"&gt;@[GraphQL::Field]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;MutationOutcome&lt;/span&gt;
    &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MutationOutcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;success: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;SignInUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authenticated_user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;authenticated_user&lt;/span&gt;
        &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;outcome&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;MutationOutcome&lt;/code&gt; object here. We haven't created this yet, or mentioned it. The GraphQL shard requires that all of the methods have a return type signature, and that type has to be some supported object. This is just an example of what you could do, but really, the return object is up to you. You can have it return a &lt;code&gt;UserSerializer?&lt;/code&gt; as well if you wanted.&lt;/p&gt;

&lt;h3&gt;
  
  
  MutationOutcome
&lt;/h3&gt;

&lt;p&gt;The idea here is that we have some sort of generic object. It has two properties &lt;code&gt;success : Bool&lt;/code&gt; and &lt;code&gt;errors : String?&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create this file in &lt;code&gt;./src/graph/outcomes/mutation_outcome.cr&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/graph/outcomes/mutation_outcome.cr&lt;/span&gt;
&lt;span class="nd"&gt;@[GraphQL::Object]&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MutationOutcome&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ObjectType&lt;/span&gt;

  &lt;span class="n"&gt;setter&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;setter&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

  &lt;span class="nd"&gt;@[GraphQL::Field]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;success&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Bool&lt;/span&gt;
    &lt;span class="vi"&gt;@success&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nd"&gt;@[GraphQL::Field]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="vi"&gt;@errors&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By putting this in a nested outcomes directory, we can organize other potential outcomes we might want to add. We will need to require this directory right before the rest of the graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# update src/app.cr&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./graph/outcomes/*"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./graph/*"&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking the code
&lt;/h3&gt;

&lt;p&gt;Before we continue on the client side, let's make sure our app boots and everything is good. We'll need some data in our database to test that our client code works.&lt;/p&gt;

&lt;p&gt;Boot the app &lt;code&gt;lucky dev&lt;/code&gt;. There shouldn't be any compilation errors, but if there are, work through those, and I'll see you when you get back....&lt;/p&gt;

&lt;p&gt;Back? Cool. Now that the app is booted, go to your &lt;code&gt;/sign_up&lt;/code&gt; page, and create an account. For this test, just use the email &lt;code&gt;test@test.com&lt;/code&gt;, and password &lt;code&gt;password&lt;/code&gt;. We will update this &lt;code&gt;/me&lt;/code&gt; page with some code to test that the graph works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Client
&lt;/h2&gt;

&lt;p&gt;Now that the back-end is all setup, all we need to do is hook up the client side to actually make a call to the Graph.&lt;/p&gt;

&lt;p&gt;For this code, I'm going to stick to very bare-bones. Everyone has their own preference as to how they want the client end configured, so I'll leave most of it up to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a button
&lt;/h3&gt;

&lt;p&gt;Open up &lt;code&gt;./src/pages/me/show_page.cr&lt;/code&gt;, and add a button&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pages/me/show_page.cr&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Me&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ShowPage&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MainLayout&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;content&lt;/span&gt;
    &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="s2"&gt;"This is your profile"&lt;/span&gt;
    &lt;span class="n"&gt;h3&lt;/span&gt; &lt;span class="s2"&gt;"Email:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Add in this button&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Send Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"test-btn"&lt;/span&gt;
    &lt;span class="n"&gt;helpful_tips&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding JS
&lt;/h3&gt;

&lt;p&gt;We will add some setup code to &lt;code&gt;./src/js/app.js&lt;/code&gt; to get the client configured.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/js/app.js&lt;/span&gt;

&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rails/ujs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbolinks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendGraphQLTest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;operationName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
        mutation Login($email: String!, $password: String!) {
          login(email: $email, password: $password) {
            success
            errors
          }
        }
      `&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data returned:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbolinks:load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#test-btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendGraphQLTest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save that, head over to your browser and click the button. In your JS console, you should see an output showing &lt;code&gt;data.login.success&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Ok, we officially have a client side JS calling some GraphQL back in to Lucky. Obviously the client code isn't flexible, and chances are you're going to use something like &lt;a href="https://www.apollographql.com/docs/tutorial/client/"&gt;Apollo&lt;/a&gt; anyway.&lt;/p&gt;

&lt;p&gt;Before you go complicating the front-end, give this challenge a try:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove the &lt;code&gt;include Api::Auth::SkipRequireAuthToken&lt;/code&gt; from your &lt;code&gt;Api::Graphql::Index&lt;/code&gt; action.&lt;/li&gt;
&lt;li&gt;Try to make a query call to &lt;code&gt;me&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query Me {
  me {
    email
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how you get an error telling you you're unauthorized.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;MutationOutcome&lt;/code&gt; to include a &lt;code&gt;token : String?&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;Set the token property to &lt;code&gt;outcome.token = UserAuthToken.generate(authenticated_user)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Take the outcome token, and pass that back to make an authenticated call to the query Me.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;It's a ton of boilerplate, and setup... I get that, and I also think we can make it a lot better. If you have some ideas on making the Lucky / GraphQL connection better, or you see anything in this tutorial that doesn't quite follow a true graph flow, let me know! Come hop in to the &lt;a href="https://luckyframework.org/"&gt;Lucky Discord&lt;/a&gt; and we can chat more on how to take this to the next level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt;: It was brought up to me that the Serializer objects should probably move to Graph Type objects. With the serializers, the &lt;code&gt;render&lt;/code&gt; method is required to be defined, but if you don't have a separate API outside of GraphQL, then that &lt;code&gt;render&lt;/code&gt; method will never be called. You can remove the inheritence, and the &lt;code&gt;render&lt;/code&gt; method, and it should all still work!&lt;/p&gt;

</description>
      <category>lucky</category>
      <category>crystal</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Why is X slow?</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Fri, 04 Sep 2020 18:46:17 +0000</pubDate>
      <link>https://dev.to/jwoertink/why-is-x-slow-1a1e</link>
      <guid>https://dev.to/jwoertink/why-is-x-slow-1a1e</guid>
      <description>&lt;h2&gt;
  
  
  Blazing fast
&lt;/h2&gt;

&lt;p&gt;If you program in the JavaScript world, then you're probably really familiar (and maybe sick of) seeing packages say "blazing fast". At some point, we developers decided that we wanted to use dynamic languages like Ruby, Python, PHP, JS, etc... but we were still behind in speed world when compared to compiled languages.&lt;/p&gt;

&lt;p&gt;This point started becoming the obsession of many devs. You started seeing a project come up, then another project that did the exact same thing, but it was "faster". In the ruby world we saw this with the &lt;a href="https://github.com/faker-ruby/faker"&gt;faker&lt;/a&gt; gem and the &lt;a href="https://github.com/ffaker/ffaker"&gt;ffaker&lt;/a&gt; gem which stood for "faster faker". In the JavaScript world, we saw this with Node and IOjs... a fork of node that was being designed to be faster (among other political reasons). &lt;/p&gt;

&lt;p&gt;Doing this is sort of messed up, but understandable in some cases. On the one hand, why not just help improve the original project instead of just forking it and writing it your own way? On the other hand, many people do submit PRs to make the original faster, but the owner says something like "I don't like this implementation" or maybe there's extra red-tape in the way. That ends up sparking the "fine, I'll rename my fork and start a new project" which creates a subdivision in the community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is having more speed bad?
&lt;/h2&gt;

&lt;p&gt;No, not at all. This is a great thing. But is it the only thing? No. I think what a lot of developers tend to forget about is that there's a lot more that a project can provide than just "speed". For example, what if you had two identical projects, but one benchmarked twice as fast in response time, but the other benchmarked at a quarter the memory consumption? Which project do you choose? Or what about a project that benchmarks at 30% faster, 30% less memory, but the other project has better and easier to understand documentation with a friendlier community?&lt;/p&gt;

&lt;p&gt;Lastly, what about that benchmark you're using? What exactly is it benchmarking? Are you benchmarking an app running locally in a Docker container on your 2015 MBP, but your application will run behind a CDN on cloud network? The numbers you get might be a decent gauge to help you filter out what you may or may not want to use. For example, you may benchmark 2 different ways to write a method, and see how much faster one is over the other. Then if you get a 0.5% speed increase, but the implementation is not as readable, is it worth the update?&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark considerations
&lt;/h2&gt;

&lt;p&gt;It's a lot of work to setup a real good benchmark. There's just so many things to consider if you're trying to really compare "apples to apples". Before you blindly look at a benchmark, and make up your mind based on what it says, let's consider a few things first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are you looking for initially? Are you currently using something that could be improved by using something else? Is this research for a new project?&lt;/li&gt;
&lt;li&gt;How does it compare in speed vs the others? This is usually where we start anyway.&lt;/li&gt;
&lt;li&gt;How does it compare in memory consumption vs the others? One project might require 4GB RAM minimum before needing to scale, where another might only need 1GB RAM.&lt;/li&gt;
&lt;li&gt;How well is the codebase written? If you can quickly glance through the source, and understand how things are organized, and what things are doing, this may help you contribute to the project faster.&lt;/li&gt;
&lt;li&gt;Does the community align with your beliefs? Yes, this is not software related, but it's really important benchmark. Do you feel welcomed, or is it hard to get help? This can make or break your project deadlines within your company.&lt;/li&gt;
&lt;li&gt;How is the documentation? Is it all just auto-generated? Is there an easily navigable website? Is it up-to-date? Writing docs is tough, and keeping them up-to-date is tougher, but you don't have time to read the whole codebase just to figure out that the method you need requires 2 arguments.&lt;/li&gt;
&lt;li&gt;What other benefits do these projects offer? They might have specific goals, or features like "human readable error messages", or built-in development tooling.&lt;/li&gt;
&lt;li&gt;How often is the project updated? It's great if some project is actually "blazing fast", but if it's only updated every few months, what happens when your company runs in to a bug, and you reported it 2 months ago with no fix in sight?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;These are all of the main things I look at when considering some project. Don't shoot down a project just because you read 1 benchmark or reddit post that said "Why is X slow?". If you are curious why a specific project is slow, you need to first ask yourself, "what do I consider slow?". Next, give it a shot anyway and run your own personal test. You may just find out that you've had the best pizza of your life that one person on yelp rated 1 star... oh, I mean code... whatever, I'm hungry, it's lunch time. &lt;/p&gt;

</description>
      <category>programming</category>
    </item>
    <item>
      <title>Render Component from Action in Lucky</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Wed, 30 Oct 2019 18:43:43 +0000</pubDate>
      <link>https://dev.to/jwoertink/render-component-from-action-in-lucky-4ii2</link>
      <guid>https://dev.to/jwoertink/render-component-from-action-in-lucky-4ii2</guid>
      <description>&lt;p&gt;&lt;strong&gt;EDIT&lt;/strong&gt;: Since this writing, Lucky now has a &lt;code&gt;component&lt;/code&gt; method (built-in) you can use in your actions. No need to create a custom macro!&lt;/p&gt;

&lt;p&gt;For one of my Lucky apps, I'm using &lt;a href="https://stimulusjs.org/"&gt;Stimulus&lt;/a&gt; which is light-weight. I wanted to avoid doing a ton on the client side so that way I can keep all the nice benefits of Crystal and Lucky.&lt;/p&gt;

&lt;p&gt;In a recent instance, I had to load some comments on to a page asynchronously. Normally in this case, you might write some javascript to make an api call, then gather all of your records in a giant JSON object. Then you'd probably iterate over the result, and create some sort of component. Maybe in Vue, or in React, and have those render each comment. With Stimulus, you don't have built-int templating. You're left with writing a lot of &lt;code&gt;document.createElement&lt;/code&gt;, and &lt;code&gt;element.setAttribute&lt;/code&gt; type stuff. Or you just use lots of &lt;code&gt;innerHTML = ''&lt;/code&gt; type calls. &lt;/p&gt;

&lt;p&gt;What I wanted was to write my markup in Lucky, then make an API call that returns the markup, and I can just shove that in to an element and be done with it. &lt;/p&gt;

&lt;p&gt;By default, the actions in Lucky want you to render an HTML Page. But I didn't want to make a blank layout just for these. So here's what I did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/components/comments_list.cr&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommentsList&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseComponent&lt;/span&gt;
  &lt;span class="n"&gt;needs&lt;/span&gt; &lt;span class="n"&gt;save_comment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;SaveComment&lt;/span&gt;
  &lt;span class="n"&gt;needs&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;CommentQuery&lt;/span&gt;

  &lt;span class="c1"&gt;# Renders the comments form, and each comment&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;
    &lt;span class="c1"&gt;# This could also move to it's own component!&lt;/span&gt;
    &lt;span class="n"&gt;form_for&lt;/span&gt; &lt;span class="no"&gt;Comments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;textarea&lt;/span&gt; &lt;span class="n"&gt;save_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"field"&lt;/span&gt;
      &lt;span class="n"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Post Comment"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I just need a handy helper macro in my ApiAction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/actions/api_action.cr&lt;/span&gt;
&lt;span class="n"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiAction&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Lucky&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Action&lt;/span&gt;
  &lt;span class="n"&gt;accepted_formats&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:json&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;default: :json&lt;/span&gt;

  &lt;span class="k"&gt;macro&lt;/span&gt; &lt;span class="nf"&gt;render_component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send_text_response&lt;/span&gt;&lt;span class="p"&gt;({{&lt;/span&gt; &lt;span class="n"&gt;component_class&lt;/span&gt; &lt;span class="p"&gt;}}.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;assigns&lt;/span&gt; &lt;span class="p"&gt;%}&lt;/span&gt;
        &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}}:&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt;
      &lt;span class="p"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;%}&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I can use this new macro in my api actions!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/actions/api/comments/index.cr&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Comments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApiAction&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/api/comments"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;save_comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SaveComment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CommentQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;

    &lt;span class="n"&gt;render_component&lt;/span&gt; &lt;span class="no"&gt;CommentList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;save_comment: &lt;/span&gt;&lt;span class="n"&gt;save_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments: &lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, my javascript is pretty simple now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stimulus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/comments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Thoughts
&lt;/h2&gt;

&lt;p&gt;I know I left out several bits like, setting up stimulus, or what the main action / page look like, but all of those should be fairly straight forward. I'll probably do a writeup later on integrating stimulus with Lucky. Hopefully these examples should get you close should you need to use this!&lt;/p&gt;

</description>
      <category>lucky</category>
      <category>crystal</category>
    </item>
    <item>
      <title>Using custom IDs in Lucky</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Fri, 26 Jul 2019 21:01:56 +0000</pubDate>
      <link>https://dev.to/jwoertink/using-custom-ids-in-lucky-4ibh</link>
      <guid>https://dev.to/jwoertink/using-custom-ids-in-lucky-4ibh</guid>
      <description>&lt;p&gt;If you're using &lt;a href="https://www.luckyframework.org/"&gt;Lucky&lt;/a&gt;, and you have a schema that's not default, you may need to create a custom primary key.&lt;/p&gt;

&lt;p&gt;I had to do this for an app where my primary keys were defined in postgres like &lt;code&gt;character varying(18)&lt;/code&gt;. Just doing &lt;code&gt;String&lt;/code&gt; wasn't going to be good enough since Avram defines those as just &lt;code&gt;text&lt;/code&gt;. Here's how I did it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/models/concerns/custom_id.cr&lt;/span&gt;
&lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;CustomID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Avram::Migrator::Columns::PrimaryKeys&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomIDPrimaryKey&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Avram&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migrator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Columns&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PrimaryKeys&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;column_type&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;
      &lt;span class="s2"&gt;"character varying(18)"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Avram::Migrator::Columns&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomIDColumn&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Avram&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migrator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Columns&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="vi"&gt;@default&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@nilable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;column_type&lt;/span&gt;
      &lt;span class="s2"&gt;"character varying(18)"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Then in my model, I can now do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/models/user.cr&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseModel&lt;/span&gt;
  &lt;span class="n"&gt;skip_default_columns&lt;/span&gt;
  &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;primary_key&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;CustomID&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;created_on&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;updated_on&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>lucky</category>
      <category>crystal</category>
    </item>
    <item>
      <title>Deploying Lucky apps to Elastic Beanstalk</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Sat, 29 Jun 2019 17:09:58 +0000</pubDate>
      <link>https://dev.to/jwoertink/deploying-lucky-apps-to-elastic-beanstalk-2p0d</link>
      <guid>https://dev.to/jwoertink/deploying-lucky-apps-to-elastic-beanstalk-2p0d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This isn't the prettiest solution, but it's what worked for us. If you have ideas on how to clean this up, I'm all for suggestions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Developing locally on macOS&lt;/li&gt;
&lt;li&gt;Production using &lt;a href="https://aws.amazon.com/elasticbeanstalk/"&gt;Elastic Beanstalk&lt;/a&gt; container&lt;/li&gt;
&lt;li&gt;EB container uses Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The concept
&lt;/h2&gt;

&lt;p&gt;We had issues getting Crystal's cross compile to work properly, so we had to use a couple extra steps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boot Docker locally to compile release build of app&lt;/li&gt;
&lt;li&gt;Move release binary, and required files to temp directory&lt;/li&gt;
&lt;li&gt;Zip up temp directory to &lt;code&gt;app.zip&lt;/code&gt; as per EB deployment instructions&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;eb&lt;/code&gt; command locally to push &lt;code&gt;app.zip&lt;/code&gt; to EB container&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If we happen to get cross-compilation working, we could remove the first two steps in this process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;There's a few files we need to create. I'll define those first, then show the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./script/deploy&lt;/code&gt;. Be sure to &lt;code&gt;chmod +x ./script/deploy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./docker/BuildDockerfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./docker/docker_run_build.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./docker/Dockerfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./docker/Dockerrun.aws.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deploy script
&lt;/h3&gt;

&lt;p&gt;Used to actually push the code to production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# don't execute next commands on error&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'exit'&lt;/span&gt; ERR

&lt;span class="c"&gt;# let echo interpret escape chars (\n)&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; xpg_echo

&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="nv"&gt;START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y%m%d@%H%M%S'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build-temp-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;SHA1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;git rev-parse HEAD&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nv"&gt;RANDOM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000000 &lt;span class="s1"&gt;'BEGIN{srand(); print int(min+rand()*(max-min+1))}'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="c"&gt;# deploy to production when on master branch&lt;/span&gt;
&lt;span class="c"&gt;# deploy to staging when on other branches&lt;/span&gt;
&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git branch | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/^\* \(.*\)/\1/p'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"master"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;STAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nv"&gt;STAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$STAGE&lt;/span&gt;-&lt;span class="nv"&gt;$SHA1&lt;/span&gt;-&lt;span class="nv"&gt;$RANDOM&lt;/span&gt;-&lt;span class="nv"&gt;$DATE&lt;/span&gt;

&lt;span class="c"&gt;# Remove the old build&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; build/app

&lt;span class="c"&gt;# Build assets&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building Assets"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; public/assets/&lt;span class="k"&gt;*&lt;/span&gt; public/mix-manifest.json
yarn prod

&lt;span class="c"&gt;# Build the application&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting Docker"&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; lucky-app-build &lt;span class="nt"&gt;-f&lt;/span&gt; docker/BuildDockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/build:/app/build lucky-app-build

&lt;span class="c"&gt;# Create a temporary directory to stage the files&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Stage the files&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;STAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.env
&lt;span class="nb"&gt;cp &lt;/span&gt;build/app &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; public &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;docker/Dockerfile &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;docker/Dockerrun.aws.json &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; .ebextensions &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Getting them zipped up&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; zip &lt;span class="nt"&gt;-Xr&lt;/span&gt; app.zip &lt;span class="k"&gt;*&lt;/span&gt; .env .ebextensions&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;app.zip ../&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ..

&lt;span class="c"&gt;# Deploy the application&lt;/span&gt;
eb deploy &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="nv"&gt;$LABEL&lt;/span&gt;

&lt;span class="c"&gt;# Cleanup&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; app.zip

&lt;span class="nv"&gt;END&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="nb"&gt;echo &lt;/span&gt;Deploy ended with success! Time elapsed: &lt;span class="k"&gt;$((&lt;/span&gt;END-START&lt;span class="k"&gt;))&lt;/span&gt; seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BuildDockerfile
&lt;/h3&gt;

&lt;p&gt;Used for building the release binary&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM crystallang/crystal:0.29.0

ADD . /app
ADD ./docker/docker_run_build.sh /app/docker_run_build.sh

WORKDIR /app

RUN shards update &amp;amp;&amp;amp; \
    rm -rf /app/build/app &amp;amp;&amp;amp; \
    crystal build src/start_server.cr --release -o app

RUN chmod +x docker_run_build.sh

CMD ["./docker_run_build.sh"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker_run_build
&lt;/h3&gt;

&lt;p&gt;I honestly don't know why we have this, but it's here, so here it is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh

cp app /app/build/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;The actual Dockerfile used in production on EB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM phusion/baseimage

ENV APP_DOMAIN=localhost
ENV SECRET_KEY_BASE=abc123abc123
ENV LUCKY_ENV=production
ENV PORT=8000
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get -q update &amp;amp;&amp;amp; \
    apt-get -qy install build-essential libgc-dev libssl-dev libxml2-dev libyaml-dev libevent-dev &amp;amp;&amp;amp; \
    apt-get -y install tzdata &amp;amp;&amp;amp; \
    apt-get -y autoremove &amp;amp;&amp;amp; \
    apt-get -y clean &amp;amp;&amp;amp; \
    rm -rf /var/lib/apt/lists/* &amp;amp;&amp;amp; \
    rm -rf /tmp/*

RUN mkdir /app

ADD ./app /app
ADD ./.env /app/.env
ADD ./public /app/public

WORKDIR /app

EXPOSE 8000

CMD trap exit TERM; ./app &amp;amp; wait
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerrun
&lt;/h3&gt;

&lt;p&gt;This file is used by EB. Learn more on &lt;a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/single-container-docker-configuration.html"&gt;Single Container Docker&lt;/a&gt; for AWS EB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWSEBDockerrunVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Logging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/log/app.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Ports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ContainerPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8000"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;You'll want to make sure you update your &lt;code&gt;.gitignore&lt;/code&gt; file with things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/build*
*.zip
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And lastly, this all assumes you have your elastic beanstalk setup. That would require getting the &lt;code&gt;eb&lt;/code&gt; binary installed locally, getting your container setup, and adding in all your config stuff. &lt;/p&gt;

</description>
      <category>lucky</category>
      <category>crystal</category>
    </item>
    <item>
      <title>Do you really want to learn how to be a programmer?</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Wed, 06 Mar 2019 00:10:48 +0000</pubDate>
      <link>https://dev.to/jwoertink/do-you-really-want-to-learn-how-to-be-a-programmer-1m29</link>
      <guid>https://dev.to/jwoertink/do-you-really-want-to-learn-how-to-be-a-programmer-1m29</guid>
      <description>&lt;h2&gt;
  
  
  So you want to change careers?
&lt;/h2&gt;

&lt;p&gt;You hate your current job/career... You don't make much money, and it's not satisfying. You heard that learning to code is the future, and you can make great money. But is this really the career you want to choose?&lt;/p&gt;

&lt;p&gt;I want to talk about what it's like to be a programmer. It's easy to learn how to write code. &lt;code&gt;puts "This is code"&lt;/code&gt;; That's a full valid ruby program. That is literally code you might write at some point. Writing code doesn't make you a programmer though. I know plenty of people that know how to write code, but they're not programmers. Being a programmer requires a specific mindset, and hopefully this post will help you to decide if you have that specific mindset or not.&lt;/p&gt;

&lt;p&gt;Let's start with an analogy, shall we? What would you consider to be the difference between a "cook" and a "chef"? Well, for me, a cook can follow a recipe and prepare a meal. A chef can create a recipe. How about a classical musician VS a jazz musician? They both can read sheet music, but a jazz musician can think on the fly to create music. This thought process is what separates the two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's look at code
&lt;/h2&gt;

&lt;p&gt;Writing code is similar to learning to speak a new language. There's plenty of spoken languages out there from Spanish, to German, or French, Swahili, Mandarin, and many others. In programming we have Ruby, Javascript, PHP, Python, C++, Rust, Crystal, and a million more. Each language is a tool that aims to have a very specific usage with pros and cons for each. We don't have to know all of them, we only need to the ones we want to use to complete a specific task. &lt;/p&gt;

&lt;p&gt;So what does this look like?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Basic variable assignment in Ruby&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Basic variable assignment in Javascript&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c"&gt;// Basic variable assignment in Rust&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Basic variable assignment in PHP&lt;/span&gt;
&lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see from these examples, we are just doing a little basic math in a few different languages. If you stare at each of these examples close enough like you're looking at one of those &lt;a href="http://mediad.publicbroadcasting.net/p/shared/npr/201805/196231009.png"&gt;Highlights Magazine image difference&lt;/a&gt; pictures, you'll see a few similarities, and a few differences between them.&lt;/p&gt;

&lt;p&gt;So the first question we must answer is, would you be ok with looking at this code every day? If your first reaction is "that seems really boring", then you'd be right! It &lt;strong&gt;is&lt;/strong&gt; really boring. But staring at code isn't what make being a programmer fun, it's the problem solving. Though, you do stare at a LOT of code, so you have to be ready for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem solving 101
&lt;/h2&gt;

&lt;p&gt;Let's think of something you know how to do now. Maybe shuffling some playing cards? The problem is that I don't know how to shuffle a deck of cards, so your goal is to teach me. There's a few "gotchas" though. You have to do it over the phone, and you can't see what I'm doing, and I can't see you. I've also never seen a deck of cards shuffled in my life, so I have no point of reference as to what it should even look like. All I know is that I've been tasked with shuffling a deck of cards, and I need you to teach me how. &lt;/p&gt;

&lt;p&gt;Where do we begin? Take a moment to think through the steps of how you shuffle cards. There's several different ways that people do it, but how do you do it? Picture your hand movements. Picture the placement of the cards. Now explain to me step by step how to shuffle.&lt;/p&gt;

&lt;p&gt;Now let's take this one step further. Imagine we're in this situation, but we don't speak the same language. You have to explain to a translator, then the translator explains to me. I report back to the translator who then tells you what I said. Think of how much longer this will take to teach me now. Remember, we're doing this over the phone, so you can't just show me. This is problem solving for a programmer. Computers only understand 2 things: &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;. That's it, binary. We have to write code similar to what's above and pass that to a translator that in turn translates to the computer. Sometimes you'll say something to the translator with one thing in mind, but the translator interprets it a specific way doing what you said and not what you meant. The computer gets these instructions, and now you have a bug in your app because it's doing something different than what you meant for it to do.&lt;/p&gt;

&lt;p&gt;Here's an exercise for you to try on your own. If I have a song to be processed, and it costs $0.03 per minute but only if I go over 100 minutes otherwise it's $0.04 per minute plus $1 for each song. Then I have to process an album of 12 songs which is about 2 hours of music. How much would it cost me to process the music? Don't use a calculator to figure this out. Instead, write out the formula. I should be able to replace the length of the songs and the number of songs to calculate what my next album or someone else's album may cost. &lt;/p&gt;

&lt;p&gt;That is called writing an algorithm inside of a function. Did you have fun doing that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem solving 102
&lt;/h2&gt;

&lt;p&gt;Let's imagine we're talking about being a chef, and not a programmer. You need to make a sandwich. You might think to yourself "that shouldn't be too hard". Well, this is what every non-programmer thinks when they come to me asking me to build them a website or app. Yeah, you'd like to &lt;em&gt;think&lt;/em&gt; it wouldn't be too difficult, but let's talk about this sandwich.&lt;/p&gt;

&lt;p&gt;We're going to make a smoked ham, turkey, and smoked cheddar sandwich on an italian roll with lettuce, tomato, onion, pickle, olive, spicy peppers, vinegar, oil, salt, and oregano. Sounds pretty delicious, right? What, you don't want the peppers on yours? Ok, fine, we'll make a second one that doesn't have that on there for you. Oh, but someone else doesn't want the onion or pickles? Hmm... So I guess we just make a third one like that? Oh great, now someone else wants to add black pepper, and no olives. At this point we have an unknown number of permutations of sandwich combinations, and we haven't even started prep!&lt;/p&gt;

&lt;p&gt;Before we start making the sandwich, we have to weigh our options for ingredients. Do we save money and make our own bread? Or do we buy bread to save time? If we make our own, we can really customize it to taste exactly how we want, but it will take a bit more work. If we buy it from a bakery, we are at the mercy of that bakery. What happens if they mess up the bread? What happens if they go out of business? What happens if they run out of the bread we use? What happens if their cost goes up?&lt;/p&gt;

&lt;p&gt;Now we need to do this for our meats and cheese. Do we buy cheddar, and ham, then smoke them ourselves? Or buy them pre-smoked? Have you ever made ham? It doesn't fall off the pig ready to eat, you have to cure it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The duration of the curing process varies by the type of ham, with, for example, Serrano ham curing in 9–12 months (&lt;a href="https://en.m.wikipedia.org/wiki/Ham"&gt;https://en.m.wikipedia.org/wiki/Ham&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we decide to go this route, we won't have our sandwich for the next year. &lt;/p&gt;

&lt;p&gt;If you're following along still, you can see that this 3 minute sandwich could potentially be a year long process, or it could be really expensive. This is not far off from programming. There's so much more that goes in to writing code and building an app. I often hear "well, can't you just use someone else's code? You can't be the first to do this." Well, yes, I can in fact. That is, provided the person that wrote that code has given it a license that allows me to use it. I know we're programmers, but now we have to consider the law. If they wrote the code for all to use for free, can I reuse it and sell it for money legally? Maybe I have to pay them to use their code? Can you afford that cost?&lt;/p&gt;

&lt;h2&gt;
  
  
  Clearing up some misconceptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't take long to build an app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We need to first determine the type of app. It could take a week, or it could take a year. I have no idea without understanding the entire scope of the project, and the amount of money the project has to spend.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A programmer can build an app alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some can, yes. Of those "some", only some of those know how to design. Have you seen an app that looks ugly? Yeah, they probably didn't hire a designer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selling an app is easy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No. It's not. Someone buying an app will want to get their money back in about 2 years or so. If you want to sell for $1 Million, you better hope your app makes near $500k/year. If not, then it has to be revolutionary technology, or have a few million active users on it. Keep in mind that the more users you have, the more expensive it is to run your app. &lt;a href="https://www.businessinsider.com/lyft-ipo-amazon-web-services-2019-3"&gt;Lyft&lt;/a&gt; is spending $8 Million a month to run their app. That doesn't include employee cost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Working on a computer all day is easy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wrist pains, shoulder pains, back pains, fatigue mentally and eye strain. Have you ever laid in bed so long that you felt tired when you got up, and you felt sore? Our bodies aren't designed to stay in one spot for several hours a day unless we're sleeping. Is it easier than doing construction? Maybe, but then again, I'd say you get fit doing that work so when you get off work, you just relax. With being a programmer, you get off work and need to go be active like hitting the gym and stuff otherwise your health drops real quick.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programmers make a lot of money&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In most cases, yes. However, this isn't always the case. My first programming job was $12/hr. I was coming from a min wage job ($7.50/hr), so this was a bump for me. My next job was $15/hr. I was stoked to have a pay raise. After a few years, I was at a job making $55k/yr but then finding out that other companies were starting their devs out at $50k/yr. People without a degree and less time working were making basically what I was making. &lt;/p&gt;

&lt;p&gt;Now we have devs in the silicon valley making anywhere between 45% equity while living out of cardboard boxes to a crazy 7 figure salary both doing the same job. Your amount will be based on so many factors. You may or may not make a lot of money. Sound like every other job?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything has been programmed already, just copy others code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sure, lots of code has already been written. That doesn't mean we have access to &lt;br&gt;
that code. It also doesn't mean that it's been written in a language I understand. Just because google solved a problem, doesn't mean I can solve that same problem. They have tons of engineers with doctorate degrees in mathematics. I have a degree in playing tuba. This job involves a lot of researching, and digging around, and playing with different bits to see what works and what doesn't.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Becoming a programmer doesn't take much time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a lot of people that go through a 3 month bootcamp and get a job as a jr dev right after. These are the people that have a very decent level knowledge of computers before starting the class. Then they worked their ass off and worked outside of class for those 3 months. These people are usually really inquisitive types that pick up on the concepts rather quick. For me, it took at least a year of doing this full time before things really started to click for me. Concepts don't come naturally to me like they do for some others, so I have to study, and do the rinse/repeat often before I really get something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Recap
&lt;/h2&gt;

&lt;p&gt;Are you still interested in becoming a programmer? Let's go back over everything real quick to be sure.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You're going to have to learn a lot of technology, and it's a never ending battle of learning. What you learned last week may be out dated next week.&lt;/li&gt;
&lt;li&gt;You'll stare at code most of the day, almost every day, and sometimes you won't understand it.&lt;/li&gt;
&lt;li&gt;Being a problem solver is the key. You're just trying to solve everyone's problems. Your goal is to work your way out of a job by making things so automated, you don't need to do anything.&lt;/li&gt;
&lt;li&gt;By trying to work your way out of a job, you'll create bugs that keep your job, and make you very frustrated.&lt;/li&gt;
&lt;li&gt;Everyone will want you to build them an app, but that doesn't mean you'll be able to.&lt;/li&gt;
&lt;li&gt;You're not guaranteed to make a ton of money.&lt;/li&gt;
&lt;li&gt;You're also not guaranteed to work less than 40 hours in a week. (Especially when you first start out).&lt;/li&gt;
&lt;li&gt;Desk jobs can be bad for your health. Plan accordingly&lt;/li&gt;
&lt;li&gt;The level of stress you get can be bad for your health. Plan accordingly&lt;/li&gt;
&lt;li&gt;Your job may be to handle the data of a billion dollar company. (see #9)&lt;/li&gt;
&lt;li&gt;You will make a mistake, and it could potentially cost the company a ton of money. (see #9)&lt;/li&gt;
&lt;li&gt;Are you willing to "nerd out" on the latest CVE patch fixing a year long "monkey patch" you've had allowing you to delete 100 LOC?&lt;/li&gt;
&lt;li&gt;Does reading #12 make you cringe?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're at this point feeling a little nervous, but still thinking it'll be a better move for you in the long run, then I say go for it! It's an amazing job, and you can have a lot of fun with it. &lt;/p&gt;

&lt;h3&gt;
  
  
  Side note:
&lt;/h3&gt;

&lt;p&gt;If you're already a programmer, leave your thoughts to how your day looks below. Different perspectives always help newcomers. &lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Programming in the Adult Entertainment Industry II</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Wed, 27 Feb 2019 16:56:00 +0000</pubDate>
      <link>https://dev.to/jwoertink/programming-in-the-adult-entertainment-industry-ii-kpd</link>
      <guid>https://dev.to/jwoertink/programming-in-the-adult-entertainment-industry-ii-kpd</guid>
      <description>&lt;p&gt;When you start programming in the Adult Entertainment industry, things start to get a little tricky. A lot of systems you used and took advantage of with previous apps are no longer available to you. It's mainly different sites &lt;a href="https://en.wikipedia.org/wiki/Terms_of_service"&gt;TOS&lt;/a&gt; prohibiting you using their service when associated with explicit material, or something like cannabis related.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emailing
&lt;/h3&gt;

&lt;p&gt;Let's take emailing for example! There's tons of email services like &lt;a href="https://mailchimp.com/"&gt;MailChimp&lt;/a&gt;, &lt;a href="https://www.mailgun.com/"&gt;MailGun&lt;/a&gt;, &lt;a href="https://sendgrid.com/"&gt;SendGrid&lt;/a&gt; and many others. But the problem is, we can't just choose the best option, or the cheapest option. We have to first see if the service would even allow for sending out any adult related content. I'm not talking about emailing porn, I'm simply talking about transactional emails like a password reset email. A simple all text related email, but it happens to be coming from "some_explicit_domain.com". So in this case, we might use something like "yourfunadultsite.com" or sometimes something even less direct like "6E92119A91B70F8BEDCC2720C69CE1CB.com". Those usually end up leading us to high bounce rates, and spam reports though.&lt;/p&gt;

&lt;p&gt;That's before we even start talking about using the service. Once we find a service that allows for sending stuff out, we have to be extra conscious about bounce rates. When people sign up for these type of sites, no one wants their personal email attached. So what do they do? They use a throw away email. This means we now have a member we can't send specials to, we can't email for billing issues, we can't contact to say we need to go down for some server maintenance. We have this person giving us money that we don't have an easy way to contact. Another thing is a lot of people will just use a fake email that doesn't exist. This would raise our bounce rate and potentially get us banned with the service we chose.&lt;/p&gt;

&lt;h3&gt;
  
  
  CDN and Hosting
&lt;/h3&gt;

&lt;p&gt;Well, you can't just choose any host, that would be too easy. The host has to be cool with what you're selling. Luckily most hosts these days are; however, that wasn't the case just 10 years ago.&lt;/p&gt;

&lt;p&gt;The tricky part these days comes with the sheer amount of data. A small company with a few thousand videos could have something ridiculous like 9PB of data... yeah, that's a P... (7 * 10^15). Could you imagine trying to make a backup? Now think of what a larger video company would have with hundreds of thousands of content files...&lt;/p&gt;

&lt;p&gt;Amazon now has these devices called &lt;a href="https://aws.amazon.com/snowball/"&gt;AWS Snowball&lt;/a&gt; that they ship you. You plug in to your physical server and copy your data, then ship the device to a new location. Unless you're working with massive physical data, this is not something you'd really ever come across.&lt;/p&gt;

&lt;p&gt;As well as the hosting, you have to look at the CDN. It's common to have users all across the globe for your sites. You want to make sure latency and connection is as fast as possible because if it takes someone longer than a few seconds to load your videos, they will move on quickly. I mean, there's no shortage out there 😛. A CDN will help that out, but you have to be sure your CDN allows for that. They are basically making a duplicate copy of your site and spreading it all over the world.&lt;/p&gt;

&lt;p&gt;Once you find a CDN, you have to consider what your traffic and bandwidth look like. These types of sites are such high traffic and high bandwidth that it gets expensive REAL quick. If you take a look at &lt;a href="https://www.cloudflare.com/plans/"&gt;Cloudflare&lt;/a&gt;, you'll see the $200/mo plan (per domain). Since these sites are generally based around multi-tennant apps, you'll run 30+ sites off the same app easily. Oh, then you need Load Balancing, Rate Limiting, and SSL?&lt;/p&gt;

&lt;h3&gt;
  
  
  How about something more positive?
&lt;/h3&gt;

&lt;p&gt;With so many things that are a challenge with this industry, why even work in it? Well, there are also some really great things (I'm talking about the dev side). For example, learning about Machine Learning stuff which could allow you to build a video recommendation engine. You watch a video, and then based on how long you watched it, which actors are in that video, maybe the types of things (we'll just call them tags) are in the video. Maybe also taking in to account how many people have commented on the video and what the average star rating is. With all of this data, we can now build a system that says "You might enjoy watching these videos as well...". Pretty much any video site you go to like youtube, or twitch, they're going to have some system that says because you watched video X, you may like video Y.&lt;/p&gt;

&lt;p&gt;Me personally, I hate the way youtube does it. I checked out one football video to see what people were talking about, and now all the videos recommended are sports related. They must not know me though since I don't watch sports. This is a tricky problem to solve, but not impossible. That's the sort of challenge you get with stuff like this.&lt;/p&gt;

&lt;p&gt;Also dealing with videos, you have to be concerned with video encoding. Most companies shoot the content in 4k these days, but not everyone can watch in 4k. Most devices won't really show a difference between 1080p and 4k anyway. So might as well save some bandwidth by showing a different quality version based on the user's device. We take a single video, and turn it in to several different files from a low res 420 to full HD 4k.Then by using HLS streaming, we can stream the right version to you based on things like your connection, screen size, etc... &lt;/p&gt;

&lt;p&gt;Some decent video encoding services are &lt;a href="https://coconut.co/"&gt;Coconut&lt;/a&gt;, &lt;a href="https://zencoder.com/en/"&gt;Zencoder&lt;/a&gt;, &lt;a href="https://bitmovin.com/encoding-service/"&gt;Bitmovin&lt;/a&gt;, and &lt;a href="http://www.telestream.net/"&gt;Telestream&lt;/a&gt;. So far Coconut seems to be the cheapest as well as providing a very nice API. Nice APIs are hard to come by in these industries.&lt;/p&gt;

&lt;p&gt;Now, I've only dealt with the adult video industry, but the cannabis industry is running in to a lot of the same issues. It's legal in a lot of places now. Way more than it was 10 years ago. That means there's a new thriving industry to build awesome new apps. For example, I saw a site that had grow stats for different people. Each day it showed the humidity, temperature, soil saturation, sun light, and tons of other unique data for each day a plant was growing. Building a system that sends data to a site through an API is something a lot of developers never get to work with. &lt;/p&gt;

&lt;p&gt;Sadly, because that industry falls in line with the "gray" area, it also gets a lot of the same stigma. When I worked in the poker industry a few years back, it was also in the same line. You could have a poker app, but it had to be hosted in Nevada, and only played by people with a Nevada based IP. &lt;/p&gt;

&lt;p&gt;Have you worked in any industry that has some unique challenges? Share your experiences! &lt;/p&gt;

</description>
      <category>nsfw</category>
      <category>programming</category>
    </item>
    <item>
      <title>Background Jobs with Lucky</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Thu, 14 Feb 2019 23:19:28 +0000</pubDate>
      <link>https://dev.to/jwoertink/background-jobs-with-lucky-543p</link>
      <guid>https://dev.to/jwoertink/background-jobs-with-lucky-543p</guid>
      <description>&lt;p&gt;As with a lot of web applications and APIs, there comes a point where you need to process some tasks in the background. To do this, you'll usually use a background job processor. This helps to make some things asynchronous and speed along the users of your app. &lt;/p&gt;

&lt;p&gt;An example of this might be logging when your users login to your site. Maybe you store the IP address they used, when they logged in, how many attempts it took to sign in, and the device they used. If you did all this in-line, then the process would be to track all that info, and the user has to wait while you create this new record in your database before they can actually login. Or, you push that info to a background job, and log the user in. Then you can update that record at your own pace since there's nothing relying on the data.&lt;/p&gt;

&lt;p&gt;If you're new to &lt;a href="https://crystal-lang.org/"&gt;Crystal Lang&lt;/a&gt; and/or you're new to &lt;a href="https://luckyframework.org/"&gt;Lucky&lt;/a&gt;, but you're coming from another language like Rails, you may be familiar with Sidekiq. Now, &lt;a href="https://github.com/mperham/sidekiq.cr/"&gt;Sidekiq&lt;/a&gt; does exist in the Crystal world which is amazing. It even exists by the same guy that created the Ruby version! But at the time of this writing, I spent nearly an hour trying to figure out how to get it integrated with Lucky, and I was running in to quite a few issues. Thankfully, I found an alternative that I had installed and running locally in maybe 2 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mosquito
&lt;/h2&gt;

&lt;p&gt;Take a look at &lt;a href="https://github.com/robacarp/mosquito"&gt;Mosquito&lt;/a&gt;. The API for it really isn't too far off from what Sidekiq uses, so for me, the concept was quick and easy. This also uses Redis, and since I was going to use Redis with Sidekiq, there's no additional things I needed to install.&lt;/p&gt;

&lt;p&gt;Assuming you have your Lucky app all setup and ready to go, you can start by adding Mosquito in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mosquito&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;robacarp/mosquito&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your &lt;code&gt;shards install&lt;/code&gt;, and you'll be good to go.&lt;/p&gt;

&lt;p&gt;Now we just need to setup our mosquito runner. Create a new file &lt;code&gt;./src/mosquito.cr&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./dependencies"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./workers/*"&lt;/span&gt;

&lt;span class="no"&gt;Mosquito&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up workers
&lt;/h2&gt;

&lt;p&gt;Next we need to setup the workers. Create a new folder &lt;code&gt;./src/workers/&lt;/code&gt;, and let's make a &lt;code&gt;login_worker.cr&lt;/code&gt; file in there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginWorker&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Mosquito&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueuedJob&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int64&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UserQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
      &lt;span class="c1"&gt;# create new Login record from the user&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the worker
&lt;/h2&gt;

&lt;p&gt;At some point we have to kick off the &lt;code&gt;LoginWorker&lt;/code&gt; we made, so in one of the actions, we can add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s2"&gt;"/login"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SessionForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
      &lt;span class="no"&gt;LoginWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;
      &lt;span class="c1"&gt;# redirect logged in&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# show your errors&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Booting the whole thing
&lt;/h2&gt;

&lt;p&gt;Now that you have mosquito installed, and setup, you'll just need to boot the whole app. Since Mosquito runs in it's own process, you have to run it in a separate process.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;Procfile.dev&lt;/code&gt; add in the line &lt;code&gt;worker: crystal src/mosquito.cr&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: lucky watch --reload-browser
assets: yarn watch
worker: crystal src/mosquito.cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you run &lt;code&gt;lucky dev&lt;/code&gt;, it'll boot mosquito in it's on separate process! &lt;/p&gt;

&lt;h3&gt;
  
  
  ProTip:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; If your app is like mine, and just an api, your &lt;code&gt;Procfile.dev&lt;/code&gt; might look more like
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: crystal src/server.cr
worker: crystal src/mosquito.cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Mosquito needs to know about your stack since it runs in a separate process. If you're trying to slim this down, just keep in mind that whatever you put in your worker file, you'll need to require that separately from your app stack.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;By default, Mosquito looks at your &lt;code&gt;REDIS_URL&lt;/code&gt; ENV, or pulls just a normal localhost value. Be sure to update that for production, as well as your &lt;code&gt;Procfile&lt;/code&gt; for your production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you would rather build the targets (maybe for docker), and run those separately, you can update your &lt;code&gt;shard.yml&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;targets:
  server:
    main: src/server.cr
  mosquito:
    main: src/mosquito.cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;shards build --production --static --release --no-debug&lt;/code&gt; to get your &lt;code&gt;server&lt;/code&gt; and &lt;code&gt;mosquito&lt;/code&gt; binary files. Just run those however you need, and you should be good.&lt;/p&gt;

</description>
      <category>lucky</category>
      <category>crystal</category>
    </item>
    <item>
      <title>5 reasons you should learn Lua</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Fri, 14 Dec 2018 17:37:32 +0000</pubDate>
      <link>https://dev.to/jwoertink/5-reasons-you-should-learn-lua-39ca</link>
      <guid>https://dev.to/jwoertink/5-reasons-you-should-learn-lua-39ca</guid>
      <description>&lt;p&gt;If you haven't heard of &lt;a href="https://www.lua.org/"&gt;Lua&lt;/a&gt;, it's a really small programming language. Maybe you have heard of it, but never had a reason to learn it because your programming language of choice does everything you need it to. Well, here are a few reasons you should learn Lua anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.
&lt;/h2&gt;

&lt;p&gt;Have you heard/used &lt;a href="https://www.nginx.com/"&gt;nginx&lt;/a&gt;? You can use mini Lua scripts inside of nginx with the &lt;a href="https://github.com/openresty/lua-nginx-module"&gt;openresty setup&lt;/a&gt;. This would allow you to do things like dynamically assign SSL certs through Let's Encrypt to different domains.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;auto_ssl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"allow_domain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"^(mysite.com|yoursite.com)$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ijo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use Apahce instead of nginx? Lua still has you &lt;a href="http://httpd.apache.org/docs/trunk/developer/lua.html"&gt;covered&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.
&lt;/h2&gt;

&lt;p&gt;Another popular tool that is used in the web is &lt;a href="https://redis.io/"&gt;Redis&lt;/a&gt;. If you use redis to store lists of timeline event IDs or something, you may need to do some crazy sorting on this data. Writing &lt;code&gt;RPOPLPUSH&lt;/code&gt; statements in redis can get a little crazy, and sometimes the language you choose may not be as fast as you'd like. For example, if you're using ruby, there's a gem called &lt;a href="https://github.com/Shopify/wolverine"&gt;Wolverine&lt;/a&gt;. You write a Lua script that does all the crazy sorting you need, and then pass that data back to your rails app. This gives you a massive performance boost.&lt;/p&gt;

&lt;h2&gt;
  
  
  3.
&lt;/h2&gt;

&lt;p&gt;If you need lots of performance for your web app, but you don't have the capacity to re-write in something different, then you may employ heavy cache in to your app. One way to do this is to throw &lt;a href="http://varnish-cache.org/"&gt;Varnish&lt;/a&gt; in front of your app. Varnish will handle all the cache, and only proxy back to your app when the cache is expired. &lt;/p&gt;

&lt;p&gt;Varnish uses these vcl scripts which can get a little confusing. With a little &lt;a href="https://github.com/flygoast/libvmod-lua"&gt;lua magic&lt;/a&gt; you can write some nice programmatic cacheing. Maybe cache different lengths based on the domain in a multi-tenant situation? &lt;/p&gt;

&lt;h2&gt;
  
  
  2.
&lt;/h2&gt;

&lt;p&gt;Are you big in to playing video games? Maybe you've heard of some of these?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angry Birds&lt;/li&gt;
&lt;li&gt;Civilization V&lt;/li&gt;
&lt;li&gt;Far Cry&lt;/li&gt;
&lt;li&gt;Garry's Mod&lt;/li&gt;
&lt;li&gt;Heroes of Might and Magic V&lt;/li&gt;
&lt;li&gt;L.A. Noire&lt;/li&gt;
&lt;li&gt;Mafia II&lt;/li&gt;
&lt;li&gt;Roblox&lt;/li&gt;
&lt;li&gt;Saints Row 2&lt;/li&gt;
&lt;li&gt;SimCity 4&lt;/li&gt;
&lt;li&gt;Star Wars Battlefront &lt;/li&gt;
&lt;li&gt;Stepmania&lt;/li&gt;
&lt;li&gt;Warhammer&lt;/li&gt;
&lt;li&gt;World of Warcraft&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these games use Lua in some capacity, and there's MANY more. Most games or game engines will be written in C++ or C#, but using Lua on the front end for scripting allows for rapid development of these games. No need to re-compile each time. Compile your engine, and have it run the lua scripts. You can get started with &lt;a href="https://love2d.org/"&gt;Love2d&lt;/a&gt; or &lt;a href="https://www.defold.com"&gt;defold&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1.
&lt;/h2&gt;

&lt;p&gt;Because Lua is fun to write! If you don't know it, it wouldn't hurt to learn a new language. If you already know how to program, picking up lua should be pretty quick. Read over this &lt;a href="http://tylerneylon.com/a/learn-lua/"&gt;15 min of lua&lt;/a&gt;, and you will have a decent understanding of the syntax. At that point it's just a matter of digging in!&lt;/p&gt;

</description>
      <category>lua</category>
    </item>
    <item>
      <title>Quick start with React on Lucky</title>
      <dc:creator>Jeremy Woertink</dc:creator>
      <pubDate>Fri, 30 Nov 2018 17:40:19 +0000</pubDate>
      <link>https://dev.to/jwoertink/quick-start-with-react-on-lucky-1mn8</link>
      <guid>https://dev.to/jwoertink/quick-start-with-react-on-lucky-1mn8</guid>
      <description>&lt;p&gt;This will be a quick start in to getting some simple &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; code with minimal setup in to a simple &lt;a href="https://luckyframework.org/" rel="noopener noreferrer"&gt;Lucky&lt;/a&gt; app also with minimal setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1, setup lucky
&lt;/h2&gt;

&lt;p&gt;Make sure you have lucky installed locally. You can check out the &lt;a href="https://luckyframework.org/guides/installing/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; for getting that installed.&lt;/p&gt;

&lt;p&gt;Once that's installed, we can generate our app!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lucky init
Project name? simple_react_on_lucky

Lucky can generate different types of projects

...

API only or full support for HTML and Webpack? (api/full): full

Lucky can be generated with email and password authentication

...

Generate authentication? (y/n): n

-----------------------

Done generating your Lucky project

  ▸ cd into simple_react_on_lucky
  ▸ check database settings in config/database.cr
  ▸ run bin/setup
  ▸ run lucky dev to start the server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run those last few steps that lucky tells you to run, and then your app will be setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2, add in react
&lt;/h2&gt;

&lt;p&gt;Next we need to add react to our app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add react
yarn add react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3, make a page to use react
&lt;/h2&gt;

&lt;p&gt;Now we need a page where we can write some code to mount our react app. We will just make a simple home page to override lucky's default home page.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;src/actions/home/index.cr&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# change&lt;/span&gt;
&lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;Lucky&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WelcomePage&lt;/span&gt;
&lt;span class="c1"&gt;# to&lt;/span&gt;
&lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IndexPage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new &lt;code&gt;src/pages/home/index_page.cr&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IndexPage&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MainLayout&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;content&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4, write some react
&lt;/h2&gt;

&lt;p&gt;Open up &lt;code&gt;src/js/app.js&lt;/code&gt; and add in your react imports. You'll see some code in there already, you can just add this stuff below that. Worry about whether you need that existing code or not later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mountElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WOOT!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;mountElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run &lt;code&gt;lucky dev&lt;/code&gt; in your terminal, and provided we didn't screw anything up, you should see "WOOT!" on your home page now! &lt;/p&gt;

&lt;p&gt;Obviously you'll want to add a lot more code for a real app, and using the &lt;code&gt;React.createElement&lt;/code&gt; functions will get super messy. This should at least give you a jumping point on how you can start working React in to your Lucky applications.&lt;/p&gt;

&lt;p&gt;You can also take a look at &lt;a href="https://dev.to/mikeeus/react-components-in-lucky-with-laravel-mix-and-lucky-react-nlb"&gt;this post&lt;/a&gt; by &lt;a href="https://dev.to/mikeeus"&gt;@mikeeus&lt;/a&gt; on some more Lucky and React.&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>lucky</category>
      <category>react</category>
    </item>
  </channel>
</rss>
