<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="/feed/index.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2024-11-20T15:25:24+00:00</updated><id>/feed/index.xml</id><title type="html">Frank’s blog</title><subtitle>I build Ruby on Rails web applications, primarily focused on OGSM at [Toolsfactory](https://toolsfactory.nl/).</subtitle><author><name>Frank Groeneveld</name></author><entry><title type="html">Replace tinymce-rails-imageupload</title><link href="/2022/07/14/replace-tinymce-rails-imageupload/" rel="alternate" type="text/html" title="Replace tinymce-rails-imageupload" /><published>2022-07-14T14:02:28+00:00</published><updated>2022-07-14T14:02:28+00:00</updated><id>/2022/07/14/replace-tinymce-rails-imageupload</id><content type="html" xml:base="/2022/07/14/replace-tinymce-rails-imageupload/"><![CDATA[<p>I maintain a number of Ruby on Rails projects that contain the great 
<a href="https://github.com/PerfectlyNormal/tinymce-rails-imageupload">tinymce-rails-imageupload gem</a>.
Unfortunately the owner has archived the repository and is no longer maintaining it. This was keeping my projects on the
rather old TinyMCE version 4. Last week I’ve invested some time in search of a solution and I found a good one that
does not rely on external dependencies!</p>

<p>Start by removing <code class="language-plaintext highlighter-rouge">tinymce-rails-imageupload</code> from your <code class="language-plaintext highlighter-rouge">Gemfile</code> and upgrading <code class="language-plaintext highlighter-rouge">tinymce-rails</code> to version 6.1.
Make sure you upgrade your <code class="language-plaintext highlighter-rouge">tinymce.yml</code> file with the renamed 6.1 plugins and options before you continue.
Test whether TinyMCE still works without the image uploads.</p>

<p>Now <strong>replace</strong> the <code class="language-plaintext highlighter-rouge">uploadimage</code> toolbar button with <code class="language-plaintext highlighter-rouge">image</code> and the <code class="language-plaintext highlighter-rouge">uploadimage</code> plugin with <code class="language-plaintext highlighter-rouge">image</code> in your
<code class="language-plaintext highlighter-rouge">tinymce.yml</code> configuration file and <strong>add</strong> these two options:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  images_upload_url: '/tinymce_assets' # for automatic_uploads
  images_upload_credentials: true # sends session information with the upload
</code></pre></div></div>

<p>To avoid some confusion this is the minimum viable tinymce.yml configuration file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>default:
  toolbar:
    - image
  plugins:
    - image
  images_upload_url: '/tinymce_assets' # for automatic_uploads
  images_upload_credentials: true # sends session information with the upload
</code></pre></div></div>

<p>Note that <code class="language-plaintext highlighter-rouge">/tinymce_assets</code> was the default endpoint for <code class="language-plaintext highlighter-rouge">tinymce-rails-imageupload</code> POST requests. That controller action
will need a small modification as well. The previous situation was probably something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>render json: {
  image: {
    url: the_url_of_the_upload_here
  }
}, content_type: 'text/html'
</code></pre></div></div>

<p>Replace it with a different JSON object and a normal content type:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>render json: { location: the_url_of_the_upload_here }
</code></pre></div></div>

<p>That’s it, you should now have a nice image upload tab when you click the image button on the toolbar.
As a bonus TinyMCE now also supports dragging an image into the editor which will use this endpoint to
store it on the server!</p>

