<?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: Elijah Goh</title>
    <description>The latest articles on DEV Community by Elijah Goh (@eligoh).</description>
    <link>https://dev.to/eligoh</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%2F182341%2Fd4e49651-57e1-4f7b-be08-4a716106f1ca.jpeg</url>
      <title>DEV Community: Elijah Goh</title>
      <link>https://dev.to/eligoh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eligoh"/>
    <language>en</language>
    <item>
      <title>From Idea to SaaS in 30 days, building Qiu</title>
      <dc:creator>Elijah Goh</dc:creator>
      <pubDate>Tue, 31 Oct 2023 15:56:48 +0000</pubDate>
      <link>https://dev.to/eligoh/from-idea-to-code-the-one-month-journey-of-qiu-4509</link>
      <guid>https://dev.to/eligoh/from-idea-to-code-the-one-month-journey-of-qiu-4509</guid>
      <description>&lt;p&gt;Having been a Ruby developer for a decade, Rails was my go-to for building Qiu. It just made sense. Rails provided a treasure trove of tools right at my fingertips. No reinventing the wheel and allowed me to build &lt;a href="https://qiu.so"&gt;Qiu&lt;/a&gt; from a concept to a live product in 30 days.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sensible Defaults
&lt;/h3&gt;

&lt;p&gt;Rails comes with great feature from the start, like ActiveJob for background processing, has_secure_password for authentication, mailers for email handling, and ActiveStorage, were invaluable. This blog engine is written using ActionText! 🤯&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Convention over configuration is the way&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Ruby Gems
&lt;/h3&gt;

&lt;p&gt;Ruby gems was what allowed us to stay productive when building Qiu. Here are some I consider great defaults.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pay-rails - rapid integration of Stripe and other payment types&lt;/li&gt;
&lt;li&gt;good_job - what powers our background job, from queuing to sending&lt;/li&gt;
&lt;li&gt;nanoid, hashid - URL obsfucuration, allows us to create URLs with nicer slugs than incremental IDs&lt;/li&gt;
&lt;li&gt;discard - soft deletion, why code one when you could install one!&lt;/li&gt;
&lt;li&gt;bool_at - personal favorite (I made it), DSL for boolean statuses&lt;/li&gt;
&lt;li&gt;name_of_person - Easy first name, last name&lt;/li&gt;
&lt;li&gt;liquid - templating engine for dynamic data (we use this for our email parser)&lt;/li&gt;
&lt;li&gt;ahoy_matey, ahoy_captain - built-in Rails analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Analytics
&lt;/h3&gt;

&lt;p&gt;Special mention to Ahoy + Ahoy Captain. It allowed us to add custom tracking without any external requirement, and it is GDPR compliant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VscDq7Dr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06nlo5578f3om2cke8j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VscDq7Dr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06nlo5578f3om2cke8j7.png" alt="Ahoy Captain!" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And includes a beautiful UI for analytics tracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding Convention
&lt;/h3&gt;

&lt;p&gt;I did not follow the skinny model, nor service models very closely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XmgrE78B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vufyvuu3j24q6mb0fx9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XmgrE78B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vufyvuu3j24q6mb0fx9w.png" alt="Qiu API code" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We deploy a mix of Rails callbacks and model methods to perform tasks.Instead of fighting Rails, we embrace class and instance methods than going immediately for a service class whenever we want to implement something. This allows us to avoid early code bloat and Service class hell early on while keeping code maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting
&lt;/h3&gt;

&lt;p&gt;Using Render with Docker allowed Qiu to iterate quickly, changing code and deployment only takes 3 minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uit7OWyZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tgo80l4v8lylbziq930a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uit7OWyZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tgo80l4v8lylbziq930a.png" alt="3 minute deploys" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It allows us to quickly iterate, fix bugs or add features without worrying about deployment times.&lt;/p&gt;

&lt;h3&gt;
  
  
  And with that
&lt;/h3&gt;

&lt;p&gt;Is what allowed us to build Qiu from idea to MVP in 30 days using Ruby + Rails. Hope you enjoyed this article!&lt;/p&gt;

&lt;p&gt;Also, if you want an easier way to send emails, try &lt;a href="https://qiu.so"&gt;Qiu&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiu.so/blog/QkaUwN/from-idea-to-code-the-one-month-journey-of-qiu"&gt;This article was originally posted in Qiu&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>saas</category>
    </item>
    <item>
      <title>Dependency Injection in Ruby 🌈</title>
      <dc:creator>Elijah Goh</dc:creator>
      <pubDate>Wed, 10 Jul 2019 09:19:31 +0000</pubDate>
      <link>https://dev.to/eligoh/dependency-injection-in-ruby-55ja</link>
      <guid>https://dev.to/eligoh/dependency-injection-in-ruby-55ja</guid>
      <description>&lt;p&gt;I once wrote an article on Medium about dependency injection - &lt;a href="https://medium.com/@choonggg/dependency-injection-is-not-scary-refactoring-rails-the-clean-way-5ce5b305fd22"&gt;Dependency Injection is not Scary!&lt;/a&gt;. Recently joined dev.to, I thought I'd bring my thoughts here as well.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Okay Eli, I know how to dependency inject now thanks to the medium article. BUT WHEN DO USE IT?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bear with me here, I'll get to that soon. Currently building an inbox feature, I needed a form object for creating the conversation AND responding/editing the conversation.&lt;/p&gt;

&lt;p&gt;What I could have done was create a form object, wipe my hands clean and be done with it. &lt;strong&gt;Or I could also make this form reusable in other controllers.&lt;/strong&gt; 🤓&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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Inbox&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConversationForm&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseForm&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sender&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="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="vi"&gt;@conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;
      &lt;span class="vi"&gt;@sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_sender&lt;/span&gt;
      &lt;span class="vi"&gt;@message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="vi"&gt;@sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&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;But making use of dependency injection, I was able to reuse the form for another controller.&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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Inbox&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConversationForm&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseForm&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sender&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="n"&gt;conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Conversation&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;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_sender&lt;/span&gt;
      &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&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="k"&gt;end&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="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="vi"&gt;@conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;
      &lt;span class="vi"&gt;@sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;
      &lt;span class="vi"&gt;@message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;converation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;sender: &lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&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;I would use &lt;code&gt;Inbox::ConversationForm.build&lt;/code&gt; when I want to create a new conversation. In another controller/action, I would predefine the conversation and inject it into the form.&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;# ConversationsController&lt;/span&gt;
&lt;span class="c1"&gt;# Creating a Conversation&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="vi"&gt;@form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Inbox&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConversationForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# My::ConversationsController&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="vi"&gt;@form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Inbox&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConversationForm&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;fetch_conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="c1"&gt;# BOTH&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;some_path&lt;/span&gt;
  &lt;span class="k"&gt;end&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;fetch_conversation&lt;/span&gt;
  &lt;span class="no"&gt;Inbox&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Conversation&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;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:conversation_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, go forth and Dependency Inject!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>codequality</category>
      <category>refactorit</category>
    </item>
  </channel>
</rss>