<p><a href="/feed/">Subscribe to the RSS feed</a> to be notified of my next blog post!</p>]]></content><author><name>Frank Groeneveld</name></author><summary type="html"><![CDATA[tinymce-rails-imageupload has been discontinued and it will keep your project on TinyMCE 4 forever. Use these steps to replace it and upgrade to TinyMCE 6.]]></summary></entry><entry><title type="html">The most underused browser feature</title><link href="/2021/08/24/most-underused-browser-feature/" rel="alternate" type="text/html" title="The most underused browser feature" /><published>2021-08-24T08:45:01+00:00</published><updated>2021-08-24T08:45:01+00:00</updated><id>/2021/08/24/most-underused-browser-feature</id><content type="html" xml:base="/2021/08/24/most-underused-browser-feature/"><![CDATA[<p>The web has been plagued by cookie consent popups and banners since the General Data Protection Regulation (GDPR) has come into effect. See for yourself when you visit articles like <a href="https://techdows.com/2015/02/enable-test-reader-mode-firefox-nightly.html">this</a> and <a href="https://www.minitool.com/news/how-to-enable-use-reader-mode-in-chrome.html">this</a>. Now what if I told you there is a one-click solution to hide those banners and make your reading experience on websites such as these examples a lot simpler?</p>

<p>I’m talking about the “reader mode”. When available for a website, it is displayed as an icon at the end of the url bar in Firefox. The same is true for Chrome, but you first need to enable it at <a href="chrome://flags/#enable-reader-mode">chrome://flags/#enable-reader-mode</a>. Safari on iOS includes it in the menu at the left of the url bar.</p>

<p>Now open the <a href="https://techdows.com/2015/02/enable-test-reader-mode-firefox-nightly.html">first example</a> again and click on the reader icon without acknowledging the cookie consent popup. That’s better, right? As an added benefit you get dark mode and typography settings as well.</p>

<p>I believe not a lot of users know about this button, especially because Chrome doesn’t want to show it by default. As you can imagine it’s quite a life saver for informative sites and articles that you just want to read and don’t care about further interacting with.</p>

<p><a href="/feed/">Subscribe to the RSS feed</a> to be notified of my next blog post!</p>]]></content><author><name>Frank Groeneveld</name></author><summary type="html"><![CDATA[A great feature that is available in almost every browser allows you to reject the cookie consent popup.]]></summary></entry><entry><title type="html">Rails system test with mobile viewport</title><link href="/2021/08/19/rails-system-test-mobile-viewport/" rel="alternate" type="text/html" title="Rails system test with mobile viewport" /><published>2021-08-19T11:25:19+00:00</published><updated>2021-08-19T11:25:19+00:00</updated><id>/2021/08/19/rails-system-test-mobile-viewport</id><content type="html" xml:base="/2021/08/19/rails-system-test-mobile-viewport/"><![CDATA[<p>While building my subscription tracking service I broke the mobile navigation menu and deployed it to production. It was only after a day or so that a user emailed me to report this bug. I directly understood why my ads did not convert! This should never happen again, let’s create a system test with mobile viewport sizes that verifies the menu works correctly.</p>

<h2 id="default-screen-size-desktop">Default screen size desktop</h2>

<p>As you might know, the default Rails system tests use dimensions that are defined in <code class="language-plaintext highlighter-rouge">test/application_system_test_case.rb</code> with a line such as:</p>

<p><code class="language-plaintext highlighter-rouge">driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]</code></p>

<p>The most simple solution would be to just modify the sizes in that array. However, this would make all system test cases use the mobile viewport, which is not what I want.</p>

<h2 id="every-test-case-a-different-screen-size">Every test case a different screen size</h2>

<p>After some searching I found that the Rails APIs did not support setting the screen size for one test case. Capybara, which is used as an underlying gem, does however. So as the first line to a mobile viewport test case you can set the screen size using:</p>

<p><code class="language-plaintext highlighter-rouge">Capybara.current_session.current_window.resize_to(360, 740) # Galaxy S9</code></p>

<p>The naive solution to resetting the screen size after this test case would be to repeat this call with the original 1400, 1400 as arguments. However, if the test does not succeed, it will not execute that line. It is therefore better to reset the viewport in a seperate teardown method, for example in your <code class="language-plaintext highlighter-rouge">test/application_system_test_case.rb</code> file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>teardown do
  Capybara.current_session.current_window.resize_to(1400, 1400)
end
</code></pre></div></div>

<p>You can DRY the resulting <code class="language-plaintext highlighter-rouge">test/application_system_test_case.rb</code> a bit by storing the default size in a const array and using that in the various calls. The one I use now looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># frozen_string_literal: true

require 'test_helper'

class ApplicationSystemTestCase &lt; ActionDispatch::SystemTestCase
  WINDOW_SIZE = [1400, 1400].freeze
  driven_by :selenium, using: :headless_chrome, screen_size: WINDOW_SIZE

  teardown do
    Capybara.current_session.current_window.resize_to(*WINDOW_SIZE)
  end
end

</code></pre></div></div>

<p><a href="/feed/">Subscribe to the RSS feed</a> to be notified of my next blog post!</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Programming" /><summary type="html"><![CDATA[It is possible to write system tests with different viewport sizes, allowing you to test both mobile and desktop renders of the same page]]></summary></entry><entry><title type="html">Third and Fourth Month as a Solo Founder</title><link href="/2021/08/04/third-forth-month-solo-founder/" rel="alternate" type="text/html" title="Third and Fourth Month as a Solo Founder" /><published>2021-08-04T10:58:12+00:00</published><updated>2021-08-04T10:58:12+00:00</updated><id>/2021/08/04/third-forth-month-solo-founder</id><content type="html" xml:base="/2021/08/04/third-forth-month-solo-founder/"><![CDATA[<p>Previously I wrote about my <a href="/2021/04/28/first-month-as-solo-founder/">first</a> and <a href="/2021/05/31/second-month-as-solo-founder/">second</a> month as a solo founder. I didn’t write about month three for some reason, so in this post I’ll describe month three and four. A lot has happend since that last blog post!</p>

<h2 id="two-projects">Two projects</h2>

<p>Another idea was to build a subscription tracking tool. What if some tool could notify me about annual contracts that were about to renew? It would save me money if I wasn’t using that subscription anymore. This would also be useful for small companies, where somebody would sign a contract for a teammate which could renew by accident if the teammate left.</p>

<p>Both launched with a waitlist and had some sign ups, although the video tool sign ups seemed spammy. They were probably not actual prospects. I decided to invest some time in the subscription tracking tool because it was something that scratched my own itch. I had a working product after a few weeks of development and started extending it with expense tracking, because I was also interested in my subscription payment history. While at it, it was not much work to add a manual expense form. You see where this is going? I think it might evolve into a personal finance tool, but lets see how customers start using it.</p>

<p>Speaking about users, how is it doing? When the MVP was ready I launched 3 monthly plans with free trials. Furthermore I launched with one lifetime plan which is only available for the first few months. So far I’ve sold this lifetime plan three times and received numerous monthly plan trial sign ups. The early sign ups were promised a longer trial, so they haven’t reached the point where conversion to paid is required. Lets see in August.</p>

<h2 id="marketing-efforts">Marketing efforts</h2>

<p>Since I started using Google Ads in month two I received a promotional offer and received almost 200% free credit. I’ve used part of this to promote a project, which seems to work quite well and has resulted in some conversions. New marketing channels I tested were Reddit and Twitter. Twitter didn’t perform that great, although I did have some retweets and obtained a new follower, I don’t think I had sign ups because of my promoted Tweet. Reddit on the other hand worked quite good. I sold some lifetime plans and had some trial sign ups. Although Reddit is probably more expensive, it did have a better efficiency when comparing views vs clicks.</p>

<h2 id="sold-callcounter-to-a-new-owner">Sold Callcounter to a new owner</h2>

<p>As described above I received quite some signals that a subscription tracker might be a viable product. That is why I decided to sell my api analytics product and focus all my time on a different project. I placed an ad on various marketplaces for SaaS sales and had quite some interesting video and text conversions with possible buyers. After a few weeks I was able to sell it. My company was still in the red up until that point, but selling Callcounter made it profitable for the first time! That is, I’m not able to give myself a salary, but all previous expenses are now covered for as well as some SaaS fees for the coming months.</p>

<h2 id="summary">Summary</h2>

<p>Quite a rocky period, but in the end my new business is profitable and I’m very motivated to work on my new project. It is much more rewarding to work on a project that has some actual users, I’m looking forward to what the coming month will bring.</p>

<p><a href="/feed/">Subscribe to the RSS feed</a> to be notified of my next blog post!</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Business" /><summary type="html"><![CDATA[I sold Callcounter, launched two new project ideas and started building one of them.]]></summary></entry><entry><title type="html">My Second Month as a Solo Founder</title><link href="/2021/05/31/second-month-as-solo-founder/" rel="alternate" type="text/html" title="My Second Month as a Solo Founder" /><published>2021-05-31T07:56:49+00:00</published><updated>2021-05-31T07:56:49+00:00</updated><id>/2021/05/31/second-month-as-solo-founder</id><content type="html" xml:base="/2021/05/31/second-month-as-solo-founder/"><![CDATA[<p>I’m writing this on the last day of my second month as a solo founder. <a href="/2021/04/28/first-month-as-solo-founder/">Read about my first month</a> if you haven’t already. This month was a month with much less coding and more marketing. The most important pieces of <a href="https://callcounter.eu">Callcounter</a> were finished last month, so this month I more actively started promoting it.</p>

<h2 id="attracting-more-visitors">Attracting More Visitors</h2>

<p>Besides using Bing Ads to attract visitors to the website, I also started to do this with Google Ads. Before starting my new business I had a strong objection against this, but this was just the simplest way to get visitors to the site.</p>

<p>This was also the month where I started a blog on the Callcounter domain. I wrote something about <a href="https://callcounter.eu/blog/choosing-rails-xml-serializer-for-your-api-in-2021">Ruby XML serializers</a> there for example, which attracted quite some visitors from Reddit. The total unique visitor count for that post count was somewhere around 200.</p>

<p>I came up with a fun experiment after reading the book Traction: How Any Startup Can Achieve Explosive Customer Growth by Gabriel Weinberg (founder of Duckduckgo) and Justin Mares. To originally grow Duckduckgo Weinberg created a karma widget, with a backlink to his new search engine. In a few hours I created something like that to show of your API usage as well.</p>

<h2 id="converting-visitors-to-trial-users">Converting Visitors to Trial Users</h2>

<p>To improve conversion rate I switched from having no trial at all, to having a trial which required a credit card. This didn’t result in trial users signups either, so I modified that to a 7 day trial without credit card requirements. So far this has resulted in a number of trial user signups. One of which was just after the “show off your api statistics widget” was published.</p>

<p>Furthermore I added an interactive demo that showcases the Callcounter API statistics itself. Visitors can now interactively see how Callcounter works without creating an account.</p>

<p>This month I also added a live chat solution to the public website. I originally didn’t intend to add one, but the low conversion rates got me doubting the quality of the website. So far I haven’t had the ability to chat with a stranger.</p>

<h2 id="converting-trial-users-to-customers">Converting Trial Users to Customers</h2>

<p>Unfortunately none of the trial users have created a project to actually test Callcounter. Very strange, why would you create an account and then stop there? Creating a project requires just a one field form submission. I’ve asked all trial users, but none have responded.  When creating the trial functionality I expected the integration installation to become an obstacle, but none of the trial users have even reached that state.</p>

<h2 id="second-month-summary">Second Month Summary</h2>

<p>I’m starting to doubt whether visitors understand why Callcounter could help them. I’m furthering improving texts and images hoping that helps. I’ve achieved quite some things this month though:</p>

<ul>
  <li>Added a free trial with credit card requirement.</li>
  <li>Modified the free trial to not require a credit card at all.</li>
  <li>An average of 30-40 unique visitors per day.</li>
  <li>5% of the visitors opened the trial sign up page.</li>
  <li>Converted 0.7% of all visitors to trial users after removing the credit card required.</li>
  <li>Launched a .NET integration thanks to Jesse Tilro and Jeroen Meijer.</li>
  <li>Added an interactive demo.</li>
  <li>Added a simple blog and wrote some content for it.</li>
  <li>Published <a href="https://nordicapis.com/a-gdpr-compliant-method-to-identify-api-clients/">a guest post</a> on Nordic APIs about the GDPR and API client tracking.</li>
  <li>Improved Bing Ads.</li>
  <li>Created and tested Google Ads.</li>
</ul>

<p><a href="/feed/">Subscribe to the RSS feed</a> to be notified of my next blog post!</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Business" /><summary type="html"><![CDATA[I started to build a Software-as-a-Service product from The Netherlands. Discover what I learned and achieved in the second month of this new adventure.]]></summary></entry><entry><title type="html">My favorite Ruby gems</title><link href="/2021/05/26/my-favorite-ruby-gems/" rel="alternate" type="text/html" title="My favorite Ruby gems" /><published>2021-05-26T14:26:23+00:00</published><updated>2021-05-26T14:26:23+00:00</updated><id>/2021/05/26/my-favorite-ruby-gems</id><content type="html" xml:base="/2021/05/26/my-favorite-ruby-gems/"><![CDATA[<p>I’m always hesistant when it comes to using gems. Every gem you add to a project is a bit of added risk, what if it contains a security bug? What if the developers stop maitaining it? What if it doesn’t work with the next Ruby version? Of course, building everything yourself is not an option either. Through the years I’ve built a list of gems I trust and use. So far I’ve never shared these anywhere public. I’m changing that with this post.</p>

<h2 id="authentication-and-authorization">Authentication and authorization</h2>

<ul>
  <li>
    <p><a href="https://github.com/heartcombo/devise">devise</a></p>

    <p>Authentication with features like locking, forgot password, remembering sign in.</p>
  </li>
  <li>
    <p><a href="https://github.com/varvet/pundit">pundit</a></p>

    <p>A much clearer manner of authorizing users when compared to cancan.</p>
  </li>
  <li>
    <p><a href="https://github.com/ErwinM/acts_as_tenant">acts_as_tenant</a></p>

    <p>Split your application data in “tenants”, which could be seperate customers, domains, etc.</p>
  </li>
</ul>

<h2 id="output-generation">Output generation</h2>

<ul>
  <li>
    <p><a href="https://github.com/vmg/redcarpet">redcarpet</a></p>

    <p>Generate Markdown to HTML.</p>
  </li>
  <li>
    <p><a href="https://github.com/Shopify/liquid">liquid</a></p>

    <p>A template language built by Shopify. Great for customizable webhook implementations for example.</p>
  </li>
  <li>
    <p><a href="https://github.com/procore/blueprinter">blueprinter</a></p>

    <p>Fast and simple JSON serializer with a simple DSL. See my <a href="/2021/02/05/choosing-rails-json-serializer-for-your-api-in-2021/">previous post about JSON serializers</a>.</p>
  </li>
  <li>
    <p><a href="https://github.com/Paxa/fast_excel">fast_excel</a></p>

    <p>A very fast and simple xlsx file generator using a C library, that doesn’t consume much memory.</p>
  </li>
  <li>
    <p><a href="https://github.com/caxlsx/caxlsx_rails">caxlsx_rails</a></p>

    <p>A more extensive xlsx file generatore (pure Ruby). It has more features than fast_excel, but that comes at a cost:
slower and more memory usage.</p>
  </li>
</ul>

<h2 id="payments">Payments</h2>

<ul>
  <li>
    <p><a href="">mollie-api-ruby</a></p>

    <p>The official Mollie Ruby gem.</p>
  </li>
  <li>
    <p><a href="https://github.com/stripe/stripe-ruby">stripe-ruby</a></p>

    <p>The official Stripe Ruby gem.</p>
  </li>
</ul>

<h2 id="testing">Testing</h2>

<ul>
  <li>
    <p><a href="https://github.com/freerange/mocha">mocha</a></p>

    <p>Mocking and stubbing</p>
  </li>
  <li>
    <p><a href="https://github.com/liufengyun/hashdiff">hashdiff</a></p>

    <p>Compare hashes and show the differences in a simple language. Great when testing JSON API responses.</p>
  </li>
  <li>
    <p><a href="https://github.com/deivid-rodriguez/byebug">byebug</a></p>

    <p>Stop execution and start debugging on an interactive console.</p>
  </li>
  <li>
    <p><a href="https://github.com/teamcapybara/capybara">capybara</a></p>

    <p>Test with real browsers.</p>
  </li>
</ul>

<h1 id="various">Various</h1>

<ul>
  <li>
    <p><a href="https://github.com/rubysolo/dentaku">dentaku</a></p>

    <p>Implement an Excel like formula system in your application. Great for giving users some computational data access.</p>
  </li>
  <li>
    <p><a href="https://github.com/nathanvda/cocoon">cocoon</a></p>

    <p>Cocoon implements the frontend parts of nested forms for you.</p>
  </li>
  <li>
    <p><a href="https://github.com/roo-rb/roo">roo</a></p>

    <p>Read Excelsheets, OpenDocument spreadsheets and csv files.</p>
  </li>
  <li>
    <p><a href="https://github.com/rubocop/rubocop">rubocop</a></p>

    <p>Linting and other techniques to improve your code quality.</p>
  </li>
</ul>

<p>Note that I’ve never built a project with all these gems in it. I always try to keep the dependencies to a minimum to avoid headaches when upgrading gems or the server operating system.</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Programming" /><summary type="html"><![CDATA[Every Ruby gem you add to a project can become a liability. The gems in this post are what I've used and trusted in the last few years.]]></summary></entry><entry><title type="html">Clean up HTML class attributes in Ruby on Rails</title><link href="/2021/05/04/clean-up-html-class-attributes-in-ruby-on-rails/" rel="alternate" type="text/html" title="Clean up HTML class attributes in Ruby on Rails" /><published>2021-05-04T12:23:12+00:00</published><updated>2021-05-04T12:23:12+00:00</updated><id>/2021/05/04/clean-up-html-class-attributes-in-ruby-on-rails</id><content type="html" xml:base="/2021/05/04/clean-up-html-class-attributes-in-ruby-on-rails/"><![CDATA[<p>I keep on discovering handy view helpers in Ruby on Rails while developing my SaaS. A few weeks back I remembed <a href="https://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-cycle">cycle</a> and last week I discovered a new helper that was introduced in Ruby on Rails 6.1: <a href="https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-class_names"><code class="language-plaintext highlighter-rouge">class_names</code></a>.</p>

<h2 id="beautify-your-views">Beautify your views</h2>

<p>Most of us will probably recognize the class name spaghetti that starts to arise when your project grows. Things like this will become littered throughout your views:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;li class="item &lt;%= 'active' if @active %&gt;
    &lt;%= 'disabled' if @unpaid_user %&gt;"&gt;
</code></pre></div></div>

<p>Not exactly readable anymore, right? Well, thats what <code class="language-plaintext highlighter-rouge">class_names</code> is trying to fix. You just pass it a hash with the class names as keys and conditions as values. Look how nice!</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">li</span> <span class="k">class</span><span class="o">=</span><span class="s2">"&lt;%= classs_name item: 1, active: @active, disabled: @unpaid_user "</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>You sometimes even need an inline ternary operator:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;li class="&lt;%= @verified_user ? 'verified' : 'unverified' %&gt;"&gt;
</code></pre></div></div>

<p>This can be cleaned up as well:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;li class="&lt;%= class_names verified: @verified, unverified: !@verified %&gt;"&gt;
</code></pre></div></div>

<p>A final improvement might be done by using the <code class="language-plaintext highlighter-rouge">tag</code> helper:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tag</span><span class="p">.</span><span class="nf">li</span> <span class="ss">class: </span><span class="n">class_names</span><span class="p">(</span><span class="ss">verified: </span><span class="vi">@verified</span><span class="p">,</span> <span class="ss">unverified: </span><span class="o">!</span><span class="vi">@verified</span><span class="p">)</span> <span class="k">do</span>
  <span class="c1"># content</span>
<span class="k">end</span>
</code></pre></div></div>

<h2 id="what-if-your-project-uses-an-older-ruby-on-rails-version">What if your project uses an older Ruby on Rails version?</h2>

<p>The great thing about these helpers is that they are so simple that you can easly backport them to older releases yourself. Something like this in <code class="language-plaintext highlighter-rouge">app/helpers/class_names_helper.rb</code> should work on older releases:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">ClassNamesHelper</span>
  <span class="k">def</span> <span class="nf">build_tag_values</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
    <span class="n">tag_values</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="n">args</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">tag_value</span><span class="o">|</span>
      <span class="k">case</span> <span class="n">tag_value</span>
      <span class="k">when</span> <span class="no">Hash</span>
        <span class="n">tag_value</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">val</span><span class="o">|</span>
          <span class="n">tag_values</span> <span class="o">&lt;&lt;</span> <span class="n">key</span><span class="p">.</span><span class="nf">to_s</span> <span class="k">if</span> <span class="n">val</span> <span class="o">&amp;&amp;</span> <span class="n">key</span><span class="p">.</span><span class="nf">present?</span>
        <span class="k">end</span>
      <span class="k">when</span> <span class="no">Array</span>
        <span class="n">tag_values</span><span class="p">.</span><span class="nf">concat</span> <span class="n">build_tag_values</span><span class="p">(</span><span class="o">*</span><span class="n">tag_value</span><span class="p">)</span>
      <span class="k">else</span>
        <span class="n">tag_values</span> <span class="o">&lt;&lt;</span> <span class="n">tag_value</span><span class="p">.</span><span class="nf">to_s</span> <span class="k">if</span> <span class="n">tag_value</span><span class="p">.</span><span class="nf">present?</span>
      <span class="k">end</span>
    <span class="k">end</span>

    <span class="n">tag_values</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">class_names</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
    <span class="n">tokens</span> <span class="o">=</span> <span class="n">build_tag_values</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">).</span><span class="nf">flat_map</span> <span class="p">{</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span> <span class="n">value</span><span class="p">.</span><span class="nf">to_s</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sr">/\s+/</span><span class="p">)</span> <span class="p">}.</span><span class="nf">uniq</span>

    <span class="n">safe_join</span><span class="p">(</span><span class="n">tokens</span><span class="p">,</span> <span class="s2">" "</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<h2 id="discussion">Discussion</h2>

<p>Let me know if you found this post useful!</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Programming" /><summary type="html"><![CDATA[A new function that was introduced by Rails 6.1 will allow you to simplify html class attributes when they contain logic to be shown or hidden.]]></summary></entry><entry><title type="html">My First Month as a Solo Founder</title><link href="/2021/04/28/first-month-as-solo-founder/" rel="alternate" type="text/html" title="My First Month as a Solo Founder" /><published>2021-04-28T14:16:31+00:00</published><updated>2021-04-28T14:16:31+00:00</updated><id>/2021/04/28/first-month-as-solo-founder</id><content type="html" xml:base="/2021/04/28/first-month-as-solo-founder/"><![CDATA[<p>On the first of april I started my first full-time month as the solo founder. I’ll try to build a bootstrapped software-as-a-service product that should get me to <a href="http://www.paulgraham.com/ramenprofitable.html">ramen profitability</a> in a year. Having previous experience running Ivaldi, an agency, was a huge help so far. Lots of entrepreneurial jobs such as doing taxes, requesting a business bank account, apply for a business credit card, and registering at the chamber of commerce were at least vaguely familiar to me. The development and hosting side of my company shouldn’t be a problem either, as at my previous position I’ve been developing and hosting Ruby on Rails web applications for 10+ years.</p>

<p>So didn’t I experience any challenges in my first month? Well of course I did experience some challenges, there are lots of new jobs to do! This was the first time I had to actually do marketing, SEO, and advertising.</p>

<h2 id="marketing--advertising">Marketing &amp; Advertising</h2>

<p>Having just finished the first minimum viable product version of an analytics product for API providers, I’ve started the marketing and advertising efforts. So far I’ve written some blog posts referencing the product, set up a Twitter account and created a Bing Ads account. Bing Ads you ask? Yes, I had not heard of it either, but apparently it’s Microsoft’s answer to Google Adwords. I’m a huge fan of <a href="https://duckduckgo.com">DuckDuckGo</a> and recently discovered <a href="https://help.duckduckgo.com/company/advertising-and-affiliates">they use Bing Ads for advertising</a>. Given DuckDuckGo’s mission, it’s quite probable that their userbase consists mostly of developers and other possible customer for me. Seems like a great match! I activated my ads a few days ago, but so far I’ve only had 8 impressions. Not enough traffic on DuckDuckGo and Bing? Wrong keywords? I’ll have to experiment some more to find out. The bidding price luckily is not the problem according to the Bing Ads portal.</p>

<h2 id="seo">SEO</h2>

<p>At my previous job we mostly build marketing sites in WordPress. That was a part I didn’t have much to do with. So for my business I was actually building a marketing site for the first time in years. It should be as accessible for search engines as possible of course. I’ve added noindex to non important pages, I’ve generated a <a href="https://www.google.com/sitemaps/protocol.html">sitemap</a>, added unique title tags to all public pages and written some articles that should give search engines an idea what Callcounter is about.</p>

<h2 id="web-analytics">Web Analytics</h2>

<p>In this first month I also setup <a href="https://plausible.io">Plausible Analytics</a> to start analysing traffic on the marketing site and this blog. I wanted to protect my visitors privacy when analysing the traffic, so that seemed like a reasonable choice.</p>

<h2 id="talking-to-customers">Talking to Customers</h2>

<p>Talking to customers was a very important part of running an agency. The big difference was that we could easily meet face to face and mostly worked with customers that we’d know for years. My new business is currently in quite a different situation. The newsletter has some subscribers that I’ve sent emails to. So far not much interaction though. Let’s hope I can improve that in the second month. It’s very important to know what parts are still missing for possible customers or which obstacles they experience. The initial version supports Ruby projects out of the box, because that is what I use myself. I’m hoping to get an idea what kind of integrations possible customers would need. Will it be .NET, Django, <a href="https://rocket.rs">Rocket</a>, or …?</p>

<h2 id="first-month-summary">First Month Summary</h2>

<p>The first month summarised in a probably incomplete list:</p>

<ul>
  <li>Registered domain names</li>
  <li>Setup a server for hosting</li>
  <li>Created an account on <a href="https://sourcehut.org">sourcehut</a> for git repo hosting and CI builds</li>
  <li>Setup Plausible Analytics</li>
  <li>Setup a Twitter account</li>
  <li>Published various blog posts</li>
  <li>Developed:
    <ul>
      <li>my corporate website</li>
      <li>the MVP for an analytics product</li>
      <li>a marketing site about it</li>
      <li>the first analytics integration for Ruby using Rack middleware</li>
    </ul>
  </li>
  <li>Started SEO optimising the marketing site</li>
</ul>

<h2 id="plans-for-the-second-month">Plans for the Second Month</h2>

<p>A short list with the most important tasks I can think of for now:</p>

<ul>
  <li>More advertising experiments with Bing Ads.</li>
  <li>Try to get more interaction with the newsletter subscribers.</li>
  <li>Develop a second integration, besides Ruby on Rails, for an important framework.</li>
  <li>Write a blog post about my second month.</li>
</ul>

<p><a href="/feed/">Subscribe to the RSS feed</a> to be notified of my next blog post!</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Business" /><summary type="html"><![CDATA[I started to build a Software-as-a-Service product from The Netherlands. Discover what I learned and achieved in the first month of this new adventure.]]></summary></entry><entry><title type="html">PostgreSQL generate_series for generating time series</title><link href="/2021/04/22/postgresql-generate-series/" rel="alternate" type="text/html" title="PostgreSQL generate_series for generating time series" /><published>2021-04-22T12:05:40+00:00</published><updated>2021-04-22T12:05:40+00:00</updated><id>/2021/04/22/postgresql-generate-series</id><content type="html" xml:base="/2021/04/22/postgresql-generate-series/"><![CDATA[<p>Ever needed to aggregate data per time period where you didn’t have data points in every part of the period? I wanted to generate a chart of requests per hour, where I didn’t have requests in every hour. Using a naive PostgreSQL query I ended up with results such as this:</p>

<pre>
=&gt; SELECT date_trunc('hour', created_at),
-&gt; COUNT(*) FROM events GROUP BY 1 LIMIT 10;

     date_trunc      | count 
---------------------+-------
 2021-03-22 10:00:00 |    32
 2021-03-22 11:00:00 |    92
 2021-03-22 13:00:00 |   213
 2021-03-22 14:00:00 |   237
 2021-03-22 16:00:00 |   331
 2021-03-22 17:00:00 |   389
 2021-03-22 18:00:00 |   452
 2021-03-22 19:00:00 |   493
 2021-03-22 20:00:00 |   273
 2021-03-22 21:00:00 |   152
(10 rows)
</pre>

<p>Clearly there is no data for 12:00-13:59 and 15:00-15:59 in the database. Wouldn’t it be great if we can fix this at the database level? That’s where the <a href="https://www.postgresql.org/docs/13/functions-srf.html">PostgreSQL <code class="language-plaintext highlighter-rouge">generate_series()</code> function</a> comes into play. We can use it to generate a consecutive range of items, which we can then join with our events table.</p>

<pre>
=&gt; SELECT * FROM generate_series('2021-04-22 10:00'::timestamp,
-&gt; '2021-04-22 21:00', '1 hours');

   generate_series   
---------------------
 2021-04-22 10:00:00
 2021-04-22 11:00:00
 2021-04-22 12:00:00
 2021-04-22 13:00:00
 2021-04-22 14:00:00
 2021-04-22 15:00:00
 2021-04-22 16:00:00
 2021-04-22 17:00:00
 2021-04-22 18:00:00
 2021-04-22 19:00:00
 2021-04-22 20:00:00
 2021-04-22 21:00:00
(12 rows)
</pre>

<p>Depending on the ORM you’re using you’ll need to find out how to join this with your events.</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Programming" /><summary type="html"><![CDATA[Joining incomplete data series will give gaps if you have to truncate the dates. Use the PostgreSQL generate_series function to join a complete date series with your data and fill the gaps with zeros.]]></summary></entry><entry><title type="html">Generating unique invoice numbers in Ruby on Rails</title><link href="/2021/04/15/generating-unique-invoice-numbers-in-ruby-on-rails/" rel="alternate" type="text/html" title="Generating unique invoice numbers in Ruby on Rails" /><published>2021-04-15T09:20:04+00:00</published><updated>2021-04-15T09:20:04+00:00</updated><id>/2021/04/15/generating-unique-invoice-numbers-in-ruby-on-rails</id><content type="html" xml:base="/2021/04/15/generating-unique-invoice-numbers-in-ruby-on-rails/"><![CDATA[<p>I’m currently building a subscription tracker with reminders. While building the subscription part of this service I of course need to generate invoices. In The Netherlands we’re required to have a unique invoice number on them. How do you build something like that in Ruby on Rails?</p>

<p>We first need to validate that the numbers we store are actually unique. Validations in Ruby have some race conditions, where two might be generated at the same time without either one noticing. So, there is only one place where you can be sure of uniqueness: in the database. I’ve added a unique index on the invoice number column to fail hard if something might generate a duplicate.</p>

<pre>add_index :invoices, :number, unique: true</pre>

<p>Next we need to build the actual generation of unique numbers. I’ve chosen to build an InvoiceService which is responsible for creating invoices and generating unique numbers. The numbers are in the format of year-number, for example 2021-123. For this I’ve made a function that that looks up the previous invoice number parts. Something like this works:</p>

<pre>
  def last_invoice_number_parts
    current_year = Time.zone.now.year.to_s
    last_invoice = Invoice.order(Arel.sql('substring(number from 6)::int')).last

    if last_invoice&amp;.number&amp;.start_with?(current_year)
      last_invoice.number.split('-')
    else
      [current_year, 0]
    end
  end
</pre>

<p>Now the tricky part, how do we generate a new number and make sure it’s unique when stored? Regenerate and save again when you catch the ActiveRecord::RecordNotUnique exception on save. If that happens it means we need to generate a new incremented number and should thus try again. Something like this should work:</p>

<pre>
  def generate_invoice_number
    parts = last_invoice_number_parts
    @invoice.number = "#{parts.first}-#{parts.last.to_i + 1}"
    @invoice.save!
  rescue ActiveRecord::RecordNotUnique
    retry # another database record was just with the same number
  end
</pre>

<p>That’s it. Let’s hope this function will generate a lot of unique invoice numbers for us!</p>]]></content><author><name>Frank Groeneveld</name></author><category term="Programming" /><summary type="html"><![CDATA[In some countries it is required for invoice numbers to be unique and sequential. Let me show you how to build a solid generator yourself if you don't want to use the simple primary keys format.]]></summary></entry></feed>