<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[All about coding]]></title><description><![CDATA[All about coding]]></description><link>https://allaboutcoding.ghinda.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Feb 2024 07:20:08 GMT</lastBuildDate><atom:link href="https://allaboutcoding.ghinda.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><atom:link rel="next" href="https://allaboutcoding.ghinda.com/rss.xml?page=1"/><item><title><![CDATA[Two insights from using Sorbet]]></title><description><![CDATA[<p>I am working with Sorbet on a relatively big Ruby on Rails project, and here are two quick learnings:</p><ol><li><p>It can help with simplifying code by removing unnecessary transformations.</p></li><li><p>The support that Ruby has for doing things in multiple ways is very helpful when hitting a case where Sorbet has a rough edge</p></li></ol><p>Let's take them step by step:</p><h2 id="heading-how-to-simplify-methods-by-removing-unnecessary-transformations">How to simplify methods by removing unnecessary transformations</h2><p>Here is a piece of code that is a public interface of an object (this is not the actual code but an approximation of the actual code)</p><pre><code class="lang-ruby"><span class="hljs-comment"># typed: strict</span>ACCEPTED_KEYS = T.let(%i[name username email].freeze, T::Array[Symbol])sig { params(<span class="hljs-symbol">attributes:</span> T::Hash[T.any(Symbol, String), Integer]).returns(T::Hash[Symbol, Integer]) }  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">accepted_attributes</span><span class="hljs-params">(attributes)</span></span>    attributes      .symbolize_keys      .slice(*ACCEPTED_KEYS)<span class="hljs-keyword">end</span></code></pre><p>Let's first focus on the <code>.symbolize_keys</code> call and the definition of the <code>attributes</code> param that is <code>T::Hash[T.any(Symbol, String), Integer]</code> allowing the method to be called with a hash with keys, symbols, or strings.</p><p>Notice that because I defined the <code>attributes</code> in that way (to accept both types of keys), I called <code>symbolize_keys</code> to transform them into symbols so that I could <code>slice</code> by <code>ACCEPTED_KEYS</code>, which are symbols.</p><p>But there is another way to think about this:</p><p><em>Because the method has type definitions, there is no need to support both String and Symbol as keys for the hash. The developer who plans to use this method can see exactly what kind of objects the keys should be.</em></p><p>So, the method can be transformed into a simpler one where I remove the <code>symbolize_keys</code> :</p><pre><code class="lang-diff"><span class="hljs-deletion">- sig { params(attributes: T::Hash[T.any(Symbol, String), Integer]).returns(T::Hash[Symbol, Integer]) }  </span><span class="hljs-addition">+ sig { params(attributes: T::Hash[Symbol, Integer]).returns(T::Hash[Symbol, Integer])} </span>def accepted_attributes(attributes)    attributes<span class="hljs-deletion">-      .symbolize_keys</span>      .slice(*ACCEPTED_KEYS)end</code></pre><p>An insight here is that Sorbet can help remove code that is there to handle the cases where the input is in various formats. Using types will provide hints to the user of the method about what kind of arguments are expected, so there is no need to handle multiple types.</p><h2 id="heading-fixing-the-sorbet-splat-arguments-not-well-supported">Fixing the Sorbet splat arguments not well supported</h2><p>Now, if you run the sorbet checker or play online on the sorbet playground, you will get an error that is something like this:</p><pre><code class="lang-ruby">editor.<span class="hljs-symbol">rb:</span><span class="hljs-number">10</span>: Splats are only supported where the size of the array                 is known statically <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/srb.help/</span><span class="hljs-number">7019</span>    <span class="hljs-number">10</span> <span class="hljs-params">|  attributes.slice(*ACCEPTED_KEYS)</span></code></pre><p>And checking <a target="_blank" href="https://sorbet.org/docs/error-reference#7019">the Sorbet docs</a> they say the following:</p><p><a target="_blank" href="https://sorbet.org/docs/error-reference#7019"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707451254452/e4a13ebd-03e6-446e-8878-7edf62f1dcec.png" alt class="image--center mx-auto" /></a></p><p>One solution would be to use <code>T.unsafe</code> but I don't want to do that unless there is no other way around.</p><p>After trying a couple of things, I finally decided to refactor it to:</p><pre><code class="lang-ruby">sig { params(<span class="hljs-symbol">attributes:</span> T::Hash[Symbol, Integer]).returns(T::Hash[Symbol, Integer]) }  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">accepted_attributes</span><span class="hljs-params">(attributes)</span></span>    attributes.select { <span class="hljs-params">|key, _|</span> ACCEPTED_KEYS.<span class="hljs-keyword">include</span>?(key) }<span class="hljs-keyword">end</span></code></pre><p>And here comes the second insight:</p><p><em>The amazing thing about Ruby is that I can refactor that method from using</em><code>slice</code><em>to using</em><code>select</code><em>and still remain explicit and read like an English sentence.</em></p><hr /><p><strong>Enjoyed this article?</strong></p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://shortruby.com/"><strong>learn.shortruby.com</strong></a>- effortless learning anytime, anywhere</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="https://shortruby.com/"><strong>rubyandrails.info</strong></a><strong>, a directory with learning content about Ruby.</strong></p><p>🤝 Let's connect on <a target="_blank" href="https://shortruby.com/"><strong>Ruby.social</strong></a> <strong>or</strong> <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> <strong>or</strong> <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> <strong>where I post mainly about Ruby and Rails.</strong></p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/two-insights-from-using-sorbet</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/two-insights-from-using-sorbet</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 09 Feb 2024 04:06:52 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1707451504809/6a9317ac-03eb-4ff3-9135-6c22ef7aa221.png</cover_image></item><item><title><![CDATA[The tech stack I choose to build my email courses project]]></title><description><![CDATA[<p>I am building a new project: <a target="_blank" href="https://learn.shortruby.com">Ruby and Ruby on Rails Courses over Email</a>.</p><p>In the beginning, I had to choose the tech stack for it. Here is an exploration of what criteria I considered, what I chose, and why.</p><h2 id="heading-criteria">Criteria</h2><p>When I think about the tech stack I want to use for this project, I have to consider the following:</p><p><strong>Time</strong></p><p>The most important part is time. This is a side project; thus, it receives a small part of my effort. This is a fixed allocation that I can put among spending time with family, having a full-time job, and taking time for myself.</p><p>I think about time in two ways:</p><ul><li><p>The time I have to build the project</p></li><li><p>The time I will have to maintain the project</p></li></ul><p><strong>Speed of change</strong></p><p>The second most important thing is being able to change. That includes releasing new features or reacting to users needs or wants.</p><p>Whats important here is the speed of change: How fast can I release something to production?</p><p><strong>Costs</strong></p><p>On the third place are costs: hosting, emailing, and additional services used to build the project.</p><p>I have not yet allocated a budget for this project but will do so over the next weeks. Having a budget will help to validate if the idea is <em>feasible</em> and <em>sustainable</em> over the long run.</p><h2 id="heading-choices">Choices</h2><h3 id="heading-programming-language-ruby">Programming language: Ruby</h3><p>I am using <a target="_blank" href="https://www.ruby-lang.org/en/"><strong>Ruby</strong></a>. It is the language the I know best and it is also a language that allows me enough flexibility to write code that is easy to change and quick to adapt.</p><h3 id="heading-web-framework-ruby-on-rails">Web framework: Ruby on Rails</h3><p>I am using <a target="_blank" href="https://rubyonrails.org"><strong>Ruby on Rails</strong></a>. I briefly considered Hanami 2 as it is fresh, and I wanted to pick it up, but considering the time and speed of change, there is not much room to learn a new framework. At the same time, Ruby on Rails comes with a lot of battery included, and that is what I need in this project where I plan to focus on the product and content.</p><h3 id="heading-persistence-sqlite">Persistence: SQLite</h3><p>I am using <strong>SQLite</strong>. Ruby on Rails now has good support for SQLite in production, and <a target="_blank" href="https://fractaledmind.github.io">Stephen Margheim</a> has a lot of easy-to-follow and to-the-point articles for configuring it. Choosing SQLite reduces the time allocated to maintenance, so it is an easy win.</p><p>I am not concerned with scalability as I dont see this project needing more than a server.</p><p>I also added the <a target="_blank" href="https://github.com/fractaledmind/activerecord-enhancedsqlite3-adapter"><code>Activerecord-enhancedsqlite3-adapter</code></a> that adds support for generated columns, deferred foreign keys, PRAGMA tunning, and extensions loading.</p><p>For backup, I am using litestream gem, which makes working with <a target="_blank" href="https://litestream.io"><code>litestream.io</code></a> easier.</p><h3 id="heading-background-running-jobs">Background running jobs</h3><p>I use <a target="_blank" href="https://github.com/oldmoe/litestack">litestack</a> gem that comes with everything, including Jobs, Caching, Search, and Metrics.</p><p>I am considering for jobs to move to <a target="_blank" href="https://github.com/basecamp/solid_queue">SolidQueue</a> at some point.</p><h3 id="heading-admin-avo">Admin: Avo</h3><p>I am using <a target="_blank" href="https://avohq.io">Avo</a> to build the Admin UI that I will use to manage the content. Avo looks excellent; it is modern and fast, and I dont want to spend time building a UI for myself to manage the content.</p><p>It is already built, it has a feel of Railsy while configuring it, and it is flexible enough so that if I need more custom things, I can do them.</p><p>The best benefit is that I get time back to invest in the content instead of the development of an admin.</p><h3 id="heading-css-tailwind-and-tailwindui">CSS: Tailwind and TailwindUI</h3><p>I am using <a target="_blank" href="https://tailwindcss.com">Tailwind</a> because I used it professionally, so I know it a bit, and also because I bought <a target="_blank" href="https://tailwindui.com">TailwindUI</a> where I can find ready-made components that I can copy/paste into my project if needed.</p><p>Rails 7 also comes with an already integrated Tailwind, and it works with import maps.</p><h3 id="heading-import-maps">Import maps</h3><p>I like <a target="_blank" href="https://github.com/rails/importmap-rails">Rails import maps</a>, and I am picking other UI-related gems only if it works with import maps. That is because I dont want to run <code>nodejs</code> or have any <code>node_modules</code> installed. It adds to maintenance, and I like to minimize maintenance tasks.</p><h3 id="heading-views-erbs-and-phlex">Views: ERBs and Phlex</h3><p>First, I will create the general views as HTML ERB files. This is because it is close to HTML, will have low maintenance, and can be changed quickly. It also helps with copying/pasting elements from TailwindUI without making too many changes.</p><p>I will organize (or extract) standard components by using <a target="_blank" href="https://www.phlex.fun">phlex</a>. Phlex is closer to my backend background, so I can keep writing Ruby while building UI elements. <a target="_blank" href="https://rubygems.org/gems/phlex-rails"><code>phlex-rails</code></a> has also a good integration with <a target="_blank" href="https://lookbook.build/guide/components/phlex"><code>Lookbook</code></a> and so I can easily preview my components.</p><p>How I use this mix is like this: the general structure of the page is done with HTML ERB, but the elements inside are mostly done with Phlex.</p><h3 id="heading-testing">Testing</h3><p>I use <a target="_blank" href="https://guides.rubyonrails.org/testing.html#rails-meets-minitest">Minitest</a>, the default testing framework for Rails.</p><p>I like Minitest in my side projects for two main reasons:</p><ul><li><p>It comes default with Rails and runs fast</p></li><li><p>It is not a DSL, but it is just Ruby code. So it will be easy to return to it and not spend time remembering a DSL.</p></li></ul><p>There is one gem that I usually install for tests and that is <a target="_blank" href="https://github.com/adammck/minitest-stub-const"><code>minitest-stub-const</code></a>. Stubbing constanta can also be done without this gem, but I kept using this gem.</p><h3 id="heading-static-pages-sitepress">Static pages: Sitepress</h3><p>For static pages, like <a target="_blank" href="https://learn.shortruby.com">homepage</a>, or <a target="_blank" href="https://learn.shortruby.com/blog">the blog section</a> I am using <a target="_blank" href="https://sitepress.cc">sitepress</a>. It has an easy integration with Rails and helps move from static files to dynamic options easily. I also like that it has Frontmatter support and comes with a way to work with each pages meta-data.</p><p>It is a maintenance and low configuration solution: Install it, write things in <code>.html.md</code> files, and all will work.</p><h3 id="heading-debugging-debug-gem">Debugging: Debug gem</h3><p><a target="_blank" href="https://github.com/ruby/debug">Debug</a> gem is now the default debugger gem for Rails, and I like it a lot.</p><p>A lot of development is happening, and new features are always added. I started moving away from <code>puts</code> debugging to real debugging, and I even use the debugger to inspect object states while I do code design.</p><p>As the <code>debug</code> gem is part of Ruby, I think it can be a safe bet for the future with little maintenance or overhead required. I am using it in all my projects.</p><h3 id="heading-asset-pipeline-propshaft">Asset pipeline: Propshaft</h3><p>On multiple occasions, <a target="_blank" href="https://github.com/rails/propshaft"><code>propshaft</code></a> mentioned that it would become the default in Rails, so I chose it because I think it will make upgrading Rails easier. I also like that <code>propshaft</code> is simple and does one thing well.</p><h3 id="heading-beautiful-web-ux-hotwire">Beautiful web UX: Hotwire</h3><p>Of course, <a target="_blank" href="https://hotwired.dev">Hotwire</a> is my choice for building web UX. It comes with Rails by default, and it has a lot of good integrations. It allows me to build promising web UX without the mental overhead of learning more JS or a new JS library.</p><p>When I need to write some JS, I like to use Stimulus because it gives me a place and a way to organize JS code. This is important for a project you dont open daily to know exactly where to find each piece of code.</p><h3 id="heading-environment-variables-direnv">Environment variables: direnv</h3><p>I started using <a target="_blank" href="https://direnv.net"><code>direnv</code></a> a while back, mostly because I needed to share some environment variables between projects. So I can put a <code>.envrc</code> file in the top folder for a group of projects and have those environment vars available in all subfolders.</p><p>It removes the need to use the <code>dotenv</code> gem and also decreases the chances of accidentally committing your <code>.env</code> file if you put the <code>.envrc</code> in the parent folder and not the code folder.</p><p>I mostly organize my projects like this:</p><pre><code class="lang-bash">projects/project/shortruby/project/shortruby/appsproject/shortruby/apps/shortruby.comproject/shortruby/apps/lean.shortruby.comproject/shortruby/apps/newsletter.shortruby.comproject/shortruby/imagesproject/shortruby/docsproject/shortruby/scripts</code></pre><p>I can add the <code>.envrc</code> file under <code>projects/shortruby/apps</code> and have the common development environment variables shared between multiple projects.</p><h3 id="heading-code-quality-rubocop-rubycritic">Code Quality: Rubocop + Rubycritic</h3><p>I use Rubocop to enforce <a target="_blank" href="https://github.com/lucianghinda/personal-ruby-style">a coding style</a> that I like and made for myself. It is well-supported by any editor and allows me to configure it as I see fit. In my case, I lean toward using new Ruby syntax so the cops are configured to allow that.</p><p>I added to it the following gems:</p><ul><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-rails">rubocop-rails</a> - Automatic Rails code style checking tool</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-performance">rubocop-performance</a> - A collection of RuboCop cops to check for performance optimizations in Ruby code</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-minitest">rubocop-minitest</a> - Automatic Minitest code style checking tool</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-capybara">rubocop-capybara</a> - Code style checking for Capybara test files (RSpec, Cucumber, Minitest)</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-rubycw">rubocop-rubycw</a> - Integrate RuboCop and ruby -cw. You can get Ruby's warning as a RuboCop offense by rubocop-rubycw</p></li></ul><p>I also use <a target="_blank" href="https://github.com/whitesmith/rubycritic">Rubycritic</a> to keep complexity low. I dont refactor for every code smell, but it is good to have an excellent overview of what complexity each change might add to the existing codebase.</p><p>You can see how I configured all these tools in my article about <a target="_blank" href="https://learn.shortruby.com/blog/first-commits">First commits</a>.</p><h3 id="heading-code-editor-rubymine">Code editor: RubyMine</h3><p>I am using <a target="_blank" href="https://www.jetbrains.com/ruby/"><code>RubyMine</code></a> as my primary code editor with <code>IdeaVim</code> plugin enabled. I worked with it quite a lot in the last few years and am fast when using it. Helps with debugging and go to definition.</p><h3 id="heading-versioning-github">Versioning: Github</h3><p>I have some experience with both <a target="_blank" href="https://github.com">Github</a> and <a target="_blank" href="https://gitlab.com">Gitlab</a>. But I think GitHub is a good choice here due to its UI simplicity. I dont want to spend time in configuration and the UI/UX choices are great. The PR flow is quick and easy to set up, and for my needs, I only use GitHub actions to run simple tasks. I think Gitlab has a more powerful CI but I dont think I need it right now.</p><h3 id="heading-sending-emails-postmark">Sending emails: Postmark</h3><p>For sending emails, I choose <a target="_blank" href="https://postmarkapp.com">Postmark</a>. It is easy to set up and offers API-sending email and SMTP services.</p><p>I dont know if Postmark will be used to send the course content. But it will be used to send all transactional emails related to the project.</p><p>I am considering <a target="_blank" href="https://convertkit.com">ConverKit</a> to write and send the actual email content. This will depend on their API support for the features I plan to build for my users: restart an email course, choose periodicity, and more.</p><h3 id="heading-hosting-hetzner-and-hatchbox">Hosting: Hetzner and Hatchbox</h3><p>I am using VPS <a target="_blank" href="https://www.hetzner.com">Hetzner</a> as they have a good price and being in EU it helps me with registering the invoice to my accounting.</p><p>I am using deployment <a target="_blank" href="https://hatchbox.io">Hatchbox</a> for ease of use. I enabled automatic deployments to make a deploy with <code>git push</code> - simple and effective.</p><h3 id="heading-analytics-plausible">Analytics: Plausible</h3><p>I discovered <a target="_blank" href="https://plausible.io">Plausible</a> while learning Elixir and Phoenix and started using it for all my side projects. I like the simplicity, and it is also a good price for what it is offering.</p><h3 id="heading-image-storage-cloudflare-r2">Image storage: Cloudflare R2</h3><p>I started looking for an AWS S3 alternative primarily due to the time spent configuring IAM and other stuff that always needed to be done when setting up a new account.</p><p>I discovered Cloudflare R2 last year (https://www.cloudflare.com/developer-platform/r2/), and I started using it. Again, I like the simplicity (it can be configured in minutes) and can be used for ActiveStorage. What I plan to build, where I will mostly upload and display images, worked perfectly and for an excellent price.</p><p>AWS S3 could be better for more complex tasks, but if the only need is to upload images and display them publicly while being a solo developer, the R2 is a good choice.</p><h2 id="heading-conclusion">Conclusion</h2><p>These are the tools I use and why I chose them to build <a target="_blank" href="https://learn.shortruby.com">the email courses project</a></p><p>I dont think there exists a thing called the best tech stack, but depending on my knowledge and purpose, I can create an excellent tech stack to help me deliver fast and with good code quality.</p><hr /><p><strong>Enjoyed this article?</strong></p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://shortruby.com/"><strong>learn.shortruby.com</strong></a>- effortless learning anytime, anywhere</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="https://shortruby.com/"><strong>rubyandrails.info</strong></a><strong>, a directory with learning content about Ruby.</strong></p><p>🤝 Let's connect on <a target="_blank" href="https://shortruby.com/"><strong>Ruby.social</strong></a> <strong>or</strong> <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> <strong>or</strong> <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> <strong>where I post mainly about Ruby and Rails.</strong></p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/the-tech-stack-i-choose-to-build-my-email-courses-project</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/the-tech-stack-i-choose-to-build-my-email-courses-project</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 25 Jan 2024 03:57:46 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1706111082387/796f04f9-4e2a-4ba3-80ea-5e943d2a0c9e.png</cover_image></item><item><title><![CDATA[First commits in a Ruby on Rails app]]></title><description><![CDATA[<p>I had the idea for this project (<a target="_blank" href="https://learn.shortruby.com">Ruby and Ruby on Rails courses over email</a>) three months ago and created the Ruby on Rails repo around that time.</p><p>I was unsure what form the product would have, so I focused first on setting up coding styles, static analysis checks, and some other defaults that I consider essential when starting a project.</p><p>In this article, I will explore what I added and why I chose that specific tool, talking specifically from the perspective of a single-person side project.</p><h2 id="heading-what-i-wanted-to-achieve-with-my-first-commits">What I wanted to achieve with my first commits</h2><p>There is a kind of inertia to the code design: new code tends to be similar to existing code.</p><p>Here is a better quote from Edmond Lau book called The Effective Engineer that shows the positive side of this inertia:</p><blockquote><p>The code quality also self-propagates; new engineers model their own code based on the excellent code that they see, creating a positive feedback loop.</p></blockquote><p>When working alone on your project, you can replace new engineer with you in the future, the quote will still be relevant.</p><p>My initial commits aimed to set up automated checks for code quality and security. I want to help my future self be fast and write quality code.</p><p>Side projects developed while having a full-time job have a unique characteristic worth noting. The time dedicated to working on the side project is not continuous. For instance, you may work on it for 1-2 hours on Saturday, and the next opportunity to work on it may only arise a week later.</p><p>It is then essential to make the code quality built-in and use as much automation as possible.</p><h2 id="heading-first-commits">First commits</h2><p>My first commits were about:</p><ul><li><p>adding Rubocop to correct and enforce coding styles automatically</p></li><li><p>check code quality with Rubycritic</p></li><li><p>security audits with brakeman, bundler-audit and importmap audit</p></li><li><p>enable Rails strict loading</p></li><li><p>make Rails console run on sandbox mode in production</p></li></ul><h3 id="heading-add-rubocop">Add Rubocop</h3><p>I have a <a target="_blank" href="https://github.com/lucianghinda/personal-ruby-style">Rubocop configuration</a> that I like to use in my side projects.</p><p>The first commit sets up Rubocop and adds my configuration to the <code>.rubocop.yml</code> file.</p><h4 id="heading-why-add-rubocop">Why add Rubocop</h4><p>I have a lot of thoughts about what cop I want enabled and what cop disabled, and sometimes I change my mind about some of them, but for this project, consistency is more important than a specific cop.</p><p>It is essential to have a consistent style while coding. I also like to have the editor auto-format my code, so I chose Rubocop because I want to use the new Ruby syntax, and thus, I can switch on/off rules that will impede this.</p><h4 id="heading-what-i-added-to-the-commit">What I added to the commit</h4><p><strong>1. Rubocop gems</strong></p><p>I am using the following Rubocop gems:</p><ul><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-rails">rubocop-rails</a> - Automatic Rails code style checking tool</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-performance">rubocop-performance</a> - A collection of RuboCop cops to check for performance optimizations in Ruby code</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-minitest">rubocop-minitest</a> - Automatic Minitest code style checking tool</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-capybara">rubocop-capybara</a> - Code style checking for Capybara test files (RSpec, Cucumber, Minitest)</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/rubocop-rubycw">rubocop-rubycw</a> - Integrate RuboCop and ruby -cw. You can get Ruby's warning as a RuboCop offense by rubocop-rubycw</p></li></ul><p><strong>2. Apply Rubocop autocorrection to files generated by</strong> <code>Rails generate</code></p><p>I want to automatically run Rubocop with autocorrect on any <code>.rb</code> file generated by the Rails generate command. This will ensure that any gem that generates Ruby files or any I generate will have the style automatically corrected.</p><p>I added the following code to the <code>config/application.rb</code> to do this:</p><pre><code class="lang-ruby">config.generators.after_generate <span class="hljs-keyword">do</span> <span class="hljs-params">|files|</span>  parsable_files = files.filter { <span class="hljs-params">|file|</span> file.end_with?(<span class="hljs-string">".rb"</span>) }  <span class="hljs-keyword">unless</span> parsable_files.empty?    system(<span class="hljs-string">"bundle exec rubocop -A --fail-level=E <span class="hljs-subst">#{parsable_files.shelljoin}</span>"</span>, <span class="hljs-symbol">exception:</span> <span class="hljs-literal">true</span>)  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>I took this code from <a target="_blank" href="https://docs.rubocop.org/rubocop-rails/usage.html#rails-configuration-tip">Rubocop Rails Configuration tip</a> and you can see it added to <a target="_blank" href="https://github.com/rails/rails/blob/main/railties/lib/rails/configuration.rb#L134">the current Rails master</a></p><p>And I made Rubocop run on Github CI with by adding the following content to the file under <code>.github/workflows/rubocop.yml</code>:</p><pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Rubocop</span> <span class="hljs-string">Linter</span><span class="hljs-attr">on:</span>  <span class="hljs-attr">push:</span>    <span class="hljs-attr">branches:</span>      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>  <span class="hljs-attr">pull_request:</span>    <span class="hljs-attr">branches:</span>      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><span class="hljs-attr">jobs:</span>  <span class="hljs-attr">build:</span>    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>    <span class="hljs-attr">steps:</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Ruby</span>        <span class="hljs-attr">uses:</span> <span class="hljs-string">ruby/setup-ruby@v1</span>        <span class="hljs-attr">with:</span>          <span class="hljs-attr">ruby-version:</span> <span class="hljs-number">3.3</span>          <span class="hljs-attr">bundler-cache:</span> <span class="hljs-literal">true</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">bundle</span> <span class="hljs-string">install</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checking</span> <span class="hljs-string">Rubocop</span> <span class="hljs-string">Styles</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">bundle</span> <span class="hljs-string">exec</span> <span class="hljs-string">rubocop</span></code></pre><p>This will run Rubocop on the following two cases:</p><ul><li><p>When code is pushed to the main branch.</p></li><li><p>When a pull request is made to the main branch.</p></li></ul><h3 id="heading-code-quality-with-rubycritic">Code Quality with Rubycritic</h3><p>The third commit adds <a target="_blank" href="https://github.com/whitesmith/rubycritic">Rubycritic</a> as a code quality static analysis.</p><h4 id="heading-why-add-rubycritic">Why add Rubycritic</h4><p>I added this because I want to ensure I create simple code. And while Rubycritic does not guarantee the code will be simple, it can help maintainability. I see maintainability as an essential metric in the context of a side project.</p><h4 id="heading-the-commit">The commit</h4><p>I dont want to run Rubycritic in CI mostly because I think about having little time and trying to be fast with feature releases, and sometimes refactoring to remove a code smell can take quite some time.</p><p>But I do want to run this locally, so I just created a file under <code>bin/quality_check</code> with the following content:</p><pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash -e</span>bundle <span class="hljs-built_in">exec</span> rubycritic</code></pre><p>Why a file and not run directly <code>rubycritic</code>? Id like to add more tools like this here or configure Rubycritic in a specific way and having a bash script allows me this flexibility.</p><p>Rubycritic uses <a target="_blank" href="https://github.com/troessner/reek">reek</a> under the hood so I added a reek config files at <code>.reek.yml</code> with the following content:</p><pre><code class="lang-yml"><span class="hljs-meta">---</span><span class="hljs-attr">detectors:</span>  <span class="hljs-attr">IrresponsibleModule:</span>    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">false</span>  <span class="hljs-attr">LongParameterList:</span>    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>    <span class="hljs-attr">exclude:</span> []    <span class="hljs-attr">max_params:</span> <span class="hljs-number">4</span>    <span class="hljs-attr">overrides:</span>      <span class="hljs-attr">initialize:</span>        <span class="hljs-attr">max_params:</span> <span class="hljs-number">5</span>  <span class="hljs-attr">DataClump:</span>    <span class="hljs-attr">max_copies:</span> <span class="hljs-number">3</span>    <span class="hljs-attr">min_clump_size:</span> <span class="hljs-number">3</span><span class="hljs-comment">### Excluding directories</span><span class="hljs-attr">exclude_paths:</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">test/</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">config/</span></code></pre><p>I mostly disabled <code>IrresponsibleModule</code> because I dont want to add a description to all classes/modules I create.</p><p>I also allowed a maximum of 4 parameters for methods and 5 for the initializers. I also allowed a maximum of 3 methods in an object to have the same parameters, and the check happens only from 3 parameters up. I can rationalize this decision, but it is mainly based on my experience with extracting objects from a long list of parameters and finding a balance when to make this effort.</p><h3 id="heading-security-checks-on-ci">Security checks on CI</h3><p>The fourth commit is concerned with adding security checks to the CI.</p><h4 id="heading-why-add-security-checks">Why add security checks</h4><p>I want to run on CI a couple of security audits that I grouped under the name of <code>Static Analysis</code>:</p><ul><li><p><a target="_blank" href="https://rubygems.org/gems/brakeman">Brakeman</a> - Brakeman detects security vulnerabilities in Ruby on Rails applications via static analysis</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/bundle-audit">Bundle audit</a> - Patch-level verification for Bundler</p></li><li><p><a target="_blank" href="https://github.com/rails/importmap-rails">Importmap audit</a> - checks the NPM registry for known security issues</p></li></ul><h4 id="heading-the-commit-1">The commit</h4><p>Here is the CI config for Github to run all these tools in <code>.github/workflows/static_analysis.yml</code>:</p><pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Static</span> <span class="hljs-string">Analysis</span><span class="hljs-attr">on:</span>  <span class="hljs-attr">push:</span>    <span class="hljs-attr">branches:</span>      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>  <span class="hljs-attr">pull_request:</span>    <span class="hljs-attr">branches:</span>      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><span class="hljs-attr">jobs:</span>  <span class="hljs-attr">static_analysis:</span>    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>    <span class="hljs-attr">steps:</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Ruby</span>        <span class="hljs-attr">uses:</span> <span class="hljs-string">ruby/setup-ruby@v1</span>        <span class="hljs-attr">with:</span>          <span class="hljs-attr">ruby-version:</span> <span class="hljs-number">3.3</span>          <span class="hljs-attr">bundler-cache:</span> <span class="hljs-literal">true</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Bundler</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">gem</span> <span class="hljs-string">install</span> <span class="hljs-string">bundler</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">|          bundle config path vendor/bundle          bundle install --jobs 4 --retry 3</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">bundler-integrity</span> <span class="hljs-string">check</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">bundle</span> <span class="hljs-string">exec</span> <span class="hljs-string">bundler-integrity</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Brakeman</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">bundle</span> <span class="hljs-string">exec</span> <span class="hljs-string">brakeman</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Bundle</span> <span class="hljs-string">Audit</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">bundle</span> <span class="hljs-string">exec</span> <span class="hljs-string">bundle-audit</span> <span class="hljs-string">check</span> <span class="hljs-string">--update</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Importmap</span> <span class="hljs-string">audit</span>        <span class="hljs-attr">run:</span> <span class="hljs-string">bundle</span> <span class="hljs-string">exec</span> <span class="hljs-string">bin/importmap</span> <span class="hljs-string">audit</span></code></pre><h3 id="heading-enable-rails-strict-loading">Enable Rails strict loading</h3><p>Here is what <code>strict_loading</code> does (<a target="_blank" href="https://github.com/rails/rails/pull/37400">source</a>):</p><blockquote><p>Add #strict to any record to prevent lazy loading of associations. strict will cascade down from the parent record to all the associations to help you catch any places where you may want to use preload instead of lazy loading.</p></blockquote><h4 id="heading-why-enable-strict-loading">Why enable strict loading</h4><p>This is an excellent way to prevent N+1 and ensure that any needed association is included or preloaded.</p><h4 id="heading-the-commit-2">The commit</h4><p>I enabled <code>strict_loading</code> in all environments:</p><pre><code class="lang-ruby"><span class="hljs-comment"># config/environments/development.rb</span>config.active_record.strict_loading_by_default = <span class="hljs-literal">true</span></code></pre><pre><code class="lang-ruby"><span class="hljs-comment"># config/environments/test.rb</span>config.active_record.strict_loading_by_default = <span class="hljs-literal">true</span></code></pre><pre><code class="lang-ruby"><span class="hljs-comment"># config/environments/production.rb</span>config.active_record.strict_loading_by_default = <span class="hljs-literal">true</span></code></pre><h3 id="heading-make-rails-console-run-on-sandbox-mode-in-production">Make Rails console run on sandbox mode in production</h3><h4 id="heading-why-make-console-run-in-sandbox-mode">Why make console run in sandbox mode</h4><p>It is a good practice to run the rails console in production in the sandbox box to ensure any change is intentional.</p><p>I use <code>rails console</code> quite often in production, but I want to make sure that if I want to make any changes, that should be intentional and not accidental.</p><h4 id="heading-the-commit-3">The commit</h4><p>This is a <a target="_blank" href="https://github.com/rails/rails/pull/48984">simple switch</a> that I can toggle in the Rails config for production:</p><pre><code class="lang-ruby"><span class="hljs-comment"># config/environments/production.rb</span>config.sandbox_by_default = <span class="hljs-literal">true</span></code></pre><p>If this is enabled, then to make changes, you have to run <code>rails console --no-sandbox</code></p><h2 id="heading-conclusion">Conclusion</h2><p>I made the first commits in this repository intentional about code quality and automatic checks.</p><p>It helps with moving fast while keeping a good code quality. A lot can be done in this area, but I tried to add the minimum that would help me.</p><hr /><h2 id="heading-a-note-about-eager-loading-code">A note about eager loading code</h2><p>A previous version of this article included a section about configuring CI to run <code>bin/rails zeitwerk:check</code> to check if the code loads correctly.</p><p><a target="_blank" href="https://hashref.com">Xavier Noria</a> correctly pointed out that running this in CI is unnecessary.</p><p>First, there is a config setting called <a target="_blank" href="https://guides.rubyonrails.org/v7.0/configuring.html#config-eager-load"><code>config.eager_load</code></a>, and if you put that on <code>true</code> in your test environment and run any test that will eagerly load your entire application. This setting exists already in Rails 6.0 (and even before 6.0).</p><p>Since Rails 7.0 the generated <code>config/environment/test.rb</code> includes the following line:</p><pre><code class="lang-ruby">config.eager_load = ENV[<span class="hljs-string">"CI"</span>].present?</code></pre><p>That means if you run your tests in Github CI (or similar CIs) that sets the <code>CI</code> environment variable, it will eagerly load your application.</p><p>So there is no need to execute <code>rails zeitwerk:check</code> separately. Either set the <code>config.eager_load</code> to true on CI, or you already have that if you generated your app with Rails &gt;= 7.0 and run your tests on CI.</p><hr /><p><strong>Enjoyed this article?</strong></p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://shortruby.com/"><strong>learn.shortruby.com</strong></a>- effortless learning anytime, anywhere</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="https://shortruby.com/"><strong>rubyandrails.info</strong></a><strong>, a directory with learning content about Ruby.</strong></p><p>🤝 Let's connect on <a target="_blank" href="https://shortruby.com/"><strong>Ruby.social</strong></a> <strong>or</strong> <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> <strong>or</strong> <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> <strong>where I post mainly about Ruby and Rails.</strong></p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/first-commits-in-a-ruby-on-rails-app</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/first-commits-in-a-ruby-on-rails-app</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 17 Jan 2024 10:45:25 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1705488125930/fdcf25bd-f905-45c1-9fce-3cfd82695c97.webp</cover_image></item><item><title><![CDATA[Finding where a method is defined in Ruby using IRB]]></title><description><![CDATA[<p>After many years in Ruby, I am still fascinated by the flexibility, simplicity, and power of <code>IRB</code> and <code>rails console</code>. Even with IDEs getting better, I like the experience of using IRB or rails console to explore the code.</p><p>In this article, I will show how to find the source where a method is defined.</p><p>There are three ways to find out where a method is defined in Ruby using IRB:</p><ul><li><p>using the <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/Method.html#method-i-source_location"><code>source_location</code></a> defined on the Method object</p></li><li><p>using the IRB <code>show_source</code> command</p></li><li><p>displaying information about an object using <code>ls</code> (for displaying the object but not the source file)</p></li></ul><p>Before we start, here is my IRB configuration:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703216915183/134180f6-02d3-49e0-a800-5419188ca747.png" alt class="image--center mx-auto" /></p><pre><code class="lang-ruby"><span class="hljs-comment"># .irbrc content</span><span class="hljs-comment"># I use the Dracula theme for IRB from https://github.com/katsyoshi/irb-theme-dracula</span><span class="hljs-keyword">require</span> <span class="hljs-string">"irb/theme/dracula/light"</span></code></pre><p>All code executed below is run inside <code>irb</code>.</p><h2 id="heading-a-simple-ruby-script">A simple Ruby script</h2><p>Let's assume the following object:</p><pre><code class="lang-ruby"><span class="hljs-comment"># frozen_string_literal: true</span><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Universe</span></span>  <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Galaxy</span></span>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Star</span></span>      <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:name</span>      <span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:name</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(name)</span></span>        @name = name      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Now let's start IRB and instantiate an object out of it:</p><pre><code class="lang-ruby">require_relative <span class="hljs-string">"universe/galaxy/star"</span>star = Universe::Galaxy::Star.new(<span class="hljs-string">"Some Other Sun"</span>)</code></pre><h3 id="heading-finding-available-methods-and-instance-variables">Finding available methods and instance variables</h3><p>When in IRB you can use <code>ls</code> to display the available methods and instance variables:</p><pre><code class="lang-ruby">ls star<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># Universe::Galaxy::Star#methods: name  name=</span><span class="hljs-comment"># instance variables: <span class="hljs-doctag">@name</span></span></code></pre><p>While this does not provide a source file and a line number you will see in examples below that it can correctly identify in case of inheritance for example where a method is defined.</p><h3 id="heading-finding-the-source-using-methodsourcelocation">Finding the source using <code>Method#source_location</code></h3><p>Executing <code>star.method(:name).source_location</code> will return the proper source location with the specific line number:</p><pre><code class="lang-ruby">star.method(<span class="hljs-symbol">:name</span>) <span class="hljs-comment"># This will return a Method object</span>star.method(<span class="hljs-symbol">:name</span>).source_location <span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># ["universe/galaxy/star.rb", 6]</span></code></pre><p>What about the setter? It will work in this case too:</p><pre><code class="lang-ruby">star.method(<span class="hljs-symbol">:name=</span>).source_location <span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># ["universe/galaxy/star.rb", 7]</span></code></pre><h3 id="heading-finding-the-source-using-showsource-from-irb">Finding the source using <code>show_source</code> from IRB</h3><p>When in IRB you can use <code>show_source</code> to display the source for a method:</p><pre><code class="lang-ruby">show_source star.name</code></pre><p>The response will be:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> universe/galaxy/star.<span class="hljs-symbol">rb:</span><span class="hljs-number">6</span>      <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:name</span>      <span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:name</span></code></pre><p>This will work for the setter too so executing the following command:</p><pre><code class="lang-ruby">show_source star.name=</code></pre><p>Will return the following response:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> universe/galaxy/star.<span class="hljs-symbol">rb:</span><span class="hljs-number">7</span>      <span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:name</span></code></pre><h2 id="heading-a-more-complex-ruby-script">A more complex Ruby script</h2><p>What if we add inheritance? How will discovering the source work in this case?<br />Let's now create a new class:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Universe</span></span>  <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Galaxy</span></span>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleSun</span> &lt; Star</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>        <span class="hljs-keyword">super</span>(<span class="hljs-string">'Sun'</span>)      <span class="hljs-keyword">end</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">our_planet</span></span>        Planet.new(<span class="hljs-string">'Earth'</span>)      <span class="hljs-keyword">end</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">name</span></span>        puts <span class="hljs-string">"SimpleSun<span class="hljs-subst">#{@name}</span>"</span>      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Let's load this in an IRB session:</p><pre><code class="lang-ruby">require_relative <span class="hljs-string">"universe/galaxy/simple_sun"</span>simple_sun = Universe::Galaxy::SimpleSun.new</code></pre><h3 id="heading-finding-available-methods-and-instance-variables-1">Finding available methods and instance variables</h3><p>Let's explore with <code>ls</code>:</p><pre><code class="lang-ruby">ls simple_sun<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># Universe::Galaxy::SimpleSun#methods: name  our_planet</span><span class="hljs-comment"># Universe::Galaxy::Star#methods: name=</span><span class="hljs-comment"># instance variables: <span class="hljs-doctag">@name</span></span></code></pre><p>Notice that it correctly showed that <code>name</code> is from <code>Star</code> and was not overwritten in the SimpleSun while <code>name=</code> is correctly identified as being from <code>Universe::Galaxy::Star</code></p><h3 id="heading-finding-source-location-with-methodsourcelocation">Finding source location with <code>Method#source_location</code></h3><p>Using <code>source_location</code> on <code>name</code> will show the source being in <code>simple_sun.rb</code> while using <code>source_location</code> on <code>name=</code> will show the source being <code>star.rb</code>:</p><pre><code class="lang-ruby">simple_sun.method(<span class="hljs-symbol">:name</span>).source_location<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># ["universe/galaxy/simple_sun.rb", 14]</span>simple_sun.method(<span class="hljs-symbol">:name=</span>).source_location<span class="hljs-comment"># =&gt;</span><span class="hljs-comment"># ["universe/galaxy/star.rb", 7]</span></code></pre><h3 id="heading-finding-source-location-with-showsource-irb-command">Finding source location with <code>show_source</code> IRB command</h3><p>Using <code>show_source</code> will correctly display the source code for both <code>name</code> and for <code>name=</code> (inherited from <code>Star</code>).</p><p>Executing:</p><pre><code class="lang-ruby">show_source simple_sun.name</code></pre><p>Will return the following response:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> universe/galaxy/simple_sun.<span class="hljs-symbol">rb:</span><span class="hljs-number">14</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">name</span></span>        puts <span class="hljs-string">"SimpleSun<span class="hljs-subst">#{@name}</span>"</span>      <span class="hljs-keyword">end</span></code></pre><p>While executing:</p><pre><code class="lang-ruby">show_source simple_sun.name=</code></pre><p>Will return the following response:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> universe/galaxy/star.<span class="hljs-symbol">rb:</span><span class="hljs-number">7</span>      <span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:name</span></code></pre><h3 id="heading-an-extra-option-for-showsource">An extra option for <code>show_source</code></h3><p>Let's execute again the <code>show_source</code> but this time on the initializer where in <code>SimpleSun</code> I wrote <code>super('Sun')</code> so I am calling super with an argument:</p><pre><code class="lang-ruby">show_source Universe::Galaxy::SimpleSun<span class="hljs-comment">#initialize</span></code></pre><p>This will return:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> universe/galaxy/simple_sun.<span class="hljs-symbol">rb:</span><span class="hljs-number">6</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>        <span class="hljs-keyword">super</span>(<span class="hljs-string">'Sun'</span>)      <span class="hljs-keyword">end</span></code></pre><p>What if you want to see the <code>super</code> method? <code>show_source</code> supports a flag <code>-s</code> that will help displaying the super class method. You can stack as many <code>-s</code> as you like eg. <code>-sssss</code></p><p>In our case there is one single super added there so I will execute:</p><pre><code class="lang-ruby">show_source Universe::Galaxy::SimpleSun<span class="hljs-comment">#initialize -s</span></code></pre><p>And this will return:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> universe/galaxy/star.<span class="hljs-symbol">rb:</span><span class="hljs-number">9</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(name)</span></span>        @name = name      <span class="hljs-keyword">end</span></code></pre><h2 id="heading-a-ruby-script-using-definemethod">A Ruby script using <code>define_method</code></h2><p>Now let's complicate a bit the things and define methods dynamically with <code>define_method</code>:</p><pre><code class="lang-ruby">require_relative <span class="hljs-string">"star"</span><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Universe</span></span>  <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Galaxy</span></span>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TwinSun</span> &lt; Star</span>      NAME_ALTERNATIVES = %i(twin_helios twin_sun twin_sol twin_solis)      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>        <span class="hljs-keyword">super</span>(<span class="hljs-string">'TwinSun'</span>)      <span class="hljs-keyword">end</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">our_planet</span></span>        <span class="hljs-string">'Twin Earth'</span>      <span class="hljs-keyword">end</span>      NAME_ALTERNATIVES.each <span class="hljs-keyword">do</span> <span class="hljs-params">|name|</span>        define_method <span class="hljs-string">"<span class="hljs-subst">#{name}</span>?"</span> <span class="hljs-keyword">do</span>          <span class="hljs-literal">true</span>        <span class="hljs-keyword">end</span>        define_method name <span class="hljs-keyword">do</span>          name        <span class="hljs-keyword">end</span>      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>With our new class defined, let's start the IRB and instantiate <code>twin_sun</code>:</p><pre><code class="lang-ruby">require_relative <span class="hljs-string">"universe/galaxy/twin_sun"</span>twin_sun = Universe::Galaxy::TwinSun.new</code></pre><h3 id="heading-finding-available-methods-and-instance-variables-2">Finding available methods and instance variables</h3><p>And we can explore with <code>ls</code>:</p><pre><code class="lang-ruby">ls twin_sun<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># Universe::Galaxy::TwinSun#methods:</span><span class="hljs-comment">#   our_planet  twin_helios  twin_helios?  twin_sol  twin_sol?  twin_solis  twin_solis?  twin_sun  twin_sun?</span><span class="hljs-comment"># Universe::Galaxy::Star#methods: name  name=</span><span class="hljs-comment"># instance variables: <span class="hljs-doctag">@name</span></span></code></pre><p>You can notice that it shows all the defined method too when doing <code>ls twin_sun</code>.</p><p><code>ls</code> also correctly identifies that <code>name</code> and <code>name=</code> are from the parent <code>Star</code> object.</p><h3 id="heading-finding-source-location-with-methodsourcelocation-1">Finding source location with <code>Method#source_location</code></h3><p>What about <code>source_location</code>?</p><p>Let's first try a method defined directly on the <code>TwinSun</code> and notice it shows the file and the line number:</p><pre><code class="lang-ruby">twin_sun.method(<span class="hljs-symbol">:our_planet</span>).source_location<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># ["universe/galaxy/twin_sun.rb", 12]</span></code></pre><p>Now let's try to identify one of the methods created with <code>define_method</code>:</p><pre><code class="lang-ruby">twin_sun.method(<span class="hljs-symbol">:twin_sun?</span>).source_location<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># ["universe/galaxy/twin_sun.rb", 17]</span></code></pre><p>And opening that directly from IRB with <code>edit "universe/galaxy/twin_sun.rb"</code> will show the exact line where <code>define_method #{name}?</code> is written:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703069102762/770fad4e-0b26-4cb5-a27c-c3d5aca6b83d.png" alt class="image--center mx-auto" /></p><h3 id="heading-finding-source-location-with-showsource-irb-command-1">Finding source location with <code>show_source</code> IRB command</h3><p>If you want to directly get a glimpse in IRB of the source code, you can use <code>show_source</code></p><pre><code class="lang-ruby">show_source twin_sun.twin_sun?</code></pre><p>The result will be:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> universe/galaxy/twin_sun.<span class="hljs-symbol">rb:</span><span class="hljs-number">17</span>        define_method <span class="hljs-string">"<span class="hljs-subst">#{name}</span>?"</span> <span class="hljs-keyword">do</span>          <span class="hljs-literal">true</span>        <span class="hljs-keyword">end</span></code></pre><p>This is super cool =&gt; it is showing the place where <code>define_method</code> is used to create the <code>twin_sun?</code> method:</p><h2 id="heading-rails">Rails</h2><p>I am using for this a newly generated Rails app (using Ruby 3.2.2 and Rails 7.1) where I just added two models. They are very simple:</p><pre><code class="lang-ruby"><span class="hljs-comment"># db/schema.db</span>create_table <span class="hljs-string">"galaxies"</span>, <span class="hljs-symbol">force:</span> <span class="hljs-symbol">:cascade</span> <span class="hljs-keyword">do</span> <span class="hljs-params">|t|</span>  t.string <span class="hljs-string">"name"</span>  t.datetime <span class="hljs-string">"created_at"</span>, <span class="hljs-symbol">null:</span> <span class="hljs-literal">false</span>  t.datetime <span class="hljs-string">"updated_at"</span>, <span class="hljs-symbol">null:</span> <span class="hljs-literal">false</span><span class="hljs-keyword">end</span>create_table <span class="hljs-string">"stars"</span>, <span class="hljs-symbol">force:</span> <span class="hljs-symbol">:cascade</span> <span class="hljs-keyword">do</span> <span class="hljs-params">|t|</span>  t.string <span class="hljs-string">"name"</span>  t.integer <span class="hljs-string">"galaxy_id"</span>, <span class="hljs-symbol">null:</span> <span class="hljs-literal">false</span>  t.datetime <span class="hljs-string">"created_at"</span>, <span class="hljs-symbol">null:</span> <span class="hljs-literal">false</span>  t.datetime <span class="hljs-string">"updated_at"</span>, <span class="hljs-symbol">null:</span> <span class="hljs-literal">false</span>  t.index [<span class="hljs-string">"galaxy_id"</span>], <span class="hljs-symbol">name:</span> <span class="hljs-string">"index_stars_on_galaxy_id"</span><span class="hljs-keyword">end</span>add_foreign_key <span class="hljs-string">"stars"</span>, <span class="hljs-string">"galaxies"</span><span class="hljs-comment"># app/models/galaxy.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Galaxy</span> &lt; ApplicationRecord</span>  has_many <span class="hljs-symbol">:stars</span><span class="hljs-keyword">end</span><span class="hljs-comment"># app/models/star.rb </span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Star</span> &lt; ApplicationRecord</span>  belongs_to <span class="hljs-symbol">:galaxy</span><span class="hljs-keyword">end</span></code></pre><p>Let's start the Rails console with <code>rails c</code> and try to find the source code or location for some methods.</p><p>Assume there is already a record for Galaxy with name <code>Andromeda</code> and a couple of stars in that galaxy: <code>Alpheratz</code> and <code>Mirach</code>.</p><h3 id="heading-finding-using-sourcelocation"><strong>Finding using</strong> <code>source_location</code></h3><p>We might want to load the galaxy with name <code>Andromeda</code> by using <code>Galaxy.find_by</code> but where is that method defined?</p><pre><code class="lang-ruby">Galaxy.method(<span class="hljs-symbol">:find_by</span>).source_location<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># [".gem/ruby/3.2.2/gems/activerecord-7.1.2/lib/active_record/core.rb", 256]</span></code></pre><p>And if we open that file, we will get:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703081049417/b9479a36-5348-467e-8c9b-c7ba35bd141d.png" alt class="image--center mx-auto" /></p><p>What about <code>exists?</code> from Active Record? That works too indicating where <code>exists?</code> is defined:</p><pre><code class="lang-ruby">Galaxy.method(<span class="hljs-symbol">:exists?</span>).source_location<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># [".gem/ruby/3.2.2/gems/activerecord-7.1.2/lib/active_record/querying.rb", 23]</span></code></pre><h3 id="heading-finding-the-source-using-showsource"><strong>Finding the source using</strong> <code>show_source</code></h3><pre><code class="lang-ruby">show_source Galaxy.find_by</code></pre><p>The result of executing that line will be:</p><pre><code class="lang-ruby"><span class="hljs-symbol">From:</span> .gem/ruby/<span class="hljs-number">3.2</span>.<span class="hljs-number">2</span>/gems/activerecord-<span class="hljs-number">7.1</span>.<span class="hljs-number">2</span>/lib/active_record/core.<span class="hljs-symbol">rb:</span><span class="hljs-number">256</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">find_by</span><span class="hljs-params">(*args)</span></span> <span class="hljs-comment"># :nodoc:</span>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span> <span class="hljs-keyword">if</span> scope_attributes?        hash = args.first        <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span> <span class="hljs-keyword">unless</span> Hash === hash        hash = hash.each_with_object({}) <span class="hljs-keyword">do</span> <span class="hljs-params">|(key, value), h|</span><span class="hljs-comment"># ...</span></code></pre><p>I only included here the first few lines from the <code>find_by</code> method but you can notice that it correctly displayed the source code.</p><h3 id="heading-finding-source-code-for-params-in-the-rails-controller">Finding source code for <code>params</code> in the Rails controller</h3><p>I generated a simple scaffolding for Galaxies and I will use the <code>debug</code> gem (included by default in Rails 7.1) to start an IRB session in the context of the <code>index</code> action:</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/controllers/galaxies_controller.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GalaxiesController</span> &lt; ApplicationController</span>  before_action <span class="hljs-symbol">:set_galaxy</span>, <span class="hljs-symbol">only:</span> %i[ show edit update destroy ]  <span class="hljs-comment"># GET /galaxies or /galaxies.json</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span></span>    binding.irb    @galaxies = Galaxy.all  <span class="hljs-keyword">end</span><span class="hljs-comment"># ... more code</span></code></pre><h4 id="heading-using-sourcelocation">Using <code>source_location</code></h4><p>Knowing that params is a method (can be easily checked with <code>self.methods.include?(:params)</code> inside the action) I can do the following:</p><pre><code class="lang-ruby"><span class="hljs-keyword">self</span>.method(<span class="hljs-symbol">:params</span>).source_location <span class="hljs-comment"># works also without self</span><span class="hljs-comment"># =&gt; </span>[<span class="hljs-string">".gem/ruby/3.2.2/gems/actionpack-7.1.2/lib/action_controller/metal/strong_parameters.rb"</span>, <span class="hljs-number">1281</span>]</code></pre><h4 id="heading-using-showsource">Using <code>show_source</code></h4><pre><code class="lang-ruby">show_source params<span class="hljs-comment"># =&gt; </span><span class="hljs-symbol">From:</span> .gem/ruby/<span class="hljs-number">3.2</span>.<span class="hljs-number">2</span>/gems/actionpack-<span class="hljs-number">7.1</span>.<span class="hljs-number">2</span>/lib/action_controller/metal/strong_parameters.<span class="hljs-symbol">rb:</span><span class="hljs-number">1281</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">params</span></span>      @_params <span class="hljs-params">||</span>= <span class="hljs-keyword">begin</span>        context = {          <span class="hljs-symbol">controller:</span> <span class="hljs-keyword">self</span>.<span class="hljs-keyword">class</span>.name,          <span class="hljs-symbol">action:</span> action_name,          <span class="hljs-symbol">request:</span> request,          <span class="hljs-symbol">params:</span> request.filtered_parameters        }        Parameters.new(request.parameters, context)      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span></code></pre><h4 id="heading-exploring-params-with-ls">Exploring <code>params</code> with ls</h4><p>You can find out more information about <code>params</code> if you use <code>ls</code> command:</p><pre><code class="lang-ruby">irb(#&lt;GalaxiesController:0x000000...):004&gt; ls paramsActionController::Parameters<span class="hljs-comment">#methods:</span>  ==                    []               []=               always_permitted_parameters  always_permitted_parameters=  as_json      compact  compact!              compact_blank    compact_blank!    converted_arrays             deep_dup                      deep_merge?  deep_transform_keys  deep_transform_keys!  delete           delete_if         dig                          each                          each_key     each_pair  each_value            empty?           encode_with       eql?                         except                        exclude?     extract!  extract_value         fetch            has_key?          has_value?                   hash                          <span class="hljs-keyword">include</span>?     init_with  inspect               keep_if          key?              keys                         member?                       merge        merge!  permit                permit!          permitted?        reject                       reject!                       <span class="hljs-keyword">require</span>      required  reverse_merge         reverse_merge!   select            select!                      slice                         slice!       stringify_keys  to_h                  to_hash          to_param          to_query                     to_s                          to_unsafe_h  to_unsafe_hash  transform_keys        transform_keys!  transform_values  transform_values!            value?                        values       values_at  with_defaults         with_defaults!   withoutActiveSupport::DeepMergeable<span class="hljs-comment">#methods: deep_merge  deep_merge!</span>instance <span class="hljs-symbol">variables:</span> @logging_context  @parameters  @permitted<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">variables</span>:</span>  @@action_on_unpermitted_parameters  @@allow_deprecated_parameters_hash_equality  @@always_permitted_parameters  @@permit_all_parameters</code></pre><h2 id="heading-when-showsource-and-sourcelocation-does-not-work">When <code>show_source</code> and <code>source_location</code> does not work</h2><h3 id="heading-when-methods-are-defined-with-methodmissing">When methods are defined with <code>method_missing</code></h3><p>In case you have your behavior defined with <code>method_missing</code> then <code>source_location</code> and <code>show_source</code> will not work:</p><pre><code class="lang-ruby"><span class="hljs-comment"># frozen_string_literal: true</span><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Universe</span></span>  <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Galaxy</span></span>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Sun</span> &lt; Star</span>      DYNAMIC_METHODS = %i(life?).freeze      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>        <span class="hljs-keyword">super</span>(<span class="hljs-string">'Sun'</span>)      <span class="hljs-keyword">end</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">our_planet</span></span>        <span class="hljs-string">'Earth'</span>      <span class="hljs-keyword">end</span>      private      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">respond_to_missing?</span><span class="hljs-params">(name, include_private = <span class="hljs-literal">false</span>)</span></span>        DYNAMIC_METHODS.<span class="hljs-keyword">include</span>?(name) <span class="hljs-params">||</span> <span class="hljs-keyword">super</span>      <span class="hljs-keyword">end</span>      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">method_missing</span><span class="hljs-params">(name, *args)</span></span>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span> <span class="hljs-keyword">unless</span> DYNAMIC_METHODS.<span class="hljs-keyword">include</span>?(name)        <span class="hljs-keyword">if</span> name == <span class="hljs-symbol">:life?</span>          <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>        <span class="hljs-keyword">end</span>      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>In this case if you load this into IRB:</p><pre><code class="lang-ruby">require_relative <span class="hljs-string">"universe/galaxy/sun"</span>sun = Universe::Galaxy::Sun.new</code></pre><p>You can notice that you can execute <code>sun.life?</code> and even check <code>sun.respond_to?(:life?)</code> :</p><pre><code class="lang-ruby">sun.life? <span class="hljs-comment"># =&gt; true</span>sun.respond_to?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment"># =&gt; true</span></code></pre><p>You will then notice that <code>life?</code> is not a method when investigating with:</p><pre><code class="lang-ruby">sun.methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment"># =&gt; false</span>sun.private_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment"># =&gt; false</span>Universe::Galaxy::Sun.public_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment">#  false</span>Universe::Galaxy::Sun.protected_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment"># false</span>Universe::Galaxy::Sun.private_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment"># false </span>Universe::Galaxy::Sun.singleton_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment"># false</span>Universe::Galaxy::Sun.instance_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:life?</span>) <span class="hljs-comment"># false</span></code></pre><p>You can still get the <code>Method</code> object for it:</p><pre><code class="lang-ruby">sun.method(<span class="hljs-symbol">:life?</span>)<span class="hljs-comment"># =&gt; #&lt;Method: Universe::Galaxy::Sun#life?(*)&gt;</span></code></pre><p>But <code>source_location</code> or <code>show_source</code> will not work:</p><pre><code class="lang-ruby">sun.method(<span class="hljs-symbol">:life?</span>).source_location<span class="hljs-comment"># =&gt; nil</span>show_source sun.life?<span class="hljs-comment"># =&gt; Error: Couldn't locate a definition for sun.life?</span></code></pre><p>It is also not displayed when using <code>ls</code> on the <code>sun</code> object:</p><pre><code class="lang-ruby">ls sun<span class="hljs-comment"># =&gt; </span><span class="hljs-comment"># Universe::Galaxy::Sun#methods: our_planet</span><span class="hljs-comment"># Universe::Galaxy::Star#methods: name  name=</span><span class="hljs-comment"># instance variables: <span class="hljs-doctag">@name</span></span></code></pre><h3 id="heading-on-the-kernel-basicobject-and-object-methods">On the Kernel, BasicObject and Object methods</h3><p>In general <code>source_location</code> and <code>show_source</code> will show you the Ruby code that defined a method, but will not show you any C code behind any method that you are using in Ruby.</p><p>So for example the following will not work:</p><pre><code class="lang-ruby"><span class="hljs-comment"># respond_to? is inherited from Object</span>sun.method(<span class="hljs-symbol">:respond_to?</span>)<span class="hljs-comment"># =&gt; #&lt;Method: Universe::Galaxy::Sun(Kernel)#respond_to?(*)&gt;</span>show_source sun.respond_to?<span class="hljs-comment"># =&gt; Error: Couldn't locate a definition for sun.respond_to?</span>sun.method(<span class="hljs-symbol">:respond_to?</span>).source_location<span class="hljs-comment"># =&gt; nil</span><span class="hljs-comment"># == is inherited from BasicObject </span>sun.method(<span class="hljs-symbol">:==</span>) <span class="hljs-comment"># =&gt; #&lt;Method: Universe::Galaxy::Sun(BasicObject)#==(_)&gt;</span>sun.method(<span class="hljs-symbol">:==</span>).source_location<span class="hljs-comment"># =&gt; nil</span>show_source sun.==<span class="hljs-comment"># Error: Couldn't locate a definition for sun.==</span></code></pre><p>If you want to make this work you have to use Ruby <a target="_blank" href="https://docs.ruby-lang.org/en/3.3/TracePoint.html">TracePoint</a> but that is out of the scope of this article.</p><h2 id="heading-conclusion">Conclusion</h2><p>I think Ruby has now very good tools to navigate to the source of a method either from the class or from the instantiated object. Using <code>IRB</code> and <code>debug</code> gem makes this navigation easy, and I would recommend anyone learning Ruby to keep an <code>irb</code> or <code>rails console</code> session open. That way, whenever you have a question about how some particular method works, you can navigate to the source of that method and read the code.</p><hr /><p><em>I would like to thank</em> <a target="_blank" href="https://fractaledmind.github.io"><em>Stephen Margheim</em></a> <em>and</em> <a target="_blank" href="https://st0012.dev"><em>Stan Lo</em></a> <em>for reviewing an early version of this article and providing valuable feedback.</em></p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/finding-where-a-method-is-defined-in-ruby-using-irb</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/finding-where-a-method-is-defined-in-ruby-using-irb</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 11 Jan 2024 09:21:18 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1704964631755/fac2a035-edb8-4724-be10-1a7c9b6dbbdd.png</cover_image></item><item><title><![CDATA[A recommended list of resources about how Ruby is evolving]]></title><description><![CDATA[<p>As Ruby 3.3 is around the corner, I suggest a list of recommended articles or videos to watch to get up to date with Ruby's evaluation as a language.</p><p>This is a partial list of some of the things happening in Ruby. Still, I think it is an excellent start to glimpse how Ruby's programming language is evolving and how much effort the community puts into keeping Ruby a joy to use.</p><h2 id="heading-context">Context</h2><p>First, I recommend you watch this video where Matz is talking about how he chooses to include in Ruby and about Ruby future:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=4MM5b2F9zrM">https://www.youtube.com/watch?v=4MM5b2F9zrM</a></div><p> </p><h2 id="heading-new-language-syntax">New language syntax</h2><p>Then I recommend you read the following series of articles (hint: They are not useless 😀 )</p><ul><li><p><a target="_blank" href="https://zverok.substack.com/p/that-useless-ruby-syntax-sugar-that">That useless Ruby syntax sugar that emerged in new versions</a></p></li><li><p><a target="_blank" href="https://zverok.substack.com/p/useless-syntax-sugar-numeric-block">Useless syntax sugar: Numeric block parameters</a></p></li><li><p><a target="_blank" href="https://zverok.substack.com/p/useless-ruby-sugar-pattern-matching">Useless Ruby sugar: Pattern matching (Pt. 1)</a></p></li><li><p>"<a target="_blank" href="https://zverok.substack.com/p/useless-ruby-sugar-pattern-matching-cad">Useless Ruby sugar: Pattern matching (Pt. 2)</a></p></li><li><p><a target="_blank" href="https://zverok.substack.com/p/useless-ruby-sugar-pattern-matching-891">Useless Ruby sugar: Pattern matching (Pt. 3/3)</a></p></li><li><p><a target="_blank" href="https://zverok.substack.com/p/useless-ruby-sugar-keyword-argument">Useless Ruby sugar: Keyword argument and hash values omission</a></p></li><li><p><a target="_blank" href="https://zverok.substack.com/p/useless-ruby-sugar-argument-forwarding">Useless Ruby sugar: Argument forwarding</a></p></li><li><p><a target="_blank" href="https://zverok.substack.com/p/useless-ruby-sugar-endless-one-line">Useless Ruby sugar: Endless (one-line) methods</a></p></li></ul><h2 id="heading-performance">Performance</h2><p>First, I would recommend you read about Object Shapes:</p><ul><li><p><a target="_blank" href="https://poddarayush.com/posts/object-shapes-improve-ruby-code-performance/">Object shapes - how this under-the-hood change in Ruby 3.2.0 will improve your code performance</a></p></li><li><p><a target="_blank" href="https://railsatscale.com/2023-10-24-memoization-pattern-and-object-shapes/">Performance impact of the memoization idiom on modern Ruby</a></p></li><li><p><a target="_blank" href="https://island94.org/2023/10/writing-object-shape-friendly-code-in-ruby">Writing Object Shape friendly code in Ruby</a></p></li></ul><p>Then I would recommend you read about YJIT:</p><ul><li><p><a target="_blank" href="https://railsatscale.com/2023-11-07-yjit-is-the-most-memory-efficient-ruby-jit/">YJIT Is the Most Memory-Efficient Ruby JIT</a></p></li><li><p><a target="_blank" href="https://railsatscale.com/2023-12-04-ruby-3-3-s-yjit-faster-while-using-less-memory/">Ruby 3.3's YJIT: Faster While Using Less Memory</a></p></li><li><p><a target="_blank" href="https://shopify.engineering/ruby-yjit-is-production-ready">Ruby 3.2s YJIT is Production-Ready</a></p></li></ul><p>I think you could also <a target="_blank" href="https://www.honeybadger.io/blog/ractors/">read a good article about Ractors</a> by Julie Kent or <a target="_blank" href="https://www.youtube.com/watch?v=GhPAMDdOnU0">watch a presentation about them</a> by Abiodun Olowode.</p><h2 id="heading-a-more-high-level-overview">A more high-level overview</h2><p>(A shameless plug) I would also recommend my presentation at <a target="_blank" href="https://friendlyrb.com">Friendly.rb</a> this year about The State of the Rubyverse where I try to show various projects happening in Ruby world:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=QrH2A70JlsM">https://www.youtube.com/watch?v=QrH2A70JlsM</a></div><p> </p><h2 id="heading-going-deep-into-details">Going deep into details</h2><p>I would recommend you browse <a target="_blank" href="https://rubyreferences.github.io/rubychanges/">https://rubyreferences.github.io/rubychanges/</a></p><p>It contains a more detailed (with examples) list of changes per each Ruby version.</p><p>Then I would recommend the series called <a target="_blank" href="https://kddnewton.com">Advent of Prism</a> from Kevin Netwon and also watch <a target="_blank" href="https://www.youtube.com/watch?v=mlcQ8DErvVs">this video from Takashi Kokubun about RJIT</a></p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/a-recommended-list-of-resources-about-how-ruby-is-evolving</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/a-recommended-list-of-resources-about-how-ruby-is-evolving</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 21 Dec 2023 10:15:43 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1703153668410/43d99930-af02-4f42-809a-8a60ea739a41.png</cover_image></item><item><title><![CDATA[Exploring `it` default block param warning in Ruby 3.3]]></title><description><![CDATA[<p>In the <a target="_blank" href="https://github.com/ruby/dev-meeting-log/blob/master/2023/DevMeeting-2023-11-30.md#feature-18980-re-reconsider-numbered-parameters-it-as-a-default-block-parameter-k0kubun">last Ruby Developer Meeting</a> it was accepted that <code>it</code> will be added to Ruby 3.4.</p><p>In preparation for this, Ruby 3.3 will show a warning when <code>it</code> is used without a receiver, argument, or block.</p><p>I made a quick video about this if you want to watch it or if you prefer the written version continue reading on:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=DnMNn13k-rg">https://www.youtube.com/watch?v=DnMNn13k-rg</a></div><p> </p><h2 id="heading-deprecation-warning">Deprecation warning</h2><p>For this, let's define some simple code where I will name a method <code>it</code> and then call it from a block:</p><pre><code class="lang-ruby">puts RUBY_VERSION<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">it</span> = "<span class="hljs-title">works</span>"</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span><span class="hljs-params">(&amp;block)</span></span> = <span class="hljs-keyword">yield</span>puts run { it }</code></pre><p>If you run this with Ruby 3.2.2 then you will get the following result:</p><pre><code class="lang-bash">3.2.2works</code></pre><p>But if you run this with Ruby 3.3.0.rc1 you will get the following result:</p><pre><code class="lang-bash">example_deprecation_warning.rb:7: warning: `it` calls without arguments will refer to the first block param <span class="hljs-keyword">in</span> Ruby 3.4; use it() or self.it3.3.0works</code></pre><h2 id="heading-how-to-fix-the-warning">How to fix the warning</h2><p>In case you have a method named <code>it</code> and still want to call it inside a block without a receiver, arguments or params you should either add paranthesis:</p><pre><code class="lang-diff"><span class="hljs-deletion">- puts run { it }</span><span class="hljs-addition">+ puts run { it() }</span></code></pre><p>or add the receiver:</p><pre><code class="lang-diff"><span class="hljs-deletion">- puts run { it }</span><span class="hljs-addition">+ puts run { self.it }</span></code></pre><h2 id="heading-when-using-it-as-local-variable-name">When using <code>it</code> as local variable name</h2><p>There will be no problem when you defined <code>it</code> as local variable name.</p><p>Here is an example where I created:</p><pre><code class="lang-ruby">puts RUBY_VERSION<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">log</span><span class="hljs-params">(&amp;block)</span></span> = puts <span class="hljs-keyword">yield</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span></span>  it = <span class="hljs-number">2</span>  log { it }<span class="hljs-keyword">end</span>run</code></pre><p>If you run this it on Ruby 3.3.0.rc1 will not display an error. The output will be:</p><pre><code class="lang-bash">3.3.02</code></pre><p>Now if you will extract the local variable to a method with the same name:</p><pre><code class="lang-diff">puts RUBY_VERSIONdef log(&amp;block) = puts yield<span class="hljs-addition">+</span><span class="hljs-addition">+ def it = 2</span><span class="hljs-addition">+</span>def run<span class="hljs-deletion">-  it = 2</span>  log { it }endrun</code></pre><p>It will show a warning:</p><pre><code class="lang-bash">it_as_local_variable.rb:8: warning: `it` calls without arguments will refer to the first block param <span class="hljs-keyword">in</span> Ruby 3.4; use it() or self.it3.3.02</code></pre><h2 id="heading-rspec">RSpec</h2><p>RSpec uses a lot <code>it</code>. But in most cases (I would argue majority of them) this will not be a problem. Because usually <code>it</code> is called with a string argument:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">"rspec"</span>describe Galaxy <span class="hljs-keyword">do</span>  context <span class="hljs-string">"when created with a name and number of stars"</span> <span class="hljs-keyword">do</span>    it <span class="hljs-string">"should hold the correct name and star count"</span> <span class="hljs-keyword">do</span>      galaxy = Galaxy.new(<span class="hljs-string">"Milky Way"</span>, <span class="hljs-number">100_000_000_000</span>)      expect(galaxy.name).to eq(<span class="hljs-string">"Milky Way"</span>)      expect(galaxy.number_of_stars).to eq(<span class="hljs-number">100_000_000_000</span>)    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>The code above will <em>NOT</em> display a warning.</p><p>But if you will try to write something like this:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">"rspec"</span>describe Galaxy <span class="hljs-keyword">do</span>  subject { Galaxy.new(<span class="hljs-string">"Milky Way"</span>, <span class="hljs-number">100_000_000_000</span>).stars }  context <span class="hljs-string">"When there are no known stars"</span> <span class="hljs-keyword">do</span>    it  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>If you run this with Ruby 3.3.0.rc.1 you will get the warning:</p><pre><code class="lang-ruby">explore-it/rspec_example/spec/more_galaxy_spec.<span class="hljs-symbol">rb:</span><span class="hljs-number">8</span>: <span class="hljs-symbol">warning:</span> <span class="hljs-string">`it`</span> calls without arguments will refer to the first block param <span class="hljs-keyword">in</span> Ruby <span class="hljs-number">3.4</span>; use it() <span class="hljs-keyword">or</span> <span class="hljs-keyword">self</span>.it*<span class="hljs-symbol">Pending:</span> (Failures listed here are expected <span class="hljs-keyword">and</span> <span class="hljs-keyword">do</span> <span class="hljs-keyword">not</span> affect your suite<span class="hljs-string">'s status)  1) Galaxy When there are no known stars      # Not yet implemented     # ./spec/more_galaxy_spec.rb:8Finished in 0.0074 seconds (files took 0.06474 seconds to load)1 example, 0 failures, 1 pending</span></code></pre><p>You can fix this by using one of the other alias for <code>it</code> from RSpec:</p><ul><li><p><a target="_blank" href="https://github.com/rspec/rspec-core/blob/f273314f575ab62092b2ad86addb6a3c93d6041f/lib/rspec/core/example_group.rb#L170"><code>specify</code></a></p></li><li><p><a target="_blank" href="https://github.com/rspec/rspec-core/blob/f273314f575ab62092b2ad86addb6a3c93d6041f/lib/rspec/core/example_group.rb#L158"><code>example</code></a></p></li></ul><p>Here is <a target="_blank" href="https://github.com/rspec/rspec-core/blob/f273314f575ab62092b2ad86addb6a3c93d6041f/lib/rspec/core/example_group.rb#L157C1-L157C1">the complete list of methods</a> defined in RSpec core:</p><pre><code class="lang-ruby"><span class="hljs-comment"># Defines an example within a group.</span>define_example_method <span class="hljs-symbol">:example</span><span class="hljs-comment"># Defines an example within a group.</span><span class="hljs-comment"># This is the primary API to define a code example.</span>define_example_method <span class="hljs-symbol">:it</span><span class="hljs-comment"># Defines an example within a group.</span><span class="hljs-comment"># Useful for when your docstring does not read well off of `it`.</span><span class="hljs-comment"># <span class="hljs-doctag">@example</span></span><span class="hljs-comment">#  RSpec.describe MyClass do</span><span class="hljs-comment">#    specify "#do_something is deprecated" do</span><span class="hljs-comment">#      # ...</span><span class="hljs-comment">#    end</span><span class="hljs-comment">#  end</span>define_example_method <span class="hljs-symbol">:specify</span><span class="hljs-comment"># Shortcut to define an example with `:focus =&gt; true`.</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:focus</span>,    <span class="hljs-symbol">:focus</span> =&gt; <span class="hljs-literal">true</span><span class="hljs-comment"># Shortcut to define an example with `:focus =&gt; true`.</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:fexample</span>, <span class="hljs-symbol">:focus</span> =&gt; <span class="hljs-literal">true</span><span class="hljs-comment"># Shortcut to define an example with `:focus =&gt; true`.</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:fit</span>,      <span class="hljs-symbol">:focus</span> =&gt; <span class="hljs-literal">true</span><span class="hljs-comment"># Shortcut to define an example with `:focus =&gt; true`.</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:fspecify</span>, <span class="hljs-symbol">:focus</span> =&gt; <span class="hljs-literal">true</span><span class="hljs-comment"># Shortcut to define an example with `:skip =&gt; 'Temporarily skipped with xexample'`.</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:xexample</span>, <span class="hljs-symbol">:skip</span> =&gt; <span class="hljs-string">'Temporarily skipped with xexample'</span><span class="hljs-comment"># Shortcut to define an example with `:skip =&gt; 'Temporarily skipped with xit'`.</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:xit</span>,      <span class="hljs-symbol">:skip</span> =&gt; <span class="hljs-string">'Temporarily skipped with xit'</span><span class="hljs-comment"># Shortcut to define an example with `:skip =&gt; 'Temporarily skipped with xspecify'`.</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:xspecify</span>, <span class="hljs-symbol">:skip</span> =&gt; <span class="hljs-string">'Temporarily skipped with xspecify'</span><span class="hljs-comment"># Shortcut to define an example with `:skip =&gt; true`</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:skip</span>,     <span class="hljs-symbol">:skip</span> =&gt; <span class="hljs-literal">true</span><span class="hljs-comment"># Shortcut to define an example with `:pending =&gt; true`</span><span class="hljs-comment"># <span class="hljs-doctag">@see</span> example</span>define_example_method <span class="hljs-symbol">:pending</span>,  <span class="hljs-symbol">:pending</span> =&gt; <span class="hljs-literal">true</span></code></pre><h2 id="heading-conclusion">Conclusion</h2><p>I really like that <code>it</code> was <a target="_blank" href="https://bugs.ruby-lang.org/issues/18980#note-47">accepted</a> as a default block parameter. Looking forward to Ruby 3.4 (next year) to write code using it.</p><p>The warning feature in Ruby 3.3 serves as a useful heads-up for potential conflicts with its usage, although these are expected to occur infrequently.</p><p>The transition should be smooth for most developers. RSpec tests should not require any changes, as it largely uses <code>it</code> with a string argument and is not likely to trigger the warning. In the instances where it does, alternatives like <code>specify</code> and <code>example</code> can be used to fix the warning.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/exploring-it-default-block-param-warning-in-ruby-33</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/exploring-it-default-block-param-warning-in-ruby-33</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 14 Dec 2023 07:33:32 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1702538831850/b7bda507-7410-43cb-a064-539f8c738153.png</cover_image></item><item><title><![CDATA[Ruby on Rails Open Source: Mastodon]]></title><description><![CDATA[<h2 id="heading-the-product">The product</h2><p><a target="_blank" href="https://joinmastodon.org">https://joinmastodon.org</a></p><blockquote><p>Mastodon is a free, open-source social network server based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network.</p></blockquote><p><a target="_blank" href="https://joinmastodon.org"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701315312358/e58506b3-c3e8-49df-a36a-c0dd870a2a05.png" alt="a social networking site with a purple background" class="image--center mx-auto" /></a></p><h2 id="heading-open-source">Open source</h2><p>The project is open source at <a target="_blank" href="https://github.com/mastodon/mastodon">https://github.com/mastodon/mastodon</a></p><h3 id="heading-license">License</h3><p>The license is <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/LICENSE">GNU Affero General Public License v3.0</a></p><p><a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/LICENSE"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701315413022/8107298f-e08b-4f0d-bf98-55b3e9675d7b.png" alt="A screenshot of the https://github.com/mastodon/mastodon/blob/main/LICENSE" class="image--center mx-auto" /></a></p><h2 id="heading-technical-review">Technical review</h2><h3 id="heading-architecture">Architecture</h3><p><strong>Ruby on Rails</strong> - used for API and some of the web pages</p><p><strong>React and Redux</strong> - used for dynamic parts of the interface</p><p><strong>Node.js</strong> - used for streaming API</p><p>I will focus on this review on the Ruby on Rails part.</p><h3 id="heading-ruby-and-rails-version">Ruby and Rails version</h3><p>It uses <strong>Ruby 3.2.2</strong> and (at the moment of writing this review) <strong>Rails 7.1.1</strong></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701315843968/0edcf5c5-72a8-43a9-bc84-c5d07647f1eb.png" alt class="image--center mx-auto" /></p><h3 id="heading-stats">Stats</h3><p>Running <code>bin/rails stats</code> will give the following:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701315952061/480be8b9-bcc7-49f7-9198-bc01ff14ad8e.png" alt class="image--center mx-auto" /></p><p>Running the VScodeCounter will give the following stats:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701316044455/ed368ba6-ca1d-4823-9bac-0cba858e2298.png" alt class="image--center mx-auto" /></p><p>There are very few actual XML files. So what is marked there as XML is probably a flavour of HTML either from the Ruby on Rails side or from the React/Nodejs side.</p><h2 id="heading-style-guide">Style Guide</h2><p>They are using Rubocop to enforce coding guidelines. They are using along with standard Rubocop:</p><ul><li><p><a target="_blank" href="https://github.com/rubocop/rubocop-rails/?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>rubocop-rails</strong></a> - <em>"Automatic Rails code style checking tool. A RuboCop extension focused on enforcing Rails best practices and coding conventions."</em></p></li><li><p><a target="_blank" href="https://github.com/rubocop/rubocop-performance/?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>rubocop-performance</strong></a> - <em>"A collection of RuboCop cops to check for performance optimizations in Ruby code."</em></p></li><li><p><a target="_blank" href="https://github.com/rubocop/rubocop-capybara?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>rubocop-capybara</strong></a> - <em>" Code style checking for Capybara test files (RSpec, Cucumber, Minitest). A plugin for the RuboCop code style enforcing &amp; linting tool"</em></p></li><li><p><a target="_blank" href="https://github.com/rubocop/rubocop-rspec?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>rubocop-rspec</strong></a> - <em>" Code style checking for RSpec files. A plugin for the RuboCop code style enforcing &amp; linting tool"</em></p></li></ul><p>And a custom cop called <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/lib/linter/rubocop_middle_dot.rb#L3">MiddleDot</a> that does the following:</p><blockquote><p>Bans the usage of  (bullet) in HTML/HAML in favour of  (middle dot) in string literals</p></blockquote><p>Here are some extra configurations for Rubocop:</p><ul><li><code>DisplayStyleGuide: true</code> so that it will display the URL for the Style guide when there is an offense.</li></ul><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701317172965/8062b808-74d6-4d91-8a19-598ced0b9fa6.png" alt class="image--center mx-auto" /></p><ul><li><code>UseCache: true</code> - to store and reuse results for making the Rubocop run faster</li></ul><p>What I noticed and liked is that for each Rubocop rule where they overwrite, they provide as a comment the URL for that rule. This makes it easier to navigate to it and read about it.</p><p><a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/.rubocop.yml#L34"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701317390401/614f3bae-e28e-4d4d-bdfb-5f04d162e0b2.png" alt class="image--center mx-auto" /></a></p><h2 id="heading-storage-persistence-and-in-memory-storage">Storage Persistence and in-memory storage</h2><p>The database used is PostgreSQL and they configure by default a primary and replica DB:</p><p>The Database pool is set by using the following:</p><pre><code class="lang-yaml"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/config/database.yml#L3</span><span class="hljs-attr">pool:</span> &lt;%=<span class="ruby"> ENV[<span class="hljs-string">"DB_POOL"</span>] <span class="hljs-params">||</span> (<span class="hljs-keyword">if</span> Sidekiq.server? <span class="hljs-keyword">then</span> Sidekiq[<span class="hljs-symbol">:concurrency</span>] <span class="hljs-keyword">else</span> ENV[<span class="hljs-string">'MAX_THREADS'</span>] <span class="hljs-keyword">end</span>) <span class="hljs-params">||</span> <span class="hljs-number">5</span> </span>%&gt;</code></pre><p>So if there is not <code>DB_POOL</code> environment it will try to use <code>Sidekiq[:concurrency]</code> else it will default to <code>`MAX_THREADS` </code></p><p>As in-memory storage it uses Redis and there is a <code>RedisConfiguration</code> object that helps with configuring the connection pool:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/lib/redis_configuration.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisConfiguration</span></span>  <span class="hljs-class"><span class="hljs-keyword">class</span> &lt;&lt; self</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">establish_pool</span><span class="hljs-params">(new_pool_size)</span></span>      @pool&amp;.shutdown(&amp;<span class="hljs-symbol">:close</span>)      @pool = ConnectionPool.new(<span class="hljs-symbol">size:</span> new_pool_size) { new.connection }    <span class="hljs-keyword">end</span>    delegate <span class="hljs-symbol">:with</span>, <span class="hljs-symbol">to:</span> <span class="hljs-symbol">:pool</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pool</span></span>      @pool <span class="hljs-params">||</span>= establish_pool(pool_size)    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pool_size</span></span>      <span class="hljs-keyword">if</span> Sidekiq.server?        Sidekiq[<span class="hljs-symbol">:concurrency</span>]      <span class="hljs-keyword">else</span>        ENV[<span class="hljs-string">'MAX_THREADS'</span>] <span class="hljs-params">||</span> <span class="hljs-number">5</span>      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">connection</span></span>    <span class="hljs-keyword">if</span> namespace?      Redis::Namespace.new(namespace, <span class="hljs-symbol">redis:</span> raw_connection)    <span class="hljs-keyword">else</span>      raw_connection    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-comment"># ... other method</span><span class="hljs-keyword">end</span></code></pre><p>It uses a class instance variables <code>@pool</code> to share the connection pool across multiple instances of this class and the size is by default <code>Sidekiq[:concurrency]</code> size if Sidekiq used or <code>MAX_THREADS</code> value.</p><p>If there is an environment variable <code>REDIS_NAMESPACE</code> it will use Redis under that specific namespace.</p><p>Further down this <code>RedisConfiguration</code> is added to other objects (controllers for example) by using a module called <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/models/concerns/redisable.rb#L3">Redisable</a></p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/models/concerns/redisable.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Redisable</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">redis</span></span>    Thread.current[<span class="hljs-symbol">:redis</span>] <span class="hljs-params">||</span>= RedisConfiguration.pool.checkout  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">with_redis</span><span class="hljs-params">(&amp;block)</span></span>    RedisConfiguration.with(&amp;block)  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>There is then a module called <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/models/concerns/lockable.rb#L3">Lockable</a> that defines <code>with_redis_lock</code>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/models/concerns/lockable.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Lockable</span></span>  <span class="hljs-comment"># <span class="hljs-doctag">@param</span> [String] lock_name</span>  <span class="hljs-comment"># <span class="hljs-doctag">@param</span> [ActiveSupport::Duration] autorelease Automatically release the lock after this time</span>  <span class="hljs-comment"># <span class="hljs-doctag">@param</span> [Boolean] raise_on_failure Raise an error if a lock cannot be acquired, or fail silently</span>  <span class="hljs-comment"># <span class="hljs-doctag">@raise</span> [Mastodon::RaceConditionError]</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">with_redis_lock</span><span class="hljs-params">(lock_name, <span class="hljs-symbol">autorelease:</span> <span class="hljs-number">15</span>.minutes, <span class="hljs-symbol">raise_on_failure:</span> <span class="hljs-literal">true</span>)</span></span>    with_redis <span class="hljs-keyword">do</span> <span class="hljs-params">|redis|</span>      RedisLock.acquire(<span class="hljs-symbol">redis:</span> redis, <span class="hljs-symbol">key:</span> <span class="hljs-string">"lock:<span class="hljs-subst">#{lock_name}</span>"</span>, <span class="hljs-symbol">autorelease:</span> autorelease.seconds) <span class="hljs-keyword">do</span> <span class="hljs-params">|lock|</span>        <span class="hljs-keyword">if</span> lock.acquired?          <span class="hljs-keyword">yield</span>        <span class="hljs-keyword">elsif</span> raise_on_failure          raise Mastodon::RaceConditionError, <span class="hljs-string">"Could not acquire lock for <span class="hljs-subst">#{lock_name}</span>, try again later"</span>        <span class="hljs-keyword">end</span>      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>They are used for example in a <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/controllers/api/v1/statuses/reblogs_controller.rb#L3">controller</a>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/api/v1/statuses/reblogs_controller.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Api::V1::Statuses::ReblogsController</span> &lt; Api::V1::<span class="hljs-title">Statuses::BaseController</span></span>  <span class="hljs-keyword">include</span> Redisable  <span class="hljs-keyword">include</span> Lockable  <span class="hljs-comment"># ... callbacks</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create</span></span>    with_redis_lock(<span class="hljs-string">"reblog:<span class="hljs-subst">#{current_account.id}</span>:<span class="hljs-subst">#{@reblog.id}</span>"</span>) <span class="hljs-keyword">do</span>      @status = ReblogService.new.call(current_account, @reblog, reblog_params)    <span class="hljs-keyword">end</span>    render <span class="hljs-symbol">json:</span> @status, <span class="hljs-symbol">serializer:</span> REST::StatusSerializer  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># ... other methods</span><span class="hljs-keyword">end</span></code></pre><h2 id="heading-gems-used">Gems used</h2><p>I picked some gems from the <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/Gemfile#L3">Gemfile</a> that I found interesting to mention:</p><ul><li><p><a target="_blank" href="https://github.com/sporkmonger/addressable?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com">addressable</a> - "Addressable is an alternative implementation to the URI implementation that is part of Ruby's standard library. It is flexible, offers heuristic parsing, and additionally provides extensive support for IRIs and URI templates"</p></li><li><p><a target="_blank" href="https://github.com/Gargron/blurhash?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>blurhash</strong></a> - <em>"Encode an image as a small string that can saved in the database and used to show a blurred preview before the real image loads"</em></p></li><li><p><a target="_blank" href="https://github.com/brianmario/charlock_holmes?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>charlock_holmes</strong></a> - <em>"charlock_holmes provides binary and text detection as well as text transcoding using libicu"</em></p></li><li><p><a target="_blank" href="https://github.com/toptal/chewy?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>chewy</strong></a> - <em>"Chewy provides functionality for Elasticsearch index handling, documents import mappings and chainable query DSL"</em></p></li><li><p><a target="_blank" href="https://github.com/thoughtbot/climate_control?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>climate_control</strong></a> - <em>"Modify your ENV"</em></p></li><li><p><a target="_blank" href="http://github.com/nathanvda/cocoon?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>cocoon</strong></a> - <em>"Unobtrusive nested forms handling, using jQuery. Use this and discover cocoon-heaven."</em></p></li><li><p><a target="_blank" href="https://github.com/hansondr/color_diff?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>color_diff</strong></a> - <em>"Calculate RGB color distances using CIEDE2000 formula"</em></p></li><li><p><a target="_blank" href="https://github.com/jhawthorn/discard?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>discard</strong></a> - <em>"Allows marking ActiveRecord objects as discarded, and provides scopes for filtering."</em></p></li><li><p><a target="_blank" href="https://github.com/doorkeeper-gem/doorkeeper?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>doorkeeper</strong></a> - <em>"Doorkeeper is an OAuth 2 provider for Rails and Grape."</em></p></li><li><p><a target="_blank" href="https://github.com/RubyCrypto/ed25519?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>ed25519</strong></a> - <em>"A Ruby binding to the Ed25519 elliptic curve public-key signature system described in RFC 8032."</em></p></li><li><p><a target="_blank" href="http://github.com/email-spec/email-spec/?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>email_spec</strong></a> - <em>"Easily test email in RSpec, Cucumber, and MiniTest"</em></p></li><li><p><a target="_blank" href="https://gitlab.com/fabrication-gem/fabrication?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>fabrication</strong></a> - <em>"Fabrication is an object generation framework for ActiveRecord, Mongoid, DataMapper, Sequel, or any other Ruby object."</em></p></li><li><p><a target="_blank" href="https://github.com/SamSaffron/fast_blank?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>fast_blank</strong></a> - <em>"Provides a C-optimized method for determining if a string is blank"</em></p></li><li><p><a target="_blank" href="http://github.com/sdsykes/fastimage?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>fastimage</strong></a> - <em>"FastImage finds the size or type of an image given its uri by fetching as little as needed."</em></p></li><li><p><a target="_blank" href="https://github.com/thekompanee/fuubar?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>fuubar</strong></a> - <em>"the instafailing RSpec progress bar formatter"</em></p></li><li><p><a target="_blank" href="https://github.com/sds/haml-lint?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>haml_lint</strong></a> - <em>"Configurable tool for writing clean and consistent HAML"</em></p></li><li><p><a target="_blank" href="https://github.com/Nexus-Mods/hcaptcha?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>hcaptcha</strong></a> - <em>"Ruby helpers for hCaptcha"</em></p></li><li><p><a target="_blank" href="https://github.com/threedaymonk/htmlentities?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>htmlentities</strong></a> - <em>"A module for encoding and decoding (X)HTML entities."</em></p></li><li><p><a target="_blank" href="https://github.com/iain/http_accept_language?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>http_accept_language</strong></a> - <em>"Find out which locale the user preferes by reading the languages they specified in their browser"</em></p></li><li><p><a target="_blank" href="https://github.com/trusche/httplog?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>httplog</strong></a> - <em>"Log outgoing HTTP requests made from your application. Helpful for tracking API calls of third party gems that don't provide their own log output."</em></p></li><li><p><a target="_blank" href="https://github.com/glebm/i18n-tasks?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>i18n-tasks</strong></a> - <em>"i18n-tasks helps you find and manage missing and unused translations. It analyses code statically for key usages, such as</em> <code>I18n.t('some.key')</code><em>, in order to report keys that are missing or unused, pre-fill missing keys (optionally from Google Translate), and remove unused keys. "</em></p></li><li><p><a target="_blank" href="http://github.com/deepfryed/idn-ruby?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>idn-ruby</strong></a> - <em>" Ruby Bindings for the GNU LibIDN library, an implementation of the Stringprep, Punycode and IDNA specifications defined by the IETF Internationalized Domain Names (IDN) working group. Included are the most important parts of the Stringprep, Punycode and IDNA APIs like performing Stringprep processings, encoding to and decoding from Punycode strings and converting entire domain names to and from the ACE encoded form. "</em></p></li><li><p><a target="_blank" href="https://github.com/ruby-rdf/json-ld?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>json-ld</strong></a> - <em>"JSON::LD parses and serializes JSON-LD into RDF and implements expansion, compaction and framing API interfaces for the Ruby RDF.rb library suite."</em></p></li><li><p><a target="_blank" href="https://github.com/ruby-rdf/json-ld-preloaded?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>json-ld-preloaded</strong></a> - <em>"A meta-release of the json-ld gem including preloaded vocabularies for the Ruby RDF.rb library suite."</em></p></li><li><p><a target="_blank" href="http://github.com/voxpupuli/json-schema/?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>json-schema</strong></a> - <em>"Ruby JSON Schema Validator"</em></p></li><li><p><a target="_blank" href="https://github.com/kreeti/kt-paperclip?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>kt-paperclip</strong></a> - <em>"Easy upload management for ActiveRecord"</em></p></li><li><p><a target="_blank" href="https://github.com/asplake/link_header?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>link_header</strong></a> - <em>"Converts conforming link headers to and from text, LinkHeader objects and corresponding (JSON-friendly) Array representations, also HTML link elements."</em></p></li><li><p><a target="_blank" href="https://github.com/marioizquierdo/mario-redis-lock?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>mario-redis-lock</strong></a> - <em>"Yet another Ruby distributed lock using Redis, with emphasis in transparency. Requires Redis &gt;= 2.6.12, because it uses the new syntax for SET to easily implement the robust algorithm described in the SET command documentation (</em><a target="_blank" href="https://github.com/marioizquierdo/mario-redis-lock?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><em>http://redis.io/commands/set</em></a><em>)."</em></p></li><li><p><a target="_blank" href="https://github.com/SamSaffron/memory_profiler?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>memory_profiler</strong></a> - <em>"Memory profiling routines for Ruby 2.5+"</em></p></li><li><p><a target="_blank" href="https://github.com/localshred/nsa?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>nsa</strong></a> - <em>"Listen to your Rails ActiveSupport::Notifications and deliver to a Statsd compatible backend"</em></p></li><li><p><a target="_blank" href="https://github.com/ohler55/oj?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>oj</strong></a> - <em>"The fastest JSON parser and object serializer."</em></p></li><li><p><a target="_blank" href="https://github.com/ohler55/ox?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>ox</strong></a> - <em>"A fast XML parser and object serializer that uses only standard C lib. Optimized XML (Ox), as the name implies was written to provide speed optimized XML handling. It was designed to be an alternative to Nokogiri and other Ruby XML parsers for generic XML parsing and as an alternative to Marshal for Object serialization. "</em></p></li><li><p><a target="_blank" href="https://github.com/kschiess/parslet?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>parslet</strong></a> - <em>"Parser construction library with great error reporting in Ruby."</em></p></li><li><p><a target="_blank" href="https://github.com/ankane/pghero?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>pghero</strong></a> - <em>"A performance dashboard for Postgres"</em></p></li><li><p><a target="_blank" href="https://github.com/jtdowney/private_address_check?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>private_address_check</strong></a> - <em>"Checks if a IP or hostname would cause a request to a private network (RFC 1918)"</em></p></li><li><p><a target="_blank" href="https://github.com/weppos/publicsuffix-ruby/tree/v5.0.4?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>public_suffix</strong></a> - <em>"PublicSuffix can parse and decompose a domain name into top level domain, domain and subdomains."</em></p></li><li><p><a target="_blank" href="https://github.com/resque/redis-namespace?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>redis-namespace</strong></a> - <em>"Adds a Redis::Namespace class which can be used to namespace calls to Redis. This is useful when using a single instance of Redis with multiple, different applications. "</em></p></li><li><p><a target="_blank" href="https://github.com/jfelchner/ruby-progressbar?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>ruby-progressbar</strong></a> - <em>"Ruby/ProgressBar is an extremely flexible text progress bar library for Ruby. The output can be customized with a flexible formatting system including: percentage, bars of various formats, elapsed time and estimated time remaining."</em></p></li><li><p><a target="_blank" href="https://github.com/scenic-views/scenic?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>scenic</strong></a> - <em>" Adds methods to ActiveRecord::Migration to create and manage database views in Rails "</em></p></li><li><p><a target="_blank" href="https://github.com/aprescott/sidekiq-bulk?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>sidekiq-bulk</strong></a> - <em>"Augments Sidekiq job classes with a push_bulk method for easier bulk pushing."</em></p></li><li><p><a target="_blank" href="https://github.com/mhenrixon/sidekiq-unique-jobs?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>sidekiq-unique-jobs</strong></a> - <em>"Prevents simultaneous Sidekiq jobs with the same unique arguments to run. Highly configurable to suite your specific needs. "</em></p></li><li><p><a target="_blank" href="http://github.com/codeplant/simple-navigation?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>simple-navigation</strong></a> - <em>"With the simple-navigation gem installed you can easily create multilevel navigations for your Rails, Sinatra or Padrino applications. The navigation is defined in a single configuration file. It supports automatic as well as explicit highlighting of the currently active navigation through regular expressions."</em></p></li><li><p><a target="_blank" href="https://github.com/orgsync/stoplight?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>stoplight</strong></a> - <em>"An implementation of the circuit breaker pattern."</em></p></li><li><p><a target="_blank" href="https://github.com/piotrmurach/tty-prompt?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>tty-prompt</strong></a> - <em>"A beautiful and powerful interactive command line prompt with a robust API for getting and validating complex inputs."</em></p></li><li><p><a target="_blank" href="https://github.com/twitter/twitter-text?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>twitter-text</strong></a> - <em>"A gem that provides text handling for Twitter"</em></p></li><li><p><a target="_blank" href="https://github.com/fny/xorcist?utm_source=shortruby&amp;utm_campaign=shortruby_0066&amp;ref=shortruby.com"><strong>xorcist</strong></a> - <em>"Blazing-fast-cross-platform-monkey-patch-free string XOR. Yes, that means JRuby too."</em></p></li></ul><h2 id="heading-code-amp-design-patterns">Code &amp; Design Patterns</h2><h3 id="heading-folders">Folders</h3><p>Here are some non-standard Rails folders:</p><ul><li><p><code>app/chewy</code> - containing Chewy objects</p></li><li><p><code>app/policies</code> - containing objects that implement policies used by Pundit gem</p></li><li><p><code>app/presenters</code> - contain objects that implement <code>ActiveModelSerializers::Model</code> interface or that exposes methods that return hashes</p></li><li><p><code>app/services</code> - contain objects that implement Service Object pattern</p></li><li><p><code>app/validators</code> - contain objects that mostly implement <code>ActiveModel::Validator</code> interface</p></li><li><p><code>app/workers</code> - contain objects that implement <code>Sidekiq::Worker</code> interface</p></li><li><p><code>app/lib</code> - contains probably POROs that do not belong anywhere else</p></li><li><p><code>dist</code> - contains <a target="_blank" href="https://systemd.io">systemd</a> files used to manage all web app services on Linux-based systems with <a target="_blank" href="https://systemd.io">systemd</a> installed</p></li><li><p><code>lib</code> - contains code that either extends or changes other libraries objects or that extends external libraries used</p></li></ul><h3 id="heading-controllers">Controllers</h3><p>There are 2 main categories of controllers when thinking about how the information will be displayed:</p><ul><li><p>General controllers (like the ones that are directly under <code>app/controllers</code> or <code>app/controllers/admin</code>) that render an SSR view that will write some HTML tags and then load the React app. They respond to <code>html</code> and <code>json</code></p></li><li><p>The controllers under <code>app/api</code> that only responds to <code>json</code></p></li></ul><h4 id="heading-caching">Caching</h4><p>It implements the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary"><code>Vary</code></a> header:</p><blockquote><p>The Vary HTTP response header describes the parts of the request message aside from the method and URL that influenced the content of the response it occurs in. Most often, this is used to create a cache key when content negotiation is in use.</p></blockquote><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/cache_concern.rb#L150</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">vary_by</span><span class="hljs-params">(value, **kwargs)</span></span>  before_action(**kwargs) <span class="hljs-keyword">do</span> <span class="hljs-params">|controller|</span>    response.headers[<span class="hljs-string">'Vary'</span>] = value.respond_to?(<span class="hljs-symbol">:call</span>) ? controller.instance_exec(&amp;value) : value  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># Used like this</span><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/follower_accounts_controller.rb#L3</span>vary_by -&gt; { public_fetch_mode? ? <span class="hljs-string">'Accept, Accept-Language, Cookie'</span> : <span class="hljs-string">'Accept, Accept-Language, Cookie, Signature'</span> }</code></pre><p>A note about using <code>instance_exec</code> is a method from <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/BasicObject.html#method-i-instance_exec">BasicObject#instance_eval</a> that can be used to execute a block within the context of the receiver.</p><p>Another method used is <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/cache_concern.rb#L172"><code>render_with_cache</code></a> used by API controllers to cache JSON responses:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/cache_concern.rb#L172 </span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">render_with_cache</span><span class="hljs-params">(**options)</span></span>  raise ArgumentError, <span class="hljs-string">'Only JSON render calls are supported'</span> <span class="hljs-keyword">unless</span> options.key?(<span class="hljs-symbol">:json</span>) <span class="hljs-params">||</span> block_given?  key        = options.delete(<span class="hljs-symbol">:key</span>) <span class="hljs-params">||</span> [[params[<span class="hljs-symbol">:controller</span>], params[<span class="hljs-symbol">:action</span>]].join(<span class="hljs-string">'/'</span>), options[<span class="hljs-symbol">:json</span>].respond_to?(<span class="hljs-symbol">:cache_key</span>) ? options[<span class="hljs-symbol">:json</span>].cache_key : <span class="hljs-literal">nil</span>, options[<span class="hljs-symbol">:fields</span>].<span class="hljs-literal">nil</span>? ? <span class="hljs-literal">nil</span> : options[<span class="hljs-symbol">:fields</span>].join(<span class="hljs-string">','</span>)].compact.join(<span class="hljs-string">':'</span>)  expires_in = options.delete(<span class="hljs-symbol">:expires_in</span>) <span class="hljs-params">||</span> <span class="hljs-number">3</span>.minutes  body       = Rails.cache.read(key, <span class="hljs-symbol">raw:</span> <span class="hljs-literal">true</span>)  <span class="hljs-keyword">if</span> body    render(options.except(<span class="hljs-symbol">:json</span>, <span class="hljs-symbol">:serializer</span>, <span class="hljs-symbol">:each_serializer</span>, <span class="hljs-symbol">:adapter</span>, <span class="hljs-symbol">:fields</span>).merge(<span class="hljs-symbol">json:</span> body))  <span class="hljs-keyword">else</span>    <span class="hljs-keyword">if</span> block_given?      options[<span class="hljs-symbol">:json</span>] = <span class="hljs-keyword">yield</span>    <span class="hljs-keyword">elsif</span> options[<span class="hljs-symbol">:json</span>].is_a?(Symbol)      options[<span class="hljs-symbol">:json</span>] = send(options[<span class="hljs-symbol">:json</span>])    <span class="hljs-keyword">end</span>    render(options)    Rails.cache.write(key, response.body, <span class="hljs-symbol">expires_in:</span> expires_in, <span class="hljs-symbol">raw:</span> <span class="hljs-literal">true</span>)  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># Used like this:</span><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/accounts_controller.rb#L35</span>format.json <span class="hljs-keyword">do</span>  expires_in <span class="hljs-number">3</span>.minutes, <span class="hljs-symbol">public:</span> !(authorized_fetch_mode? &amp;&amp; signed_request_account.present?)  render_with_cache <span class="hljs-symbol">json:</span> @account, <span class="hljs-symbol">content_type:</span> <span class="hljs-string">'application/activity+json'</span>, <span class="hljs-symbol">serializer:</span> ActivityPub::ActorSerializer, <span class="hljs-symbol">adapter:</span> ActivityPub::Adapter<span class="hljs-keyword">end</span></code></pre><p>And a third method that is used is <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/cache_concern.rb#L193-L194"><code>cache_collection</code></a> used with models that include the <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/models/concerns/cacheable.rb#L3"><code>Cacheable</code></a> concern:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/cache_concern.rb#L193-L194</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">cache_collection</span><span class="hljs-params">(raw, klass)</span></span>  <span class="hljs-keyword">return</span> raw <span class="hljs-keyword">unless</span> klass.respond_to?(<span class="hljs-symbol">:with_includes</span>)  raw = raw.cache_ids.to_a <span class="hljs-keyword">if</span> raw.is_a?(ActiveRecord::Relation)  <span class="hljs-keyword">return</span> [] <span class="hljs-keyword">if</span> raw.empty?  cached_keys_with_value = <span class="hljs-keyword">begin</span>    Rails.cache.read_multi(*raw).transform_keys(&amp;<span class="hljs-symbol">:id</span>).transform_values { <span class="hljs-params">|r|</span> ActiveRecordCoder.load(r) }  <span class="hljs-keyword">rescue</span> ActiveRecordCoder::Error    {} <span class="hljs-comment"># The serialization format may have changed, let's pretend it's a cache miss.</span>  <span class="hljs-keyword">end</span>  uncached_ids = raw.map(&amp;<span class="hljs-symbol">:id</span>) - cached_keys_with_value.keys  klass.reload_stale_associations!(cached_keys_with_value.values) <span class="hljs-keyword">if</span> klass.respond_to?(<span class="hljs-symbol">:reload_stale_associations!</span>)  <span class="hljs-keyword">unless</span> uncached_ids.empty?    uncached = klass.where(<span class="hljs-symbol">id:</span> uncached_ids).with_includes.index_by(&amp;<span class="hljs-symbol">:id</span>)    uncached.each_value <span class="hljs-keyword">do</span> <span class="hljs-params">|item|</span>      Rails.cache.write(item, ActiveRecordCoder.dump(item))    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span>  raw.filter_map { <span class="hljs-params">|item|</span> cached_keys_with_value[item.id] <span class="hljs-params">||</span> uncached[item.id] }<span class="hljs-keyword">end</span><span class="hljs-comment"># Used like this:</span>format.rss <span class="hljs-keyword">do</span>  expires_in <span class="hljs-number">1</span>.minute, <span class="hljs-symbol">public:</span> <span class="hljs-literal">true</span>  limit     = params[<span class="hljs-symbol">:limit</span>].present? ? [params[<span class="hljs-symbol">:limit</span>].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE  @statuses = filtered_statuses.without_reblogs.limit(limit)  @statuses = cache_collection(@statuses, Status)<span class="hljs-keyword">end</span></code></pre><h4 id="heading-controller-with-multiple-respondto">Controller with multiple respond_to</h4><p>Here is an example of a controller action with multiple respond_to formats:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/accounts_controller.rb#L17</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span></span>  respond_to <span class="hljs-keyword">do</span> <span class="hljs-params">|format|</span>    format.html <span class="hljs-keyword">do</span>      expires_in(<span class="hljs-number">15</span>.seconds, <span class="hljs-symbol">public:</span> <span class="hljs-literal">true</span>, <span class="hljs-symbol">stale_while_revalidate:</span> <span class="hljs-number">30</span>.seconds, <span class="hljs-symbol">stale_if_error:</span> <span class="hljs-number">1</span>.hour) <span class="hljs-keyword">unless</span> user_signed_in?      @rss_url = rss_url    <span class="hljs-keyword">end</span>    format.rss <span class="hljs-keyword">do</span>      expires_in <span class="hljs-number">1</span>.minute, <span class="hljs-symbol">public:</span> <span class="hljs-literal">true</span>      limit     = params[<span class="hljs-symbol">:limit</span>].present? ? [params[<span class="hljs-symbol">:limit</span>].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE      @statuses = filtered_statuses.without_reblogs.limit(limit)      @statuses = cache_collection(@statuses, Status)    <span class="hljs-keyword">end</span>    format.json <span class="hljs-keyword">do</span>      expires_in <span class="hljs-number">3</span>.minutes, <span class="hljs-symbol">public:</span> !(authorized_fetch_mode? &amp;&amp; signed_request_account.present?)      render_with_cache <span class="hljs-symbol">json:</span> @account, <span class="hljs-symbol">content_type:</span> <span class="hljs-string">'application/activity+json'</span>, <span class="hljs-symbol">serializer:</span> ActivityPub::ActorSerializer, <span class="hljs-symbol">adapter:</span> ActivityPub::Adapter    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-applicationcontroller">ApplicationController</h4><p>It includes a list of concerns, defines some helper methods and define a couple of <code>rescue_from</code> responses.</p><p>What I discovered here and I find useful to have a common language when writing custom controllers:</p><p><code>truthy_param?</code> the method that I think is useful to use to check if a param is a truthy value:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L91</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">truthy_param?</span><span class="hljs-params">(key)</span></span>  ActiveModel::Type::Boolean.new.cast(params[key])<span class="hljs-keyword">end</span><span class="hljs-comment"># Used like this</span><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/api/v1/timelines/public_controller.rb#L37-L44</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">public_feed</span></span>  PublicFeed.new(    current_account,    <span class="hljs-symbol">local:</span> truthy_param?(<span class="hljs-symbol">:local</span>),    <span class="hljs-symbol">remote:</span> truthy_param?(<span class="hljs-symbol">:remote</span>),    <span class="hljs-symbol">only_media:</span> truthy_param?(<span class="hljs-symbol">:only_media</span>)  )<span class="hljs-keyword">end</span></code></pre><p>A series of methods that can be used to respond with errors:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L169-L174</span><span class="hljs-comment"># First there is a respond_with_error defined: </span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">respond_with_error</span><span class="hljs-params">(code)</span></span>  respond_to <span class="hljs-keyword">do</span> <span class="hljs-params">|format|</span>    format.any  { render <span class="hljs-string">"errors/<span class="hljs-subst">#{code}</span>"</span>, <span class="hljs-symbol">layout:</span> <span class="hljs-string">'error'</span>, <span class="hljs-symbol">status:</span> code, <span class="hljs-symbol">formats:</span> [<span class="hljs-symbol">:html</span>] }    format.json { render <span class="hljs-symbol">json:</span> { <span class="hljs-symbol">error:</span> Rack::Utils::HTTP_STATUS_CODES[code] }, <span class="hljs-symbol">status:</span> code }  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># Then there a series of methods defined like this:</span><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L96</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forbidden</span></span>  respond_with_error(<span class="hljs-number">403</span>)<span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_found</span></span>  respond_with_error(<span class="hljs-number">404</span>)<span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gone</span></span>  respond_with_error(<span class="hljs-number">410</span>)<span class="hljs-keyword">end</span></code></pre><h3 id="heading-models">Models</h3><p>Some stats:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701852187787/445496b1-0088-4655-9190-d55a94670bef.png" alt class="image--center mx-auto" /></p><ul><li><p><code>189</code> files in <code>app/models</code> and <code>29</code> of them are in <code>app/models/concerns</code></p></li><li><p><code>71</code> files are not directly inherited from <code>ApplicationRecord</code></p></li><li><p><code>89</code> are <code>ActiveRecord</code> models</p></li></ul><p>The <code>ApplicationRecord</code> model is connected to writing and reading databases and defines one common instance method <code>boolean_with_default</code> that will be used to define some getters that return a default value when nil. It is a simple method but very useful.</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/models/application_record.rb#L16</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">boolean_with_default</span><span class="hljs-params">(key, default_value)</span></span>  value = attributes[key]  <span class="hljs-keyword">if</span> value.<span class="hljs-literal">nil</span>?    default_value  <span class="hljs-keyword">else</span>    value  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># Used like this</span><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/models/tag.rb#L71</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">usable</span></span>  boolean_with_default(<span class="hljs-string">'usable'</span>, <span class="hljs-literal">true</span>)<span class="hljs-keyword">end</span><span class="hljs-keyword">alias</span> usable? usable</code></pre><p>Also, notice the choice to use <code>alias</code> to define the predicate method <code>usable?</code> instead of defining it as a normal method (e.g. <code>def usable? = usable</code>)</p><p>Here are some things that I found in the models:</p><h4 id="heading-counting-cache"><strong>Counting cache</strong></h4><p>The <code>Account</code> model has a couple of associated stats models implemented by using a <code>has_one :account_stat</code> that includes some colums like: <code>statuses_count</code>, <code>following_count</code>, <code>followers_count</code> . The count is incremented by executing the following SQL:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/models/concerns/account_counters.rb#L34</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_count!</span><span class="hljs-params">(key, value)</span></span><span class="hljs-comment"># omitted code ...</span>  sql = <span class="hljs-keyword">if</span> value.positive? &amp;&amp; key == <span class="hljs-symbol">:statuses_count</span>          <span class="hljs-string">&lt;&lt;-SQL.squish            INSERT INTO account_stats(account_id, <span class="hljs-subst">#{key}</span>, created_at, updated_at, last_status_at)              VALUES (:account_id, :default_value, now(), now(), now())            ON CONFLICT (account_id) DO UPDATE            SET <span class="hljs-subst">#{key}</span> = account_stats.<span class="hljs-subst">#{key}</span> + :value,                last_status_at = now(),                updated_at = now()            RETURNING id;          SQL</span>        <span class="hljs-keyword">else</span>          <span class="hljs-string">&lt;&lt;-SQL.squish            INSERT INTO account_stats(account_id, <span class="hljs-subst">#{key}</span>, created_at, updated_at)              VALUES (:account_id, :default_value, now(), now())            ON CONFLICT (account_id) DO UPDATE            SET <span class="hljs-subst">#{key}</span> = account_stats.<span class="hljs-subst">#{key}</span> + :value,                updated_at = now()            RETURNING id;          SQL</span>        <span class="hljs-keyword">end</span>  sql = AccountStat.sanitize_sql([sql, <span class="hljs-symbol">account_id:</span> id, <span class="hljs-symbol">default_value:</span> default_value, <span class="hljs-symbol">value:</span> value])  account_stat_id = AccountStat.connection.exec_query(sql)[<span class="hljs-number">0</span>][<span class="hljs-string">'id'</span>]  <span class="hljs-comment"># Omitted code ..</span><span class="hljs-keyword">end</span><span class="hljs-comment"># Used like this</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">increment_count!</span><span class="hljs-params">(key)</span></span> = update_count!(key, <span class="hljs-number">1</span>)<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decrement_count!</span><span class="hljs-params">(key)</span></span> = update_count!(key, -<span class="hljs-number">1</span>)</code></pre><p>The reason for using the custom SQL is <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/models/concerns/account_counters.rb#L41-L44">explained</a> in the same method:</p><blockquote><p>We do an upsert using manually written SQL, as Rails' upsert method does not seem to support writing expressions in the UPDATE clause, but only re-insert the provided values instead. Even ARel seem to be missing proper handling of upserts.</p></blockquote><p>This will be used in the following way:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Favourite</span> &lt; ApplicationRecord</span>  after_create <span class="hljs-symbol">:increment_cache_counters</span>  belongs_to <span class="hljs-symbol">:status</span>,  <span class="hljs-symbol">inverse_of:</span> <span class="hljs-symbol">:favourites</span>  <span class="hljs-comment"># Code omitted ... </span>  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">increment_cache_counters</span></span>    status&amp;.increment_count!(<span class="hljs-symbol">:favourites_count</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># code omitted ...</span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-user-roles"><strong>User Roles</strong></h4><p>User Roles are defined in the UserRole AR model using the bitwise shift left operator:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserRole</span> &lt; ApplicationRecord</span>  FLAGS = {    <span class="hljs-symbol">administrator:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">0</span>),    <span class="hljs-symbol">view_devops:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">1</span>),    <span class="hljs-symbol">view_audit_log:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">2</span>),    <span class="hljs-symbol">view_dashboard:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">3</span>),    <span class="hljs-symbol">manage_reports:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">4</span>),    <span class="hljs-symbol">manage_federation:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">5</span>),    <span class="hljs-symbol">manage_settings:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">6</span>),    <span class="hljs-symbol">manage_blocks:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">7</span>),    <span class="hljs-symbol">manage_taxonomies:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">8</span>),    <span class="hljs-symbol">manage_appeals:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">9</span>),    <span class="hljs-symbol">manage_users:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">10</span>),    <span class="hljs-symbol">manage_invites:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">11</span>),    <span class="hljs-symbol">manage_rules:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">12</span>),    <span class="hljs-symbol">manage_announcements:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">13</span>),    <span class="hljs-symbol">manage_custom_emojis:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">14</span>),    <span class="hljs-symbol">manage_webhooks:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">15</span>),    <span class="hljs-symbol">invite_users:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">16</span>),    <span class="hljs-symbol">manage_roles:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">17</span>),    <span class="hljs-symbol">manage_user_access:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">18</span>),    <span class="hljs-symbol">delete_user_data:</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">19</span>),  }.freeze  <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Flags</span></span>    NONE = <span class="hljs-number">0</span>    ALL  = FLAGS.values.reduce(&amp;<span class="hljs-symbol">:|</span>)    DEFAULT = FLAGS[<span class="hljs-symbol">:invite_users</span>]    <span class="hljs-comment"># code omitted ... </span>  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># code omitted ... </span><span class="hljs-keyword">end</span></code></pre><p>In case you want to understand how they are used, here are some expressions:</p><ul><li><p>If you execute <code>(1 &lt;&lt; 2).to_s(2)</code> =&gt; <code>100</code></p></li><li><p>If you execute <code>(1 &lt;&lt; 3).to_s(2)</code> =&gt; <code>1000</code></p></li><li><p>If you execute <code>((1&lt;&lt;2) | (1&lt;&lt;3)).to_s(2)</code> =&gt; <code>1100</code></p></li><li><p>If you execute <code>ALL.to_s(2)</code> will get <code>11111111111111111111</code></p></li></ul><p>Then in the UserRole model there are the following methods defined:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/models/user_role.rb#L164</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">can?</span><span class="hljs-params">(*any_of_privileges)</span></span>  any_of_privileges.any? { <span class="hljs-params">|privilege|</span> in_permissions?(privilege) }<span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">in_permissions?</span><span class="hljs-params">(privilege)</span></span>  raise ArgumentError, <span class="hljs-string">"Unknown privilege: <span class="hljs-subst">#{privilege}</span>"</span> <span class="hljs-keyword">unless</span> FLAGS.key?(privilege)  computed_permissions &amp; FLAGS[privilege] == FLAGS[privilege]<span class="hljs-keyword">end</span></code></pre><p>They are used in <code>AccountPolicy</code> like this:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/policies/account_policy.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccountPolicy</span> &lt; ApplicationPolicy</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index?</span></span>    role.can?(<span class="hljs-symbol">:manage_users</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show?</span></span>    role.can?(<span class="hljs-symbol">:manage_users</span>)  <span class="hljs-keyword">end</span><span class="hljs-comment"># code omitted ...</span><span class="hljs-keyword">end</span></code></pre><h3 id="heading-services">Services</h3><h4 id="heading-baseservice">BaseService</h4><p>There is a simple <code>BaseService</code> that looks like this:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/services/base_service.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BaseService</span></span>  <span class="hljs-keyword">include</span> ActionView::Helpers::TextHelper  <span class="hljs-keyword">include</span> ActionView::Helpers::SanitizeHelper  <span class="hljs-keyword">include</span> RoutingHelper  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span><span class="hljs-params">(*)</span></span>    raise NotImplementedError  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>and then almost all services are inherited from this <code>BaseService</code></p><p>The services defined here can be called from controllers or from models.</p><p>Here is an example of the Follow service (will just add here the #call method):</p><ul><li><p>It documents the call using the YARD format</p></li><li><p>It uses comments to explain why the home feed is marked as partial! (that will set a key in Redis to regenerate the home feed)</p></li></ul><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/services/follow_service.rb#L3</span><span class="hljs-comment"># Follow a remote user, notify remote user about the follow</span><span class="hljs-comment"># <span class="hljs-doctag">@param</span> [Account] source_account From which to follow</span><span class="hljs-comment"># <span class="hljs-doctag">@param</span> [Account] target_account Account to follow</span><span class="hljs-comment"># <span class="hljs-doctag">@param</span> [Hash] options</span><span class="hljs-comment"># <span class="hljs-doctag">@option</span> [Boolean] :reblogs Whether or not to show reblogs, defaults to true</span><span class="hljs-comment"># <span class="hljs-doctag">@option</span> [Boolean] :notify Whether to create notifications about new posts, defaults to false</span><span class="hljs-comment"># <span class="hljs-doctag">@option</span> [Array&lt;String&gt;] :languages Which languages to allow on the home feed from this account, defaults to all</span><span class="hljs-comment"># <span class="hljs-doctag">@option</span> [Boolean] :bypass_locked</span><span class="hljs-comment"># <span class="hljs-doctag">@option</span> [Boolean] :bypass_limit Allow following past the total follow number</span><span class="hljs-comment"># <span class="hljs-doctag">@option</span> [Boolean] :with_rate_limit</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span><span class="hljs-params">(source_account, target_account, options = {})</span></span>  @source_account = source_account  @target_account = target_account  @options        = { <span class="hljs-symbol">bypass_locked:</span> <span class="hljs-literal">false</span>, <span class="hljs-symbol">bypass_limit:</span> <span class="hljs-literal">false</span>, <span class="hljs-symbol">with_rate_limit:</span> <span class="hljs-literal">false</span> }.merge(options)  raise ActiveRecord::RecordNotFound <span class="hljs-keyword">if</span> following_not_possible?  raise Mastodon::NotPermittedError  <span class="hljs-keyword">if</span> following_not_allowed?  <span class="hljs-keyword">if</span> @source_account.following?(@target_account)    <span class="hljs-keyword">return</span> change_follow_options!  <span class="hljs-keyword">elsif</span> @source_account.requested?(@target_account)    <span class="hljs-keyword">return</span> change_follow_request_options!  <span class="hljs-keyword">end</span>  ActivityTracker.increment(<span class="hljs-string">'activity:interactions'</span>)  <span class="hljs-comment"># When an account follows someone for the first time, avoid showing</span>  <span class="hljs-comment"># an empty home feed while the follow request is being processed</span>  <span class="hljs-comment"># and the feeds are being merged</span>  mark_home_feed_as_partial! <span class="hljs-keyword">if</span> @source_account.not_following_anyone?  <span class="hljs-keyword">if</span> (@target_account.locked? &amp;&amp; !@options[<span class="hljs-symbol">:bypass_locked</span>]) <span class="hljs-params">||</span> @source_account.silenced? <span class="hljs-params">||</span> @target_account.activitypub?    request_follow!  <span class="hljs-keyword">elsif</span> @target_account.local?    direct_follow!  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-fetchlinkcardservice">FetchLinkCardService</h4><p>Here is a piece of code from <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/services/fetch_link_card_service.rb#L7"><code>FetchLinkCardService</code></a> that parses a status and extracts shared URLs.</p><p>First, it defines a Regexp for searching for URL patterns:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/services/fetch_link_card_service.rb#L7</span>  URL_PATTERN = <span class="hljs-regexp">%r{    (<span class="hljs-subst">#{Twitter::TwitterText::Regex[<span class="hljs-symbol">:valid_url_preceding_chars</span>]}</span>)                                                                #   $1 preceding chars    (                                                                                                                           #   $2 URL      (https?://)                                                                                                               #   $3 Protocol (required)      (<span class="hljs-subst">#{Twitter::TwitterText::Regex[<span class="hljs-symbol">:valid_domain</span>]}</span>)                                                                           #   $4 Domain(s)      (?::(<span class="hljs-subst">#{Twitter::TwitterText::Regex[<span class="hljs-symbol">:valid_port_number</span>]}</span>))?                                                                #   $5 Port number (optional)      (/<span class="hljs-subst">#{Twitter::TwitterText::Regex[<span class="hljs-symbol">:valid_url_path</span>]}</span>*)?                                                                      #   $6 URL Path and anchor      (\?<span class="hljs-subst">#{Twitter::TwitterText::Regex[<span class="hljs-symbol">:valid_url_query_chars</span>]}</span>*<span class="hljs-subst">#{Twitter::TwitterText::Regex[<span class="hljs-symbol">:valid_url_query_ending_chars</span>]}</span>)? #   $7 Query String    )  }iox</span></code></pre><p>Then it extracts URLs with:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/services/fetch_link_card_service.rb#L73</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_urls</span></span>  urls = <span class="hljs-keyword">if</span> @status.local?            @status.text.scan(URL_PATTERN).map { <span class="hljs-params">|array|</span> Addressable::URI.parse(array[<span class="hljs-number">1</span>]).normalize }          <span class="hljs-keyword">else</span>            document = Nokogiri::HTML(@status.text)            links = document.css(<span class="hljs-string">'a'</span>)            links.filter_map { <span class="hljs-params">|a|</span> Addressable::URI.parse(a[<span class="hljs-string">'href'</span>]) <span class="hljs-keyword">unless</span> skip_link?(a) }.filter_map(&amp;<span class="hljs-symbol">:normalize</span>)          <span class="hljs-keyword">end</span>  urls.reject { <span class="hljs-params">|uri|</span> bad_url?(uri) }.first<span class="hljs-keyword">end</span></code></pre><h4 id="heading-tagsearchservice"><strong>TagSearchService</strong></h4><p>Here is a nice and easy-to-read method in <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/services/tag_search_service.rb#L3"><code>TagSearchService</code></a></p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/services/tag_search_service.rb#L4</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span><span class="hljs-params">(query, options = {})</span></span>  @query   = query.strip.delete_prefix(<span class="hljs-string">'#'</span>)  @offset  = options.delete(<span class="hljs-symbol">:offset</span>).to_i  @limit   = options.delete(<span class="hljs-symbol">:limit</span>).to_i  @options = options  results   = from_elasticsearch <span class="hljs-keyword">if</span> Chewy.enabled?  results <span class="hljs-params">||</span>= from_database  results<span class="hljs-keyword">end</span></code></pre><h3 id="heading-views">Views</h3><p>The views have the following structure:</p><ul><li><p>They are written using <code>HAML</code></p></li><li><p>The contains in the HAML file some content_for header or open graph</p></li><li><p>And they call at the end <code>shared/web_app</code></p></li></ul><p>Example from the <code>Home#index</code>:</p><pre><code class="lang-haml">-<span class="ruby"> <span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/views/home/index.html.haml#L1</span></span>-<span class="ruby"> content_for <span class="hljs-symbol">:header_tags</span> <span class="hljs-keyword">do</span></span>  -<span class="ruby"> <span class="hljs-keyword">unless</span> request.path == <span class="hljs-string">'/'</span></span><span class="hljs-tag">    %<span class="hljs-selector-tag">meta</span>{ name: 'robots', content: 'noindex' }</span>/  =<span class="ruby"> render <span class="hljs-symbol">partial:</span> <span class="hljs-string">'shared/og'</span></span>=<span class="ruby"> render <span class="hljs-string">'shared/web_app'</span></span></code></pre><p>Where the <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/views/shared/_web_app.html.haml#L1"><code>web_app</code></a> will do something like this:</p><ul><li><p>Preload some assets based on user sign-in</p></li><li><p><a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/helpers/application_helper.rb#L192">Render</a> by using InitialStatePresenter as JSON in a Script tag</p></li><li><p>Include <code>javascript_pack_tag</code> for <code>application</code></p></li></ul><p>I will not go into this review into the Javascript part as this is focused on Ruby and Rails.</p><h2 id="heading-testing">Testing</h2><p>It uses RSPec for testing.</p><p>In general, the tests are pretty straightforward, with very few custom matches defined.</p><p>The tests are easy to find as the folder inside <code>spec</code> matches the folder in <code>app</code>.</p><p>The coverage is a bit hard to guess from the test folder structure:</p><ul><li><p>not all controllers have a corresponding test in controller specs</p></li><li><p>not all models have a corresponding test in model specs</p></li></ul><p>Just looking at some numbers (as I don't have time to understand the entire codebase) here is a comparison of the number of files in the app folder vs the number of files in the corresponding specs folder.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701922888325/d3168ce8-8296-44e8-a924-4c157a917a58.png" alt class="image--center mx-auto" /></p><h3 id="heading-generating-data">Generating data</h3><p>It uses <a target="_blank" href="https://fabricationgem.org"><code>Fabricator</code></a> gem to generate test data. Here is an example of generating a User:</p><pre><code class="lang-ruby">Fabricator(<span class="hljs-symbol">:user</span>) <span class="hljs-keyword">do</span>  account      { Fabricate.build(<span class="hljs-symbol">:account</span>, <span class="hljs-symbol">user:</span> <span class="hljs-literal">nil</span>) }  email        { sequence(<span class="hljs-symbol">:email</span>) { <span class="hljs-params">|i|</span> <span class="hljs-string">"<span class="hljs-subst">#{i}</span><span class="hljs-subst">#{Faker::Internet.email}</span>"</span> } }  password     <span class="hljs-string">'123456789'</span>  confirmed_at { Time.zone.now }  current_sign_in_at { Time.zone.now }  agreement <span class="hljs-literal">true</span><span class="hljs-keyword">end</span></code></pre><p>and then it can be called as <code>Fabricator(:user)</code></p><h3 id="heading-testing-views">Testing views</h3><p>Probably the React part is tested in some other specific way. I will not dig into that. But here is a test for open graph tags included in the status view:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/spec/views/statuses/show.html.haml_spec.rb#L5</span>describe <span class="hljs-string">'statuses/show.html.haml'</span>, <span class="hljs-symbol">:without_verify_partial_doubles</span> <span class="hljs-keyword">do</span>  before <span class="hljs-keyword">do</span>    allow(view).to receive_messages(<span class="hljs-symbol">api_oembed_url:</span> <span class="hljs-string">''</span>, <span class="hljs-symbol">show_landing_strip?:</span> <span class="hljs-literal">true</span>, <span class="hljs-symbol">site_title:</span> <span class="hljs-string">'example site'</span>, <span class="hljs-symbol">site_hostname:</span> <span class="hljs-string">'example.com'</span>, <span class="hljs-symbol">full_asset_url:</span> <span class="hljs-string">'//asset.host/image.svg'</span>, <span class="hljs-symbol">current_account:</span> <span class="hljs-literal">nil</span>, <span class="hljs-symbol">single_user_mode?:</span> <span class="hljs-literal">false</span>)    allow(view).to receive(<span class="hljs-symbol">:local_time</span>)    allow(view).to receive(<span class="hljs-symbol">:local_time_ago</span>)    assign(<span class="hljs-symbol">:instance_presenter</span>, InstancePresenter.new)  <span class="hljs-keyword">end</span>  it <span class="hljs-string">'has valid opengraph tags'</span> <span class="hljs-keyword">do</span>    alice  = Fabricate(<span class="hljs-symbol">:account</span>, <span class="hljs-symbol">username:</span> <span class="hljs-string">'alice'</span>, <span class="hljs-symbol">display_name:</span> <span class="hljs-string">'Alice'</span>)    status = Fabricate(<span class="hljs-symbol">:status</span>, <span class="hljs-symbol">account:</span> alice, <span class="hljs-symbol">text:</span> <span class="hljs-string">'Hello World'</span>)    Fabricate(<span class="hljs-symbol">:media_attachment</span>, <span class="hljs-symbol">account:</span> alice, <span class="hljs-symbol">status:</span> status, <span class="hljs-symbol">type:</span> <span class="hljs-symbol">:video</span>)    assign(<span class="hljs-symbol">:status</span>, status)    assign(<span class="hljs-symbol">:account</span>, alice)    assign(<span class="hljs-symbol">:descendant_threads</span>, [])    render    header_tags = view.content_for(<span class="hljs-symbol">:header_tags</span>)    expect(header_tags).to match(<span class="hljs-regexp">/&lt;meta content=".+" property="og:title"&gt;/</span>)    expect(header_tags).to match(<span class="hljs-regexp">/&lt;meta content="article" property="og:type"&gt;/</span>)    expect(header_tags).to match(<span class="hljs-regexp">/&lt;meta content=".+" property="og:image"&gt;/</span>)    expect(header_tags).to match(<span class="hljs-regexp">%r{&lt;meta content="http://.+" property="og:url"&gt;}</span>)  <span class="hljs-keyword">end</span><span class="hljs-comment"># code omitted ...</span><span class="hljs-keyword">end</span></code></pre><h3 id="heading-testing-rackattack">Testing Rack::Attack</h3><p>This is done via shared_examples:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/spec/config/initializers/rack/attack_spec.rb#L5</span>describe Rack::Attack, <span class="hljs-symbol">type:</span> <span class="hljs-symbol">:request</span> <span class="hljs-keyword">do</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">app</span></span>    Rails.application  <span class="hljs-keyword">end</span>  shared_examples <span class="hljs-string">'throttled endpoint'</span> <span class="hljs-keyword">do</span>    before <span class="hljs-keyword">do</span>      <span class="hljs-comment"># Rack::Attack periods are not rolling, so avoid flaky tests by setting the time in a way</span>      <span class="hljs-comment"># to avoid crossing period boundaries.</span>      <span class="hljs-comment"># The code Rack::Attack uses to set periods is the following:</span>      <span class="hljs-comment"># https://github.com/rack/rack-attack/blob/v6.6.1/lib/rack/attack/cache.rb#L64-L66</span>      <span class="hljs-comment"># So we want to minimize `Time.now.to_i % period`</span>      travel_to Time.zone.at(counter_prefix * period.seconds)    <span class="hljs-keyword">end</span>    context <span class="hljs-string">'when the number of requests is lower than the limit'</span> <span class="hljs-keyword">do</span>      before <span class="hljs-keyword">do</span>        below_limit.times { increment_counter }      <span class="hljs-keyword">end</span>      it <span class="hljs-string">'does not change the request status'</span> <span class="hljs-keyword">do</span>        expect { request.call }.to change { throttle_count }.by(<span class="hljs-number">1</span>)        expect(response).to_not have_http_status(<span class="hljs-number">429</span>)      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">throttle_count</span></span>      described_class.cache.read(<span class="hljs-string">"<span class="hljs-subst">#{counter_prefix}</span>:<span class="hljs-subst">#{throttle}</span>:<span class="hljs-subst">#{remote_ip}</span>"</span>) <span class="hljs-params">||</span> <span class="hljs-number">0</span>    <span class="hljs-keyword">end</span> <span class="hljs-comment"># code omitted ..</span><span class="hljs-keyword">end</span></code></pre><p>and the shared example will be used like this:</p><pre><code class="lang-ruby">context <span class="hljs-string">'when accessed through the website'</span> <span class="hljs-keyword">do</span>  let(<span class="hljs-symbol">:throttle</span>) { <span class="hljs-string">'throttle_sign_up_attempts/ip'</span> }  let(<span class="hljs-symbol">:limit</span>)  { <span class="hljs-number">25</span> }  let(<span class="hljs-symbol">:period</span>) { <span class="hljs-number">5</span>.minutes }  let(<span class="hljs-symbol">:request</span>) { -&gt; { post path, <span class="hljs-symbol">headers:</span> { <span class="hljs-string">'REMOTE_ADDR'</span> =&gt; remote_ip } } }  context <span class="hljs-string">'with exact path'</span> <span class="hljs-keyword">do</span>    let(<span class="hljs-symbol">:path</span>) { <span class="hljs-string">'/auth'</span> }    it_behaves_like <span class="hljs-string">'throttled endpoint'</span>  <span class="hljs-keyword">end</span><span class="hljs-comment"># code omitted ..</span><span class="hljs-keyword">end</span></code></pre><h2 id="heading-conclusion">Conclusion</h2><p>Mastodon open-source web app is a well-structured code base, easy to navigate (when focusing on the Ruby on Rails part). There is a good structure for testing and it probably covers most of the important functions, but there are still some models and controllers left out of testing that could be covered.</p><p>The code design is simple and easy to understand. The app tried to have slim controllers and pretty well-contained models while delegating more complex business logic to Service objects and simple Ruby objects. This makes the existing tests simple to follow and understand.</p><p>I think it could be a good pick for someone who wants to contribute. There are useful pieces of code that can be used as inspiration for other parts.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/ruby-on-rails-open-source-mastodon</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/ruby-on-rails-open-source-mastodon</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 08 Dec 2023 06:47:47 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1702017876116/cf6f3bd5-5c5b-4588-9add-af1ee3dd4cf0.png</cover_image></item><item><title><![CDATA[An Endless Method Use Case]]></title><description><![CDATA[<p>While preparing my article for the series that I started about <a target="_blank" href="https://allaboutcoding.ghinda.com/series/open-source-ruby">Open Source Ruby</a>, I found an example of code that I think could benefit from using the endless method.</p><p>Here is a video version of this article:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=nTHL0dvqmI0">https://www.youtube.com/watch?v=nTHL0dvqmI0</a></div><p> </p><p>or continue reading for the text version 👇</p><h2 id="heading-the-original-code">The original code</h2><p>The original code is based on Mastodon <a target="_blank" href="https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L3">ApplicationController</a>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationController</span> &lt; ActionController::Base</span>  <span class="hljs-comment"># other code ...</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forbidden</span></span>    respond_with_error(<span class="hljs-number">403</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_found</span></span>    respond_with_error(<span class="hljs-number">404</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gone</span></span>    respond_with_error(<span class="hljs-number">410</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">unprocessable_entity</span></span>    respond_with_error(<span class="hljs-number">422</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_acceptable</span></span>    respond_with_error(<span class="hljs-number">406</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bad_request</span></span>    respond_with_error(<span class="hljs-number">400</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">internal_server_error</span></span>    respond_with_error(<span class="hljs-number">500</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">service_unavailable</span></span>    respond_with_error(<span class="hljs-number">503</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">too_many_requests</span></span>    respond_with_error(<span class="hljs-number">429</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># other code ...</span><span class="hljs-keyword">end</span></code></pre><h2 id="heading-refactoring">Refactoring</h2><h3 id="heading-use-the-endless-method">Use the endless method</h3><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L3</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationController</span> &lt; ActionController::Base</span>  <span class="hljs-comment"># other code ...</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forbidden</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">403</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_found</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">404</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gone</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">410</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">unprocessable_entity</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">422</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_acceptable</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">406</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bad_request</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">400</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">internal_server_error</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">500</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">service_unavailable</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">503</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">too_many_requests</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">429</span>)</span></span>  <span class="hljs-comment"># other code ...</span><span class="hljs-keyword">end</span></code></pre><p>In this specific case, the endless method is the best to be used here.</p><p>It is almost like aliasing the same method but giving it different names based on the parameters passed.</p><p>Let's take a look at a side-by-side comparison:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701846368932/bd86a9e4-0aa1-4b4b-a4c2-00600c539978.png" alt class="image--center mx-auto" /></p><h3 id="heading-extract-to-a-concern">Extract to a concern</h3><p>I think we can take this one step further and extract it all to a concern or simple module if you want to:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">RespondWithError</span></span>  extend ActiveSupport::Concern  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forbidden</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">403</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_found</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">404</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gone</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">410</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">unprocessable_entity</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">422</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_acceptable</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">406</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bad_request</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">400</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">internal_server_error</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">500</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">service_unavailable</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">503</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">too_many_requests</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">429</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">respond_with_error</span><span class="hljs-params">(code)</span></span>    respond_to <span class="hljs-keyword">do</span> <span class="hljs-params">|format|</span>      format.any  { render <span class="hljs-string">"errors/<span class="hljs-subst">#{code}</span>"</span>, <span class="hljs-symbol">layout:</span> <span class="hljs-string">'error'</span>, <span class="hljs-symbol">status:</span> code, <span class="hljs-symbol">formats:</span> [<span class="hljs-symbol">:html</span>] }      format.json { render <span class="hljs-symbol">json:</span> { <span class="hljs-symbol">error:</span> Rack::Utils::HTTP_STATUS_CODES[code] }, <span class="hljs-symbol">status:</span> code }    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>This way all behavior about responding with error status code is contained in a single file. Adding or changing this behaviour could be made in this one single place.</p><h3 id="heading-group-together-by-status-code">Group together by status code</h3><p>Based on the reply from <a target="_blank" href="https://ruby.social/@rgarner@mastodon.social/111533461326388462">Russell Garner</a> and <a target="_blank" href="https://ruby.social/@soulcutter/111533488000059521">Bradley Schaefer</a> we can refactor this more by removing new lines between endless methods and grouping them by status:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">RespondWithError</span></span>  extend ActiveSupport::Concern  private  <span class="hljs-comment"># Client error responses 4xx</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bad_request</span>           = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">400</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forbidden</span>             = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">403</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_found</span>             = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">404</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_acceptable</span>        = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">406</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gone</span>                  = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">410</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">unprocessable_entity</span>  = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">422</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">too_many_requests</span>     = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">429</span>)</span></span>  <span class="hljs-comment"># Server error responses 5xx</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">internal_server_error</span> = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">500</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">service_unavailable</span>   = <span class="hljs-title">respond_with_error</span><span class="hljs-params">(<span class="hljs-number">503</span>)</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">respond_with_error</span><span class="hljs-params">(code)</span></span>    respond_to <span class="hljs-keyword">do</span> <span class="hljs-params">|format|</span>      format.any  { render <span class="hljs-string">"errors/<span class="hljs-subst">#{code}</span>"</span>, <span class="hljs-symbol">layout:</span> <span class="hljs-string">'error'</span>, <span class="hljs-symbol">status:</span> code, <span class="hljs-symbol">formats:</span> [<span class="hljs-symbol">:html</span>] }      format.json { render <span class="hljs-symbol">json:</span> { <span class="hljs-symbol">error:</span> Rack::Utils::HTTP_STATUS_CODES[code] }, <span class="hljs-symbol">status:</span> code }    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h2 id="heading-read-more-about-the-endless-method">Read more about the endless method</h2><p>I strongly recommend this article from Victor Shepelev about the endless method:</p><p><a target="_blank" href="https://zverok.substack.com/p/useless-ruby-sugar-endless-one-line">https://zverok.substack.com/p/useless-ruby-sugar-endless-one-line</a></p><p>You will find there more cases and examples about when to use and when not to use this construct.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p><hr /><h2 id="heading-updates">Updates</h2><ul><li>2023-12-06: Added a new refactoring variant based on replies to <a target="_blank" href="https://ruby.social/@lucian/111533361426467938">my share on Ruby.social</a> - see <code>Group together by status code</code></li></ul>]]></description><link>https://allaboutcoding.ghinda.com/an-endless-method-use-case</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/an-endless-method-use-case</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 06 Dec 2023 07:18:25 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1701847030088/5f1a959f-772c-41c7-a1d9-31b0f2b3ac39.png</cover_image></item><item><title><![CDATA[Review Ruby on Rails Code using Rubymine AI and ChatGPT]]></title><description><![CDATA[<p>I wrote a piece of code that will take as input a string that could be in one of the following two formats:</p><ul><li><p>a list of gem names, one per line</p></li><li><p>Gemfile format</p></li></ul><h2 id="heading-initial-code">Initial code</h2><p>This is for my Rails app that I use to curate the content for the <a target="_blank" href="https://newsletter.shortruby.com">Short Ruby Newsletter</a> and so in general I have little time to write code for it.</p><p>So I wrote a very quick draft of a working code along with some simple tests.</p><p>Here is the initial code:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Parser</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(list, <span class="hljs-symbol">rubygems_client:</span> RubyGems::Client.new)</span></span>    @list = list    @rubygems_client = rubygems_client  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse</span></span>    <span class="hljs-keyword">return</span> parse_gemfile_format <span class="hljs-keyword">if</span> gemfile_format?    parse_single_line_format  <span class="hljs-keyword">end</span>  private    <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:list</span>, <span class="hljs-symbol">:rubygems_client</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gemfile_format?</span> = <span class="hljs-title">lines</span>.<span class="hljs-title">any?</span> { <span class="hljs-title">gem_method_call?</span><span class="hljs-params">(_1)</span></span> }    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_gemfile_format</span></span>      ast = Prism.parse(list)      root_node = ast.value      parsed_gems = []      root_node.statements.body.each <span class="hljs-keyword">do</span> <span class="hljs-params">|call_node|</span>        <span class="hljs-keyword">case</span> call_node.name        <span class="hljs-keyword">when</span> <span class="hljs-symbol">:gem</span>          name = gem_name_from_call_node(call_node)          parsed_gems &lt;&lt; name        <span class="hljs-keyword">when</span> <span class="hljs-symbol">:group</span>          names = gem_names_from_group(call_node)          parsed_gems.concat(names) <span class="hljs-keyword">if</span> names.present?        <span class="hljs-keyword">end</span>      <span class="hljs-keyword">end</span>      parsed_gems.compact_blank.map { rubygems_client.info(_1) }    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_single_line_format</span></span>      lines.filter_map <span class="hljs-keyword">do</span> <span class="hljs-params">|line|</span>        gem_name = line.strip        gem_info = rubygems_client.info(gem_name) <span class="hljs-keyword">unless</span> gem_name.empty?        gem_info      <span class="hljs-keyword">end</span>.compact_blank    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_names_from_group</span><span class="hljs-params">(call_node)</span></span>      statements_node = call_node&amp;.block&amp;.body      statements_node.body.map { gem_name_from_call_node(_1) }    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_name_from_call_node</span><span class="hljs-params">(call_node)</span></span> = call_node&amp;.arguments&amp;.arguments&amp;.first&amp;.unescaped    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_method_call?</span><span class="hljs-params">(line)</span></span> = line.strip.<span class="hljs-keyword">include</span>?(<span class="hljs-string">"gem "</span>)    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lines</span> = <span class="hljs-title">list</span>.<span class="hljs-title">lines</span></span><span class="hljs-keyword">end</span></code></pre><p>You can notice that the code is not very OOP and it also has some issues with naming. Add to that the fact that <code>parse_gemfile_format</code> is quite a long method and it can get a bit complex. It does not handle exceptions at all and it abuses a bit the Ruby safe operator (<code>&amp;.</code>) when calling methods. Also, there are some cases that this parser will fail to handle like a more complex syntax of Gemfile.</p><p>So it seemed to me like a good fit to use it to assess the usefulness of using ChatGPT and RubyMine AI to review this code and propose improvements.</p><h2 id="heading-the-prompt">The prompt</h2><p>I wrote a simple prompt that explains what I wanted to achieve with this code and describes some areas of concern:</p><pre><code class="lang-markdown">I have a Ruby on Rails object designed for parsing a list of gems in various formats, including the Gemfile format, and for each gem, I will call a RubyGems client to get information about it.  The current implementation differentiates between the format of specifying one gem name per line and the Gemfile format and, based on this, will branch out into separate logic for each case. It uses Prism to parse the Gemfile format and identify the name of the gems. I'm concerned about the efficiency and readability of object and code design. I aim to refine the code to be more idiomatic and maintainable, ensuring it aligns with Ruby and Rails best practices. I also want to make use of the latest language features and syntax. One concern I have is how I use <span class="hljs-code">`prism`</span> gem (repository is at https://github.com/ruby/prism and documentation at https://ruby.github.io/prism/rb/index.html).The application runs on Ruby 3.2 and Rails 7. Any suggestions should be compatible with these versions.Extra guidelines:I want the private methods ordered in the order the public methods require.When guidelines are conflicting, choose readability over anything else.</code></pre><p>I know that the prompt can be made better but I wanted to make it as close as possible (while still making it a prompt) to maybe a discussion that anyone could have with a friend or colleague when asking for feedback.</p><h2 id="heading-chatgpt">ChatGPT</h2><p>Here is the response from ChatGPT after I provided the prompt and the code:</p><blockquote><p>Your Ruby on Rails code for parsing a list of gems is well-structured, but there are some improvements and refinements that can be made to enhance its efficiency, readability, and maintainability. Here are my suggestions:</p><ol><li><p><strong>Refactor Conditional Logic</strong>:</p><ul><li>The <code>parse</code> method uses a conditional to differentiate between formats. This is clear, but you might want to encapsulate the format-checking logic in more descriptive methods to improve readability.</li></ul></li><li><p><strong>Use Memoization</strong>:</p><ul><li>For methods that are called multiple times and return the same result, like <code>lines</code>, consider memoizing their results to improve efficiency.</li></ul></li><li><p><strong>Refactor</strong> <code>parse_gemfile_format</code>:</p><ul><li>This method can be refactored to reduce its complexity and increase readability. Extract parts of its logic into smaller, more focused methods.</li></ul></li><li><p><strong>Refine</strong> <code>gem_names_from_group</code> <strong>and</strong> <code>gem_name_from_call_node</code> Methods:</p><ul><li>These methods can be made more robust. Ensure that they handle potential <code>nil</code> cases gracefully.</li></ul></li><li><p><strong>Update Method Names for Clarity</strong>:</p><ul><li>Renaming methods like <code>gem_method_call?</code> to something more descriptive can improve readability.</li></ul></li><li><p><strong>Error Handling</strong>:</p><ul><li>Consider adding error handling for cases where the gem list might be in an unexpected format or when external calls fail.</li></ul></li><li><p><strong>Documentation and Comments</strong>:</p><ul><li>Adding comments to explain complex parts of the code or the reasoning behind certain decisions can be very helpful for maintainability.</li></ul></li><li><p><strong>Leverage Ruby 3.2 Features</strong>:</p><ul><li>Review the new features in Ruby 3.2 to see if any can be applied to improve the code. For example, pattern matching might be useful in parsing different formats.</li></ul></li><li><p><strong>Private Method Ordering</strong>:</p><ul><li>Reorder the private methods to match the sequence in which they are called by the public methods.</li></ul></li></ol></blockquote><p>It provided the following code:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Parser</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(list, <span class="hljs-symbol">rubygems_client:</span> RubyGems::Client.new)</span></span>    @list = list    @rubygems_client = rubygems_client  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse</span></span>    <span class="hljs-keyword">return</span> parse_gemfile_format <span class="hljs-keyword">if</span> gemfile_format?    parse_single_line_format  <span class="hljs-keyword">end</span>  private    <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:list</span>, <span class="hljs-symbol">:rubygems_client</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gemfile_format?</span></span>      lines.any? { <span class="hljs-params">|line|</span> line.<span class="hljs-keyword">include</span>?(<span class="hljs-string">'gem '</span>) }    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_gemfile_format</span></span>      ast = Prism.parse(list).value      parse_ast_nodes(ast.statements.body)    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_single_line_format</span></span>      lines.filter_map <span class="hljs-keyword">do</span> <span class="hljs-params">|line|</span>        <span class="hljs-keyword">next</span> <span class="hljs-keyword">if</span> line.strip.empty?        rubygems_client.info(line.strip)      <span class="hljs-keyword">end</span>.compact    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_ast_nodes</span><span class="hljs-params">(nodes)</span></span>      nodes.each_with_object([]) <span class="hljs-keyword">do</span> <span class="hljs-params">|node, gems|</span>        <span class="hljs-keyword">case</span> node.name        <span class="hljs-keyword">when</span> <span class="hljs-symbol">:gem</span>          gems &lt;&lt; gem_name_from_call_node(node)        <span class="hljs-keyword">when</span> <span class="hljs-symbol">:group</span>          gems.concat(gem_names_from_group(node))        <span class="hljs-keyword">end</span>      <span class="hljs-keyword">end</span>.compact.map { <span class="hljs-params">|name|</span> rubygems_client.info(name) }    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_names_from_group</span><span class="hljs-params">(group_node)</span></span>      block_node = group_node&amp;.block&amp;.body      block_node&amp;.body&amp;.map { <span class="hljs-params">|node|</span> gem_name_from_call_node(node) } <span class="hljs-params">||</span> []    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_name_from_call_node</span><span class="hljs-params">(node)</span></span>      node&amp;.arguments&amp;.first&amp;.unescaped    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lines</span></span>      @lines <span class="hljs-params">||</span>= list.lines    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h3 id="heading-review-of-the-provided-code">Review of the provided code</h3><p>It is a bit more readable, the methods are smaller and I like the split into smaller methods.</p><p>It added memoization for lines. I am not sure how long the execution of <code>String#lines</code> take but could be an improvement to have them memoized. But it does not consider <a target="_blank" href="https://island94.org/2023/10/writing-object-shape-friendly-code-in-ruby">Object Shapes</a> when introducing this change even if it was asked to use Ruby 3.2 features</p><p>It adds a bug that makes tests fail in the method <code>gem_name_from_call_node(node)</code> where it did the following change:</p><pre><code class="lang-diff"><span class="hljs-deletion">- node&amp;.arguments&amp;.arguments&amp;.first&amp;.unescaped</span><span class="hljs-addition">+ node&amp;.arguments&amp;.first&amp;.unescaped</span></code></pre><p>This bug is a bit subtle and to catch it before running the tests one has to know that Prism returns the arguments from a CallNode (<code>Prism:CallNode</code>) as <a target="_blank" href="https://ruby.github.io/prism/rb/Prism/ArgumentsNode.html"><code>Prism::ArgumentsNode</code></a> that has an attribute called <code>arguments</code> that will return the arguments as Arary.</p><p>Looking at this method:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_ast_nodes</span><span class="hljs-params">(nodes)</span></span>  nodes.each_with_object([]) <span class="hljs-keyword">do</span> <span class="hljs-params">|node, gems|</span>    <span class="hljs-keyword">case</span> node.name    <span class="hljs-keyword">when</span> <span class="hljs-symbol">:gem</span>      gems &lt;&lt; gem_name_from_call_node(node)    <span class="hljs-keyword">when</span> <span class="hljs-symbol">:group</span>      gems.concat(gem_names_from_group(node))    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span>.compact.map { <span class="hljs-params">|name|</span> rubygems_client.info(name) }<span class="hljs-keyword">end</span></code></pre><p>It is more idiomatic to use <code>each_with_object</code> to collect the gem names over the previous implementation where I manually created an accumulator variable but I find the <code>end.compact.map</code> syntax as inclining more to idiomatic than readability. At the same time, this is very subjective so I cannot say it is wrong.</p><p>I would have preferred:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_ast_nodes</span><span class="hljs-params">(nodes)</span></span>  gemlist = nodes.each_with_object([]) <span class="hljs-keyword">do</span> <span class="hljs-params">|node, gems|</span>    <span class="hljs-keyword">case</span> node.name    <span class="hljs-keyword">when</span> <span class="hljs-symbol">:gem</span>      gems &lt;&lt; gem_name_from_call_node(node)    <span class="hljs-keyword">when</span> <span class="hljs-symbol">:group</span>      gems.concat(gem_names_from_group(node))    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span>  gemlist.compact.map { <span class="hljs-params">|name|</span> rubygems_client.info(name) }<span class="hljs-keyword">end</span></code></pre><p>It also removed all numbered block params and all endless methods, even if they are valid syntax for Ruby 3.2 and I dare say they are more modern.</p><h2 id="heading-rubymine-ai">RubyMine AI</h2><p>Here is the response from RubyMine AI:</p><blockquote><p>From looking at your current implementation, you've done a great job at keeping your code well-structured and easy to reason about. The usage of syntax and language features from Ruby 3.2 shows a good understanding of modern Ruby idioms.</p><p>Here are some suggestions for refinements:</p><ol><li><p><code>include?</code> is not the most accurate method to use in <code>gem_method_call?</code>. Prefixing a gem name with 'gem' might not be on purpose and can lead to false positives. Using a regex that checks specifically for a 'gem' followed by a space at the start of a line would work better here; This avoids matching lines where 'gem' is somewhere in the gem's name rather than at the start.</p></li><li><p>Prism's parsing will fail for Gemfiles that include logic or DSL methods beyond simple <code>gem</code> or <code>group</code> calls, For robustness, consider adding error handling, or even falling back to <code>parse_single_line_format</code> method, This way you can always handle gem lists which are not neatly formatted.</p></li><li><p>You could consolidate the private methods <code>gem_name_from_call_node</code> and <code>gem_names_from_group</code> together into a single method that extracts gem names from a node, as they are both doing something quite similar.</p></li><li><p>To make the code a bit cleaner and more maintainable, you could refactor the <code>parse_gemfile_format</code> method further by extracting a few more smaller private methods out. This should make the code easier to read and understand.</p></li></ol></blockquote><p>Here is the code that is suggested:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Parser</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(list, <span class="hljs-symbol">rubygems_client:</span> RubyGems::Client.new)</span></span>    @list = list    @rubygems_client = rubygems_client  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse</span></span>    <span class="hljs-keyword">return</span> parse_gemfile_format <span class="hljs-keyword">if</span> gemfile_format?    parse_single_line_format  <span class="hljs-keyword">rescue</span> StandardError =&gt; e    Rails.logger.error(<span class="hljs-string">"Parsing gemfile format failed with exception: <span class="hljs-subst">#{e.message}</span>. Falling back to single line format."</span>)    parse_single_line_format  <span class="hljs-keyword">end</span>  private  <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:list</span>, <span class="hljs-symbol">:rubygems_client</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gemfile_format?</span> = <span class="hljs-title">lines</span>.<span class="hljs-title">any?</span> { <span class="hljs-title">gem_method_call?</span><span class="hljs-params">(_1)</span></span> }  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_gemfile_format</span></span>    ast = Prism.parse(list)    root_node = ast.value    parsed_gems = nodes_to_gem_names(root_node.statements.body)    parsed_gems.compact_blank.map { rubygems_client.info(_1) }  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">nodes_to_gem_names</span><span class="hljs-params">(nodes)</span></span>    nodes.map <span class="hljs-keyword">do</span> <span class="hljs-params">|node|</span>      <span class="hljs-keyword">case</span> node.name      <span class="hljs-keyword">when</span> <span class="hljs-symbol">:gem</span> <span class="hljs-keyword">then</span> gem_name_from_node(node)      <span class="hljs-keyword">when</span> <span class="hljs-symbol">:group</span> <span class="hljs-keyword">then</span> gem_names_from_node(node.block.body)      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>.flatten.compact  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_single_line_format</span></span>    lines.filter_map <span class="hljs-keyword">do</span> <span class="hljs-params">|line|</span>      gem_name = line.strip      gem_info = rubygems_client.info(gem_name) <span class="hljs-keyword">unless</span> gem_name.empty?      gem_info    <span class="hljs-keyword">end</span>.compact_blank  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_name_from_node</span><span class="hljs-params">(node)</span></span> = node&amp;.arguments&amp;.arguments&amp;.first&amp;.unescaped  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_names_from_node</span><span class="hljs-params">(node)</span></span> = nodes_to_gem_names(node.body)  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gem_method_call?</span><span class="hljs-params">(line)</span></span> = line.lstrip.start_with?(<span class="hljs-string">"gem "</span>)  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lines</span> = <span class="hljs-title">list</span>.<span class="hljs-title">lines</span></span><span class="hljs-keyword">end</span></code></pre><h3 id="heading-review-of-the-response">Review of the response</h3><p>First, I think it identified two issues: (1) That a more complex Gemfile syntax will fail to be parsed and (2) Having a logic based on <code>include?("gem")</code> will fail if the name of a gem has the word <code>gem</code> inside.</p><p>Regarding the second case, I think it failed to see that the actual check is using <code>gem&lt;space&gt;</code> so it could fail for gem names that end with <code>gem</code> . But will work even in those cases because there is a <code>strip</code> call just before that (the actual code is <code>strip.include?("gem ")</code> ) so it will work well even for gems that finish in <code>&lt;name&gt;gem</code> because of the strip.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701409137791/ab6c9820-6288-421b-8e9f-296dec6612bb.png" alt class="image--center mx-auto" /></p><p>I appreciate that it did not refactor my endless methods and left them alone. But it did a subtle change and aligned all private methods to the <code>private</code> keyword, while the original code had the private method aligned and then nested under <code>private</code> keyword. This is not a bug but more of a style choice.</p><p>I like that it added a Rails logger when the parse fails in any way and in that case, will try to parse them as simple gem names.</p><p>I think extraction of the logic to <code>nodes_to_gem_names</code> makes it a bit better in readability.</p><p>And it passes all tests.</p><h2 id="heading-conclusion">Conclusion</h2><p>Both failed to identify some things that I think could make the code better:</p><ul><li>There are quite a few methods named like this <code>gem_</code> and that is not a good name. I provided this example on purpose but, indeed, the prompt did not instruct specifically to give good names. Still, as the prompt says "readability" I think having good names is a pre-requisite of readability.</li></ul><p>ChatGPT provided code that failed tests while RubyMine AI provided code that passed the tests.</p><p>ChatGPT removed endless methods and refactored them to normal methods. RubyMine AI respected my choice to use endless methods.</p><p>I would use an AI tool to kick start a code review and see their answers as a conversaation starter, but will not refactor based on their own review.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/review-ruby-on-rails-code-using-rubymine-ai-and-chatgpt</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/review-ruby-on-rails-code-using-rubymine-ai-and-chatgpt</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 01 Dec 2023 06:37:50 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1701408626639/5ff1e5a8-bc19-4dad-a4f2-3bb8161a6450.png</cover_image></item><item><title><![CDATA[Ruby open source: feedbin]]></title><description><![CDATA[<h2 id="heading-the-product">The product</h2><p><a target="_blank" href="https://feedbin.com">https://feedbin.com</a></p><blockquote><p>"Feedbin is the best way to enjoy content on the Web. By combining RSS, and newsletters, you can get all the good parts of the Web in one convenient location"</p></blockquote><p><a target="_blank" href="https://feedbin.com/about"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699934509194/dcaac2f3-34e3-4dad-8125-12cc20aa51a9.png" alt class="image--center mx-auto" /></a></p><h2 id="heading-open-source">Open source</h2><p>The open-source repository can be found at <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/LICENSE.md">https://github.com/feedbin/feedbin</a></p><h3 id="heading-license">License</h3><p>The <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/LICENSE.md">license</a> they use is MIT:</p><p><a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/LICENSE.md"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699934721882/26c3731c-c7c1-4d6b-833f-2f394834e360.png" alt class="image--center mx-auto" /></a></p><h2 id="heading-technical-review">Technical review</h2><h3 id="heading-ruby-and-rails-version">Ruby and Rails version</h3><p>They are currently using:</p><ul><li><p>Ruby version 3.2.2</p></li><li><p>They used a fork of Rails at <a target="_blank" href="https://github.com/feedbin/rails">https://github.com/feedbin/rails</a> forked from <a target="_blank" href="https://github.com/Shopify/rails">https://github.com/Shopify/rails</a>. They are using a branch called <a target="_blank" href="https://github.com/feedbin/rails/tree/7-1-stable-invalid-cache-entries">7-1-stable-invalid-cache-entries</a> - It seems to be Rails 7.1 and about 1 month behind the Shopify/rails which is usually pretty up to date with main Rails</p></li></ul><h3 id="heading-architecture">Architecture</h3><p>Code Architecture:</p><ul><li>They are using the standard Rails organisation of MVC.</li></ul><p>Database:</p><ul><li>The DB is PostgreSQL</li></ul><p>Jobs queue:</p><ul><li>Sidekiq</li></ul><p>On the front-end side:</p><ul><li><p>They use <code>.html.erb</code></p></li><li><p>They are using Phlex for <a target="_blank" href="https://github.com/feedbin/feedbin/tree/main/app/views/components">components</a></p></li><li><p>They are using <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/Gemfile#L38">Jquery</a> for the JS library</p></li><li><p>They have some custom JS code written in <a target="_blank" href="https://github.com/feedbin/feedbin/tree/main/app/assets/javascripts/web">CoffeeScript</a></p></li><li><p>They are using Hotwire via <a target="_blank" href="https://github.com/feedbin/feedbin/blob/abf1ad883dab8a3464fe12e4653de6323296175b/config/importmap.rb#L1">importmaps</a></p></li><li><p>They are using <a target="_blank" href="https://github.com/feedbin/feedbin/blob/abf1ad883dab8a3464fe12e4653de6323296175b/Gemfile#L66">Tailwind</a></p></li></ul><h3 id="heading-stats">Stats</h3><p>Running <code>/bin/rails stats</code> will output the following:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699935469149/12169b38-413f-43c4-a9ae-08cbf61e2db7.png" alt class="image--center mx-auto" /></p><p>Running VSCodeCounter will give the following stats:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699935527677/99f9ce55-540f-452f-a585-cea43fd8dcd4.png" alt class="image--center mx-auto" /></p><h3 id="heading-style-guide">Style Guide</h3><p>For Ruby:</p><ul><li>They are using <a target="_blank" href="https://github.com/feedbin/feedbin/blob/abf1ad883dab8a3464fe12e4653de6323296175b/Gemfile#L94">standardrb</a> as the Style Guide with no customisations.</li></ul><h3 id="heading-storage-persistence-and-in-memory-storage">Storage, Persistence and in-memory storage</h3><p>The DB is PostgreSQL.</p><p>They are not using the schema.rb but the <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/db/structure.sql">structure.sql</a> format for DB schema dump is configured via application.rb:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Feedbin</span></span>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Application</span> &lt; Rails::Application</span>        <span class="hljs-comment"># other configs</span>        config.active_record.schema_format = <span class="hljs-symbol">:sql</span>        <span class="hljs-comment"># other configs</span>    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Enabled PSQL extensions:</p><ul><li><p>hstore - "data type for storing sets of (key, value) pairs"</p></li><li><p>pg_stat_statements - "track planning and execution statistics of all SQL statements executed"</p></li><li><p>uuid-ossp - "generate universally unique identifiers (UUIDs)"</p></li></ul><pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> EXTENSION <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> hstore <span class="hljs-keyword">WITH</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span>;<span class="hljs-keyword">COMMENT</span> <span class="hljs-keyword">ON</span> EXTENSION hstore <span class="hljs-keyword">IS</span> <span class="hljs-string">'data type for storing sets of (key, value) pairs'</span>;<span class="hljs-keyword">CREATE</span> EXTENSION <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> pg_stat_statements <span class="hljs-keyword">WITH</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span>;<span class="hljs-keyword">COMMENT</span> <span class="hljs-keyword">ON</span> EXTENSION pg_stat_statements <span class="hljs-keyword">IS</span> <span class="hljs-string">'track planning and execution statistics of all SQL statements executed'</span>;<span class="hljs-keyword">CREATE</span> EXTENSION <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> <span class="hljs-string">"uuid-ossp"</span> <span class="hljs-keyword">WITH</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span>;<span class="hljs-keyword">COMMENT</span> <span class="hljs-keyword">ON</span> EXTENSION <span class="hljs-string">"uuid-ossp"</span> <span class="hljs-keyword">IS</span> <span class="hljs-string">'generate universally unique identifiers (UUIDs)'</span>;</code></pre><p>Redis is configured to be used with Sidekiq.</p><p>This is what the <a target="_blank" href="https://github.com/feedbin/feedbin/blob/abf1ad883dab8a3464fe12e4653de6323296175b/config/initializers/redis.rb#L1">redis initializer</a> looks like:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/config/initializers/redis.rb#L1</span>defaults = {<span class="hljs-symbol">connect_timeout:</span> <span class="hljs-number">5</span>, <span class="hljs-symbol">timeout:</span> <span class="hljs-number">5</span>}defaults[<span class="hljs-symbol">:url</span>] = ENV[<span class="hljs-string">"REDIS_URL"</span>] <span class="hljs-keyword">if</span> ENV[<span class="hljs-string">"REDIS_URL"</span>]$redis = {}.tap <span class="hljs-keyword">do</span> <span class="hljs-params">|hash|</span>  options2 = defaults.dup  <span class="hljs-keyword">if</span> ENV[<span class="hljs-string">"REDIS_URL_PUBLIC_IDS"</span>] <span class="hljs-params">||</span> ENV[<span class="hljs-string">"REDIS_URL_CACHE"</span>]    options2[<span class="hljs-symbol">:url</span>] = ENV[<span class="hljs-string">"REDIS_URL_PUBLIC_IDS"</span>] <span class="hljs-params">||</span> ENV[<span class="hljs-string">"REDIS_URL_CACHE"</span>]  <span class="hljs-keyword">end</span>  hash[<span class="hljs-symbol">:refresher</span>] = ConnectionPool.new(<span class="hljs-symbol">size:</span> <span class="hljs-number">10</span>) { Redis.new(options2) }<span class="hljs-keyword">end</span></code></pre><p>Further, there is a <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/app/models/redis_lock.rb#L1">RedisLock</a> configured like this:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/models/redis_lock.rb#L1</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisLock</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">acquire</span><span class="hljs-params">(lock_name, expiration_in_seconds = <span class="hljs-number">55</span>)</span></span>    Sidekiq.redis { _1.set(lock_name, <span class="hljs-string">"locked"</span>, <span class="hljs-symbol">ex:</span> expiration_in_seconds, <span class="hljs-symbol">nx:</span> <span class="hljs-literal">true</span>) }  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Further down this is used in a <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/lib/clock.rb#L8">clock.rb</a> (that defines scheduled tasks to run):</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/lib/clock.rb#L8</span>every(<span class="hljs-number">10</span>.seconds, <span class="hljs-string">"clockwork.very_frequent"</span>) <span class="hljs-keyword">do</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:send_stats:v3"</span>, <span class="hljs-number">8</span>)    SendStats.perform_async  <span class="hljs-keyword">end</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:cache_entry_views"</span>, <span class="hljs-number">8</span>)    CacheEntryViews.perform_async(<span class="hljs-literal">nil</span>, <span class="hljs-literal">true</span>)  <span class="hljs-keyword">end</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:downloader_migration"</span>, <span class="hljs-number">8</span>)    FeedCrawler::PersistCrawlData.perform_async  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>every(<span class="hljs-number">1</span>.minutes, <span class="hljs-string">"clockwork.frequent"</span>) <span class="hljs-keyword">do</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:feed:refresher:scheduler:v2"</span>)    FeedCrawler::ScheduleAll.perform_async  <span class="hljs-keyword">end</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:harvest:embed:data"</span>)    HarvestEmbeds.perform_async(<span class="hljs-literal">nil</span>, <span class="hljs-literal">true</span>)  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>every(<span class="hljs-number">1</span>.day, <span class="hljs-string">"clockwork.daily"</span>, <span class="hljs-symbol">at:</span> <span class="hljs-string">"7:00"</span>, <span class="hljs-symbol">tz:</span> <span class="hljs-string">"UTC"</span>) <span class="hljs-keyword">do</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:delete_entries:v2"</span>)    EntryDeleterScheduler.perform_async  <span class="hljs-keyword">end</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:trial_expiration:v2"</span>)    TrialExpiration.perform_async  <span class="hljs-keyword">end</span>  <span class="hljs-keyword">if</span> RedisLock.acquire(<span class="hljs-string">"clockwork:web_sub_maintenance"</span>)    WebSub::Maintenance.perform_async  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h3 id="heading-gems-used">Gems used</h3><p>Here are some of the gems used:</p><ul><li><p><a target="_blank" href="https://github.com/pauldix/sax-machine">sax-machine</a> - "A declarative sax parsing library backed by Nokogiri"</p></li><li><p><a target="_blank" href="https://github.com/feedjira/feedjira">feedjira</a> - "Feedjira is a Ruby library designed to parse feeds"</p></li><li><p><a target="_blank" href="https://github.com/feedbin/html-pipeline">html-pipeline</a> - "HTML processing filters and utilities. This module is a small framework for defining CSS-based content filters and applying them to user provided content"</p></li><li><p><a target="_blank" href="https://github.com/ostinelli/apnotic">apnotic</a> - "A Ruby APNs HTTP/2 gem able to provide instant feedback"</p></li><li><p><a target="_blank" href="https://github.com/ai/autoprefixer-rails">autoprefixer-rails</a> - "Autoprefixer is a tool to parse CSS and add vendor prefixes to CSS rules using values from the Can I Use database. This gem provides Ruby and Ruby on Rails integration with this JavaScript tool"</p></li><li><p><a target="_blank" href="https://github.com/Rykian/clockwork">clockwork</a> - "Clockwork is a cron replacement. It runs as a lightweight, long-running Ruby process which sits alongside your web processes (Mongrel/Thin) and your worker processes (DJ/Resque/Minion/Stalker) to schedule recurring work at particular times or dates"</p></li><li><p><a target="_blank" href="https://github.com/janko/down">down</a> - "Streaming downloads using net/http, http.rb, HTTPX or wget"</p></li><li><p><a target="_blank" href="https://github.com/phlex-ruby/phlex-rails">phlex-rails</a> - "Phlex is a framework that lets you compose web views in pure Ruby"</p></li><li><p><a target="_blank" href="https://github.com/fphilipe/premailer-rails">premailer-rails</a> - "This gem is a drop in solution for styling HTML emails with CSS without having to do the hard work yourself"</p></li><li><p><a target="_blank" href="https://rubygems.org/gems/raindrops">raindrops</a> - "raindrops is a real-time stats toolkit to show statistics for Rack HTTP servers. It is designed for preforking servers such as unicorn, but should support any Rack HTTP server on platforms supporting POSIX shared memory"</p></li><li><p><a target="_blank" href="https://github.com/ankane/strong_migrations">strong_migrations</a> - "Catch unsafe migrations in development"</p></li><li><p><a target="_blank" href="https://github.com/pushpad/web-push">web-push</a> - "This gem makes it possible to send push messages to web browsers from Ruby backends using the Web Push Protocol"</p></li><li><p><a target="_blank" href="https://github.com/stripe-ruby-mock/stripe-ruby-mock">stripe-ruby-mock</a> - "A drop-in library to test stripe without hitting their servers"</p></li><li><p><a target="_blank" href="https://github.com/rails/rails-controller-testing">rails-controller-testing</a> - "Brings back <code>assigns</code> and <code>assert_template</code> to your Rails tests"</p></li></ul><p>There are many other gems used, I only selected few here. Browse the <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/Gemfile">Gemfile</a> to discover more.</p><p>What could be mentioned is that they use their fork for some of the gems included in the file:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/Gemfile</span><span class="hljs-comment"># other gems</span>gem <span class="hljs-string">"rails"</span>, <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/rails"</span>, <span class="hljs-symbol">branch:</span> <span class="hljs-string">"7-1-stable-invalid-cache-entries"</span><span class="hljs-comment"># some other gems</span>gem <span class="hljs-string">"http"</span>,                <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/http"</span>,                <span class="hljs-symbol">branch:</span> <span class="hljs-string">"feedbin"</span>gem <span class="hljs-string">"carrierwave"</span>,         <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/carrierwave"</span>,         <span class="hljs-symbol">branch:</span> <span class="hljs-string">"feedbin"</span>gem <span class="hljs-string">"sax-machine"</span>,         <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/sax-machine"</span>,         <span class="hljs-symbol">branch:</span> <span class="hljs-string">"feedbin"</span>gem <span class="hljs-string">"feedjira"</span>,            <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/feedjira"</span>,            <span class="hljs-symbol">branch:</span> <span class="hljs-string">"f2"</span>gem <span class="hljs-string">"feedkit"</span>,             <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/feedkit"</span>,             <span class="hljs-symbol">branch:</span> <span class="hljs-string">"master"</span>gem <span class="hljs-string">"html-pipeline"</span>,       <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/html-pipeline"</span>,       <span class="hljs-symbol">branch:</span> <span class="hljs-string">"feedbin"</span>gem <span class="hljs-string">"html_diff"</span>,           <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/html_diff"</span>,           <span class="hljs-symbol">ref:</span> <span class="hljs-string">"013e1bb"</span>gem <span class="hljs-string">"twitter"</span>,             <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/twitter"</span>,             <span class="hljs-symbol">branch:</span> <span class="hljs-string">"feedbin"</span><span class="hljs-comment"># other gems </span>group <span class="hljs-symbol">:development</span>, <span class="hljs-symbol">:test</span> <span class="hljs-keyword">do</span>  gem <span class="hljs-string">"stripe-ruby-mock"</span>, <span class="hljs-symbol">github:</span> <span class="hljs-string">"feedbin/stripe-ruby-mock"</span>, <span class="hljs-symbol">branch:</span> <span class="hljs-string">"feedbin"</span>, <span class="hljs-symbol">require:</span> <span class="hljs-string">"stripe_mock"</span><span class="hljs-comment"># other gems</span><span class="hljs-keyword">end</span><span class="hljs-comment"># other gem groups</span></code></pre><h3 id="heading-code-amp-design-patterns">Code &amp; Design Patterns</h3><h4 id="heading-code-organisation">Code Organisation</h4><p>Under <code>/app</code> there are 3 folders different from the ones that Rails comes with:</p><ul><li><p><code>presenters</code></p></li><li><p><code>uploaders</code></p></li><li><p><code>validators</code></p></li></ul><p>The <code>lib</code> folder includes very few extra objects. Most of them seems to be related to communicating with external services.</p><p>Maybe worth mentioning from <code>lib</code> folder is the <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/lib/conditional_sass_compressor.rb#L1"><code>ConditionalSassCompressor</code></a></p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/lib/conditional_sass_compressor.rb#L1</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConditionalSassCompressor</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">compress</span><span class="hljs-params">(string)</span></span>    <span class="hljs-keyword">return</span> string <span class="hljs-keyword">if</span> string =~ <span class="hljs-regexp">/tailwindcss/</span>    options = { <span class="hljs-symbol">syntax:</span> <span class="hljs-symbol">:scss</span>, <span class="hljs-symbol">cache:</span> <span class="hljs-literal">false</span>, <span class="hljs-symbol">read_cache:</span> <span class="hljs-literal">false</span>, <span class="hljs-symbol">style:</span> <span class="hljs-symbol">:compressed</span>}    <span class="hljs-keyword">begin</span>      Sprockets::Autoload::SassC::Engine.new(string, options).render    <span class="hljs-keyword">rescue</span> =&gt; e      puts <span class="hljs-string">"Could not compress '<span class="hljs-subst">#{string[<span class="hljs-number">0</span>..<span class="hljs-number">65</span>]}</span>'...: <span class="hljs-subst">#{e.message}</span>, skipping compression"</span>      string    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>This is used to configure:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/config/application.rb#L47</span>config.assets.css_compressor = ConditionalSassCompressor.new</code></pre><h4 id="heading-routes">Routes</h4><p>There is a combination of RESTful routes and non-restful routes.</p><p>Here is an example from <code>entries</code> in the <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/config/routes.rb#L133"><code>routes.rb</code></a> :</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/config/routes.rb#L133</span>  resources <span class="hljs-symbol">:entries</span>, <span class="hljs-symbol">only:</span> [<span class="hljs-symbol">:show</span>, <span class="hljs-symbol">:index</span>, <span class="hljs-symbol">:destroy</span>] <span class="hljs-keyword">do</span>    member <span class="hljs-keyword">do</span>      post <span class="hljs-symbol">:content</span>      post <span class="hljs-symbol">:unread_entries</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"unread_entries#update"</span>      post <span class="hljs-symbol">:starred_entries</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"starred_entries#update"</span>      post <span class="hljs-symbol">:mark_as_read</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"entries#mark_as_read"</span>      post <span class="hljs-symbol">:recently_read</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"recently_read_entries#create"</span>      post <span class="hljs-symbol">:recently_played</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"recently_played_entries#create"</span>      get <span class="hljs-symbol">:push_view</span>      get <span class="hljs-symbol">:newsletter</span>    <span class="hljs-keyword">end</span>    collection <span class="hljs-keyword">do</span>      get <span class="hljs-symbol">:starred</span>      get <span class="hljs-symbol">:unread</span>      get <span class="hljs-symbol">:preload</span>      get <span class="hljs-symbol">:search</span>      get <span class="hljs-symbol">:recently_read</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"recently_read_entries#index"</span>      get <span class="hljs-symbol">:recently_played</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"recently_played_entries#index"</span>      get <span class="hljs-symbol">:updated</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">"updated_entries#index"</span>      post <span class="hljs-symbol">:mark_all_as_read</span>      post <span class="hljs-symbol">:mark_direction_as_read</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span></code></pre><h4 id="heading-controllers">Controllers</h4><p>The controllers are mostly what I would call <em>vanilla Rails controllers</em>.</p><p>Three notes about them:</p><ul><li><p>Some of them are responding with JS usually using USJ or JQuery to change elements from the page.</p></li><li><p>They contain non-Rails standard actions (actions that are not <code>show</code>, <code>index</code>, <code>new</code>, <code>create</code> ...)</p></li><li><p>There is a namespaced <code>api</code> folder that contains APIs used by mobile apps</p></li></ul><p>Here is one simple example for <code>DELETE /entries/:id</code> , the controller looks like this:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/controllers/entries_controller.rb#L238</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">destroy</span></span>    @user = current_user    @entry = @user.entries.find(params[<span class="hljs-symbol">:id</span>])    <span class="hljs-keyword">if</span> @entry.feed.pages?      EntryDeleter.new.delete_entries(@entry.feed_id, @entry.id)    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span></code></pre><p>And here is the view <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/app/views/entries/destroy.js.erb#L1"><code>destroy.js.erb</code></a> :</p><pre><code class="lang-javascript">$(<span class="hljs-string">'[data-behavior~=entries_target] [data-entry-id=&lt;%= @entry.id %&gt;]'</span>).remove();feedbin.Counts.get().removeEntry(&lt;%= @entry.id %&gt;, &lt;%= @entry.feed_id %&gt;, <span class="hljs-string">'unread'</span>)feedbin.Counts.get().removeEntry(&lt;%= @entry.id %&gt;, &lt;%= @entry.feed_id %&gt;, <span class="hljs-string">'starred'</span>)feedbin.applyCounts(<span class="hljs-literal">true</span>)feedbin.clearEntry();feedbin.fullScreen(<span class="hljs-literal">false</span>)</code></pre><p>The main pattern adopted to controllers is to have some logic in them and delegate to jobs some part of the processing.</p><p>The repo contains mostly straight-forward controllers like this one:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/controllers/pages_internal_controller.rb#L1</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PagesInternalController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create</span></span>    @entry = SavePage.new.perform(current_user.id, params[<span class="hljs-symbol">:url</span>], <span class="hljs-literal">nil</span>)    get_feeds_list  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>But also few controllers that include some logic:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/controllers/api/podcasts/v1/feeds_controller.rb#L8</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span></span>  url = hex_decode(params[<span class="hljs-symbol">:id</span>])  @feed = Feed.find_by_feed_url(url)  <span class="hljs-keyword">if</span> @feed.present?    <span class="hljs-keyword">if</span> @feed.standalone_request_at.blank?      FeedStatus.new.perform(@feed.id)      FeedUpdate.new.perform(@feed.id)    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">else</span>    feeds = FeedFinder.feeds(url)    @feed = feeds.first  <span class="hljs-keyword">end</span>  <span class="hljs-keyword">if</span> @feed.present?    @feed.touch(<span class="hljs-symbol">:standalone_request_at</span>)  <span class="hljs-keyword">else</span>    status_not_found  <span class="hljs-keyword">end</span><span class="hljs-keyword">rescue</span> =&gt; exception  <span class="hljs-keyword">if</span> Rails.env.production?    ErrorService.notify(exception)    status_not_found  <span class="hljs-keyword">else</span>    raise exception  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Even with this structure, I find all controllers easy to read and I think they can be easier to change.</p><h4 id="heading-models">Models</h4><p>The <code>app/models</code> folders contain both ActiveRecord and normal Ruby objects. With few exceptions, they are not namespaced.</p><h4 id="heading-jobs">Jobs</h4><p>The <code>jobs</code> folder contains Sidekiq jobs which are used to do processing on various objects. They are usually called from controllers and most of them are async.</p><p>Here is one job that is caching views:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/jobs/cache_entry_views.rb#L1</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CacheEntryViews</span></span>  <span class="hljs-keyword">include</span> Sidekiq::Worker  <span class="hljs-keyword">include</span> SidekiqHelper  SET_NAME = <span class="hljs-string">"<span class="hljs-subst">#{name}</span>-ids"</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">perform</span><span class="hljs-params">(entry_id, process = <span class="hljs-literal">false</span>)</span></span>    <span class="hljs-keyword">if</span> process      cache_views    <span class="hljs-keyword">else</span>      add_to_queue(SET_NAME, entry_id)    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">cache_views</span></span>    entry_ids = dequeue_ids(SET_NAME)    entries = Entry.where(<span class="hljs-symbol">id:</span> entry_ids).includes(<span class="hljs-symbol">feed:</span> [<span class="hljs-symbol">:favicon</span>])    ApplicationController.render({      <span class="hljs-symbol">partial:</span> <span class="hljs-string">"entries/entry"</span>,      <span class="hljs-symbol">collection:</span> entries,      <span class="hljs-symbol">format:</span> <span class="hljs-symbol">:html</span>,      <span class="hljs-symbol">cached:</span> <span class="hljs-literal">true</span>    })    ApplicationController.render({      <span class="hljs-symbol">layout:</span> <span class="hljs-literal">nil</span>,      <span class="hljs-symbol">template:</span> <span class="hljs-string">"api/v2/entries/index"</span>,      <span class="hljs-symbol">assigns:</span> {<span class="hljs-symbol">entries:</span> entries},      <span class="hljs-symbol">format:</span> <span class="hljs-symbol">:html</span>,      <span class="hljs-symbol">locals:</span> {        <span class="hljs-symbol">params:</span> {<span class="hljs-symbol">mode:</span> <span class="hljs-string">"extended"</span>}      }    })  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-presenters">Presenters</h4><p>There is a <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/app/presenters/base_presenter.rb#L1"><code>BasePresenter</code></a> and all other presenters are extending it via inheritance:</p><p>This controller defines a private method called <code>presents</code>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/presenters/base_presenter.rb#L1</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BasePresenter</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(object, locals, template)</span></span>    @object = object    @locals = locals    @template = template  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># ...</span>  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">presents</span><span class="hljs-params">(name)</span></span>    define_method(name) <span class="hljs-keyword">do</span>      @object    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>and it is used like this for example:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/presenters/user_presenter.rb#L2</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserPresenter</span> &lt; BasePresenter</span>  presents <span class="hljs-symbol">:user</span>  delegate_missing_to <span class="hljs-symbol">:user</span>  <span class="hljs-comment"># ... more code</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">theme</span></span>    result = settings[<span class="hljs-string">"theme"</span>].present? ? settings[<span class="hljs-string">"theme"</span>] : <span class="hljs-literal">nil</span>    result <span class="hljs-params">||</span> user.theme <span class="hljs-params">||</span> <span class="hljs-string">"auto"</span>  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># ... other code</span><span class="hljs-keyword">end</span></code></pre><p>To use the presenters, there is a helper defined in <code>ApplicationHelper</code> will instantiate the proper helper based on the object class:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">ApplicationHelper</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">present</span><span class="hljs-params">(object, locals = <span class="hljs-literal">nil</span>, klass = <span class="hljs-literal">nil</span>)</span></span>    klass <span class="hljs-params">||</span>= <span class="hljs-string">"<span class="hljs-subst">#{object.<span class="hljs-keyword">class</span>}</span>Presenter"</span>.constantize    presenter = klass.new(object, locals, <span class="hljs-keyword">self</span>)    <span class="hljs-keyword">yield</span> presenter <span class="hljs-keyword">if</span> block_given?    presenter  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># more code ...</span><span class="hljs-keyword">end</span></code></pre><p>and it is used <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/app/views/layouts/settings.html.erb#L1">like this in views</a>:</p><pre><code class="lang-erb"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">%</span></span></span><span class="ruby"> present @user <span class="hljs-keyword">do</span> <span class="hljs-params">|user_presenter|</span> </span><span class="xml"><span class="hljs-tag">%&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">%</span></span></span><span class="ruby"> @class = <span class="hljs-string">"settings-body settings-<span class="hljs-subst">#{params[<span class="hljs-symbol">:action</span>]}</span> theme-<span class="hljs-subst">#{user_presenter.theme}</span>"</span></span><span class="xml"><span class="hljs-tag">%&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%</span></span></span><span class="ruby"> <span class="hljs-keyword">end</span> </span><span class="xml"><span class="hljs-tag">%&gt;</span></span></code></pre><h4 id="heading-applicationcomponents">ApplicationComponents</h4><p>Components are based on Phlex and they inherit from <a target="_blank" href="https://github.com/feedbin/feedbin/blob/main/app/views/components/application_component.rb#L3"><code>ApplicationComponent</code></a></p><p>It defines a method to add Stimulus controller in components like this:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/views/components/application_component.rb#L25</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">stimulus</span><span class="hljs-params">(<span class="hljs-symbol">controller:</span>, <span class="hljs-symbol">actions:</span> {}, <span class="hljs-symbol">values:</span> {}, <span class="hljs-symbol">outlets:</span> {}, <span class="hljs-symbol">classes:</span> {}, <span class="hljs-symbol">data:</span> {})</span></span>    stimulus_controller = controller.to_s.dasherize    action = actions.map <span class="hljs-keyword">do</span> <span class="hljs-params">|event, function|</span>      <span class="hljs-string">"<span class="hljs-subst">#{event}</span>-&gt;<span class="hljs-subst">#{stimulus_controller}</span>#<span class="hljs-subst">#{function.camelize(<span class="hljs-symbol">:lower</span>)}</span>"</span>    <span class="hljs-keyword">end</span>.join(<span class="hljs-string">" "</span>).presence    values.transform_keys! <span class="hljs-keyword">do</span> <span class="hljs-params">|key|</span>      [controller, key, <span class="hljs-string">"value"</span>].join(<span class="hljs-string">"_"</span>).to_sym    <span class="hljs-keyword">end</span>    outlets.transform_keys! <span class="hljs-keyword">do</span> <span class="hljs-params">|key|</span>      [controller, key, <span class="hljs-string">"outlet"</span>].join(<span class="hljs-string">"_"</span>).to_sym    <span class="hljs-keyword">end</span>    classes.transform_keys! <span class="hljs-keyword">do</span> <span class="hljs-params">|key|</span>      [controller, key, <span class="hljs-string">"class"</span>].join(<span class="hljs-string">"_"</span>).to_sym    <span class="hljs-keyword">end</span>    { <span class="hljs-symbol">controller:</span> stimulus_controller, <span class="hljs-symbol">action:</span> }.merge!({ **values, **outlets, **classes, **data})  <span class="hljs-keyword">end</span></code></pre><p>Where we can also see a bit of hash literal omission at <code>{controller: stimulus_controller, action: }</code></p><p>But more interesting that this method that helps defining a Stimulus controller, is the method used to define a Stimulus item that uses binding to get variables from the object where it is used:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/app/views/components/application_component.rb#L47</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">stimulus_item</span><span class="hljs-params">(<span class="hljs-symbol">target:</span> <span class="hljs-literal">nil</span>, <span class="hljs-symbol">actions:</span> {}, <span class="hljs-symbol">params:</span> {}, <span class="hljs-symbol">data:</span> {}, <span class="hljs-symbol">for:</span>)</span></span>    stimulus_controller = binding.local_variable_get(<span class="hljs-symbol">:for</span>).to_s.dasherize    action = actions.map <span class="hljs-keyword">do</span> <span class="hljs-params">|event, function|</span>      <span class="hljs-string">"<span class="hljs-subst">#{event}</span>-&gt;<span class="hljs-subst">#{stimulus_controller}</span>#<span class="hljs-subst">#{function.to_s.camelize(<span class="hljs-symbol">:lower</span>)}</span>"</span>    <span class="hljs-keyword">end</span>.join(<span class="hljs-string">" "</span>).presence    params.transform_keys! <span class="hljs-keyword">do</span> <span class="hljs-params">|key|</span>      <span class="hljs-symbol">:<span class="hljs-string">"<span class="hljs-subst">#{binding.local_variable_get(<span class="hljs-symbol">:for</span>)}</span>_<span class="hljs-subst">#{key}</span>_param"</span></span>    <span class="hljs-keyword">end</span>    defaults = { **params, **data }    <span class="hljs-keyword">if</span> action      defaults[<span class="hljs-symbol">:action</span>] = action    <span class="hljs-keyword">end</span>    <span class="hljs-keyword">if</span> target      defaults[<span class="hljs-symbol">:<span class="hljs-string">"<span class="hljs-subst">#{binding.local_variable_get(<span class="hljs-symbol">:for</span>)}</span>_target"</span></span>] = target.to_s.camelize(<span class="hljs-symbol">:lower</span>)    <span class="hljs-keyword">end</span>    defaults  <span class="hljs-keyword">end</span></code></pre><p>The part with <code>binding</code> does the following:</p><ul><li><p><code>stimulus_controller = binding.local_variable_get(:for).to_s.dasherize</code> This line retrieves the value of the local variable <code>for</code>, converts it to a string, and then applies the dasherize method (presumably to format it for use in a specific context, like a CSS class or an identifier in HTML).</p></li><li><p>Apparently <code>binding.local_variable_get</code> should not be needed as the variable is passed a keyword parameter to the method. But the name of the variable is <code>for</code> which is a reserved word and thus if the code would have been <code>stimulus_controller = for.to_s_dasherize</code> that would have raised <code>syntax error, unexpected '.' (SyntaxError)</code></p></li></ul><p>This is a way to have keyword arguments named as reserved words and still be able to use them.</p><h4 id="heading-componentspreview">ComponentsPreview</h4><p>All components can be previewed via Lookbook and they can be found in <code>test/components</code></p><h2 id="heading-testing">Testing</h2><p>For testing it uses Minitest, the default testing framework from Rails. It uses fixtures to set up the test db.</p><p>Tests are simple and direct, containing all preconditions and postconditions in each test. This is great for following what each test is doing.</p><p>There are controller tests, model tests, job tests and some system tests. There are more controller tests than system tests making the test suite run quite fast. Also the jobs are covered pretty good with testing as there is a log of logic in the jobs.</p><h4 id="heading-custom-assertions">Custom assertions</h4><p>There are some custom assertions created specifically to work with collections: <code>assert_has_keys</code> will check if all keys are included in the hash and <code>assert_equal_ids</code> will check if the two collections provided have the same ids (one being a collection of objects and the other one being a hash).</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/feedbin/feedbin/blob/main/test/support/assertions.rb#L3</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">assert_has_keys</span><span class="hljs-params">(keys, hash)</span></span>      assert(keys.all? { <span class="hljs-params">|key|</span> hash.key?(key) })    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">assert_equal_ids</span><span class="hljs-params">(collection, results)</span></span>      expected = Set.new(collection.map(&amp;<span class="hljs-symbol">:id</span>))      actual = Set.new(results.map { <span class="hljs-params">|result|</span> result[<span class="hljs-string">"id"</span>] })      assert_equal(expected, actual)    <span class="hljs-keyword">end</span></code></pre><h2 id="heading-conclusion">Conclusion</h2><p>In conclusion, Feedbin is an open-source project that combines RSS feeds and newsletters into a convenient platform.</p><p>It utilizes Ruby on Rails, PostgreSQL, Sidekiq, and various other technologies to provide a robust and efficient service.</p><p>The code is well-organized and simple to follow the logic and what is happening. I think it will make it easy for anyone to contribute to this repo. If you want to run this yourself locally you should take a look at the <a target="_blank" href="https://github.com/angristan/feedbin-docker">feedbin-docker</a>.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/ruby-open-source-feedbin</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/ruby-open-source-feedbin</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 17 Nov 2023 08:57:56 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1700119795610/ad6866be-40ac-42d7-baa1-47cef8181b38.png</cover_image></item><item><title><![CDATA[Ruby on Rails projects that might inspire you to start your own]]></title><description><![CDATA[<p>I discovered this <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j">Reddit thread on /r/rails</a> and I thought it would be a good idea to pick some of the things posted there in an article and also share it on my social media channels.</p><p>So the question they asked there was the following:</p><blockquote><p><strong>Anyone here use Rails to start their own business?</strong></p></blockquote><p>Here are some answers that I picked from there.</p><h3 id="heading-airnoiseiohttpsairnoiseio"><a target="_blank" href="https://airnoise.io">airnoise.io</a></h3><p><em>"The fast, easy way to file airport noise complaints"</em></p><p><a target="_blank" href="https://airnoise.io"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700031763105/9e0578aa-5b90-454a-a076-544bf211cc44.png" alt class="image--center mx-auto" /></a></p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8npo1j/?utm_source=share&amp;utm_medium=web2x&amp;context=3">His creator</a> describes it as:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8npo1j/?utm_source=share&amp;utm_medium=web2x&amp;context=3"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700031902608/b4ab69e4-c019-4f54-a7f5-104b1680083d.png" alt="&quot;Its been live for over seven years. Ive got several thousand users and the system has processed over 15 million noise complaints. Its mainly hosted on Heroku and AWS though Im investigating using Kamal to Dockerize everything to run it more cheaply. It still pays for itself, though. My biggest issue is simply updating the app to the latest Rails version, which would have been easier incrementally but here we are! Whatever your idea  just build it and launch it. If you know Rails, use that. Do not agonize over whether or not its the perfect or even best platform, just build the damn thing and the rest will work itself out. Dont wait. Start now. And have fun!&quot;" class="image--center mx-auto" /></a></p><p>I want to add here that by looking at what people share online, the upgrades are easier and easier. Also now Shopify and Github along with Basecamp are I think running on Rail's main branch so the upgrade should be way easier</p><h3 id="heading-saashubcomhttpswwwsaashubcom"><a target="_blank" href="https://www.saashub.com/">saashub.com</a></h3><p><em>"Software Alternatives And Reviews"</em></p><p><a target="_blank" href="https://www.saashub.com/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032004459/2b857da0-5697-4735-b859-15f44b92f0bc.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-libhuntcomhttpswwwlibhuntcom"><a target="_blank" href="https://www.libhunt.com">libhunt.com</a></h3><p><em>"Discover trending open-source projects and their alternatives"</em></p><p><a target="_blank" href="https://www.libhunt.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032130732/0eadec1d-0b63-46fd-bebb-751ca612aaab.png" alt class="image--center mx-auto" /></a></p><p>Both projects SaasHub and LibHunt are created by the same developer. Here is they say about these projects:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8oqodh"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032208589/16066d8f-f224-4d11-8f1d-a1947b722cb7.png" alt="&quot;🤚 Im running SaaSHub, LibHunt (both 1mm+ monthly visits), and some other websites. Its been a full-time business for a few years now. Im the single dev, and I cant imagine doing it without Rails! Some Rails productivity context - My SaaSHub competitors only, e.g. G2 or ProductHunt, have teams of dozens of devs each.&quot;" class="image--center mx-auto" /></a></p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8umc94"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032235474/18c27aca-6109-42dd-a3b6-31ef75f0a69a.png" alt="&quot;Each of them run on a single 8GB server (loaded at about 30-40%). There's CloudFlare as CDN. That's all. Everything else is pretty standard. Postgres and some Redis cache here and there.&quot;" class="image--center mx-auto" /></a></p><p>I would add here that with the help of <a target="_blank" href="https://github.com/oldmoe/litestack">litestack</a> you can start a Rails 7 project and host it all in one single Hetzner VPS. No extra servers are needed for DB, Cronjobs, or processing queues. Everything is in one single server.</p><h3 id="heading-radiustohttpswwwradiusto"><a target="_blank" href="https://www.radius.to">radius.to</a></h3><p><em>"Radius is a platform for finding and organising events, both online and in person"</em></p><p><a target="_blank" href="https://www.radius.to"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032517337/34dfe6a3-af70-4e72-aafc-14895b901af3.png" alt class="image--center mx-auto" /></a></p><p>Here is what <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8nc4wb">the creator says about</a> this project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8nc4wb"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032549523/8a472496-3997-4efc-a088-bf02adbd8057.png" alt="&quot;Yep, using Rails to try and build a better alternative to meetup.com / Eventbrite - https://www.radius.to/ - been building it slowly for the last few years. Not yet launched so can't really call it a business. No idea if people other than me will use it but it's been a good project to learn more about Rails at the very least. I admire anyone who's able to launch a business whilst still employed, and without a large bucket of cash - it's tough work! Luckily though since I'm using Rails, code has been the easy part. The difficult bits have mostly been design/UX related&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-ordonestcomhttpsordonestcom"><a target="_blank" href="https://ordonest.com">ordonest.com</a></h3><p><em>"Tracking bills made simple"</em></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032652308/3be67d8c-dbec-48d8-93df-55d7733ea380.png" alt class="image--center mx-auto" /></p><p>Here is what <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8mipcd">the creator says about</a> this project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8mipcd"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032693375/5215b68f-409e-4976-ae1e-ec42983225af.png" alt="&quot;I've recently soft launched https://ordonest.com Which basically grabs all your invoices received in your inbox Having limited free time as I work full time rails has actually allowed me to move very quickly, The app originally started of as a golang cli app.&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-creatornestworkhttpscreatornestwork"><a target="_blank" href="https://creatornest.work">creatornest.work</a></h3><p><em>"It's the job platform where creators and innovators unite to redefine the landscape of content, technology, and design"</em></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032791896/9034996a-9189-46b8-991a-e3864ee03ebd.png" alt class="image--center mx-auto" /></p><p>Here is what <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8mx6x9/?utm_source=share&amp;utm_medium=web2x&amp;context=3">the creator says about</a> this project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8mx6x9"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700032835451/fc829a68-5dbf-4806-a50c-7f2bf3b529c4.png" alt="&quot;Yes. I use Rails api for https://creatornest.work/ (almost ready to soft launch)&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-logcheckcomhttpswwwlogcheckcom"><a target="_blank" href="https://www.logcheck.com">LogCheck.com</a></h3><p><em>"LogCheck is the easiest way to stay on top of routine maintenance tasks, inspections, and meter readings"</em></p><p><a target="_blank" href="https://www.logcheck.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033116014/4fb09bcd-be88-47fc-806d-0c821f54b5a1.png" alt class="image--center mx-auto" /></a></p><p>Here is what <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8n36yw">the creator says about</a> the project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8n36yw"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033176260/bf7cb54d-32df-48c3-ab27-f67003aad5f3.png" alt="&quot;I co-founded LogCheck about ten years ago. The product includes a native mobile app but the backend and web UI is a Rails app. The initial backend was written in PHP, because I was using a free hosting service, but when it was becoming a real thing, I replaced it with a Rails app (to this day, the mobile API URLs still end in .php). I picked Rails because I had some experience with it and it was more important to get something built than it was to have fun using something new. I spent the first few years mostly insulated from Javascript/Node/NPM drama and that probably helped the business. Unfortunately, we eventually integrated React because I was led to believe that was the right way to do highly interactive UI on the web. We only used it for a few components (like managing a graph view) but after the company got acquired our new parent got strict about security vulnerabilities and keeping all that junk upgraded soaked up a lot of time. If I were starting a new business today Id use Rails and HTMX&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-smartynamescomhttpssmartynamescom"><a target="_blank" href="https://smartynames.com">smartynames.com</a></h3><p><em>"Instant Business Name Generator For Entrepreneurs"</em></p><p><a target="_blank" href="https://smartynames.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033239571/0eaa3a2d-fa86-436c-be35-41ed5acb6d8f.png" alt class="image--center mx-auto" /></a></p><p>Here is what <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8o1ane">the creator says about</a> this project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8o1ane"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033293769/d7196c5b-9866-4b8f-8059-34b3192b4f54.png" alt="&quot;Yup, I've been using Rails for over ten years now and love it. Just started Smartynames.com using Rails in January, and sold it in October. You should use whatever tools you know to achieve the outcome and not worry about what others thinks or say. The goal of the business is profit, and what gets you there is unimportant. Facebook was running on PHP for the first ten years of their operation, so really, you could use anything. That said, Rails is easy and snappy, and the latest edition with Hotwire tools is a joy to use. Whether or not it's the best choice, not always, but it doesn't matter much if you are just starting out.&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-transcribesimplecomhttpstranscribesimplecom"><a target="_blank" href="https://transcribesimple.com">transcribesimple.com</a></h3><p><a target="_blank" href="https://transcribesimple.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033451859/a81b3694-cf44-44c7-82d3-7951a4743065.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-lectronzcomhttpslectronzcom"><a target="_blank" href="https://lectronz.com">lectronz.com</a></h3><p><em>"The marketplace for makers. Support open-source hardware creators"</em></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033616055/16d05ec1-acbc-49d2-9e78-a959e6c850e7.png" alt class="image--center mx-auto" /></p><p>Here is what <a target="_blank" href="https://lectronz.com/u/lectronz/articles/a-peak-behind-the-curtain">the creator says about</a> the project:</p><p><a target="_blank" href="https://lectronz.com/u/lectronz/articles/a-peak-behind-the-curtain"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033829546/e994f38c-73f4-4cc6-89eb-b38cce22316e.png" alt="&quot;From the onset, it meant that Lectronz needed to adopt a lean tech stack, which allows any developer to be operational on both frontend and backend concerns, with minimal moving parts and third-party dependencies. A lean tech stack will also enable changes to respond to evolving demands. This drove us towards using Ruby on Rails, which offers a platform that enables small teams of developers to build full modern web applications in Ruby. The release of Rails 7 at the end of 2021 has marked the beginning of an exciting new era for Rails developers, making it the ideal lean tech stack for a web application today. As a result, we were able to built Lectronz with the equivalent of a single developper working part-time.&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-statusgatorcomhttpsstatusgatorcom"><a target="_blank" href="https://statusgator.com">statusgator.com</a></h3><p><em>"StatusGator gives your team a unified status page showing the status of all of your cloud vendors, hosted applications, services, websites, and more"</em></p><p><a target="_blank" href="https://statusgator.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033926604/b272026f-f7df-459e-9c0f-6a78a955281b.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-tracksslcomhttpstracksslcom"><a target="_blank" href="https://trackssl.com">trackssl.com</a></h3><p><em>"Monitor SSL Certificate Expiration"</em></p><p><a target="_blank" href="https://trackssl.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700033973385/311d5c43-280d-42f5-82bf-01bfcb26d906.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-gemfilecheckcomhttpsgemfilecheckcom"><a target="_blank" href="https://gemfilecheck.com">gemfilecheck.com</a></h3><p><em>"Gemfilecheck is the easiest way to keep an eye on your Ruby and Rails projects for security issues and vulnerabilities. Go to bed every night knowing your project is safe from hackers"</em></p><p><a target="_blank" href="https://gemfilecheck.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034017050/07c71e1a-1851-4079-80ae-d0dd5e646c6d.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-callstackingcomhttpscallstackingcom"><a target="_blank" href="https://callstacking.com">callstacking.com</a></h3><p><em>"Decode even the most convoluted Ruby on Rails production incidents with our debugger's powerful visualization, guiding you through the labyrinth of call chains and data flow"</em></p><p><a target="_blank" href="https://callstacking.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034105352/03349dae-f20b-4241-86d9-8638971417ac.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-wwwinvoiceworkcominvoicesnewhttpswwwinvoiceworkcominvoicesnew"><a target="_blank" href="https://www.invoicework.com/invoices/new/">www.invoicework.com/invoices/new</a></h3><p>"Free invoice generator"</p><p><a target="_blank" href="https://www.invoicework.com/invoices/new/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034182756/e5580634-d044-47f6-a020-dfdea6a23155.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-wwwmotivwarecomhttpswwwmotivwarecom"><a target="_blank" href="https://www.motivware.com">www.motivware.com</a></h3><p><em>"We're Motivware, and we've got a prototype in the oven for running coaches. We're cooking up a tool that's helping manage clients, payments, and all the important stuff. It's like your coaching sidekick in the making"</em></p><p><a target="_blank" href="https://www.motivware.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034289857/bae4fe70-eec0-45e7-b92f-d6f39636bd06.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-wwwgaialawhttpswwwgaialaw"><a target="_blank" href="https://www.gaia.law">www.gaia.law</a></h3><p><em>"Discover our legal management solution tailored for SMBs, startups, and growing companies. Our innovative platform offers a template library, simplifies contract creation and contract lifecycle management and streamlines legal workflows"</em></p><p><a target="_blank" href="https://www.gaia.law"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034365364/bb31920d-2c22-4a67-8658-57e8e94ddecb.png" alt class="image--center mx-auto" /></a></p><p>Here is what <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8noq6i">the creator says</a> about the project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8noq6i"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034436032/602b19c5-dddb-4a30-8f0c-e03541bb4862.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-wwwcomprehensionclubhttpswwwcomprehensionclub"><a target="_blank" href="https://www.comprehension.club">www.comprehension.club</a></h3><p><em>"Simple language exchanges to improve your listening and reading in another language"</em></p><p><a target="_blank" href="https://www.comprehension.club"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034551397/585c1131-18f8-4de2-8e57-4485a2299406.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-wwwvocabspotcomhttpswwwvocabspotcom"><a target="_blank" href="https://www.vocabspot.com">www.vocabspot.com</a></h3><p><em>"Vocab Spot helps language teachers create a library of teaching materials and share them with students in a way that's easy and fun"</em></p><p><a target="_blank" href="https://www.vocabspot.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034616478/c4c916fa-bec7-446e-be9e-95e881205563.png" alt class="image--center mx-auto" /></a></p><p>Here is what <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8p9lkr">the creator of comprehension.club and vocabspot.com says about</a> these projects:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8p9lkr"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700034660468/2ea88798-45b7-4bc1-b711-bec76cc29f47.png" alt="&quot;Yep, as others have said if you're looking to build something on a shoestring or with limited resources, rails is pretty awesome. I've built three apps to take a run at being their own business. One thing I realized pretty quickly is that building the app isn't the hard part of making a business successful. In a way, that's also another plus for Rails. It's solid and simple enough that I don't have to constantly be working on it at the expense of other stuff like marketing, user research, etc... For my fairly limited coding skills, rails is pretty much the only way I'd be able to do everything.&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-dimerunhttpsdimerun"><a target="_blank" href="https://dime.run">dime.run</a></h3><p><em>"Faster CI with Custom Machines"</em></p><p><a target="_blank" href="https://dime.run"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700058158781/7eb529af-ac64-440f-99d4-20a4cc34ea73.png" alt class="image--center mx-auto" /></a></p><p>Here is <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8qzejg">what the creator says about</a> the project:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700058203481/6e7f6ee4-c102-4e46-8a9b-4a47cdaed815.png" alt class="image--center mx-auto" /></p><h3 id="heading-receipt-aicomhttpsreceipt-aicom"><a target="_blank" href="https://receipt-ai.com">receipt-ai.com</a></h3><p><em>"Receipt management with AI and Text messages"</em></p><p><a target="_blank" href="https://receipt-ai.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700058381489/d68013e6-9f54-4fee-93bd-b08943426964.png" alt class="image--center mx-auto" /></a></p><p>Here is <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8r648z">what the creator says about</a> the project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8r648z"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700058414478/f98f9ddc-52cb-44fa-b8e6-b0da45b6d17b.png" alt="&quot;I'm building Receipt-AI.com, a receipt management tool with AI and text messages. I have tried many other languages like Swift, Java, Kotlin, React, Vue, etc. Nothing is quite like Rails: it's fast, simple, and elegantly written. I love Rails. If you are looking to start a business, Rails is the best option. Also, the Rails community has always been welcoming and supportive.&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-wwwkindmindcomhttpswwwkindmindcom"><a target="_blank" href="https://www.kindmind.com">www.kindmind.com</a></h3><p><em>"KindMind is a delighfully simple online journal with a goal: helping you become a more accepting, mindful, better you"</em></p><p><a target="_blank" href="https://www.kindmind.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700058497344/86efd08c-51e0-4229-94dc-3b2bf0d402c5.png" alt class="image--center mx-auto" /></a></p><p>Here is <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8urq8x">what the creator says about</a> the project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8urq8x"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700058544201/3a346c88-e1ba-435a-b089-df4aa79b16c9.png" alt="&quot;Its an online journal with a focus on mental health and wellness. Was totally free for the first 8 years but I started monetizing it last month through paid memberships. I also have a full time job as a Senior SE. If KindMind continues to grow, Im hoping I might be able to turn it into a full time gig at some point.&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-looknforgroupcomhttpslooknforgroupcom"><a target="_blank" href="https://looknforgroup.com">looknforgroup.com</a></h3><p><em>"Whether you're looking for someone to go running with, play basketball, pickleball, tennis, go rollerblading, or a sweaty gym session, or to the movies or the museum, browse posts created by other people and join the ones that interest you"</em></p><p><a target="_blank" href="https://looknforgroup.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700059088499/f7503a5e-867a-4d7c-b12d-96964795da25.png" alt class="image--center mx-auto" /></a></p><p>Here is <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8xdk38">what the creator says about</a> the project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k8xdk38"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700059146884/adfacb2b-5d57-4bb1-a062-84b03226a74b.png" alt="&quot;Yup, built a cross-platform mobile app of the same name, looknforgroup using Flutter on the frontend and Ruby on Rails backend api. Used Rails because it's what I knew and loved working with the most and looking back boy did it make it easy and I even learned a lot more and I'm still learning as I add more features.&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-wwwlocaledatacomhttpswwwlocaledatacom"><a target="_blank" href="https://www.localedata.com">www.localedata.com</a></h3><p><em>"LocaleData is a simple translation management platform that helps you work on your YAML translation files even with your non-technical teammates"</em></p><p><a target="_blank" href="https://www.localedata.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700068592841/604b2209-92f5-417c-ad9a-28f9120eabe6.png" alt class="image--center mx-auto" /></a></p><p>Here is <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k9421cl">what the creator says about</a> the project:</p><p><a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/comment/k9421cl"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700068666532/a0f517f8-86f6-441e-a8d9-a9f34b9566d1.png" alt="&quot;Yes! I use Rails for all my side projects, such as LocaleData.com. Rails is an awesome (but not only) one person framework&quot;" class="image--center mx-auto" /></a></p><h3 id="heading-n28frhttpsn28fr"><a target="_blank" href="https://n28.fr">n28.fr</a></h3><p><em>"A micro saas to collect monthly rents that I use for my properties"</em></p><p><a target="_blank" href="https://n28.fr"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700068731711/27276e43-5ea6-4519-a28e-16b3a8116552.png" alt class="image--center mx-auto" /></a></p><h2 id="heading-conclusion">Conclusion</h2><p>I think you should <a target="_blank" href="https://www.reddit.com/r/rails/comments/17s073j/anyone_here_use_rails_to_start_their_own_business/">read the original thread on /r/rails</a> there are more comments there than what I included there.</p><p>I just wanted to show the diversity of projects built with Ruby on Rails. I find it inspiring to look at these projects and ideas.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/ruby-on-rails-projects-that-might-inspire-you-to-start-your-own</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/ruby-on-rails-projects-that-might-inspire-you-to-start-your-own</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 16 Nov 2023 03:08:28 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1700103590447/6b3f45f7-487f-4e0d-af68-41edbf06fb1e.png</cover_image></item><item><title><![CDATA[Ruby Open Source: chatwoot]]></title><description><![CDATA[<p>Continuing the series about open-source Ruby with <a target="_blank" href="https://www.chatwoot.com">chatwoot</a></p><p>As a reminder, this is a 30-minute review so this is a high-level overview and there might be things that I missed or that I misunderstood.</p><h2 id="heading-the-product">The product</h2><p><a target="_blank" href="https://github.com/chatwoot/chatwoot">chatwoot</a> is an <em>"Open-source customer engagement suite, an alternative to Intercom, Zendesk, Salesforce Service Cloud etc"</em></p><p><a target="_blank" href="https://chatwoot.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699329500672/3fed05ce-d1c7-486a-bf15-c33b50394a51.png" alt="What is chatwoot from their website" class="image--center mx-auto" /></a></p><p>The interface looks like this (<a target="_blank" href="https://www.chatwoot.com">source their website</a>)</p><p><a target="_blank" href="https://www.chatwoot.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699329651363/7faa599d-775d-4be2-b3fa-d1ec4cf9ae3b.png" alt="an example of chatwood main interface" class="image--center mx-auto" /></a></p><p>They were part of <a target="_blank" href="https://www.ycombinator.com/companies/chatwoot">YCombinator batch in 2021</a>.</p><h2 id="heading-open-source">Open Source</h2><h3 id="heading-repository-and-license">Repository and License</h3><p>The repository is on Github at <a target="_blank" href="https://github.com/chatwoot/chatwoot">https://github.com/chatwoot/chatwoot</a> and it is open sourced with <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/LICENSE">a license that seems a variant of MIT license</a>:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699330118730/f1d3e6ee-101b-4b8a-bfca-2644146b7975.png" alt class="image--center mx-auto" /></p><h3 id="heading-ruby-and-rails-version">Ruby and Rails version</h3><p>At the moment of writing this article (November 2023), it runs on <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/.ruby-version#L1">Ruby 3.2.2</a> and <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/Gemfile#L7">Rails 7.0.8</a></p><h3 id="heading-architecture">Architecture</h3><p><strong>Backend</strong></p><ul><li>It uses Rails to create a REST API with <a target="_blank" href="https://github.com/rails/jbuilder">jbuilder</a> as the serializer</li></ul><p><strong>Frontend</strong></p><ul><li><p>It uses Vue for the front end with Tailwind CSS. You can explore the package.json <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/package.json">here</a></p></li><li><p>It also uses Administrate gem as an admin dashboard</p></li></ul><p><strong>Background processing queue</strong></p><ul><li>Sidekiq with Redis</li></ul><p><strong>Database:</strong></p><ul><li>PostgreSQL</li></ul><h3 id="heading-stats">Stats</h3><p>Running <code>rails stats</code> returned the following:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699330889681/57df088e-0ad1-4d5f-b5ff-900528110363.png" alt class="image--center mx-auto" /></p><p>As this project uses Vue on the front, here is the output of running an extension in VScode called Code Counter:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699331122243/cc7b39cd-d5d4-4de3-9376-a0d2f2973a32.png" alt class="image--center mx-auto" /></p><p>Not sure how it does all these calculations but it seems the application has quite some lines of code in JS (JavaScript and Vue files together).</p><h3 id="heading-style-guide">Style Guide</h3><p>They use Rubocop with some custom settings added in their <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/.rubocop.yml">.rubocop.yml file</a>:</p><pre><code class="lang-yaml"><span class="hljs-attr">require:</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">rubocop-performance</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">rubocop-rails</span>  <span class="hljs-bullet">-</span> <span class="hljs-string">rubocop-rspec</span></code></pre><p>Among the cops that have their default settings changed:</p><ul><li><p>RSpec/ExampleLength with max=25</p></li><li><p>Style/FrozenStringLiteralComment with enabled=false</p></li><li><p>Style/OpenStructUse with enabled=false</p></li><li><p>Style/GlobalVars are allowed in redis and rack_attack initializers and in <code>lib/global_config.rb</code></p></li><li><p>Style/ClassVars are allowed in one file <code>app/services/email_templates/db_resolver_service.rb</code></p></li><li><p>Style/HashSyntax has the shorthand syntax disabled</p></li><li><p>Naming/VariableNumber is disabled</p></li></ul><p>and there are more there.</p><h3 id="heading-storage-persistence-and-in-memory-storage">Storage, Persistence and in-memory storage</h3><p>As a database it uses PostgreSQL.</p><p>It defines two global variables for Redis:</p><pre><code class="lang-ruby"><span class="hljs-comment"># Alfred</span><span class="hljs-comment"># Add here as you use it for more features</span><span class="hljs-comment"># Used for Round Robin, Conversation Emails &amp; Online Presence</span>$alfred = ConnectionPool.new(<span class="hljs-symbol">size:</span> <span class="hljs-number">5</span>, <span class="hljs-symbol">timeout:</span> <span class="hljs-number">1</span>) <span class="hljs-keyword">do</span>  redis = Rails.env.test? ? MockRedis.new : Redis.new(Redis::Config.app)  Redis::Namespace.new(<span class="hljs-string">'alfred'</span>, <span class="hljs-symbol">redis:</span> redis, <span class="hljs-symbol">warning:</span> <span class="hljs-literal">true</span>)<span class="hljs-keyword">end</span><span class="hljs-comment"># Velma : Determined protector</span><span class="hljs-comment"># used in rack attack</span>$velma = ConnectionPool.new(<span class="hljs-symbol">size:</span> <span class="hljs-number">5</span>, <span class="hljs-symbol">timeout:</span> <span class="hljs-number">1</span>) <span class="hljs-keyword">do</span>  config = Rails.env.test? ? MockRedis.new : Redis.new(Redis::Config.app)  Redis::Namespace.new(<span class="hljs-string">'velma'</span>, <span class="hljs-symbol">redis:</span> config, <span class="hljs-symbol">warning:</span> <span class="hljs-literal">true</span>)<span class="hljs-keyword">end</span></code></pre><p>Then these global variables are used in other places in the code:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699343659174/7f687fa8-4cb2-4705-8c12-57b5b7b4429e.png" alt class="image--center mx-auto" /></p><h3 id="heading-gems-used">Gems used</h3><p>Here is a selection from all the gems they use:</p><ul><li><p><a target="_blank" href="https://github.com/mbleigh/acts-as-taggable-on">act_as_taggable_on</a> - <em>"A tagging plugin for Rails applications that allows for custom tagging along dynamic contexts"</em></p></li><li><p><a target="_blank" href="https://github.com/barsoom/attr_extras">attr_extras</a> - <em>"Takes some boilerplate out of Ruby with methods like attr_initialize"</em></p></li><li><p><a target="_blank" href="https://github.com/hashie/hashie">hashie</a> - <em>"Hashie is a growing collection of tools that extend Hashes and make them more useful"</em></p></li><li><p><a target="_blank" href="https://github.com/heartcombo/responders">responders</a> - <em>"A set of Rails responders to dry up your application"</em></p></li><li><p><a target="_blank" href="https://github.com/mobi/telephone_number">telephone_number</a> - <em>"TelephoneNumber is global phone number validation gem based on Google's libphonenumber library"</em></p></li><li><p><a target="_blank" href="https://github.com/micke/valid_email2">valid_email2</a> - "<em>Validate emails with the help of the mail gem instead of some clunky regexp. Aditionally validate that the domain has a MX record. Optionally validate against a static list of disposable email services. Optionally validate that the email is not subaddressed (RFC5233)"</em></p></li><li><p><a target="_blank" href="https://github.com/pboling/flag_shih_tzu">flag_shih_tzu</a> - <em>"This gem lets you use a single integer column in an ActiveRecord model to store a collection of boolean attributes (flags). Each flag can be used almost in the same way you would use any boolean attribute on an ActiveRecord object"</em></p></li><li><p><a target="_blank" href="https://github.com/usmanbashir/haikunator">haikunator</a> - <em>"Generate Heroku-like memorable random names to use in your apps or anywhere else."</em></p></li><li><p><a target="_blank" href="https://github.com/gjtorikian/commonmarker">commonmarker</a> - <em>"Ruby wrapper for Rust's comrak crate. It passes all of the CommonMark test suite, and is therefore spec-complete. It also includes extensions to the CommonMark spec as documented in the GitHub Flavored Markdown spec, such as support for tables, strikethroughs, and autolinking"</em></p></li><li><p><a target="_blank" href="https://github.com/janko/down">down</a> -<em>"Down is a utility tool for streaming, flexible and safe downloading of remote files. It can use open-uri + Net::HTTP, http.rb, HTTPX, or wget as the backend HTTP library"</em></p></li><li><p><a target="_blank" href="https://github.com/nfo/gmail_xoauth">gmail_xoauth</a> - <em>"Get access to Gmail IMAP and SMTP via OAuth2 and OAuth 1.0a, using the standard Ruby Net libraries. The gem supports 3-legged OAuth, and 2-legged OAuth for Google Apps Business or Education account owners"</em></p></li><li><p><a target="_blank" href="https://github.com/zvory/csv-safe">csv-safe</a> - <em>"This gem decorates the built in CSV library to prevent CSV injection attacks. Wherever you would use CSV in your code, use CSVSafe. The gem will encode your fields in UTF-8"</em></p></li><li><p><a target="_blank" href="https://github.com/jenseng/hair_trigger">hairtrigger</a> - <em>"lets you create and manage database triggers in a concise, db-agnostic, Rails-y way. You declare triggers right in your models in Ruby, and a simple rake task does all the dirty work for you"</em></p></li><li><p><a target="_blank" href="https://github.com/krisleech/wisper">wisper</a> - <em>"A micro library providing Ruby objects with Publish-Subscribe capabilities"</em></p></li><li><p><a target="_blank" href="https://github.com/ankane/groupdate">groupdate</a> - <em>"The simplest way to group by: day, week, hour of the day, and more"</em></p></li><li><p><a target="_blank" href="https://github.com/soundasleep/html2text_ruby">html2text</a> - <em>"a very simple gem that uses DOM methods to convert HTML into a format similar to what would be rendered by a browser - perfect for places where you need a quick text representation"</em></p></li><li><p><a target="_blank" href="https://github.com/ankane/informers">informers</a> - <em>"State-of-the-art natural language processing for Ruby: Sentiment analysis, Question answering, Named-entity recognition, Text generation"</em></p></li><li><p><a target="_blank" href="https://github.com/thoughtbot/climate_control">climate_control</a> - <em>"Climate Control modifies environment variables only within the context of the block, ensuring values are managed properly and consistently"</em></p></li></ul><h3 id="heading-design-patterns">Design Patterns</h3><p>The "app" directory has the following sub-folders that are not the ones included by default in Rails:</p><ul><li><p>actions</p></li><li><p>builders</p></li><li><p>dashboards</p></li><li><p>dispatchers</p></li><li><p>drops</p></li><li><p>fields</p></li><li><p>finders</p></li><li><p>listeners</p></li><li><p>mailboxes</p></li><li><p>policies</p></li><li><p>presenters</p></li><li><p>services</p></li><li><p>workers</p></li></ul><p>Here are some examples of some patterns from the codebase:</p><h4 id="heading-actions">Actions</h4><p>Inside actions, some objects define the <code>perform</code> method where they execute a series of steps:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeAction</span></span>  pattr_initialize [<span class="hljs-symbol">:user</span>, <span class="hljs-symbol">:base</span>]  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">perform</span></span>    ActiveRecord::Base.transaction <span class="hljs-keyword">do</span>      action1      action2      <span class="hljs-comment"># ...</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Where <code>pattr_initialize</code> comes from <a target="_blank" href="https://github.com/barsoom/attr_extras?tab=readme-ov-file#pattr_initialize">attr_extras</a> gem and it will create an initializer with those variables and declare them as private.</p><p><strong>Builders</strong></p><p>These have the same interface where they define <code>perform</code> and it seems to be a flavor of Builder Pattern maybe with a service object where the builder will create or find one or more records.</p><p>Here is an example of the <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/abbb4180ea9fb1da007a659ef8cb28188321c641/app/builders/account_builder.rb#L7"><code>AccountBuilder</code></a> that will create an account and then create a user and link these two together.</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccountBuilder</span></span>  <span class="hljs-keyword">include</span> CustomExceptions::Account  pattr_initialize [<span class="hljs-symbol">:account_name!</span>, <span class="hljs-symbol">:email!</span>, <span class="hljs-symbol">:confirmed</span>, <span class="hljs-symbol">:user</span>, <span class="hljs-symbol">:user_full_name</span>, <span class="hljs-symbol">:user_password</span>, <span class="hljs-symbol">:super_admin</span>, <span class="hljs-symbol">:locale</span>]  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">perform</span></span>    <span class="hljs-keyword">if</span> @user.<span class="hljs-literal">nil</span>?      validate_email      validate_user    <span class="hljs-keyword">end</span>    ActiveRecord::Base.transaction <span class="hljs-keyword">do</span>      @account = create_account      @user = create_and_link_user    <span class="hljs-keyword">end</span>    [@user, @account]  <span class="hljs-keyword">rescue</span> StandardError =&gt; e    puts e.inspect    raise e  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># ... more methods</span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-controllers">Controllers</h4><p>Take a look at the <code>app/controllers/api</code> there is a versioning of the API using namespaces (<code>app/controllers/api/v1</code> , <code>app/controllers/api/v2</code> )</p><p>The controllers appears to be slim calling other objects and usually returning a JSON represented in views via <code>.json.builder</code> files.</p><h4 id="heading-dashboards">Dashboards</h4><p>Here they define the <a target="_blank" href="https://github.com/thoughtbot/administrate">Administrate</a> custom dashboards</p><h4 id="heading-dispatchers">Dispatchers</h4><p>They implement the <a target="_blank" href="https://github.com/krisleech/wisper"><code>Wisper::Publisher</code></a> module to dispatch events. <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/abbb4180ea9fb1da007a659ef8cb28188321c641/app/dispatchers/async_dispatcher.rb#L1">Here</a> is an example of an async dispatcher using a job:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/chatwoot/chatwoot/blob/develop/app/dispatchers/async_dispatcher.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AsyncDispatcher</span> &lt; BaseDispatcher</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dispatch</span><span class="hljs-params">(event_name, timestamp, data)</span></span>    EventDispatcherJob.perform_later(event_name, timestamp, data)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">publish_event</span><span class="hljs-params">(event_name, timestamp, data)</span></span>    event_object = Events::Base.new(event_name, timestamp, data)    publish(event_object.method_name, event_object)  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">listeners</span></span>    [      CampaignListener.instance,      CsatSurveyListener.instance,      HookListener.instance,      InstallationWebhookListener.instance,      NotificationListener.instance,      ReportingEventListener.instance,      WebhookListener.instance,      AutomationRuleListener.instance    ]  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-finders">Finders</h4><p>Finders are another patterns used here which takes care of querying the DB while taking care of filters or other conditions coming from <code>params</code> in controllers. They usually define a <code>perform</code> method and the initializer accepts a <code>params</code> to get the params from the controller.</p><p>Here is an <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/app/finders/conversation_finder.rb#L1">example</a>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/chatwoot/chatwoot/blob/develop/app/finders/conversation_finder.rb#L1</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConversationFinder</span></span>  <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:current_user</span>, <span class="hljs-symbol">:current_account</span>, <span class="hljs-symbol">:params</span>  <span class="hljs-comment"># params</span>  <span class="hljs-comment"># assignee_type, inbox_id, :status</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(current_user, params)</span></span>    @current_user = current_user    @current_account = current_user.account    @params = params  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">perform</span></span>   <span class="hljs-comment"># calls to compose the response from other methods </span>  <span class="hljs-keyword">end</span>  private  <span class="hljs-comment"># other methods ...</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">find_all_conversations</span></span>    @conversations = current_account.conversations.where(<span class="hljs-symbol">inbox_id:</span> @inbox_ids)    filter_by_conversation_type <span class="hljs-keyword">if</span> params[<span class="hljs-symbol">:conversation_type</span>]    @conversations  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter_by_assignee_type</span></span>    <span class="hljs-keyword">case</span> @assignee_type    <span class="hljs-keyword">when</span> <span class="hljs-string">'me'</span>      @conversations = @conversations.assigned_to(current_user)    <span class="hljs-keyword">when</span> <span class="hljs-string">'unassigned'</span>      @conversations = @conversations.unassigned    <span class="hljs-keyword">when</span> <span class="hljs-string">'assigned'</span>      @conversations = @conversations.assigned    <span class="hljs-keyword">end</span>    @conversations  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter_by_conversation_type</span></span>    <span class="hljs-keyword">case</span> @params[<span class="hljs-symbol">:conversation_type</span>]    <span class="hljs-keyword">when</span> <span class="hljs-string">'mention'</span>      conversation_ids = current_account.mentions.where(<span class="hljs-symbol">user:</span> current_user).pluck(<span class="hljs-symbol">:conversation_id</span>)      @conversations = @conversations.where(<span class="hljs-symbol">id:</span> conversation_ids)    <span class="hljs-keyword">when</span> <span class="hljs-string">'participating'</span>      @conversations = current_user.participating_conversations.where(<span class="hljs-symbol">account_id:</span> current_account.id)    <span class="hljs-keyword">when</span> <span class="hljs-string">'unattended'</span>      @conversations = @conversations.unattended    <span class="hljs-keyword">end</span>    @conversations  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter_by_query</span></span>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">unless</span> params[<span class="hljs-symbol">:q</span>]    allowed_message_types = [Message.message_types[<span class="hljs-symbol">:incoming</span>], Message.message_types[<span class="hljs-symbol">:outgoing</span>]]    @conversations = conversations.joins(<span class="hljs-symbol">:messages</span>).where(<span class="hljs-string">'messages.content ILIKE :search'</span>, <span class="hljs-symbol">search:</span> <span class="hljs-string">"%<span class="hljs-subst">#{params[<span class="hljs-symbol">:q</span>]}</span>%"</span>)                                  .where(<span class="hljs-symbol">messages:</span> { <span class="hljs-symbol">message_type:</span> allowed_message_types }).includes(<span class="hljs-symbol">:messages</span>)                                  .where(<span class="hljs-string">'messages.content ILIKE :search'</span>, <span class="hljs-symbol">search:</span> <span class="hljs-string">"%<span class="hljs-subst">#{params[<span class="hljs-symbol">:q</span>]}</span>%"</span>)                                  .where(<span class="hljs-symbol">messages:</span> { <span class="hljs-symbol">message_type:</span> allowed_message_types })  <span class="hljs-keyword">end</span>  <span class="hljs-comment"># some other methods ... </span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-listeners">Listeners</h4><p>The listeners are implemented using the <code>Singleton</code> interface and all inherit from the <code>BaseListener</code> and usually will do an action like calling a Job or Builder or some other object when the event is received.</p><h4 id="heading-mailboxes">Mailboxes</h4><p>This could be a good repo if you want to see how to read and process inbound emails. Take a look at <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/app/mailboxes/application_mailbox.rb#L1https://github.com/chatwoot/chatwoot/blob/develop/app/mailboxes/application_mailbox.rb#L1"><code>ApplicationMailbox</code></a> which it matches either a reply to a conversation or a new conversation received via a channel:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationMailbox</span> &lt; ActionMailbox::Base</span>  <span class="hljs-keyword">include</span> MailboxHelper  <span class="hljs-comment"># Last part is the regex for the UUID</span>  <span class="hljs-comment"># Eg: email should be something like : reply+6bdc3f4d-0bec-4515-a284-5d916fdde489<span class="hljs-doctag">@domain</span>.com</span>  REPLY_EMAIL_UUID_PATTERN = <span class="hljs-regexp">/^reply\+([0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12})$/i</span>  CONVERSATION_MESSAGE_ID_PATTERN = <span class="hljs-regexp">%r{conversation/([a-zA-Z0-9-]*?)/messages/(\d+?)@(\w+\.\w+)}</span>  <span class="hljs-comment"># routes as a reply to existing conversations</span>  routing(    -&gt;(inbound_mail) { reply_uuid_mail?(inbound_mail) <span class="hljs-params">||</span> in_reply_to_mail?(inbound_mail) } =&gt; <span class="hljs-symbol">:reply</span>  )  <span class="hljs-comment"># routes as a new conversation in email channel</span>  routing(    -&gt;(inbound_mail) { EmailChannelFinder.new(inbound_mail.mail).perform.present? } =&gt; <span class="hljs-symbol">:support</span>  )<span class="hljs-comment"># ... more methods</span><span class="hljs-keyword">end</span></code></pre><h4 id="heading-mailers">Mailers</h4><p>The Mailers are implemented using <a target="_blank" href="https://shopify.github.io/liquid/">liquid</a> gem. The logic to decide how to deliver a reply based on the inbox_type can be found in the <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/app/mailers/conversation_reply_mailer_helper.rb#L1"><code>ConversationReplymailerHelper</code></a> in a method that looks like this:</p><pre><code class="lang-ruby"><span class="hljs-comment"># https://github.com/chatwoot/chatwoot/blob/develop/app/mailers/conversation_reply_mailer_helper.rb#L2</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">prepare_mail</span><span class="hljs-params">(cc_bcc_enabled)</span></span>    @options = {      <span class="hljs-symbol">to:</span> to_emails,      <span class="hljs-symbol">from:</span> email_from,      <span class="hljs-symbol">reply_to:</span> email_reply_to,      <span class="hljs-symbol">subject:</span> mail_subject,      <span class="hljs-symbol">message_id:</span> custom_message_id,      <span class="hljs-symbol">in_reply_to:</span> in_reply_to_email    }    <span class="hljs-keyword">if</span> cc_bcc_enabled      @options[<span class="hljs-symbol">:cc</span>] = cc_bcc_emails[<span class="hljs-number">0</span>]      @options[<span class="hljs-symbol">:bcc</span>] = cc_bcc_emails[<span class="hljs-number">1</span>]    <span class="hljs-keyword">end</span>    ms_smtp_settings    set_delivery_method    mail(@options)  <span class="hljs-keyword">end</span></code></pre><h4 id="heading-models">Models</h4><p>Models are Active Record models with just the right amount of logic. They are not very big but they are also not slim.</p><h4 id="heading-policies">Policies</h4><p>They use <a target="_blank" href="https://github.com/varvet/pundit">pundit</a> gem and thus this folder contains policies. Each method is simple and easy to understand.</p><h4 id="heading-services">Services</h4><p>There are 63 files inside the services having a similar interface, defining a <code>perform</code> or <code>perform_reply</code> method.</p><h4 id="heading-lib">Lib</h4><p>There are over 60 fiels in the <code>/lib</code> folder. They don't use the same pattern and are doing various things. From defining some types or constants like <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/lib/events/types.rb#L3"><code>Events::Types</code></a> to for example a client connecting to <a target="_blank" href="https://github.com/chatwoot/chatwoot/blob/develop/lib/microsoft_graph_auth.rb#L11"><code>MicrosoftGraphAuth</code></a></p><h3 id="heading-testing">Testing</h3><p>They use RSpec for testing with FactoryBot and some fixtures.</p><p>Looking a bit at the structure of some tests:</p><ul><li><p>They use nested <code>describe</code> or <code>describe</code> with nested <code>context</code></p></li><li><p><code>let</code> and <code>let!</code></p></li><li><p><code>subject</code></p></li><li><p>A limited number of <code>shared_examples</code> inside <code>models</code></p></li></ul><p>The tests appear to be simple with little setup in a before block and some <code>let!</code> and the test is self-contained.</p><h2 id="heading-conclusion">Conclusion</h2><p>In conclusion, chatwoot is an open-source customer engagement suite with a well-structured codebase and a variety of design patterns. Most of them are trying to define a common interface (like <code>perform</code> method). The controllers and models are pretty close to vanilla Rails: slim controllers, with some logic in models, making use of callbacks.</p><p>It employs several gems and libraries to enhance its functionality but without changing too much the flavour of Rails.</p><p>The project uses Ruby 3.2.2, Rails 7.0.8, and Vue for its front end and also has a bit of ERB for the Administrate gem.</p><p>From the Ruby on Rails perspective it could be a project easy to grasp with a bit of mental load on the testing side where there is a mix of <code>let</code>, <code>subject</code> and <code>shared_examples</code>.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/ruby-open-source-chatwoot</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/ruby-open-source-chatwoot</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 10 Nov 2023 06:51:20 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1699418020706/623fc755-3f2a-410a-b77d-2e76d7980c4c.png</cover_image></item><item><title><![CDATA[Ruby Gems Download Trends: An Analysis from 2013 to 2023]]></title><description><![CDATA[<p>While creating the Short Ruby Newsletter I discovered <a target="_blank" href="https://bestgems.org">bestgems.org</a> and I noticed there was an interesting graph and would like to discuss it a bit.</p><h1 id="heading-downloads-trends-of-all-gems">Downloads trends of all gems</h1><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699187477990/f3d7a87e-6c9f-4aee-814e-6f49e94461e9.png" alt="A graph showing downloads trends of all gems" class="image--center mx-auto" /></p><p><a target="_blank" href="https://bestgems.org/stat/downloads">source</a></p><h2 id="heading-data-points">Data points</h2><p>Here is how I read this, first there are two data sets: the total downloads and the daily downloads</p><p><strong>Total downloads</strong></p><p>It is a sum of downloads of all gems -&gt; so of course this will always be increasing.</p><p>But what I think is interesting is how fast the total downloads are increasing. I think it can be seen with the naked eye that starting with say 2019/2020 the total downloads are increasing a lot:</p><ul><li><p><strong>~2B downloads increase from 2013 to 2019</strong></p></li><li><p><strong>~133 B downloads increase from 2019 to 2023</strong></p></li></ul><p>How do I get to this differences:</p><ul><li><p>1.47B downloads in 2013-07-01 and 3.71B downloads in 2019-07-01 =&gt; a difference of about <strong>2.24B</strong></p></li><li><p>3.71B downloads in 2019-07-01 and 137.53B downloads in 2023-07-01 =&gt; a difference of about <strong>133.82B</strong></p></li></ul><p><em>Conclusion:</em> There are two orders of magnitude increase in the total downloads in the last 4 years versus the previous 5 years.</p><p><strong>Daily downloads</strong></p><p>If the total downloads are increased of course also daily downloads are increased.</p><ul><li><p><strong>5.75M</strong> daily downloads in 2013 (data point taken on 2013-07-03)</p></li><li><p><strong>50M</strong> daily downloads in 2019 (data point taken on 2019-07-03)</p></li><li><p><strong>127M</strong> daily downloads in 2023 (data point taken on 2023-07-11)</p></li></ul><p><em>Conclusion</em>: the number of downloads per day is more than double, with some periods from 2019 until 2023 with a higher number of downloads per day (like 160M or 154M).</p><h2 id="heading-what-does-this-mean">What does this mean?</h2><p><strong>For sure it means the number of downloaded gems accelerated since 2019.</strong></p><p><strong>One explanation</strong> could be running tests using CI pipelines and containers so that for every run there is a download of gems.</p><p>Some data points:</p><ul><li><p>GitHub launched its Github Actions in 2018 (<a target="_blank" href="https://resources.github.com/devops/tools/automation/actions/#">source</a>)</p></li><li><p>Gitlab launched its first version of CI in 2012 (<a target="_blank" href="https://handbook.gitlab.com/handbook/company/history/#2012-gitlabcom">source</a>)</p></li><li><p>Jenkins launched in 2011 (<a target="_blank" href="https://www.cloudbees.com/jenkins/what-is-jenkins">source</a>)</p></li><li><p>Circle CI launched in 2011 (<a target="_blank" href="https://circleci.com/careers/">source</a>)</p></li><li><p>Travis CI launched in 2011 (<a target="_blank" href="https://www.travis-ci.com/about-us/">source</a>)</p></li><li><p>Almost 30% of businesses were already using CI/CD in 2013-2014 (<a target="_blank" href="https://www.apexon.com/blog/the-road-to-cicd-a-short-history-of-agile-development/">source</a>)</p></li></ul><p>I will assume these features took off the next year so it would be 2019 for Github and 2013 for Gitlab and the other major CIs 2012.</p><p>It is hard to know if this explanation is the only one. I don't feel it is accounting for all the fast growth since 2019. The only data point that matches that is Github launching their Actions in 2019 but that alone cannot explain the increase of two orders of magnitude since then.</p><p><strong>Another explanation</strong> could be that Ruby is growing and the pace is increasing. There could be two root causes for this:</p><ul><li><p>More often version updates for gems result in multiple organisations' teams updating</p></li><li><p>The pace of new projects started is increasing</p></li></ul><p>Again it is hard to know exactly which one is the reality. But I add this explanation that Ruby is growing because I see signs of it also in other places (more conferences, more talk on social media, an increased number of articles, more releases, more jobs, more contacts on Linkedin about jobs ...)</p><p><strong>There can be other causes for the increase</strong> we have seen since 2019 - maybe the way data is gathered changed, it is more granular or better captured.</p><p>I think it is a combination of both explanations - CI being adopted on a large scale while also the Ruby number of projects growing more rapidly in the last 3-4 years.</p><h2 id="heading-data-validation">Data validation</h2><p><a target="_blank" href="https://bestgems.org"><strong>bestgems.org</strong></a> is not <a target="_blank" href="https://rubygems.org">rubygems.org</a> and I could not yet find how they are taking their data.</p><p>Still, when looking at the total number of downloads (see the comparison image below), it seems to have the same data so I wrote this article starting with the assumption that bestgems.org data is valid.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699273394712/48c741aa-ebdb-467e-9106-4433399e1160.png" alt class="image--center mx-auto" /></p><p>(left) <a target="_blank" href="https://rubygems.org/stats">https://rubygems.org/stats</a> vs (right) <a target="_blank" href="https://bestgems.org/stat/downloads">https://bestgems.org/stat/downloads</a></p><p>On another side, <a target="_blank" href="https://www.ruby-toolbox.com/pages/docs/features/historical_rubygem_download_data">ruby-toolbox.com</a> is using bestgems.org as a data source:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699497691382/ce3bdcdd-9b47-443f-8e5d-ba977ef571b4.png" alt class="image--center mx-auto" /></p><h2 id="heading-maybe-a-conclusion">Maybe a conclusion</h2><p>The rapid increase in Ruby gem downloads since 2019 can be attributed to the widespread adoption of CI/CD pipelines (with a small note that a lot of CI/CD tools were launched years before) and also to the growing popularity of Ruby in new projects.</p><p>This trend reflects the growing importance of Ruby in the software development ecosystem.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/ruby-gems-download-trends-an-analysis-from-2013-to-2023</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/ruby-gems-download-trends-an-analysis-from-2013-to-2023</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 08 Nov 2023 03:16:22 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1699270501957/e58a4923-cffa-41b1-915d-a697e4460d36.png</cover_image></item><item><title><![CDATA[Ruby Open Source: Zammad example]]></title><description><![CDATA[<p>I will probably start a series of open-source Ruby Projects. Maybe I will call it #opensource #Friday.</p><p><a target="_blank" href="https://zammad.com/en">Zammad</a> is an open-source ticketing system, that also offers an on-cloud product.</p><p><a target="_blank" href="https://zammad.com/en"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697602812644/7f4332e3-f201-4b13-9665-edcc4b62ba93.png" alt class="image--center mx-auto" /></a></p><p>They have their product open-sourced on <a target="_blank" href="https://github.com/zammad/zammad">Github</a> and it is built using (at the moment of writing this article) <strong>Ruby 3.1.3</strong> and <strong>Rails 7</strong></p><p><a target="_blank" href="https://github.com/zammad/zammad"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697602714959/cb56b30a-549e-425b-b779-aa6d0c4439fc.png" alt class="image--center mx-auto" /></a></p><h2 id="heading-licensing">Licensing</h2><p>The license is <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/LICENSE">GNU AGPL</a></p><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/LICENSE"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697612651893/a97191ad-5d59-4f61-b9f4-d310eda87e2d.png" alt class="image--center mx-auto" /></a></p><p>They <a target="_blank" href="https://zammad.com/en/company/open-source">mention on their website</a> why they chose to make the product open source:</p><p><a target="_blank" href="https://zammad.com/en/company/open-source"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697612717159/df787418-80d9-4629-9b45-12104b3fca55.png" alt class="image--center mx-auto" /></a></p><p>Each file in the repository has a license line like:</p><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/Gemfile"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697613596157/626b9c49-9262-47c2-a3b2-144bf95f9728.png" alt class="image--center mx-auto" /></a></p><h2 id="heading-some-ideas-from-the-open-source-repo">Some ideas from the open-source repo</h2><p>I don't have enough time to analyse in depth the repo. So just looking around for 30 minutes, here are some things that I extracted</p><h3 id="heading-stats">Stats</h3><p>Running <code>rails stats</code> returned the following:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698983056409/424bdfb1-3437-4fb2-a49a-6e421b6066b4.png" alt="Result of executing rails stats on the repo" class="image--center mx-auto" /></p><h3 id="heading-styling-guide">Styling Guide</h3><p>They use Rubocop with extra cops added. They require some cops provided by gems and custom cops written by them:</p><pre><code class="lang-ruby"><span class="hljs-symbol">require:</span>  - rubocop-capybara  - rubocop-factory_bot  - rubocop-faker  - rubocop-graphql  - rubocop-inflector  - rubocop-performance  - rubocop-rails  - rubocop-rspec  - ../config/initializers/inflections.rb  - ./rubocop_zammad.rb</code></pre><p>In <code>rubocop_zammad.rb</code> they are loading custom cops from <a target="_blank" href="https://github.com/zammad/zammad/tree/develop/.rubocop/cop/zammad"><code>.rubocop/cop/zammad</code></a></p><p>Here are <em>some</em> custom cops written by them:</p><ul><li><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/.rubocop/cop/zammad/correct_migration_timestamp.rb">Checking</a> if the migration file starts with a valid timestamp</p></li><li><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/.rubocop/cop/zammad/exists_condition.rb">Checking</a> usages of <code>find_by</code> to check if an Active Record exists and replace it with <code>exists?</code></p></li><li><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/.rubocop/cop/zammad/forbid_default_scope.rb">Checking</a> that the only allowed <code>default_scope</code> is about simple ordering</p></li><li><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/.rubocop/cop/zammad/forbid_rand.rb">Checking</a> for using <code>rand</code></p></li><li><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/.rubocop/cop/zammad/no_to_sym_on_string.rb">Checking</a> usages of <code>.to_sym</code> on strings and change to <code>:</code> prefix</p></li><li><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/.rubocop/cop/zammad/prefer_negated_if_over_unless.rb">Checking</a> usages of <code>unless</code> and suggest using <code>!</code></p></li><li><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/.rubocop/cop/zammad/update_copyright.rb">Checking</a> copyright notice and adding it when missing</p></li></ul><p>More about their style guide can be found at <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/doc/developer_manual/standards/code-style-guide.md"><code>doc/developer_manual/standards</code></a></p><h3 id="heading-persistence">Persistence</h3><p>They appear to use 3 DBs, each one having their group. One (<a target="_blank" href="https://github.com/nulldb/nulldb">the activerecord-nulldb-adapter</a>) is actually a NullObject pattern implemented for Active Record.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697613005230/e9580f24-8d99-4bf4-a9cd-df14a0726516.png" alt class="image--center mx-auto" /></p><p>Based on the loaded connection they do a preflight check <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/config/initializers/db_preflight_check.rb">in an initializer</a>. This is what it looks like:</p><pre><code class="lang-ruby">Rails.application.config.after_initialize <span class="hljs-keyword">do</span>  Zammad::Application::Initializer::DbPreflightCheck.perform<span class="hljs-keyword">end</span></code></pre><p>and you can go check <a target="_blank" href="https://github.com/zammad/zammad/tree/develop/lib/zammad/application/initializer/db_preflight_check"><code>lib/zammad/application/initializer</code></a> to see what kind of checks are executed for each adapter.</p><p>This is a way to make sure that the actual DB server respects a contract they have defined in these initializers (e.g. what extensions are activated or config defaults or minimum version).</p><p>See this example of a check for MySQL from the same file:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698982343291/90dad251-2dda-44dc-86e4-842e72de559e.png" alt="Code sample from MySQL DB preflight check" class="image--center mx-auto" /></p><h3 id="heading-some-gems-used">Some gems used</h3><ul><li><p><a target="_blank" href="https://github.com/technion/ruby-argon2">argon2</a> - <em>"A Ruby gem offering bindings for Argon2 password hashing"</em></p></li><li><p><a target="_blank" href="https://github.com/mtgrosser/rszr">rszr</a> - "<em>Rszr is an image resizer for Ruby based on the Imlib2 library. It is faster and consumes less memory than MiniMagick, GD2 and VIPS, and comes with an optional drop-in interface for Rails ActiveStorage image processing</em>"</p></li><li><p><a target="_blank" href="https://github.com/zendesk/biz">biz</a> - <em>"Time calculations using business hours"</em></p></li><li><p><a target="_blank" href="https://github.com/samg/diffy">diffy</a> - <em>"It provides a convenient way to generate a diff from two strings or files. Instead of reimplementing the LCS diff algorithm Diffy uses battle tested Unix diff to generate diffs, and focuses on providing a convenient interface, and getting out of your way"</em></p></li><li><p><a target="_blank" href="https://github.com/wvanbergen/chunky_png">chunky_png</a> - <em>"ChunkyPNG is a pure Ruby library to read and write PNG images and access textual metadata. It has no dependency on RMagick, or any other library for that matter"</em></p></li><li><p><a target="_blank" href="https://github.com/socketry/localhost">localhost</a> - <em>"This gem provides a convenient API for generating per-user self-signed root certificates"</em></p></li><li><p><a target="_blank" href="https://github.com/nulldb/nulldb">activerecord-nulldb</a> - "<em>NullDB is the Null Object pattern as applied to ActiveRecord database adapters. It is a database backend that translates database interactions into no-ops. Using NullDB enables you to test your model business logic - including after_save hooks - without ever touching a real database"</em></p></li></ul><h3 id="heading-design-patterns">Design Patterns</h3><p>They use <strong>service objects</strong>. There is a <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/services/service/base.rb"><code>Service::Base</code></a> class that is empty and then there is also <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/services/service/base_with_current_user.rb"><code>Service::BaseWithCurrentUser</code></a> that looks something like this:</p><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/services/service/base_with_current_user.rb"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697617174559/c087e526-1895-4740-b0a0-261223e24d47.png" alt class="image--center mx-auto" /></a></p><p>The main method that a Service object should define is <code>execute</code> but there is no enforcement of this. It is just that everything under <a target="_blank" href="https://github.com/zammad/zammad/tree/develop/app/services/service">app/services/service</a> has this method defined.</p><p><strong>GraphQL objects</strong></p><p>All the logic about GraphQL is inside <a target="_blank" href="https://github.com/zammad/zammad/tree/develop/app/graphql">app/graphql</a>, namescoped to <code>Gql.</code></p><p><strong>Jobs</strong></p><p>They can be found at <a target="_blank" href="https://github.com/zammad/zammad/tree/develop/app/jobs">app/jobs</a><strong>.</strong> Job priority is defined in a concern called <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/jobs/application_job/has_queuing_priority.rb"><code>ApplicationJob::HasQueuingPriorit</code></a><code>y</code> that looks like this:</p><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/jobs/application_job/has_queuing_priority.rb"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697617529027/969eb9ec-d08d-47ed-9877-7a8d8a5c7b9b.png" alt class="image--center mx-auto" /></a></p><p>And all jobs have a default priority of <strong>200</strong> while low_priority is defined as being <strong>300</strong></p><p><strong>Models</strong></p><p>They are under <a target="_blank" href="https://github.com/zammad/zammad/tree/develop/app/models">app/models</a> and there is an <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/models/application_model.rb"><code>ApplicationModel</code></a> defined that includes some defaults like <a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/models/application_model.rb">this</a>:</p><p><a target="_blank" href="https://github.com/zammad/zammad/blob/develop/app/models/application_model.rb"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697617801805/e23db320-1e38-4684-b485-2e42f50ec466.png" alt class="image--center mx-auto" /></a></p><p>There are probably a lot more interesting things to discover about the codebase but this is what I got in a short review.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/ruby-open-source-zammad-example</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/ruby-open-source-zammad-example</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 03 Nov 2023 03:52:55 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1698983459643/94aeb1b5-df0d-48b1-a725-e14dfb466096.png</cover_image></item><item><title><![CDATA[How to skip all callbacks for all models in Rails]]></title><description><![CDATA[<h2 id="heading-problem">Problem</h2><p>You want to have a way to skip all callbacks for all models in Rails.</p><h2 id="heading-before-we-begin">Before we begin</h2><p>Two things:</p><ol><li><p>Skip callbacks if this is a debugging session</p></li><li><p>Don't try to skip all callbacks in your business logic or production code. This is a strong hint for refactoring.</p></li></ol><h2 id="heading-context">Context</h2><p>Let's assume you have the following model:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> &lt; ApplicationRecord</span>  before_save <span class="hljs-symbol">:downcase_name</span>  after_save <span class="hljs-symbol">:add_title</span>  before_validation <span class="hljs-symbol">:ensure_username</span>  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">downcase_name</span></span>    <span class="hljs-keyword">self</span>.name = name.downcase  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_title</span></span>    <span class="hljs-keyword">self</span>.title = <span class="hljs-string">"The <span class="hljs-subst">#{name}</span>"</span>  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">ensure_username</span></span>    <span class="hljs-keyword">self</span>.username = <span class="hljs-keyword">self</span>.email.split(<span class="hljs-string">"@"</span>).first <span class="hljs-keyword">if</span> username.blank?  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>I used one single type of callback on <code>save</code>. But the solutions we will explore will try to skip all callbacks.</p><p>If you plan to use this I think you should first make sure that all callbacks work:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">"test_helper"</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserTest</span> &lt; ActiveSupport::TestCase</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_callback_changes_name_and_title</span></span>    user = User.new(<span class="hljs-symbol">name:</span> <span class="hljs-string">"TEST"</span>, <span class="hljs-symbol">email:</span> <span class="hljs-string">"username@domain.com"</span>)    user.save    assert_equal <span class="hljs-string">"test"</span>, user.name    assert_equal <span class="hljs-string">"The test"</span>, user.title    assert_equal <span class="hljs-string">"username"</span>, user.username  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h2 id="heading-solution-1-using-an-suppressor">Solution 1: Using an <code>Suppressor</code></h2><p>I think this is the idiomatic way to achieve this in Rails if you need to suppress <code>create</code> and <code>destroy</code> callbacks -&gt; use <a target="_blank" href="https://api.rubyonrails.org/classes/ActiveRecord/Suppressor.html">ActiveRecord::Suppressor</a> (thank you <a target="_blank" href="https://x.com/robzolkos/status/1715356883198828709?s=20">Rob</a> for sharing this)</p><blockquote><p>ActiveRecord::Suppressor prevents the receiver from being saved during a given block</p></blockquote><p>Here is how to use it:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">"test_helper"</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserTest</span> &lt; ActiveSupport::TestCase</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_suppressor_skip_callbacks</span></span>    user = User.new(<span class="hljs-symbol">name:</span> <span class="hljs-string">"TEST"</span>, <span class="hljs-symbol">email:</span> <span class="hljs-string">"username@domain.com"</span>)    <span class="hljs-comment"># 👇 An example of using User.supress</span>    User.suppress <span class="hljs-keyword">do</span>      user.save    <span class="hljs-keyword">end</span>    assert_equal <span class="hljs-string">"TEST"</span>, user.name    refute user.title    refute user.username  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Looking at the <a target="_blank" href="https://github.com/rails/rails/blob/main/activerecord/test/cases/suppressor_test.rb">tests for it from Rails codebase</a> it seems that Suppressor only works for save and create callbacks.</p><p>Here is a test that shows that it does not work on create do</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/models/user.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> &lt; ApplicationRecord</span>  before_destroy <span class="hljs-symbol">:before_destroy_action</span>  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">before_destroy_action</span></span>    raise <span class="hljs-string">"Can't destroy this record"</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># test/models/user_test.rb</span><span class="hljs-keyword">require</span> <span class="hljs-string">"test_helper"</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserTest</span> &lt; ActiveSupport::TestCase</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_suppressor_does_not_work_on_destroy</span></span>    user = User.create(<span class="hljs-symbol">name:</span> <span class="hljs-string">"TEST"</span>, <span class="hljs-symbol">email:</span> <span class="hljs-string">"username@domain.com"</span>)    User.suppress <span class="hljs-keyword">do</span>      <span class="hljs-comment"># It confirms that it will raise RuntimeError </span>      assert_raise RuntimeError <span class="hljs-keyword">do</span>        user.destroy      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>So I will explore below two more methods to stop all callbacks. But again if you are only trying to stop the creation of other records from callbacks try to use this.</p><h2 id="heading-solution-2-use-conditional-callbacks">Solution 2: Use conditional callbacks</h2><p>You can use the following <a target="_blank" href="https://guides.rubyonrails.org/active_record_callbacks.html#using-if-and-unless-with-a-symbol">feature from Rails</a>:</p><p><a target="_blank" href="https://guides.rubyonrails.org/active_record_callbacks.html#using-if-and-unless-with-a-symbol"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697772038965/e1688aa5-e7fb-4953-9687-bb458998fdf8.png" alt class="image--center mx-auto" /></a></p><p>Here the solution will be to have an attribute that is by default <code>nil</code> or <code>false</code> and then define all callbacks with <code>unless :attribute</code> so that when we set that attribute to true, it will skip that callback:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> &lt; ApplicationRecord</span>  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:skip_callbacks</span>  before_save <span class="hljs-symbol">:downcase_name</span>, <span class="hljs-symbol">unless:</span> <span class="hljs-symbol">:skip_callbacks?</span>  after_save <span class="hljs-symbol">:add_title</span>, <span class="hljs-symbol">unless:</span> <span class="hljs-symbol">:skip_callbacks?</span>  before_validation <span class="hljs-symbol">:ensure_username</span>, <span class="hljs-symbol">unless:</span> <span class="hljs-symbol">:skip_callbacks?</span>  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">skip_callbacks?</span></span>    skip_callbacks  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">downcase_name</span></span>    <span class="hljs-keyword">self</span>.name = name.downcase  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_title</span></span>    <span class="hljs-keyword">self</span>.title = <span class="hljs-string">"The <span class="hljs-subst">#{name}</span>"</span>  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">ensure_username</span></span>    <span class="hljs-keyword">self</span>.username = <span class="hljs-keyword">self</span>.email.split(<span class="hljs-string">"@"</span>).first <span class="hljs-keyword">if</span> username.blank?  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Here is how you can use it for one model:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">"test_helper"</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserTest</span> &lt; ActiveSupport::TestCase</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_with_setting_skip_callbacks</span></span>    user = User.new(<span class="hljs-symbol">name:</span> <span class="hljs-string">"TEST"</span>)    user.skip_callbacks = <span class="hljs-literal">true</span>    user.save    assert_equal <span class="hljs-string">"TEST"</span>, user.name    refute user.title    refute user.username  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>This solution is simple to add it to one model or to start using it if you start a greenfield project. But if you already have 1000 models, it will be painful to change callbacks on all models to change them to conditional callbacks. Extra pain points if they already have a condition defined.</p><p><em>Advantage of this solution: It uses only public APIs/methods from Active Record so it should be safe to use and Rails upgrades will not break it.</em></p><h2 id="heading-solution-3-patching-applicationrecord-to-allow-removing-all-callbacks">Solution 3: Patching <code>ApplicationRecord</code> to allow removing all callbacks</h2><p>For this solution, we will use <a target="_blank" href="https://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html#method-i-reset_callbacks"><code>reset_callback</code></a> and <a target="_blank" href="https://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html#method-i-set_callback"><code>set_callback</code></a> and patch <code>ApplicationRecord</code> to allow execution of something like <code>without_callbacks</code> with a block.</p><p>Let's first define this to work with one or multiple callbacks:</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/models/application_record.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationRecord</span> &lt; ActiveRecord::Base</span>  primary_abstract_class  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">without_callbacks</span><span class="hljs-params">(*callbacks)</span></span>    saved_callbacks = save_current_callbacks(callbacks)    remove_callbacks(callbacks)    <span class="hljs-keyword">yield</span>    restore_callbacks(saved_callbacks)  <span class="hljs-keyword">end</span>  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">save_current_callbacks</span><span class="hljs-params">(callbacks)</span></span>    callbacks.map { <span class="hljs-params">|callback|</span> [callback, <span class="hljs-keyword">self</span>.__callbacks[callback]] }.to_h  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">remove_callbacks</span><span class="hljs-params">(callbacks)</span></span>    callbacks.each { <span class="hljs-params">|callback|</span> reset_callbacks(callback) }  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">restore_callbacks</span><span class="hljs-params">(saved_callbacks)</span></span>    saved_callbacks.each { <span class="hljs-params">|callback, chain|</span> set_callbacks(callback, chain) }  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>and you can use it like this for one callback or many:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">"test_helper"</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserTest</span> &lt; ActiveSupport::TestCase</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_without_callbacks_does_not_change_name_or_title</span></span>    user = User.new(<span class="hljs-symbol">name:</span> <span class="hljs-string">"TEST"</span>)    User.without_callbacks(<span class="hljs-symbol">:save</span>) <span class="hljs-keyword">do</span>      user.save    <span class="hljs-keyword">end</span>    assert_equal <span class="hljs-string">"TEST"</span>, user.name    refute user.title  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_without_callbacks_does_not_change_name_title_or_username</span></span>    user = User.new(<span class="hljs-symbol">name:</span> <span class="hljs-string">"TEST"</span>, <span class="hljs-symbol">email:</span> <span class="hljs-string">"username@domain.com"</span>)    User.without_callbacks(<span class="hljs-symbol">:save</span>, <span class="hljs-symbol">:validation</span>) <span class="hljs-keyword">do</span>      user.save    <span class="hljs-keyword">end</span>    refute user.title    refute user.username    assert_equal <span class="hljs-string">"TEST"</span>, user.name  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>And then if you want you can <em>add</em> one more thing to the <code>ApplicationRecord</code>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/models/application_record.rb</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">without_all_callbacks</span></span>    callbacks = <span class="hljs-keyword">self</span>.__callbacks.keys    without_callbacks(*callbacks) { <span class="hljs-keyword">yield</span> }  <span class="hljs-keyword">end</span></code></pre><p>and then you can use this like this:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">"test_helper"</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserTest</span> &lt; ActiveSupport::TestCase</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_without_callbacks_does_not_change_name_title_or_username</span></span>    user = User.new(<span class="hljs-symbol">name:</span> <span class="hljs-string">"TEST"</span>, <span class="hljs-symbol">email:</span> <span class="hljs-string">"username@domain.com"</span>)    User.without_all_callbacks <span class="hljs-keyword">do</span>      user.save    <span class="hljs-keyword">end</span>    refute user.title    refute user.username    assert_equal <span class="hljs-string">"TEST"</span>, user.name  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h3 id="heading-warning">Warning</h3><p>I would advise against using this solution because it uses private methods from ActiveRecord. These methods are open to be changed/removed and should be treated that way.</p><p>From all the methods I used in the code, only <code>reset_callbacks</code> is a public Ruby on Rails API. See it <a target="_blank" href="https://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html#method-i-reset_callbacks">on api.rubyonrails.org</a>. The others are methods not listed publicly on the api.rubyonrails.org. So they might change without any deprecation or warning.</p><h3 id="heading-improving-on-solution-3-put-the-custom-code-in-irbrc">Improving on solution 3: Put the custom code in .<code>irbrc</code></h3><p>An intermediate solution would be not to pollute your application code with patching Active Record and put the patching into <code>.irbrc</code> file that you can put in the root of your Ruby on Rails app:</p><pre><code class="lang-ruby"><span class="hljs-comment"># .irbrc</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationRecord</span> &lt; ActiveRecord::Base</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">without_all_callbacks</span></span>    callbacks = <span class="hljs-keyword">self</span>.__callbacks.keys    without_callbacks(*callbacks) { <span class="hljs-keyword">yield</span> }  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">without_callbacks</span><span class="hljs-params">(*callbacks)</span></span>    saved_callbacks = save_current_callbacks(callbacks)    remove_callbacks(callbacks)    <span class="hljs-keyword">yield</span>    restore_callbacks(saved_callbacks)  <span class="hljs-keyword">end</span>  private  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">save_current_callbacks</span><span class="hljs-params">(callbacks)</span></span>    callbacks.map { <span class="hljs-params">|callback|</span> [callback, <span class="hljs-keyword">self</span>.__callbacks[callback]] }.to_h  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">remove_callbacks</span><span class="hljs-params">(callbacks)</span></span>    callbacks.each { <span class="hljs-params">|callback|</span> reset_callbacks(callback) }  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">restore_callbacks</span><span class="hljs-params">(saved_callbacks)</span></span>    saved_callbacks.each { <span class="hljs-params">|callback, chain|</span> set_callbacks(callback, chain) }  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>So now when you start the Rails server the ApplicationRecord is not affected. So if you wrote those tests they will fail.</p><p>But if you open <code>rails c</code> then the <code>without_all_callbacks</code> and <code>without_callbacks</code> methods will be available on all your models.</p><h3 id="heading-checking-before-upgrade">Checking before upgrade</h3><p>If you decide to go for adding this kind of code, then I suggest you add the following test:</p><pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">'test_helper'</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ActiveRecordCallbacksTest</span> &lt; ActiveSupport::TestCase</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setup</span></span>    @model = User.new  <span class="hljs-keyword">end</span>  test <span class="hljs-string">"should have access to reset_callbacks"</span> <span class="hljs-keyword">do</span>    assert(      User.respond_to?(<span class="hljs-symbol">:reset_callbacks</span>, <span class="hljs-literal">true</span>),      <span class="hljs-string">"User models should have access to `reset_callbacks` to implement without_callbacks method"</span>    )  <span class="hljs-keyword">end</span>  test <span class="hljs-string">"should have access to __callbacks hash"</span> <span class="hljs-keyword">do</span>    assert_respond_to(      @model,      <span class="hljs-symbol">:__callbacks</span>,      <span class="hljs-string">"User model should have access to `__callbacks` hash to implement without_callbacks method"</span>    )  <span class="hljs-keyword">end</span>  test <span class="hljs-string">"__callbacks should be a hash with keys as symbols to implement without_callbacks method"</span> <span class="hljs-keyword">do</span>    callbacks = @model.__callbacks.keys    assert_kind_of(      Symbol,      callbacks.first,      <span class="hljs-string">"Expected `ApplicationRecords#__callbacks` should be a hash with keys as symbols"</span>    )  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>And run this check when you upgrade your Rails version.</p><h2 id="heading-special-case-rails-71-normalizes-method">Special case: Rails 7.1 <code>normalizes</code> method</h2><p> None of the techniques I presented above will skip <a target="_blank" href="https://api.rubyonrails.org/classes/ActiveRecord/Normalization/ClassMethods.html#method-i-normalizes"><code>normalizes</code></a> transformations.</p><p>If you have something like this in your models:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> &lt; ApplicationRecord</span>  normalizes <span class="hljs-symbol">:email</span>, <span class="hljs-symbol">with:</span> -&gt; email { email.strip.downcase }  normalizes <span class="hljs-symbol">:name</span>, <span class="hljs-symbol">with:</span> -&gt; name { name.strip.downcase }<span class="hljs-keyword">end</span></code></pre><p>Then skipping callbacks will not stop normalization from happening. Thus if you save the User record it will update the email and name as specified in the transformation</p><h2 id="heading-conclusion">Conclusion</h2><p>In conclusion, skipping all callbacks for all models in Rails can be achieved using various methods such as <strong>ActiveRecord::Suppressor</strong>, <strong>conditional callbacks</strong>, or <strong>patching ApplicationRecord</strong>.</p><p>However, it is important to consider the potential drawbacks of each method and use them cautiously, especially in production code. Always test your solutions and be mindful of Rails version upgrades that may affect your implementation</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/how-to-skip-all-callbacks-for-all-models-in-rails</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/how-to-skip-all-callbacks-for-all-models-in-rails</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 25 Oct 2023 03:37:40 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1697784631340/ec56aa78-41d0-4065-90f7-7f13f2daa213.png</cover_image></item><item><title><![CDATA[Using ChatGPT, Github Copilot and Phind to generate Tailwind config for width classes]]></title><description><![CDATA[<p>In this article, I will show how I used ChatGPT, Github Copilot and Phind to generate a custom Tailwind config for width classes up to 800px for desktop resolution.</p><p>These language models can serve as helpful conversation partners, providing different solutions to the same problem.</p><p>Here, we'll compare their responses to a specific request involving the golden ratio and discuss their effectiveness.</p><h2 id="heading-problem">Problem</h2><p>As this was a personal project, I did not have a complete design, just some inspirational ideas that I wanted to try.</p><p>I wanted to add some custom width-sized in a Tailwind config for a web app up until about 800px for the desktop resolution.</p><p>I asked both Chat GPT 4 and Github Copilot Chat the same question and got different answers.</p><h2 id="heading-chat-gpt-4">Chat GPT 4</h2><p>Here is what I asked Chat GPT 4:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697726678692/92c6083a-5624-458c-81c2-9bbe8c750f70.png" alt class="image--center mx-auto" /></p><p>Here is what it suggested:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697726704852/adf0a679-7f42-4889-a216-7b01c26e8181.png" alt class="image--center mx-auto" /></p><p>As you can notice it used the constraint that I added (to use the golden ratio) to calculate the actual size from the last one provided by me (384px) to reach the 800px that I asked for and then tried to create a series of width utilities matching a bit how Tailwind does it - by spacing them out.</p><p>Did not check all of the utilities proposed, but the first one and the last one are calculated ok from the rem to px ratio.</p><h2 id="heading-github-copilot">Github Copilot</h2><p>I then tried to use Github Copilot via VSCode to generate this list.</p><p>Here is the prompt and the response:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697729803377/e86239e8-0d0c-4f4c-bdbe-50c94230ae4a.png" alt class="image--center mx-auto" /></p><p>It has some fine-grained steps, it reaches the 800 px and it correctly started from 400 (as Tailwind already defined 384px) but I don't see how it used the golden ratio (which is around 1.61).</p><p>It ignored the request to use the golden ratio: did not use it for spacing the utilities, nor used it for calculating the distance in rem or pixels between two utility classes.</p><h2 id="heading-github-copilot-chat">Github Copilot Chat</h2><p>I then tried Github Copilot Chat:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697734445999/c7e90baf-7c0b-4d1b-82fd-65b38659b935.png" alt class="image--center mx-auto" /></p><p>And it replied with the following:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697734476571/133df7c4-652b-40d4-8c12-f6d35166614d.png" alt class="image--center mx-auto" /></p><p>It seems that it was not aware that Tailwind already has width classes up until 384px. But it did apply with an approximation of the golden ratio when spacing the width classes.</p><h2 id="heading-phind">Phind</h2><p>I asked <a target="_blank" href="https://www.phind.com">Phind</a> the same question and here is the answer:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697801297781/4c884fb9-62bc-4655-87b5-673d1eccccc0.png" alt class="image--center mx-auto" /></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697801327316/722b2e88-d14f-42e6-bff8-7e728a9884f3.png" alt class="image--center mx-auto" /></p><p>I find the Phind answer better. It explains how it did the calculation, it used the golden ratio for each step and it also provides the Tailwind config and shows how to use it.</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/using-chatgpt-github-copilot-and-phind-to-generate-tailwind-config-for-width-classes</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/using-chatgpt-github-copilot-and-phind-to-generate-tailwind-config-for-width-classes</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 20 Oct 2023 11:00:07 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1697801455326/e065a4e3-a08d-4097-9299-8f4b89de16e5.png</cover_image></item><item><title><![CDATA[About Ruby: A tale of searching for the main]]></title><description><![CDATA[<p>Here is a fun way to play with Ruby while trying to explore some basic concepts.</p><p>If you are learning Ruby, I hope this article demonstrates that Ruby enables excellent self-exploration, and most questions about its functionality can be answered through hands-on experimentation and just a little documentation.</p><h2 id="heading-context">Context</h2><p>Someone asked me the following question:</p><blockquote><p><strong>When I write in IRB or in a file the following</strong></p></blockquote><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span><span class="hljs-keyword">end</span></code></pre><blockquote><p>Where is the <code>my_method</code> created? What object will contain this method? And what is the access modifier for this method?</p></blockquote><h2 id="heading-detective-tools">Detective Tools</h2><p>I will use some simple but powerful tools:</p><ol><li><p>Ruby interpreter installed on the local machine</p></li><li><p>Ruby documentation at <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/">https://docs.ruby-lang.org/en/3.2</a></p></li></ol><h2 id="heading-solution">Solution</h2><p>Instead of giving a direct answer, let's put on the hat of an investigator and try to find some clues about this while having fun with Ruby.</p><h3 id="heading-first-question-when-i-run-a-script-who-is-there-already">First question: When I run a script who is there already?</h3><p>First, let's create a file called <code>exploration.rb</code> and ask it about <code>self</code>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># exploring.rb</span>puts <span class="hljs-string">"Who is here? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span><span class="hljs-comment"># =&gt; `Who is here? main`</span></code></pre><p>We notice this return <code>main</code>. But what is this <code>main</code>?</p><p>Maybe we can ask Ruby about it. I will add a couple of methods to find out more about <code>main</code> while trying to dig around:</p><pre><code class="lang-ruby">puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span>puts <span class="hljs-string">"What is your class: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.<span class="hljs-keyword">class</span>}</span>"</span>puts <span class="hljs-string">"What are your ancestors: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.<span class="hljs-keyword">class</span>.ancestors}</span>"</span>puts <span class="hljs-string">"What is the current exection stack: <span class="hljs-subst">#{caller.inspect}</span>"</span><span class="hljs-comment"># =&gt;</span>Who is <span class="hljs-symbol">here:</span> mainWhat is your <span class="hljs-class"><span class="hljs-keyword">class</span>: <span class="hljs-title">Object</span></span>What are your <span class="hljs-symbol">ancestors:</span> [Object, Kernel, BasicObject]What is the current caller <span class="hljs-symbol">stack:</span> []</code></pre><p>We found out that <code>main</code> exists inside <code>Object</code>. Good, let's poke around then:</p><pre><code class="lang-ruby">puts <span class="hljs-string">"Is `main` a public method? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.public_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:main</span>)}</span>"</span>puts <span class="hljs-string">"Is `main` a public method? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.protected_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:main</span>)}</span>"</span>puts <span class="hljs-string">"Is `main` a private method? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.private_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:main</span>)}</span>"</span>puts <span class="hljs-string">"Is `main` a singleton method? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.singleton_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:main</span>)}</span>"</span><span class="hljs-comment"># =&gt; </span>Is <span class="hljs-string">`main`</span> a public method? <span class="hljs-literal">false</span>Is <span class="hljs-string">`main`</span> a public method? <span class="hljs-literal">false</span>Is <span class="hljs-string">`main`</span> a private method? <span class="hljs-literal">false</span>Is <span class="hljs-string">`main`</span> a singleton method? <span class="hljs-literal">false</span></code></pre><p>It appears that <code>main</code> is not a method. That seems strange, right? If <code>main</code> is not a method then what could it be?</p><p>I think we should now explore how we are asking the question: we are using <code>puts</code> to ask the question. Here is what <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/IO.html#method-i-puts">the documentation</a> says</p><p><a target="_blank" href="https://docs.ruby-lang.org/en/3.2/IO.html#method-i-puts"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697512187685/fa5a696d-0ae5-46ce-9c25-8967e646fb68.png" alt class="image--center mx-auto" /></a></p><p>Then maybe something is deturning the <code>puts self</code> and that something could be a method <code>to_s</code> on <code>Object</code>. Here let's check a bit the documentation for <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/Object.html#method-i-to_s">Object#to_s</a></p><p><a target="_blank" href="https://docs.ruby-lang.org/en/3.2/Object.html#method-i-to_s"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697095009683/c71beafd-ee66-4419-9303-65f319153654.png" alt class="image--center mx-auto" /></a></p><p><strong>CLUE A - FOUND -&gt;</strong> <code>main</code> is just a string printed by Ruby in the initial execution context. Nothing special here.</p><h3 id="heading-second-where-is-a-method-created-when-i-dont-define-an-object">Second: Where is a method created when I don't define an object?</h3><p>For this, we will create a second file called <code>exploration_method.rb</code> and again ask it about itself:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span>  puts <span class="hljs-string">"What is your class: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.<span class="hljs-keyword">class</span>}</span>"</span>  puts <span class="hljs-string">"What are your ancestors: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.<span class="hljs-keyword">class</span>.ancestors}</span>"</span>  puts <span class="hljs-string">"What is the current exection stack: <span class="hljs-subst">#{caller.inspect}</span>"</span>  puts <span class="hljs-string">"What is your current file: <span class="hljs-subst">#{__FILE_<span class="hljs-number">_</span>}</span>"</span><span class="hljs-keyword">end</span>my_method<span class="hljs-comment"># =&gt; </span>Who is <span class="hljs-symbol">here:</span> mainWhat is your <span class="hljs-class"><span class="hljs-keyword">class</span>: <span class="hljs-title">Object</span></span>What are your <span class="hljs-symbol">ancestors:</span> [Object, Kernel, BasicObject]What is the current exection <span class="hljs-symbol">stack:</span> [<span class="hljs-string">"exploration_method.rb:10:in `&lt;main&gt;'"</span>]What is your current <span class="hljs-symbol">file:</span> exploration_method.rb</code></pre><p><strong>CLUE B - FOUND -&gt;</strong> <code>my_method</code> is created inside <code>Object</code></p><h3 id="heading-third-what-kind-of-method-is-mymethod">Third: What kind of method is <code>my_method</code>?</h3><p>Let's ask <code>Object</code> about this method:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span><span class="hljs-keyword">end</span>puts <span class="hljs-string">"Is `my_method` a public method? <span class="hljs-subst">#{Object.public_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:my_method</span>)}</span>"</span>puts <span class="hljs-string">"Is `my_method` a private method? <span class="hljs-subst">#{Object.private_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:my_method</span>)}</span>"</span>puts <span class="hljs-string">"Is `my_method` a private method? <span class="hljs-subst">#{Object.protected_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:my_method</span>)}</span>"</span>puts <span class="hljs-string">"Is `my_method` a singleton method? <span class="hljs-subst">#{Object.singleton_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:my_method</span>)}</span>"</span><span class="hljs-comment"># =&gt; </span>Is <span class="hljs-string">`my_method`</span> a public method? <span class="hljs-literal">false</span>Is <span class="hljs-string">`my_method`</span> a private method? <span class="hljs-literal">true</span>Is <span class="hljs-string">`my_method`</span> a private method? <span class="hljs-literal">false</span>Is <span class="hljs-string">`my_method`</span> a singleton method? <span class="hljs-literal">false</span></code></pre><p><strong>CLUE C - FOUND</strong> -&gt; Seems like <code>my_method</code> is a private method defined on <code>Object</code></p><p>I will try to test this. I know that <code>public_send</code> can only call public methods on an object. And I know that <code>send</code> will call any method.</p><p>I will create a simple method that will print <code>self</code> and <code>object_id</code> and try out various things:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span>my_method<span class="hljs-comment"># =&gt; </span>Who is <span class="hljs-symbol">here:</span> main with object_id <span class="hljs-number">60</span></code></pre><p>What happens if I try the following things:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span>Object.new.my_method<span class="hljs-comment"># =&gt; </span>testing.<span class="hljs-symbol">rb:</span><span class="hljs-number">5</span><span class="hljs-symbol">:in</span> <span class="hljs-string">`&lt;main&gt;': private method `</span>my_method<span class="hljs-string">' called for #&lt;Object:0x00000001054229c0&gt; (NoMethodError)</span></code></pre><p>What about using <code>public_send</code> -&gt; the same result</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span>Object.new.public_send(<span class="hljs-symbol">:my_method</span>)<span class="hljs-comment"># =&gt; </span>testing.<span class="hljs-symbol">rb:</span><span class="hljs-number">5</span><span class="hljs-symbol">:in</span> <span class="hljs-string">`public_send': private method `</span>my_method<span class="hljs-string">' called for #&lt;Object:0x0000000105952968&gt; (NoMethodError)</span></code></pre><p>Then <code>send</code> should work:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span>Object.new.send(<span class="hljs-symbol">:my_method</span>)<span class="hljs-comment"># =&gt; </span>Who is <span class="hljs-symbol">here:</span> <span class="hljs-comment">#&lt;Object:0x0000000107412960&gt; with object_id 60</span></code></pre><p>So if <code>my_method</code> will be defined on the <code>Object</code> then I must be able to use it in my custom objects right?</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleObject</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test</span></span>    my_method  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>SimpleObject.new.test</code></pre><p>Indeed it works! I now confirmed in two ways that <code>my_method</code> is indeed created at runtime in <code>Object</code></p><h3 id="heading-four-is-mymethod-defined-in-object-or-added-to-object">Four: Is <code>my_method</code> defined in Object or added to Object?</h3><p>Just to push things more, let's try to find out what kind of mechanism is used to add this method to the <code>Object</code> (well from how I formulated this you probably can guess):</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleObject</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test</span></span>    my_method  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>puts <span class="hljs-string">"Are you defining `my_method`? <span class="hljs-subst">#{SimpleObject.private_method_defined?(<span class="hljs-symbol">:my_method</span>)}</span>"</span>uts <span class="hljs-string">"Do you have this method? <span class="hljs-subst">#{Object.private_methods.<span class="hljs-keyword">include</span>?(<span class="hljs-symbol">:my_method</span>)}</span>"</span><span class="hljs-comment"># =&gt; </span>Are you defining <span class="hljs-string">`my_method`</span>? <span class="hljs-literal">true</span>Do you have this method? <span class="hljs-literal">true</span></code></pre><p>This is a method that was added to the <code>Object</code> and <code>Object</code> is defining this method as a private method.</p><p>We can test this by using <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/Module.html#method-i-method_added"><code>Object#method_added</code></a></p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Object</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">method_added</span><span class="hljs-params">(method_name)</span></span>    puts <span class="hljs-string">"I just added <span class="hljs-subst">#{method_name.inspect}</span> on <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>  puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span><span class="hljs-comment"># =&gt; </span>I just added <span class="hljs-symbol">:my_method</span> on Object</code></pre><p><strong>CLUE D - FOUND</strong> The method is <em>defined</em> on <code>Object</code> but as a private method. That's why it is accessible everywhere, but that is also why if you add this kind of method, you would be polluting every object with extra private methods if you define top-level methods like that or you might redefine already existing methods.</p><h3 id="heading-five-is-mymethod-part-of-basicobject">Five: Is <code>my_method</code> part of BasicObject</h3><p>In the beginning, I asked this question inside the <code>exploration.rb</code> file:</p><pre><code class="lang-ruby">puts <span class="hljs-string">"What are your ancestors: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.<span class="hljs-keyword">class</span>.ancestors}</span>"</span><span class="hljs-comment"># =&gt; What are your ancestors: [Object, Kernel, BasicObject]</span></code></pre><p>I think it is worth investigating a bit what happens with <code>BasicObject</code> and <code>Kernel</code>.</p><p><strong>Question 1: Does</strong> <code>BasicObject</code> <strong>include</strong> <code>my_method</code><strong>?</strong></p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>    puts <span class="hljs-string">"Who is here: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span> with object_id <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.object_id}</span>"</span><span class="hljs-keyword">end</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AVeryBasicObject</span> &lt; BasicObject</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">look_for_my_method</span></span>    my_method  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-keyword">begin</span>  AVeryBasicObject.new.look_for_my_method<span class="hljs-keyword">rescue</span> NameError =&gt; <span class="hljs-number">_</span>  puts <span class="hljs-string">"`my_method` is not here"</span><span class="hljs-keyword">end</span><span class="hljs-comment"># =&gt; `my_method` is not here</span></code></pre><p><code>BasicObject</code> does not include <code>my_method</code></p><p><strong>Question 2: Does</strong> <code>Kernel</code> <strong>module include</strong> <code>my_method</code><strong>?</strong></p><p>First I will include <code>Kernel</code> and test to make sure it is included properly by testing that the newly created object has <code>puts</code></p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AnObjectWithKernel</span> &lt; BasicObject</span>  <span class="hljs-keyword">include</span> <span class="hljs-symbol">:</span><span class="hljs-symbol">:Kernel</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">a_method</span></span>    puts <span class="hljs-string">"Inside AnObjectWithKernel - it now includes puts"</span>  <span class="hljs-keyword">end</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">look_for_my_method</span></span>    my_method  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>obj = AnObjectWithKernel.newobj.a_method<span class="hljs-comment"># =&gt; Inside AnObjectWithKernel - it now includes puts</span><span class="hljs-keyword">begin</span>  obj.look_for_my_method<span class="hljs-keyword">rescue</span> NameError =&gt; <span class="hljs-number">_</span>  puts <span class="hljs-string">"`my_method` is not here"</span><span class="hljs-keyword">end</span><span class="hljs-comment"># =&gt; `my_method` is not here</span></code></pre><p><code>Kernel</code> does not include <code>my_method</code></p><h2 id="heading-conclusion">Conclusion</h2><p>In conclusion, when defining a method without specifying an object in Ruby, the method is created as a private method inside the <code>Object</code> class.</p><p>This method can be called on any custom object, as it is added during the execution of the file.</p><p>Make sure you are not defining in the main context a method that is already defined by Object, Kernel or any other classes by Ruby cause that will create some not-wanted side effects.</p><p>Check out the result of executing the following code that I added inside a file called <code>side_effects.rb</code>:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleObject</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>    puts <span class="hljs-string">"Who is here inside? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span>    puts <span class="hljs-string">"Where is `to_s` defined? <span class="hljs-subst">#{method(<span class="hljs-symbol">:to_s</span>).source_location.inspect}</span>"</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>puts <span class="hljs-string">"Who is here in the initial execution context? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span>SimpleObject.new.my_method<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">to_s</span></span>  <span class="hljs-string">"THIS IS NOT MAIN"</span><span class="hljs-keyword">end</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SecondSimpleObject</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_method</span></span>    puts <span class="hljs-string">"Who is here inside? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span>    puts <span class="hljs-string">"Where is `to_s` defined? <span class="hljs-subst">#{method(<span class="hljs-symbol">:to_s</span>).source_location.inspect}</span>"</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>puts <span class="hljs-string">"Who is here in the initial execution context? <span class="hljs-subst">#{<span class="hljs-keyword">self</span>}</span>"</span>SecondSimpleObject.new.my_method</code></pre><p>The output will be:</p><pre><code class="lang-ruby">Who is here <span class="hljs-keyword">in</span> the initial execution context? mainWho is here inside? #&lt;SimpleObject:0x00000001057b21f8&gt;Where is <span class="hljs-string">`to_s`</span> <span class="hljs-keyword">defined</span>? <span class="hljs-literal">nil</span>Who is here <span class="hljs-keyword">in</span> the initial execution context? mainWho is here inside? THIS IS NOT MAINWhere is <span class="hljs-string">`to_s`</span> <span class="hljs-keyword">defined</span>? [<span class="hljs-string">"side_effects.rb"</span>, <span class="hljs-number">12</span>]</code></pre><p>Notice that <code>to_s</code> from SecondSimpleObject has now the value <code>THIS IS NOT MAIN</code> and it is reported to be defined in <code>side_effects.rb</code> file?</p><h2 id="heading-updates">Updates</h2><ul><li><a target="_blank" href="https://ufuk.dev">Ufuk Kayserilioglu</a> gave me <a target="_blank" href="https://ruby.social/@ufuk/111256244982093392">valuable feedback</a> on an earlier version of this article that I incorrectly used <code>method_defined?</code> instead of <code>private_method_defined?</code> in the section about if method was added or defined. Thus I refactored that section and now it states clearly that <code>my_method</code> is defined as a private method on the <code>Object</code></li></ul><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/about-ruby-a-tale-of-searching-for-the-main</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/about-ruby-a-tale-of-searching-for-the-main</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 18 Oct 2023 03:14:12 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1697597762162/c7ecfdd2-4dc6-4b53-9e95-13dd55407ca4.png</cover_image></item><item><title><![CDATA[Ruby on Rails can scale - here is one example]]></title><description><![CDATA[<p>Here is a website <a target="_blank" href="https://archiveofourown.org">https://archiveofourown.org</a> built with Ruby on Rails</p><h2 id="heading-data">Data</h2><ul><li><p><strong>311 million visits</strong> last month (September 2023) - according to <a target="_blank" href="https://www.similarweb.com/website/archiveofourown.org/">similarweb.com</a></p></li><li><p><strong>6.3 million</strong> users (as publicly shared by <a target="_blank" href="https://archiveofourown.org">archiveofourown.org</a> on their main website)</p></li></ul><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697116113042/dd3b0924-ca80-4984-b81e-31081535c9da.png" alt class="image--center mx-auto" /></p><p>You can find data that is a bit bigger if you look at for example <a target="_blank" href="https://www.semrush.com/website/archiveofourown.org/overview/">semrush.com</a>:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697116394894/5e6f1e44-f203-4fc0-bcee-174d6d165b8c.png" alt class="image--center mx-auto" /></p><p>Whatever data you pick, this is a big web app with 6 million users and between 300 million and 1 billion visits.</p><h2 id="heading-tech-stack">Tech Stack</h2><p>They use at the date of writing this article:</p><ul><li><p><strong>Ruby</strong> version 3.0.5</p></li><li><p><strong>Rails</strong> version 6</p></li></ul><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697116217073/5ddb979d-dd24-4882-98fb-9629a5d60b68.png" alt class="image--center mx-auto" /></p><p>Source: their <a target="_blank" href="https://github.com/otwcode/otwarchive">open-source Github repo</a></p><h2 id="heading-conclusion">Conclusion</h2><p>Ruby on Rails does scale!</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/ruby-on-rails-can-scale-here-is-one-example</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/ruby-on-rails-can-scale-here-is-one-example</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 12 Oct 2023 13:24:04 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1697116909014/9e10318c-6fe5-4467-906b-3bf5da644f3e.png</cover_image></item><item><title><![CDATA[How to create a new Rails app running Rails 7.1 beta or main branch]]></title><description><![CDATA[<h3 id="heading-new-app-with-rails-71beta1">New app with Rails 7.1.beta1</h3><p>Here is how to create a new Rails app that runs on Rails 7.1 beta1</p><pre><code class="lang-bash">gem install -v 7.1.0.beta1 railsrails _7.1.0.beta1_ new myrails71app</code></pre><p>Replace <code>myrails71app</code> with your own app name and you are good to go.</p><p>This will generate a <code>Gemfile</code> that has something like this inside and then install required gems for <code>Rails 7.1.0.beta1</code></p><pre><code class="lang-ruby"><span class="hljs-comment"># Gemfile</span>gem <span class="hljs-string">"rails"</span>, <span class="hljs-string">"~&gt; 7.1.0.beta1"</span></code></pre><h3 id="heading-new-app-with-rails-main-branch">New app with Rails main branch</h3><p>Here is how to create a new Rails app that runs on Rails main branch from GitHub:</p><pre><code class="lang-bash">rails new myrailsapp --main</code></pre><p>This will generate a <code>Gemfile</code> that has something like this inside:</p><pre><code class="lang-ruby">gem <span class="hljs-string">"rails"</span>, <span class="hljs-symbol">github:</span> <span class="hljs-string">"rails/rails"</span>, <span class="hljs-symbol">branch:</span> <span class="hljs-string">"main"</span></code></pre><p>May you have a lot of ideas to try!</p><hr /><p>Enjoyed this article?</p><p>👉 Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community and visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>,</strong> a directory with learning content about Ruby.</p><p>👐 Subscribe to my Ruby and Ruby on rails courses over email at <a target="_blank" href="https://learn.shortruby.com">learn.shortruby.com</a> - effortless learning anytime, anywhere</p><p>🤝 Let's connect on <a target="_blank" href="https://ruby.social/@lucian"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a> or <a target="_blank" href="https://x.com/lucianghinda"><strong>Twitter</strong></a> where I post mainly about Ruby and Rails.</p><p>🎥 Follow me on <a target="_blank" href="https://www.youtube.com/@shortruby"><strong>my YouTube channel</strong></a> for short videos about Ruby</p>]]></description><link>https://allaboutcoding.ghinda.com/how-to-create-a-new-rails-app-running-rails-71-beta-or-main-branch</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/how-to-create-a-new-rails-app-running-rails-71-beta-or-main-branch</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 15 Sep 2023 10:08:55 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1694772438809/911c019d-e1b2-4a69-8623-4055a47dd407.png</cover_image></item><item><title><![CDATA[Using GPT-4 to refactor a simple HTML page]]></title><description><![CDATA[<p>Continuing with my endeavour of publishing more about using GPT-4 while coding, here I am going to show you how I refactored some HTML when writing the homepage for <a target="_blank" href="https://shortruby.com">ShortRuby.com</a></p><p><em>Note: When I am writing in this text GPT-4 I want to say that I am using Cursor IDE with GPT-4 option.</em></p><p>First, this is the result. I am pretty happy with it as I spent little time making it and it was a small iteration. This will probably evolve over the next weeks thus it is better to capture here how it looks like at the end of this coding session:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694523891427/d6915a8a-0bdc-4842-add9-8e27366d4995.png" alt class="image--center mx-auto" /></p><h2 id="heading-adding-the-logo-to-the-top-left">Adding the logo to the top left</h2><p>After I created a very simple hero section that contained the title and a description I wanted to add a logo to the left side.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694523927220/948b509e-a6ff-4cd4-80a7-9cc2c2bf27f8.png" alt class="image--center mx-auto" /></p><p>The prompt here is simple, it includes a reference to the hero partial (I am using Cursor IDE) and I asked it as clearly as I could what to do.</p><p>I received a good enough response along with an explanation of the solution. I was pleased to notice that the logo was indeed on the left side.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694523989986/78925f19-aa04-4912-a9db-932299e32973.png" alt class="image--center mx-auto" /></p><p>The only thing is that I asked for a "square with rounded corners" but I received a full-circle image. I kept it because I liked how it looked. Yet I appreciated that it told me how to make the corners "less rounded"</p><h2 id="heading-decreasing-the-top-and-bottom-space">Decreasing the top and bottom space</h2><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694524166284/2aca0dc3-9bea-4763-92fe-2266b96b80cd.png" alt class="image--center mx-auto" /></p><p>You cannot see it in the final form of the website but you can see in the above image that the vertical padding on the second div is quite big <code>py-32</code> or <code>py-40</code> depending on the screen size.</p><p>Thus as you can notice I asked it to decrease the spacing while choosing the appropriate Tailwind dimension. Notice that here I tried to be explicit but did not use any Tailwind (like asking for a specific utility class) or CSS-specific terms (like padding or margin ...)</p><p>The response was again good enough:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694529650743/2a2a0dbc-8ea4-435f-8a33-f4ae9c38c99f.png" alt class="image--center mx-auto" /></p><p>I like the explanation about how it arrived to transform <code>py-32</code> to <code>py-20</code> (reducing in this case to about 62.5%) and <code>py-40</code> to <code>py-28</code> (reducing in this case to about 70%). I found them pretty close to the 1/3 that I asked for and they looked good enough to keep this change.</p><h2 id="heading-making-the-columns-the-same-height">Making the columns the same height</h2><p>I then had a page with 3 columns (or cells) each one having a different height because the description had a different length. Thus I asked GPT-4 to change it:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694529992274/720ae96e-fcca-480a-9b07-ef287818d563.png" alt class="image--center mx-auto" /></p><p>Here I tried again to be as descriptive as possible without giving into what solution I am looking for.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694609993063/4f5d8783-00e8-4980-bcb3-15e906ab4aca.png" alt class="image--center mx-auto" /></p><p>I received a response that uses <code>flex-grow</code> to achieve this result. It worked, but in the end, I needed to edit more. After seeing the result I also added height to the title of the column.</p><h2 id="heading-replacing-a-div-with-a">Replacing a <code>div</code> with <code>a</code></h2><p>After playing a bit with the design I figured out that I wanted to make the entire column clickable and not only the small button to visit.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694571956528/ecf8a008-a729-4849-ae33-d5a0513a7980.png" alt class="image--center mx-auto" /></p><p>This time I used the in-editor chat and selected one column that I asked to be transformed. What you can notice in the above prompt is that I ask specifically to keep the format/layout of the column. I asked this based on previous experiences when I asked for changing tags and I noticed that it needs guidance on what to choose when merging styles.</p><p>Here is the diff that Cursor GPT-4 proposed:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694571998359/ad7c78fb-1dac-4e96-af33-a365b5ab3388.png" alt class="image--center mx-auto" /></p><p>It is a good result, it does do the job that I asked for. I noticed that it added a <code>bg-white</code> class that was not necessary, but I think it makes sense to add it so that it is clear what happens on <code>hover</code> - what background colour it transitions from.</p><h2 id="heading-making-an-element-show-when-the-column-is-hovered">Making an element show when the column is hovered</h2><p>I wanted to add a bit of colour on the entire page because it is a bit grey. Thus I added a small animated bullet in blue.</p><p>Thus my objective was to display that element only when the column is hovered:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694572821442/9cee199f-7ea1-44de-97f1-192c863a7d26.png" alt class="image--center mx-auto" /></p><p>In this case, because this is about show and hide, from my previous experiments I noticed it needs to know what to do when what is added will conflict (using <code>flex</code> and <code>block</code> or <code>hidden</code> and <code>block</code>). I added, <em>"when there is a conflict between added CSS classes please choose the ones that will achieve this functionality"</em>.</p><p>The result works and it uses <code>group</code> and <code>group-hover</code> to achieve the result:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694572887592/6b8a327e-3b10-431d-8242-658937422e95.png" alt class="image--center mx-auto" /></p><p>It did the work and now I can have this:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=d7KgiMu0-bk">https://www.youtube.com/watch?v=d7KgiMu0-bk</a></div><p> </p><h2 id="heading-conclusions">Conclusions</h2><p>Here are some learning points from this experiment:</p><ul><li><p>GPT-4 is not aware of CSS classes that are doing similar things through different CSS rules. Thus it cannot act like a linter nor it can choose one or the other =&gt; it needs to be instructed on what to do in case such conflict occurs. I instructed it generally in the examples above asking to pick only classes that will achieve the functionality but you can also instruct it specifically like preferring <code>flex</code> or <code>grid</code> or something similar</p></li><li><p>Among the most important learning for me - not only from this small experiment but for many others - is that the effort to write prompt forces you to put your thoughts in writing and this simple act will help to make some choices or be aware of some choices and thus will contribute to having a better code design.</p></li><li><p>Even with better prompts and the results containing an explanation, the programmer still needs to be able to understand the solution and assess if it works and if they want to keep it.</p></li><li><p>I still like more the side chat for Cursor IDE and prefer it over the inline chat. Probably it is a preference that comes from because I used it before for 1-2 months Github Copilot Chat and thus it probably was the start of a habit</p></li></ul><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates from the community. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>.</strong> You can also find me on <a target="_blank" href="http://Ruby.social"><strong>Ruby.social</strong></a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda"><strong>Linkedin</strong></a></p>]]></description><link>https://allaboutcoding.ghinda.com/using-gpt-4-to-refactor-a-simple-html-page</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/using-gpt-4-to-refactor-a-simple-html-page</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 13 Sep 2023 03:10:33 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1694574451251/55baa478-5296-4967-a60c-e0df5ec37063.png</cover_image></item><item><title><![CDATA[Using Cursor IDE for some small changes in a Rails app]]></title><description><![CDATA[<p>Path of my learning path about how to use AI/LLMs to augment my developer productivity I started using <a target="_blank" href="https://www.cursor.so">www.cursor.so</a></p><p>Here let me show you how I made two changes to the <a target="_blank" href="https://rubyandrails.info">rubyandrails.info</a> website.</p><h2 id="heading-replacing-a-form-with-a-phlex-component">Replacing a form with a Phlex component</h2><p>The first task that I asked was about replacing an HTML with a component. To achieve this in Cursor (MacOS edition) you have to select the text and then press CMD+K. Then a pop-up will appear where you can ask your question (or write your prompt) like this:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694187809116/87d1c036-b168-45a0-add3-5348446119f4.png" alt class="image--center mx-auto" /></p><p>After submitting Cursor (that uses GPT4 in this case under the hood) will make a diff for the selected code and ask you to accept it or not:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694187930106/e567c53a-7bd2-4763-a3c5-63c362b86a8b.png" alt class="image--center mx-auto" /></p><p>I like this idea that it proposed a diff because I can review it and check if it is correct or not.</p><p>This was a small change and I was happy with the result. I did a lot more replaces like this but I did not use Cursor LLM for this.</p><p><strong>My conclusion</strong> is that for this very small case, writing the prompt and reviewing the change is much more effort than directly writing the code myself and the return on investment is small. It does not even contain any new insights or something to learn for me.</p><p>What I would like (and maybe Cursor knows this but I am just starting to use it) is to ask to replace all forms like that with the component. I will still need to review the code because the risk would be to replace a code that looks like that but it is not the same search functionality.</p><h2 id="heading-a-small-security-pr">A small security PR</h2><p>Next, I moved from the Edit with LLM functionality to the Chat with LLM functionality that Cursor IDE offers.</p><p>Looking at the source code I noticed the following code, which I think it is a security risk.</p><pre><code class="lang-erb"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Search Term: <span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span></span></span><span class="ruby"> params[<span class="hljs-symbol">:search_term</span>] </span><span class="xml"><span class="hljs-tag">%&gt;</span></span></code></pre><p>Thus I asked the following to the Cursor chat:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694057596283/9774c46e-8518-4680-b073-ea7746c722b4.png" alt="GPT4 prompt to ask for security analysis" class="image--center mx-auto" /></p><p>One advantage I found while using Cursor chat is that it makes it easy to reference open files in a prompt.</p><p>Notice that I opened <code>_index_nav.html.erb</code> and reference it in my prompt with <code>@_index_nav.html.erb</code> and Cursor read the content to provide it to GPT4. The same can be achieved by selecting the text and pressing <code>CMD+L</code> will add the code itself as the context in the chat. By the way with CMD+L you can add multiple pieces of code from multiple files (but we will explore that in another article).</p><p>Here is the response:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694057733418/5afc1558-facf-4d22-a9f9-3e4ed2f90b7c.png" alt="Response from Cursor chat" class="image--center mx-auto" /></p><p>What I notice in the response is that indeed <a target="_blank" href="https://api.rubyonrails.org/classes/ERB/Util.html#method-c-h"><code>h</code></a> is an alias for <code>html_escape</code> but the response is a bit outdated, thus incorrect. Since <a target="_blank" href="https://guides.rubyonrails.org/3_0_release_notes.html#other-changes">Rails 3.0</a> there is no need to escape with <code>h</code> because:</p><blockquote><ul><li>You no longer need to call <code>h(string)</code> to escape HTML output, it is on by default in all view templates. If you want the unescaped string, call <code>raw(string)</code>.</li></ul></blockquote><p>The response, although it won't break anything, is also unnecessary and should not be followed. It is incorrect because until a vulnerability - if any - is found in the escape Rails does, the code should be considered safe. If we don't do this then we cannot work with abstractions or trust a framework.</p><p>Furthermore, since this information is from Rails 3.0, GPT-4 should already be aware of it.</p><p>I followed up with this question:</p><pre><code class="lang-markdown">In this context is <span class="hljs-code">`html_escape`</span> or <span class="hljs-code">`h`</span> enough to mitigate the security risk of displaying an URL parameter provided by the user inside an ERB file?</code></pre><p>Here is the response:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694060388888/a14db7f7-37d9-4b8f-9a97-77452e1e822f.png" alt="GPT4 response about using html_escape" class="image--center mx-auto" /></p><p>Also, the response here is a bit out of place. We are in the context of an ERB file where the code is just printing a variable. There is no SQL query executed. Again while the response sounds logical apparently it is unhelpful. It also shows that once the GPT gets into a direction it hardly self-corrects itself unless instructed to do so.</p><p>I still decided to extract into a component the display of the search and thus have it ready for further UI improvements across all pages.</p><p>You can see the PRs that I implemented with the Cursor IDE at:</p><ul><li><p><a target="_blank" href="https://github.com/ShortRuby/rubyandrails.info/pull/105">Add search component to all other pages</a></p></li><li><p><a target="_blank" href="https://github.com/ShortRuby/rubyandrails.info/pull/107/files">Refactor Search Term Display into a Reusable Component</a></p></li></ul><h2 id="heading-some-temporary-conclusions">Some temporary conclusions</h2><p>I used very simple prompts. Almost no context was given except for the ruby files or code itself. Nor did I ask for some proper follow-ups to nudge it in the desired direction. There was also no instruction about what a good code looks like for me.</p><p>I already had a good idea of what to look for. Thus, it was easy to know when a result was what I expected and when it was not.</p><p>The changes I made were small. Thus, it was easy to assess the code.</p><p>One challenging task is to determine if a response is up-to-date, as demonstrated by the interaction about escaping the parameter.</p><p>Another challenging task is to be able to detect a response that sounds logical, but is out of context and thus would be wrong to be applied in that specific situation.</p><h2 id="heading-update">Update</h2><ol><li><p>In a previous version of this article, I was evaluating the response from GPT-4 which recommends using <code>h</code> to escape the HTML output when using in ERB as good enough. <a target="_blank" href="https://hashref.com">Xavier Noria</a> pointed out that it is not needed to use <code>h</code> as Rails ERB <code>&lt;%=</code> is escaping HTML output by default. I confirmed this by testing and also found the <a target="_blank" href="https://guides.rubyonrails.org/3_0_release_notes.html#other-changes">changelog that mentions this since Rails 3.0</a>.</p></li><li><p>After a second review from <a target="_blank" href="https://hashref.com">Xavier</a> and following a discussion we had I am reconsidering my evaluation of the response about XSS vulnerability from unnecessary to incorrect. Until proven otherwise (by someone else finding a bug in the html_escape) the escape provided by Rails should be considered safe and this should be the correct response from LLM.</p></li></ol><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a>. You can also find me on <a target="_blank" href="https://ruby.social/@lucian">Ruby.social</a> or <a target="_blank" href="https://linkedin.com/in/lucianghinda">Linkedin</a></p>]]></description><link>https://allaboutcoding.ghinda.com/using-cursor-ide-for-some-small-changes-in-a-rails-app</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/using-cursor-ide-for-some-small-changes-in-a-rails-app</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 08 Sep 2023 16:09:22 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1694189086850/959a2e87-c56b-495a-affa-85f7b75c42f6.png</cover_image></item><item><title><![CDATA[Bugs, errors and causes from an 1975 paper]]></title><description><![CDATA[<p>Discover the timeless relevance of a 1975 paper that sheds light on the causes of bugs and errors in system programs.</p><p>In this article, we delve into Albert Endres' findings and explore how understanding the problem, effective communication, and domain knowledge play crucial roles in reducing errors.</p><p>These principles remain highly relevant in current software development practices, where collaboration, clear communication, and comprehensive knowledge of the project's domain could be crucial for the success of the product.</p><h3 id="heading-about-the-paper">About the paper</h3><p>In 1975, <a target="_blank" href="https://ieeexplore.ieee.org/author/37087903653">Albert Endres</a> published a paper called <a target="_blank" href="https://ieeexplore.ieee.org/document/6312834">"An Analysis of Errors and their Causes in System Programs"</a>.</p><p>In this paper, they analyze the errors detected in an operating system DOS/VS developed by IBM Laboratories in Germany.</p><p>The system was released in 1973. The paper analyses:</p><p>- 500 modules, with an average of 360 lines per module and 480 lines of comments, resulting in 190K instructions and 60K comments.</p><p>- A total of 740 problems were found, of which 432 were classified as program errors.</p><h3 id="heading-areas-where-errors-where-found">Areas where errors where found</h3><p>And here are some of the conclusions from this paper:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691661579216/060b5daa-657b-41c9-bbfe-a40cccd1ae2c.png" alt="&quot;Almost half of all errors are found in the area of understanding the problem, problem communication and of the knowledge of possibilities and procedures for problem-solving&quot;" class="image--center mx-auto" /></p><p>If we want to reduce the number of errors then we should focus not only on better programming techniques but also on the problem definition, and understanding domain knowledge:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691661619042/bed763a4-6280-4892-a686-2b8d96a2eb9a.png" alt="&quot;This fact is alarming or encouraging, depending on the expectations we had for a hundred percent automation of software production.  More specifically, only half of the mistakes can be avoided with better programming techniques (better programming languages, more comprehensive test tools). The other half must be attacked with better methods of problem definition (specification languages), a better understanding of basic system concepts (training, education), and by making applicable algorithms available&quot;" class="image--center mx-auto" /></p><p>And based on the paper here are the categories of causes for errors or bugs:</p><p>1. Technological = <em>"definability of the problem, feasibility of solving it, available procedures and tools"</em></p><p>2. Organisational = <em>"division of work load, available in- formation, communication, resources"</em></p><p>3. Historic = <em>"history of the project, of the program, special situations, and external influences"</em></p><p>4. Group dynamic = <em>"willingness to cooperate, distribution of roles inside the project group"</em></p><p>5. Individual = <em>"experience, talent, and constitution of the individual programmer"</em></p><p>6. Other</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691661730037/ac6a15bd-2a23-4328-8c10-9b74359a0f7a.png" alt class="image--center mx-auto" /></p><h2 id="heading-some-conclusions">Some conclusions:</h2><ol><li><p>When considering quality in a software product, we should consider all aspects, not only the tools and programming techniques we use.</p></li><li><p>When joining a product or team, it is important to understand the history of the product &amp; team.</p></li><li><p>To become a better developer one has to learn more than coding/engineering techniques.</p></li><li><p>When we think about a possible future of using AI to augment programming, we should consider that if this study remains true it is not enough to reduce the number of errors. As half of them are not caused by code.</p></li></ol><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a></p>]]></description><link>https://allaboutcoding.ghinda.com/bugs-errors-and-causes-from-an-1975-paper</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/bugs-errors-and-causes-from-an-1975-paper</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 11 Aug 2023 02:41:12 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1691721296253/2e2e77e5-39f4-42c0-98cc-56a3948d38ab.png</cover_image></item><item><title><![CDATA[Insights from Stack Overflow Developer Survey 2023 about Ruby]]></title><description><![CDATA[<h2 id="heading-about-the-most-popular-technology-section">About the Most popular technology section</h2><p><em>Numbers (</em><a target="_blank" href="https://survey.stackoverflow.co/2023/#technology-most-popular-technologies">source</a>):</p><ul><li><p>All respondents: in 2023 Ruby has 6.23% while in 2022 had 6.05%. It also <strong>moved up</strong> a spot in 2023</p></li><li><p>Professional Developers: 2023 is at 6.94% while in 2022 was at 6.72%</p></li><li><p>Learn to code: in 2023 is at 2.55% while in 2022 was at 2.52%</p></li></ul><p><em>What does this mean:</em></p><ul><li><p>Ruby is growing in adoption, with a higher adoption rate for professionals than for beginners.</p></li><li><p>Our efforts to bring new people toward Ruby are working but at a slow pace. Here we need more power, more people and more money and energy to spread the idea to learn Ruby for people that are learning to code.</p></li><li><p>I started to see more new content created addressed to beginners in the last 3 months that might contribute toward the end of this year to more beginners discovering Ruby</p></li></ul><h3 id="heading-top-paying-technologies">Top paying technologies</h3><p><em>Numbers (</em><a target="_blank" href="https://survey.stackoverflow.co/2023/#technology-top-paying-technologies"><em>source</em></a><em>):</em></p><p>Ruby is this year (2023) in 4th position from the top, with an average of <strong>$98.552</strong>, moving up from last year (2022) when it was in the fifth position with <em>$93.000</em></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691304721330/0c6a63ac-f7f3-4b06-8a68-2ac7a7a34f2c.png" alt="Source: https://survey.stackoverflow.co/2023/#technology-top-paying-technologies" class="image--center mx-auto" /></p><p><em>What does this say:</em></p><ul><li><p>Even if they were quite a few layoffs in the Ruby community, the remaining Ruby developers had (as a group) an increase in their compensation</p></li><li><p>Ruby is still among the top 10 top-paying technologies</p></li></ul><h2 id="heading-to-consider">To consider</h2><ul><li><p>This is a survey filled in by people that are using Stack Overflow.</p></li><li><p>This year's survey had more responses than last year's survey, 90.000 this year compared with 70.000 last year and so in some cases (like the growth of Ruby in the Most popular technology category) it means not only a percentage growth but also in absolute terms.</p></li><li><p>(subjective) Stack Overflow is not too friendly to Ruby beginners because there are a lot of old questions with answers not being updated to match the current knowledge, version, and capabilities of both Ruby and Rails in 2023.</p></li></ul><h2 id="heading-what-can-we-do">What can we do?</h2><p>First, when considering the Ruby community as a whole, I believe that there are likely more senior members than junior ones, with a ratio of at least 2:1 or greater. Although I don't have data to support this claim, my impression is based on the online presence of community members in social media, forums, and comments.</p><p>I think there are already projects in the community (both new and old) that are great and successful in addressing the needs of senior programmers. With more to be launched and I discover more and more every week while I curate content for <a target="_blank" href="https://newsletter.shortruby.com">Short Ruby Newsletter</a>.</p><p>As others have pointed out in various discussions this year, <strong>we need to invest more effort in welcoming new people to the ecosystem</strong>. This means creating more content for beginners, more jobs and at-work learning experiences for beginners, and providing learning materials and books for those just starting. A broader range of perspectives and voices will help attract a larger audience.</p><p>In today's rapidly evolving online content, we should also try to find ways to encourage beginners to share their experiences and write tutorials to teach others. We should want to put Ruby on the beginner's radar and talk about why they should pick Ruby as a programming language and then provide learning support.</p><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a></p>]]></description><link>https://allaboutcoding.ghinda.com/insights-from-stack-overflow-developer-survey-2023-about-ruby</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/insights-from-stack-overflow-developer-survey-2023-about-ruby</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 09 Aug 2023 03:07:59 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1691549780456/254f1a1a-f4cc-4d1b-9bb2-5bf6d9cb7cdf.png</cover_image></item><item><title><![CDATA[Projects ideas for learning Ruby or any Ruby web framework]]></title><description><![CDATA[<p><em>I shared this on</em> <a target="_blank" href="https://twitter.com/lucianghinda/status/1354069119704977409"><em>Twitter</em></a> <em>a while back (you can</em> <a target="_blank" href="https://nitter.net/lucianghinda/status/1354069119704977409"><em>read it also on nitter</em></a><em>) and I think it is better to be published on the web instead of just remaining there of course also adding some more context to it.</em></p><p>When I talk with someone that is learning Ruby or Ruby on Rails I usually receive this question: what project should I build when I learn Ruby on Rails?</p><p>The same goes for me: when I am learning something new, I feel the need to build something with it. And it works best if what I am trying to build is as real as possible and not only a "theoretical" abstract learning project.</p><p>In this article, I will try to make the case that it is best to:</p><ol><li><p>Try to solve a problem you have while learning a new programming language or framework</p></li><li><p>If that does not work or if it feels too hard to tackle in the beginning I will present a list of projects that I think are good to be picked up as a learning project</p></li></ol><h2 id="heading-solve-a-problemneed-you-have">Solve a problem/need you have</h2><p>I think from the learning process point of view the best project to work on is to solve a need you have. This works best because it will keep your motivation up and you will also get enthusiastic about seeing that using Ruby will help you directly.</p><h3 id="heading-create-a-script-to-solve-your-problem"><strong>Create a script to solve your problem</strong></h3><p>I recommend starting with this more if you are learning Ruby now and did not yet start to learn a web framework.</p><p>A script that will take some inputs, execute some logic and then output something on the terminal is a small time investment and will let you focus on Ruby and writing idiomatic code instead of dealing with UI.</p><p>I have a lot of scripts written in Ruby. Here are some examples:</p><ul><li><p>parse CSVs and extract information from there: you can do counts, averages, sum ... on some specific fields. I use this for example to parse Stripe exports and some other financial exports and then get quick information like total amount per month, unpaid invoices ...</p></li><li><p>a script that takes a screenshot of a web page and will save it in a specific folder</p></li><li><p>a script that will read the <code>&lt;title&gt;</code> and <code>&lt;meta name="description" content="..."&gt;</code> and will print them out</p></li></ul><p>I can go on with more examples, but I hope you got the idea. Write a small script that is executing some code that will solve one single need.</p><p>How to approach this:</p><ol><li><p>Write a script that will solve your problem and make it as specific as possible. Eg if you are writing a parse CSV script to read an export you have and print a total sum, just write this script based on the CSV file that you currently have. Don't try to make it work with any CSV or try to make it work with columns being moved around or renamed.</p></li><li><p>Then try to redesign the code and improve the code quality</p></li><li><p>Then try to make your script more generic or customizable: like accepting a different CSV file with other columns and other column names.</p></li></ol><p>I know this probably sounds very simple, but trust me. I got so many times into rabbit holes trying to write a script that can handle so many edge cases and be so flexible and I almost every time invented a kind of framework.</p><h3 id="heading-create-a-simple-web-app-to-solve-your-problem"><strong>Create a simple web app to solve your problem</strong></h3><p>Again my recommendation is to look at a problem or need that you have and try to build a simple web app for it.</p><p>The same advice applies:</p><ul><li><p>Just focus on directly solving the problem. Please don't add authentication to it from the beginning or automated deployment or any extra flexibility. Pick one single thing and focus on solving that.</p></li><li><p>Make it work on your computer. Don't bother to create a big architecture that will handle millions of edge cases. That is good learning maybe, but not the first time you learn a web framework.</p></li><li><p>Try to focus on following the guides for that specific web framework. Even if you disagree with some of the things there, it is good to understand first what are the cases that the framework solves and how it tries to solve them before going into making it work for you.</p></li><li><p>Then after you have something working you can now expand your web app to handle more cases.</p></li></ul><p>If you want to pickup some good habits when building a web app, after you did the initial version consider working to improve the following 3 areas:</p><ol><li><p>Accessibility</p></li><li><p>Security</p></li><li><p>Performance</p></li></ol><h2 id="heading-examples-of-web-apps-that-you-can-build">Examples of web apps that you can build</h2><p>The following is a list of possible web apps that you can build as a learning process. I choose them because I think they are easy to be approached and their domain knowledge is not hard to understand.</p><h3 id="heading-weekly-meal-planner">Weekly Meal Planner</h3><p>A web app where users will submit their recipes, food items and then do a meal plan for a week (breakfast, lunch, dinner). Bonus if you can support multiple family members and allow them to vote on which day they liked the most.</p><p>Here is a possible list of features and the order to build them:</p><ol><li><p>Create a new recipe with a title, description, steps, and time to cook</p></li><li><p>Create a Week with an attribute called Title</p></li><li><p>Add multiple recipes to a Week</p></li></ol><p>From here you can add accounts, owners, voting ...</p><h3 id="heading-lists-share">Lists Share</h3><p>An app that allows a user to create a list of things gives a short tile of that list and a description about it, and then after saving it allows the user to share it with anyone. A visitor should be able to see the list without being authenticated.</p><p>Here is a possible list of features and the order to build them:</p><ol><li><p>Create a list with a title, description</p></li><li><p>Add items to a list</p></li><li><p>Create users</p></li></ol><p>From here you can add more complex features:</p><ul><li><p>Display a view counter</p></li><li><p>Add a feature to allow authenticated users to do a thumbs up kind of vote for other users' lists</p></li><li><p>display on your app's home page the top 10 most-seen lists and the last 10 recently created lists</p></li></ul><h3 id="heading-habits-tracker">Habits Tracker</h3><p>Create a web app that will allow the user to set up a list of habits the user wants to track daily and then ask every day user to mark with a check if they made that habit or now that day. Display completion progress for 30 days.</p><p>Here is a possible list of features and the order to build them:</p><ol><li><p>Create a habit</p></li><li><p>Mark a habit as done for today</p></li><li><p>Display a calendar view showing if the habit was completed each day</p></li></ol><p>From here you can add more complex features:</p><ul><li><p>Tracking multiple habits per day</p></li><li><p>Add users</p></li><li><p>Display a graphic about how the completion of each habit worked (see D3 or Charkick JS libraries for inspiration - both have rails gems)</p></li><li><p>Send a monthly email with a summary of progress for each habit.</p></li></ul><h3 id="heading-symptoms-tracker">Symptoms Tracker</h3><p>An app to track symptoms daily. The user should add one or more symptoms by clicking on a specific day and choosing the time and adding a text describing the symptoms. Display a monthly calendar and mark the days with symptoms.</p><p>Here is a possible list of features and the order to build them:</p><ol><li><p>Add a symptom for today from a predefined list</p></li><li><p>Add a custom symptom for the current day</p></li><li><p>Add symptoms in the past: choose any day and add one or many symptoms</p></li></ol><p>From here you can add more complex features like:</p><ol><li><p>Add a tag for the symptoms and categorize them</p></li><li><p>Display the evolution of a tag/symptom during a week/month. Like adding a scale for each symptom</p></li><li><p>Add users support</p></li><li><p>Allow for a user to track symptoms for multiple people (like a partner, child )</p></li></ol><h3 id="heading-swot-analysis">SWOT Analysis</h3><p>Create a web app to allow users to create a SWOT analysis on any topic. This should allow the user to fill in all 4 quadrants and then share the Swot Analysis with other people.</p><p>Here is a possible list of features and the order to build them:</p><ol><li><p>Create a new SWOT analysis and fill in the 4 quadrants</p></li><li><p>Create a unique shareable link for each SWOT analysis</p></li><li><p>Edit SWOT analysis</p></li></ol><p>From here you can add more complex features like:</p><ol><li><p>It should also allow adding comments to each section of the analysis.</p></li><li><p>Add users</p></li><li><p>Allow other users to suggest new lines to be added to each section and let the owner accept it or reject it</p></li></ol><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a></p>]]></description><link>https://allaboutcoding.ghinda.com/projects-ideas-for-learning-ruby-or-any-ruby-web-framework</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/projects-ideas-for-learning-ruby-or-any-ruby-web-framework</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 02 Aug 2023 08:42:29 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1690965437292/f42d0c24-14fe-4282-8299-3a60cc1926c0.png</cover_image></item><item><title><![CDATA[Where to promote an article about Ruby]]></title><description><![CDATA[<p>Somebody asked me in a private message about where can they promote an article they wrote about Ruby.</p><p>Here is where I think you should submit/publish your article to reach a wider audience.</p><h2 id="heading-links-aggregators-andamp-newsletters">Links aggregators &amp; Newsletters</h2><p>Here are some places where I would submit an article:</p><ul><li><p><a target="_blank" href="https://rubyflow.com">rubyflow.com</a> - there is a <a target="_blank" href="https://rubyflow.com/#submitform">submit</a> button there and it is easy to submit it</p></li><li><p><a target="_blank" href="https://rubyland.news">rubyland.news</a> - here you will need to go to <a target="_blank" href="https://rubyland.news/about">About</a> and write an email to the owner of the website asking them to add your RSS feed. Then every time you write your article will automatically appear on Rubyland</p></li><li><p><a target="_blank" href="https://ruby.libhunt.com">ruby.libhunt.com</a> - also here you can submit your article by clicking on the <a target="_blank" href="https://ruby.libhunt.com/contribute">Submit</a> button from the menu</p></li><li><p><a target="_blank" href="https://news.ycombinator.com">news.ycombinator.com</a> - here you can submit your article by going to <a target="_blank" href="https://news.ycombinator.com/submit">Submit</a></p></li><li><p><a target="_blank" href="https://lobste.rs">lobste.rs</a> - here you can submit your article but you will need an invite to create your account</p></li><li><p><a target="_blank" href="https://rubyweekly.com">RubyWeekly</a> - while there is no official way to submit an article for RubyWeekly it is worth trying to suggest your article there as it has the largest audience. One way would be to subscribe to the newsletter and then reply to an issue with your proposal.</p></li><li><p><a target="_blank" href="https://www.rubyradar.com">RubyRadar</a> - you can submit your article to be included in the Ruby Radar newsletter by creating a discussion <a target="_blank" href="https://github.com/therubyradar/submissions/discussions/new/choose">on their Github repo</a></p></li><li><p><a target="_blank" href="https://newsletter.shortruby.com">Short Ruby Newsletter</a> - you can submit your article as an issue to this <a target="_blank" href="https://github.com/ShortRuby/submit">Github Repo</a></p></li><li><p><a target="_blank" href="https://masilotti.com/hotwire/">Hotwire Dev Newsletter</a> - if you write anything related to Hotwire you should send it to <a target="_blank" href="https://masilotti.com/about/">Joe Masilotti</a></p></li></ul><p>In almost all cases for these websites, you will have to create an account or authenticate via Github.</p><h2 id="heading-cross-posting">Cross-posting</h2><p>There are two places where I recommend you should republish your article:</p><ul><li><p><a target="_blank" href="https://dev.to">Dev.to</a> community. They are a great and supportive community for developers and there is even a weekly Rubyist Hangout Thread</p></li><li><p><a target="_blank" href="https://hashnode.com">Hashnode</a> - another quite active community of fellow developers</p></li></ul><p>How this works:</p><ul><li><p>You should just copy/paste your blog content. Both Dev.to and Hashnode support markdown so if you wrote your blog post as markdown publishing it there should be as fast as a copy/pasting and re-uploading some images</p></li><li><p>Then after you copied your content, DO NOT YET publish until you made sure you specified the canonical URL where you write the original URL of your article (the one from your blog)</p></li></ul><p>Here is where you find the canonical URL at Dev.to:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688613523859/c20d4722-bb96-4209-b389-b5e44e6f4253.png" alt="An image showing where to find the input for writing canonical URL" class="image--center mx-auto" /></p><p>Here is where you find the place to write the canonical URL at Hashnode:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688613618792/0e564ae1-cc6c-46a5-89b9-bb5cdf30451f.png" alt="Where to find the input for writing Canonical URL at Hashnode" class="image--center mx-auto" /></p><p>If you want you can do the same for <a target="_blank" href="https://medium.com">Medium</a>. They also support the option to republish and can specify the canonical URL. There is still an audience there that reads technical content so from an authorship perspective if you want to spread your message as wide as possible you can consider publishing there too.</p><h2 id="heading-online-communities">Online Communities</h2><p><a target="_blank" href="https://daily.dev">daily.dev</a></p><p>You can publish on daily.dev and you can ask for your RSS feed to be included automatically on their website. I think they started as an aggregator but they are adding a lot of features that could support a community like Squads.</p><p><a target="_blank" href="https://www.linkedin.com/groups/22413/">Ruby on Rails group on Linkedin</a></p><p>This is I think the biggest group on Linkedin for Ruby and Rails developers. They accept posting your article and usually, people will engage with your content.</p><p><a target="_blank" href="https://www.linkedin.com/groups/62218/"><strong>Web Developers United on Linkedin</strong></a></p><p>This is a group that is not specific to Ruby but there is a Ruby audience there and the group accepts submissions for technical articles.</p><p><a target="_blank" href="https://www.reddit.com/r/ruby/">Reddit /r/ruby</a></p><p>An active community of Ruby developers on Reddit. They are engaging with your articles and will usually reply with direct feedback.</p><p><a target="_blank" href="https://www.reddit.com/r/rails/">Reddit /r/rails</a></p><p>Also an active community of Ruby on Rails developers where you can submit an article and get direct feedback about it.</p><h2 id="heading-other-places">Other places</h2><p>Of course, you should publish this on your social media channels. If you keep publishing Ruby content more people will probably follow you knowing that this is the type of content that you publish.</p><h3 id="heading-what-other-places-to-promote-an-article-about-ruby-do-you-know">What other places to promote an article about Ruby do you know?</h3><p>If you know other places where an author of an article about Ruby can promote their content please let me know in the comments here.</p><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a></p>]]></description><link>https://allaboutcoding.ghinda.com/where-to-promote-an-article-about-ruby</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/where-to-promote-an-article-about-ruby</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 19 Jul 2023 03:26:40 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1688629881068/b4330729-7968-4c8c-8ec4-1a2631e97b08.png</cover_image></item><item><title><![CDATA[About Ruby: pass by value or pass by reference?]]></title><description><![CDATA[<p>Here is a simple code in Ruby:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init_options</span><span class="hljs-params">(options)</span></span>  options = { <span class="hljs-symbol">widget:</span> <span class="hljs-literal">true</span> }<span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_default_config</span><span class="hljs-params">(options)</span></span>  options[<span class="hljs-symbol">:add_on</span>] <span class="hljs-params">||</span>= <span class="hljs-literal">true</span><span class="hljs-keyword">end</span></code></pre><p>Do not focus on the code design. There might be better ways to express this, but I want here to focus on what happens when executing the following statements:</p><pre><code class="lang-ruby">options = { <span class="hljs-symbol">plugin:</span> <span class="hljs-literal">true</span> }add_default_config(options)puts options <span class="hljs-comment"># =&gt; {:plugin=&gt;true, :add_on=&gt;true}</span>init_options(options)puts options <span class="hljs-comment"># =&gt; {:plugin=&gt;true, :add_on=&gt;true}</span></code></pre><p>Notice that after executing <code>init_options</code> the content is still the same even if inside there is a statement that would instantiate a new hash.</p><p>Or take a look at the following code:</p><pre><code class="lang-ruby">options = { <span class="hljs-symbol">widget:</span> <span class="hljs-literal">true</span> }options.tap <span class="hljs-keyword">do</span> <span class="hljs-params">|opt|</span>  opt[<span class="hljs-symbol">:widget</span>] = <span class="hljs-literal">false</span><span class="hljs-keyword">end</span>puts options <span class="hljs-comment"># =&gt; { :widget =&gt; false }</span>new_options = { <span class="hljs-symbol">plugin:</span> <span class="hljs-literal">true</span> }new_options.tap <span class="hljs-keyword">do</span> <span class="hljs-params">|opt|</span>  opt = {}  opt = { <span class="hljs-symbol">plugin:</span> <span class="hljs-literal">false</span> }<span class="hljs-keyword">end</span>puts new_options <span class="hljs-comment"># =&gt; { :plugin =&gt; true }</span></code></pre><p>Why in this case the <code>new_options</code> is not changed after the tap was executed?</p><p>To answer this we will need to understand how objects are passed in Ruby: pass by value or pass by reference?</p><p>Let's dig in to find out how Ruby works when talking about passing objects.</p><h2 id="heading-a-variable-is-a-reference-to-an-object">A variable is a reference to an object</h2><p>To understand this I will execute a series of statements and explain step by step how they work:</p><pre><code class="lang-ruby">options = {}puts options.object_id <span class="hljs-comment"># =&gt; 60</span></code></pre><p>This will do two things:</p><ul><li><p>Creates a new Hash object that in this specific case on my machine has the id <code>60</code></p></li><li><p>Create a new label called <code>options</code> and associate that with the newly created object.</p></li></ul><p>Then calling <code>options.object_id</code> on it will return an integer that identifies uniquely that object during the execution of the program.</p><p>Imagine that Ruby is just creating a link between the label <code>options</code>) and the object that is associated with that label.</p><p>This association might look (visually) like this:</p><pre><code class="lang-md">+-------------+-----------+| label       | object id |+-------------+-----------+| options     | 60        |+-------------+-----------+</code></pre><p>In this case, we can affirm: <em>"options points to object with id 60"</em> or <em>"options references object with id 60"</em>.</p><p>Now I will add a second variable called <code>new_options</code>:</p><pre><code class="lang-ruby">new_options = optionsputs new_options.object_id === options.object_id <span class="hljs-comment"># =&gt; true</span></code></pre><p>This second variable will reference now to the same object as <code>options</code>:</p><pre><code class="lang-md">+-------------+-----------+| label        | object id |+-------------+-----------+| options     | 60        || new<span class="hljs-emphasis">_options | 60        |+-------------+-----------+</span></code></pre><p>What do you think will happen if I start adding more hash keys to either <code>options</code> or <code>new_options</code>?</p><pre><code class="lang-ruby">options[<span class="hljs-symbol">:plugin</span>] = <span class="hljs-literal">true</span>puts options <span class="hljs-comment"># =&gt; { :plugin =&gt;true }</span>puts new_options <span class="hljs-comment"># =&gt; { :plugin =&gt;true }</span>new_options[<span class="hljs-symbol">:widget</span>] = <span class="hljs-literal">true</span>puts options <span class="hljs-comment"># =&gt; { :plugin =&gt;true, :widget =&gt;true }</span>puts new_options <span class="hljs-comment"># =&gt; { :plugin =&gt;true, :widget =&gt;true }</span><span class="hljs-comment"># Are they still the same object?</span><span class="hljs-comment"># Yes, they are.</span>puts new_options.object_id == options.object_id <span class="hljs-comment"># =&gt; true</span></code></pre><p>Because they are both references to the same object, calling a method on any of those variables like for example <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/Hash.html#method-i-5B-5D-3D"><code>[]=</code></a> will be called on the referenced object (in the specific example the object with object_id 60).</p><p>What happens when I try to instantiate a new object and assign that to one of the existing variables?</p><pre><code class="lang-ruby"><span class="hljs-comment"># What if I try to assign the `options` variable to a new object?</span>options = Hash.newputs options.object_id <span class="hljs-comment"># =&gt; 80</span>puts new_options.object_id <span class="hljs-comment"># =&gt; 60</span>puts new_options <span class="hljs-comment"># =&gt; { :plugin =&gt;true, :widget =&gt;true }</span>puts options <span class="hljs-comment"># =&gt; {}</span><span class="hljs-comment"># They are not the same object</span>puts new_options.object_id == options.object_id <span class="hljs-comment"># =&gt; false</span></code></pre><p>In this case the <code>options</code> will point to a new object while <code>new_options</code> is still pointing to the existing one:</p><pre><code class="lang-ruby">+-------------+-----------+<span class="hljs-params">| label       |</span> object id <span class="hljs-params">|+-------------+-----------+|</span> options     <span class="hljs-params">| 80        |</span><span class="hljs-params">| new_options |</span> <span class="hljs-number">60</span>        <span class="hljs-params">|+-------------+-----------+</span></code></pre><p>Here we can draw a couple of lessons:</p><ol><li><p>Variables are references to objects, but not the objects</p></li><li><p>Assigning a new object to a variable will change it to reference the new object</p></li></ol><h2 id="heading-passing-arguments-to-methods">Passing arguments to methods</h2><p>When passing an argument to a method we are in a way creating a local variable inside that method that references the object that is passed.</p><p>Let's start with a simple example:</p><pre><code class="lang-ruby">options = { <span class="hljs-symbol">widget:</span> <span class="hljs-literal">true</span> }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_default_config</span><span class="hljs-params">(options)</span></span>  puts <span class="hljs-string">"Object has id: <span class="hljs-subst">#{options.object_id}</span>"</span><span class="hljs-keyword">end</span>add_default_config(options) <span class="hljs-comment"># =&gt; Object has id: 60</span></code></pre><p>What happens here is that inside the <code>add_default_config</code> method a new variable called <code>options</code> is created and it is assigned to the same object that is passed to the method as a reference. This means that the <code>options</code> variable inside the method is pointing to the same object as the <code>options</code> variable outside the method.</p><p>To represent the references we will need to add a new column that is defining the scope of the variable:</p><pre><code class="lang-plaintext">+--------------------+------------+-----------+| lexical scope      | label      | object id ||--------------------|------------|-----------|| main               | my_options | 60        || add_default_config | options    | 60        |+--------------------+------------+-----------+</code></pre><p>This simply says:</p><ul><li><p>There exists a label called <code>my_options</code> that is referencing an object with id <code>60</code> in the main scope</p></li><li><p>There exists a label called <code>options</code> that is referencing an object with id <code>60</code> in the <code>add_default_config</code> scope or the local scope of the method <code>add_default_config</code></p></li></ul><p>Now let's try to change the object inside the method:</p><pre><code class="lang-ruby">my_options = { <span class="hljs-symbol">widget:</span> <span class="hljs-literal">true</span> }puts my_options.object_id <span class="hljs-comment"># =&gt; 60</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_default_config</span><span class="hljs-params">(options)</span></span>  puts <span class="hljs-string">"Object has id: <span class="hljs-subst">#{options.object_id}</span>"</span>  options[<span class="hljs-symbol">:add_on</span>] <span class="hljs-params">||</span>= <span class="hljs-literal">true</span><span class="hljs-keyword">end</span>add_default_config(my_options) <span class="hljs-comment"># =&gt; Object has id: 60</span>puts my_options <span class="hljs-comment"># =&gt; { :widget =&gt; true, :add_on =&gt; true }</span></code></pre><p>It just works because both the local variable <code>my_options</code> in the main scope and the local variable <code>options</code> in the method scope are referencing the same object. Calling a method by referencing it via any of those two variables will change the same object.</p><p>What happens if I try to assign a new object to the local variable <code>options</code> ?</p><pre><code class="lang-ruby">my_options = { <span class="hljs-symbol">widget:</span> <span class="hljs-literal">true</span> }puts my_options.object_id <span class="hljs-comment"># =&gt; 60</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_default_config</span><span class="hljs-params">(options)</span></span>  puts <span class="hljs-string">"Object has id: <span class="hljs-subst">#{options.object_id}</span>"</span>  options = {}  puts <span class="hljs-string">"Object now has id: <span class="hljs-subst">#{options.object_id}</span>"</span>  options[<span class="hljs-symbol">:add_on</span>] = <span class="hljs-literal">true</span><span class="hljs-keyword">end</span>add_default_config(my_options)<span class="hljs-comment"># will print</span><span class="hljs-comment"># =&gt; Object has id: 60</span><span class="hljs-comment"># =&gt; Object now has id: 80</span>puts my_options <span class="hljs-comment"># =&gt; { :widget =&gt; true }</span></code></pre><p>If we try to assign a new object via hash literal <code>{}</code> inside the method <code>add_default_config</code> that will create a new object and assign that object to the variable called <code>object</code> that exists inside the method scope. This means that the local variable <code>options</code> in the method scope is now referencing a new object with an id <code>80</code> while the local variable <code>my_options</code> in the main scope is still referencing the object with id <code>60</code>.</p><pre><code class="lang-plaintext">+--------------------+------------+-----------+| lexical scope      | label      | object id ||--------------------|------------|-----------|| main               | my_options | 60        || add_default_config | options    | 80        |+--------------------+------------+-----------+</code></pre><h2 id="heading-is-ruby-pass-by-value-or-pass-by-reference">Is Ruby pass-by-value or pass-by-reference?</h2><p>I think the best answer is given by <a target="_blank" href="https://en.wikipedia.org/wiki/Yukihiro_Matsumoto">Yukihiro Matsumoto</a> (Matz) and David Flanagan in the book called <a target="_blank" href="https://www.oreilly.com/library/view/the-ruby-programming/9780596516178/">"Programming Ruby"</a> (O'Reilly, 2008):</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688527578475/b3a154e3-2ebc-4c84-8d22-248947ccc15e.png" alt="&quot;When we work with objects in Ruby, we are really working with object references. It is not the object itself we manipulate but a reference to it. When we assign a value to a variable, we are not copying an object into that variable; we are merely storing a reference to an object into that variable&quot;" class="image--center mx-auto" /></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688530030380/8b38a69c-4bd5-4417-bd4c-ce469109b838.png" alt="&quot;When you pass an object to a method in Ruby, it is an object reference that is passed to the method. It is not the object itself, and it is not a reference to the reference to the object. Another way to say this is that method arguments are passed by value rather than by reference, but that the values passed are object references.  Because object references are passed to methods, methods can use those references to modify the underlying object. These modifications are then visible when the method returns&quot;" class="image--center mx-auto" /></p><p>And I would add to this <a target="_blank" href="https://launchschool.medium.com/object-passing-in-ruby-pass-by-reference-or-pass-by-value-6886e8cdc34a">the following quote</a> that describes how to think about the reference itself and why <code>options = {}</code> inside the method will not change the reference outside the method:</p><p><a target="_blank" href="https://launchschool.medium.com/object-passing-in-ruby-pass-by-reference-or-pass-by-value-6886e8cdc34a"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688530155046/4a082b73-7d83-4be1-a8a7-d9783442375c.png" alt="&quot;While we can change which object is bound to a variable inside of a method, we cant change the binding of the original arguments. We can change the objects if the objects are mutable, but the references themselves are immutable as far as the method is concerned&quot;" class="image--center mx-auto" /></a></p><p>Coming back to the definition of pass-by-value and pass-by-reference, I like to think about it in the following way:</p><ul><li><p>Ruby is <code>pass by value</code> because when passing an argument to a method it will pass a copy of the reference to the object and not the reference itself</p></li><li><p>Thus Ruby is <code>pass by value where the value is a reference to an object</code></p></li></ul><p>And implications of this are:</p><ul><li><p>Because the reference is pointing to an object, changing the object via the reference inside the method will be visible outside the method</p></li><li><p>Because what is passed is a copy of the reference and not the reference/pointer itself, we cannot change the reference. This is why doing <code>parameter = Object.new</code> inside a method will not change the variable outside the method to point to a new object.</p></li></ul><p>Or simply put Ruby is <code>pass-reference-by-value</code> as defined by Robert Heaton in his very nice article <a target="_blank" href="https://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/">Is Ruby pass-by-reference or pass-by-value?</a></p><p>In conclusion, Ruby uses a pass-reference-by-value approach when passing objects to methods. This means that while methods can modify the objects they receive, they cannot change the original references. Understanding this concept is crucial for effectively working with objects in Ruby and avoiding unexpected behaviour.</p><h2 id="heading-more-to-read">More to read</h2><p>If you want to read more about this and see other examples here are some good resources:</p><ul><li><p><a target="_blank" href="https://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value">Is Ruby pass by reference or by value? - StackOverflow</a> (<a target="_blank" href="https://web.archive.org/web/20220522104444/https://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value?noredirect=1&amp;lq=1">Internet Archive</a> version)</p></li><li><p><a target="_blank" href="https://mixandgo.com/learn/ruby/pass-by-reference-or-value">Is Ruby pass-by-reference or pass-by-value?</a> (<a target="_blank" href="https://web.archive.org/web/20230323231901/https://mixandgo.com/learn/ruby/pass-by-reference-or-value">Internet Archive</a> version)</p></li><li><p><a target="_blank" href="https://www.infoq.com/articles/ruby-parameter-passing/">Is Ruby Pass-by-Value Or Pass-by-Reference?</a> (<a target="_blank" href="https://web.archive.org/web/20230616203433/https://www.infoq.com/articles/ruby-parameter-passing/">Internet Archive</a> version)</p></li><li><p><a target="_blank" href="https://launchschool.medium.com/object-passing-in-ruby-pass-by-reference-or-pass-by-value-6886e8cdc34a">Object Passing in Ruby  Pass by Reference or Pass by Value</a> ( <a target="_blank" href="https://archive.is/tQDHg">archive.is</a> version)</p></li><li><p><a target="_blank" href="https://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/">Is Ruby pass-by-reference or pass-by-value?</a> (<a target="_blank" href="https://web.archive.org/web/20230129220622/https://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/">Internet Archive</a> version)</p></li><li><p><a target="_blank" href="http://rubyblog.pro/2017/09/pass-by-value-or-pass-by-reference">Ruby: pass by value or pass by reference</a> (<a target="_blank" href="https://web.archive.org/web/20230325023052/http://rubyblog.pro/2017/09/pass-by-value-or-pass-by-reference">Internet Archive</a> version)</p></li></ul><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>.</strong></p>]]></description><link>https://allaboutcoding.ghinda.com/about-ruby-pass-by-value-or-pass-by-reference</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/about-ruby-pass-by-value-or-pass-by-reference</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 12 Jul 2023 03:36:19 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1688527313372/29dc84e7-001b-4418-bf11-b92563a0c886.png</cover_image></item><item><title><![CDATA[A comparison of multiple generative AI tools when asking for Ruby on Rails code]]></title><description><![CDATA[<p>I asked the following query for multiple AI code generator tools:</p><pre><code class="lang-markdown">Please transform this code:<span class="hljs-code">```&lt;div class="flex justify-center py-6 mt-8 mb-3" role="separator"&gt;  &lt;span class="dot"&gt;&lt;/span&gt;  &lt;span class="dot"&gt;&lt;/span&gt;  &lt;span class="dot"&gt;&lt;/span&gt;&lt;/div&gt;```</span>into a Rails Helper with content tags</code></pre><p>I tried to make the request a mix of simple wording while also providing a small context by specifying the <code>content tags</code> and mentioning <code>Rails Helper</code>.</p><h2 id="heading-chat-gpt">Chat GPT</h2><p>Here is the <a target="_blank" href="https://chat.openai.com">ChatGPT</a> response:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688530493250/ad93ac4a-78e2-404c-81d9-2b83c306da5d.png" alt="ChatGPT response" class="image--center mx-auto" /></p><p>It works, it will display the HTML as required.</p><p>My main review here is that I would use <code>concat</code> instead of <code>join.html_safe</code></p><p>So I did a follow-up:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688531042782/27e44625-a05c-4631-9797-454aaec44960.png" alt="Followup with ChatGPT asking to remove html_safe" class="image--center mx-auto" /></p><p>Of course, this was not what I was looking for, but I also agree that my request was not very specific. I did this intentionally to keep the conversation casual, and not suggest a specific solution. I also notice that it changed the second param of <code>content_tag</code> from an empty string to <code>nil</code>. I did not request this specific change but I assume asking <code>refactor</code> triggered a wider change.</p><p>I again follow up with the request to remove <code>safe_join</code>:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688531139711/98f0ca85-871d-46af-8aa2-cb514030e09f.png" alt="Follow up with ChatGPT asking to remove the safe_join" class="image--center mx-auto" /></p><p>And this indeed is the solution that I think is a bit close to what I wanted. I don't think <code>.map</code> is needed there and I usually like helpers to make things explicit so I would have written 3 lines of <code>concat(content_tag)</code> but I think in general the solution is usable.</p><h2 id="heading-phind">Phind</h2><p><a target="_blank" href="https://www.phind.com">Phind</a> describes themselves as <em>"The AI search engine for developers"</em>.</p><p>Here is the response from Phind:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688531368588/9fda455a-8964-4258-98a6-5d2ddc7a4bae.png" alt="Response from Phind showing a DivHelper that has a method dot_separator" class="image--center mx-auto" /></p><p>I was quite happy with the result:</p><ul><li><p>Good name <code>dot_separator</code></p></li><li><p>I like the idea that it extracted the actual dot to a private method <code>dot_span</code> but I think the name could have been improved to only <code>dot</code>.</p></li><li><p>I like that it used <code>concat</code></p></li><li><p>And I welcome the explicitness of having 3 lines of <code>contact(dot_span)</code> instead of <code>3.times { contact(dot_span) }</code></p></li></ul><p>I did not feel here that I need to follow up so I moved to the next tool.</p><h2 id="heading-cody">Cody</h2><p><a target="_blank" href="https://docs.sourcegraph.com/cody">Cody</a> is an AI assistant from Source Graph - <em>"Cody is an AI coding assistant that writes code and answers questions for you by reading your entire codebase and the code graph"</em></p><p>Here is the first response from Cody:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688532008501/ef7380c3-6eb0-4e06-a625-e5861bad1d20.png" alt="First response from Cody AI" class="image--center mx-auto" /></p><p>This will not work as intended as it will only display one single dot.</p><p>So I said to Cody:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688532070321/21614697-fa59-4c49-b7e0-d24f7dd00039.png" alt="Followup with Cody AI describing that it does not work as it shows one single dot" class="image--center mx-auto" /></p><p>Now this code will not display anything, so I asked Cody to fix it:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688532158833/f168bb1c-cf92-4787-8bc0-eede273c5277.png" alt="Second followup with Cody AI asking to fix the bug where it does not show anything" class="image--center mx-auto" /></p><p>Again this code will not show anything so I asked again Cody to fix it but now I asked also to explain why it does not work. This is a trick with LLMs that will usually give better results because it brings more context about what is not working and the cause of it:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688532242727/e5c28101-b2a0-4ce4-8af1-e94a8d9b7af8.png" alt="Third follow up with Cody AI saying again it does not work and asking to fix it" class="image--center mx-auto" /></p><p>Now this works - it will display 3 dots, but it is still a version that I don't like as it uses <code>html_safe</code>. So I asked for a refactor:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688532408929/e6e97f7d-7381-4d9f-ae4e-d257ae0c03dc.png" alt="Asking Cody AI to refactor to remove usage of html_safe" class="image--center mx-auto" /></p><p>This works and it is a solution that I consider ok(ish).</p><h2 id="heading-github-copilot-chat">Github Copilot Chat</h2><p><a target="_blank" href="https://github.com/github-copilot/chat_waitlist_signup"><strong>Github Copilot Chat</strong></a> is <em>"A ChatGPT-like experience in your editor with GitHub Copilot chat"</em> that I installed in a VScode Insiders version.</p><p>Here is the response I got from it:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688532643905/980c2492-5c6b-4324-9f2f-82821ccc2114.png" alt="Github Copilot Chat response" class="image--center mx-auto" /></p><p>First, this solution works. Then I like that it tried to give a good name <code>separator_dots</code> and I appreciate that it used <code>concat</code> instead of first trying to use <code>html_safe</code></p><p>Copilot Chat is a version of the Open AI GPT-4 model I think but probably has a better system prompt.</p><h2 id="heading-rix">Rix</h2><p><a target="_blank" href="https://hashnode.com/rix">Rix</a> is a Hashnode tool described as <em>"The AI search companion, optimized for developers"</em></p><p>Here is the response I got:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688533066934/ceb2e7d6-774f-45a8-8b91-9ee82c179a43.png" alt="Rix response" class="image--center mx-auto" /></p><h2 id="heading-temporary-conclusion">(temporary) conclusion</h2><p>In the context of Ruby programming, AI code generator tools offer varying results and sometimes require follow-ups to achieve the desired output. While some tools, like Phind and Github Copilot Chat, provide satisfactory solutions quicker, others may need additional guidance. It is essential to thoroughly review the generated code to ensure security and avoid unintended changes.</p><h2 id="heading-word-of-caution">Word of caution</h2><p><strong>Do not feed private information:</strong> I used this with a piece of code that does not show any private information about the project I worked with.</p><p><strong>Be careful what code you feed into these tools:</strong> When using generative AI tools - I only add those to my side projects and most of the time when I know that I will anyhow make the project open source. I would not use them or give them access to a client code base unless explicitly accepted by the client in a contract.</p><p><strong>Be careful of security issues:</strong> Also please be careful of copying/pasting code generated by AI in your project and running that code on servers. Nobody can guarantee that the code does not have security issues.</p><p><strong>Read all the generated code, not just the changed parts:</strong> One last point, if you are asking for refactoring, read every time the entire code and not only the lines that you required to be changed. Sometimes the AI will change some other things that you did not specifically ask for.</p><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>.</strong></p>]]></description><link>https://allaboutcoding.ghinda.com/a-comparison-of-multiple-generative-ai-tools-when-asking-for-ruby-on-rails-code</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/a-comparison-of-multiple-generative-ai-tools-when-asking-for-ruby-on-rails-code</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 05 Jul 2023 12:05:02 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1688558607458/f7fe440f-acb1-4855-b3c4-92aae90ae3a7.png</cover_image></item><item><title><![CDATA[Ruby's range literals and their effect on Rails Active Record queries]]></title><description><![CDATA[<p>The following Active Record query:</p><pre><code class="lang-ruby">User.where(<span class="hljs-symbol">created_at:</span> <span class="hljs-number">1</span>.day.ago...Time.current).to_sql</code></pre><p>will generate this SQL:</p><pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span>  <span class="hljs-string">"users"</span>.*<span class="hljs-keyword">FROM</span>  <span class="hljs-string">"users"</span><span class="hljs-keyword">WHERE</span>  <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> &gt;= <span class="hljs-string">'2023-06-21 06:38:26.330063'</span>  <span class="hljs-keyword">AND</span>   <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> &lt; <span class="hljs-string">'2023-06-22 06:38:26.330184'</span></code></pre><p>While the following Active Record query:</p><pre><code class="lang-ruby">User.where(<span class="hljs-symbol">created_at:</span> Date.current.all_day).to_sql</code></pre><p>will generate the following SQL:</p><pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span>  <span class="hljs-string">"users"</span>.*<span class="hljs-keyword">FROM</span>  <span class="hljs-string">"users"</span><span class="hljs-keyword">WHERE</span>  <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> <span class="hljs-keyword">BETWEEN</span> <span class="hljs-string">'2023-06-21 21:00:00'</span> <span class="hljs-keyword">AND</span> <span class="hljs-string">'2023-06-22 20:59:59.999999'</span></code></pre><p>I noticed this difference while having a discussion with <a target="_blank" href="https://twitter.com/monovertex">Cosmin Stamate</a> and <a target="_blank" href="https://twitter.com/jcsrb">Jakob Cosoroab</a> on a Discord group about data ranges.</p><p>Thus I started asking myself:</p><ul><li><p>Why is there a difference?</p></li><li><p>Why use <code>BETWEEN</code> in the second example or why not use it in the first example?</p></li></ul><p>And had a hint that it must be related to ranges and the inclusion/exclusion of their ends.</p><p>To answer these questions, we need to explore the following concepts:</p><ol><li><p>What is <code>BETWEEN</code> doing in PostgreSQL?</p></li><li><p>What does <code>.all_day</code> do?</p></li><li><p>What is the difference between <code>...</code> and <code>..</code> ?</p></li></ol><h3 id="heading-postgresql-between">PostgreSQL <code>BETWEEN</code></h3><p>This is straightforward. According to the <a target="_blank" href="https://www.postgresql.org/docs/current/functions-comparison.html">PostgreSQL documentation</a> on function comparisons, BETWEEN includes its endpoints:</p><p><a target="_blank" href="https://www.postgresql.org/docs/current/functions-comparison.html"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687489677305/3d317949-9371-444d-9ef6-3e4f5a207b40.png" alt class="image--center mx-auto" /></a></p><h3 id="heading-what-does-allday-do">What does <code>all_day</code> do?</h3><p><code>.all_day</code> seems to be <a target="_blank" href="https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb#L310">defined in ActiveSupport</a>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># Source: https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb#L310</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">all_day</span></span>  beginning_of_day..end_of_day<span class="hljs-keyword">end</span></code></pre><p>So it is a <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/syntax/literals_rdoc.html#label-Range+Literals">range literal</a> that includes its end value.</p><h3 id="heading-what-is-the-difference-between-and">What is the difference between <code>...</code> and <code>..</code> ?</h3><p>Here is what Ruby 3.2 documentation defines <a target="_blank" href="https://docs.ruby-lang.org/en/3.2/syntax/literals_rdoc.html#label-Range+Literals">Range Literals</a>:</p><p><a target="_blank" href="https://docs.ruby-lang.org/en/3.2/syntax/literals_rdoc.html#label-Range+Literals"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687490091376/f1580101-8ebb-4a15-ace5-4e864f02ec18.png" alt class="image--center mx-auto" /></a></p><p>A range has a starting point and an endpoint. Different from the math notation of using <code>(.)</code> to signify that the endpoints are not included and <code>[.]</code> to signify that the endpoints are included, in Ruby both <code>..</code> and <code>...</code> include the start point.</p><p>The difference between them is with regards to the endpoint:</p><ul><li><p><code>..</code> includes its ending value</p></li><li><p><code>...</code> does NOT include its ending value</p></li></ul><pre><code class="lang-ruby">(<span class="hljs-number">1</span>..<span class="hljs-number">2</span>).to_a <span class="hljs-comment"># =&gt; [1, 2]</span>(<span class="hljs-number">1</span>...<span class="hljs-number">2</span>).to_a <span class="hljs-comment"># =&gt; [1]</span></code></pre><p>They even have a method to tell you that:</p><pre><code class="lang-ruby">(<span class="hljs-number">1</span>..<span class="hljs-number">2</span>).exclude_end? <span class="hljs-comment"># =&gt; false</span>(<span class="hljs-number">1</span>...<span class="hljs-number">2</span>).exclude_end? <span class="hljs-comment"># =&gt; true</span></code></pre><h3 id="heading-so-why-is-the-difference-in-sql-for-allday-vs-1dayago-timecurrent">So, why is the difference in SQL for <code>.all_day</code> vs <code>1.day.ago ...Time.current</code></h3><p>The difference is that <code>all_day</code> uses an inclusive end range and <code>1.day.ago ... Time.current</code> is an exclusive end range.</p><pre><code class="lang-ruby">(<span class="hljs-number">1</span>.day.ago...Time.current).exclusive_end? <span class="hljs-comment"># =&gt; true</span>Date.current.all_day.exclusive_end? <span class="hljs-comment"># =&gt; false</span></code></pre><p>Thus in the case when using an inclusive end range, Active Record will generate an SQL statement with <code>BETWEEN</code> and when using an exclusive end range it will use the normal comparison <code>&gt;= AND &lt;</code> SQL statement.</p><p>Let's make the calls explicit. Here is the SQL query when using the exclusive end range:</p><pre><code class="lang-ruby">User.  where(<span class="hljs-symbol">created_at:</span> <span class="hljs-number">1</span>.day.ago.beginning_of_day ... <span class="hljs-number">1</span>.day.ago.end_of_day).  to_sql<span class="hljs-comment"># will generate the following statement</span><span class="hljs-comment"># please keep in mind this is executed with </span><span class="hljs-comment"># Europe/Bucharest set as time zone</span>SELECT <span class="hljs-string">"users"</span>.* FROM <span class="hljs-string">"users"</span>     WHERE     <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> &gt;= <span class="hljs-string">'2023-06-21 21:00:00'</span>     AND     <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> &lt; <span class="hljs-string">'2023-06-22 20:59:59.999999'</span></code></pre><p>Here is the query using inclusive end range:</p><pre><code class="lang-ruby">User.  where(<span class="hljs-symbol">created_at:</span> <span class="hljs-number">1</span>.day.ago.beginning_of_day .. <span class="hljs-number">1</span>.day.ago.end_of_day).  to_sql<span class="hljs-comment"># will generate the following statement</span><span class="hljs-comment"># please keep in mind this is executed with </span><span class="hljs-comment"># Europe/Bucharest set as time zone</span>SELECT <span class="hljs-string">"users"</span>.* FROM <span class="hljs-string">"users"</span>     WHERE     <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> BETWEEN <span class="hljs-string">'2023-06-21 21:00:00'</span> AND <span class="hljs-string">'2023-06-22 20:59:59.999999'</span></code></pre><h3 id="heading-what-about-using-arel">What about using AREL?</h3><p>It will do the same thing:</p><pre><code class="lang-ruby">users = User.arel_tableusers.  project(Arel.star).  where(    users[<span class="hljs-symbol">:created_at</span>].between(<span class="hljs-number">1</span>.day.ago.beginning_of_day ... <span class="hljs-number">1</span>.day.ago.end_of_day)  ).to_sql<span class="hljs-comment"># will print</span>SELECT <span class="hljs-string">"users"</span>.* FROM <span class="hljs-string">"users"</span>     WHERE     <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> &gt;= <span class="hljs-string">'2023-06-21 21:00:00'</span>     AND     <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> &lt; <span class="hljs-string">'2023-06-22 20:59:59.999999'</span><span class="hljs-comment"># while using inclusive end range</span>users.  project(Arel.star).  where(    users[<span class="hljs-symbol">:created_at</span>].between(<span class="hljs-number">1</span>.day.ago.beginning_of_day .. <span class="hljs-number">1</span>.day.ago.end_of_day)  ).to_sql<span class="hljs-comment"># will print</span>SELECT <span class="hljs-string">"users"</span>.* FROM <span class="hljs-string">"users"</span>     WHERE     <span class="hljs-string">"users"</span>.<span class="hljs-string">"created_at"</span> BETWEEN <span class="hljs-string">'2023-06-21 21:00:00'</span> AND <span class="hljs-string">'2023-06-22 20:59:59.999999'</span></code></pre><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>.</strong></p>]]></description><link>https://allaboutcoding.ghinda.com/rubys-range-literals-and-their-effect-on-rails-active-record-queries</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/rubys-range-literals-and-their-effect-on-rails-active-record-queries</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 28 Jun 2023 09:40:26 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1687852997681/b8ceb6e1-0262-409c-a34f-c384ea7d6f88.png</cover_image></item><item><title><![CDATA[Why do I include a Community section in Short Ruby News?]]></title><description><![CDATA[<p>If you browse one of the latest editions of <a target="_blank" href="https://newsletter.shortruby.com">Short Ruby News</a>, you will notice that each one has a Community section and that is the first section of the newsletter.</p><p>Here is a short explanation of why I always include a Community section:</p><p>I think the community is as important as the code when I think about the Ruby language. They are both a side I love about Ruby and want to see it grow.</p><p>I find it inspiring to see new projects dedicated to the community or to see new events launched. I think we should support and encourage people that are doing projects that improve our community.</p><p>Events are a great way to form new connections and out of these connections, I am sure new things might come.</p>]]></description><link>https://allaboutcoding.ghinda.com/why-do-i-include-a-community-section-in-short-ruby-news</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/why-do-i-include-a-community-section-in-short-ruby-news</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Mon, 19 Jun 2023 13:58:18 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1687183048220/b3e146bd-85da-4e9e-803c-8ee4a9cd3257.png</cover_image></item><item><title><![CDATA[A simple idea of how to grow the Ruby community]]></title><description><![CDATA[<p>As a followup from my article yesterday about <a target="_blank" href="https://allaboutcoding.ghinda.com/why-write-technical-content-on-a-blog-and-not-only-on-social-media">Why write technical content on a blog and not only on social media</a> about how I think we can grow the Ruby community:</p><p><strong>Write more technical content about Ruby.</strong></p><h2 id="heading-why-will-this-help">Why will this help?</h2><p>First, more content means more reach and thus will offer more people the chance to learn about Ruby and how we solve problems with Ruby.</p><p>Second, it will contribute to having more learning resources. And as more people will write, more diverse writing styles will be and thus better chances for someone to find something that fits their learning style.</p><p>Third, will increase the business/tech leads exposure to Ruby and solutions created in Ruby.</p><p>On the long term, publishing continuously will have a compound effect on growing our Ruby community. </p><h2 id="heading-what-to-write-about">What to write about?</h2><p>Anything about how to use Ruby to solve problems. If you don't have any idea, just take any old article and write a fresh version of it.</p><p>Here are some more ideas:</p><ul><li><p>You can write a small introduction to how to use a web framework, like Rails or Hanami 2 or Sinatra or Roda any other web framework</p></li><li><p>Or you can write how you solved something in your daily job where you work with Ruby. Tell us the problem and the solution</p></li><li><p>Write about how you think people that are learning Ruby or Rails or Hanami or Roda should approach their learning path</p></li><li><p>Write about how you are writing Ruby code, what is important for you and what you like about Ruby</p></li></ul><p>I assure you, YOU have something to write about. Even if other people wrote about the same topic, you must write your own article in your own style. This will add value to the conversation about that specific topic by providing your point of view or writing style.</p><p>In general, I recommend you read this blog post by Julia Evans -&gt; <a target="_blank" href="https://jvns.ca/blog/2023/06/05/some-blogging-myths">Some Blogging Myths</a></p><h3 id="heading-maybe">Maybe</h3><p>You will say that what's the point of writing such articles now that we have LLMs and maybe people will go to ChatGPT for learning Ruby.</p><p>If we don't write fresh content then the next generation of LLMs will be trained on less Ruby content and so it might be less fit to answer questions about Ruby in the future.</p><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>.</strong></p>]]></description><link>https://allaboutcoding.ghinda.com/a-simple-idea-of-how-to-grow-the-ruby-community</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/a-simple-idea-of-how-to-grow-the-ruby-community</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 15 Jun 2023 11:11:26 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1686827202511/d4b25070-61ba-4f15-8dcc-b02a87a967a1.jpeg</cover_image></item><item><title><![CDATA[Why write technical content on a blog and not only on social media]]></title><description><![CDATA[<p>I often recommend that people publish their social media content as a blog post or a GitHub gist if they don't have a blog.</p><p>Here are the reasons:</p><ul><li><p><strong>Content Resilience</strong>: No matter what happens on the social media platform, your content will remain available for everybody. Take, for example, the migration that happened with some users who left Twitter and closed their accounts. Their content is no longer easily accessible. Maybe it was archived by the Internet Archive, or maybe not.</p></li><li><p><strong>Don't depend on a platform to reach your audience</strong>: Limiting your content sharing to any (or more) social media platform makes you dependent on it. If any issues arise with your account, such as being blocked, you will lose the ability to communicate with your audience.</p></li><li><p><strong>Safeguard against unjust or erroneous termination or suspension of your social media account:</strong> Picture the worst-case scenario where an incident occurs and your account gets suspended. Suddenly, your content and, consequently, your voice will no longer be heard.</p></li></ul><p>These reasons also apply to any website where you use a subdomain (eg: username.example.com) or path (eg: example.com/username) rather than your own domain.</p><h2 id="heading-what-to-do">What to do</h2><ol><li><p>Get a domain</p></li><li><p>Create a blog with your domain or find a platform that offers this feature</p></li><li><p>Ensure your blog has RSS support</p></li><li><p>When you write quality content on social media, also post it on your blog. You can do this before or after sharing on social media.</p></li><li><p>Make sure you have a backup setup for your own content</p></li><li><p>(optional) Add newsletter support so people can get your content in their email when you post something.</p></li></ol><p>Optional:</p><ul><li><p>I recommend you choose a blogging engine/platform where you can write your content in markdown format or at least they can export the content in markdown. This will ensure you can easily migrate between blog engines without any effort to rewrite your content</p></li><li><p>I also recommend choosing a blogging engine that will help you with some kind of automatic backup. Here I like to set up a flow that will create a commit with the markdown files in Github.</p></li></ul><p>That's it. Now your content is safeguarded from any dependency on a specific platform.</p><h2 id="heading-simple-recommendations">Simple recommendations</h2><p>Regarding the selection of a blog engine and its setup, I will share my recommendations here. However, please note that these suggestions may not necessarily suit everyone's needs.</p><h3 id="heading-build-everything-yourself">Build everything yourself</h3><p>If you want to have a different UI or your blog to look in a very specific way I recommend using <a target="_blank" href="https://jekyllrb.com">Jekyll</a> or <a target="_blank" href="https://www.bridgetownrb.com">Bridgetown</a>.</p><p>With both options, you can directly host your blog on GitHub. See documentation <a target="_blank" href="https://www.bridgetownrb.com/docs/bundled-configurations#github-pages-configuration">here for Bridgetown</a> and documentation <a target="_blank" href="https://pages.github.com">here for Jekyll</a>.</p><h3 id="heading-use-an-existing-blogging-platform">Use an existing blogging platform</h3><p>I am using it for this blog <a target="_blank" href="https://hashnode.com">Hashnode</a> because they offer two options that I like a lot:</p><ul><li><p>custom domains so I can set up my domain for this blog</p></li><li><p>save to Github - Hashnode offers the option to back up what you write to Github and also publish from Github. <a target="_blank" href="https://townhall.hashnode.com/start-using-github-to-publish-articles-on-hashnode">Checkout this option</a>. This way everything that I publish here is saved on <a target="_blank" href="https://github.com/lucianghinda/allaboutcoding">my Github repo</a>.</p></li></ul><p>They also recently added newsletter support and sponsorship.</p><p>If you are the kind of person (like me) that will spend days customizing their theme and then configure servers and then start building new features for a blogging platform I suggest you choose an existing blogging platform and just focus on writing.</p><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>.</strong></p>]]></description><link>https://allaboutcoding.ghinda.com/why-write-technical-content-on-a-blog-and-not-only-on-social-media</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/why-write-technical-content-on-a-blog-and-not-only-on-social-media</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 14 Jun 2023 07:44:59 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1686628581492/150bf307-9da1-4435-8ae0-e984d8dda588.png</cover_image></item><item><title><![CDATA[This week focus: Refactoring]]></title><description><![CDATA[<p>Look at your code and identify any areas where you can improve. Refactoring isn't about fixing bugs but improving your code's readability, efficiency, and maintainability.</p><p>Look for complex methods that could be broken down, repeated code that could be extracted into its method or class, and any design patterns that could be better applied.</p><p>But mostly, ask yourself: How much effort will it take a new colleague to understand what this code does?</p><p>For inspiration, take a look at these eloquent words from Yukihiro Matz (from Beautiful Code book). I get back to reading that chapter from time to time when I contemplate refactoring and how I write Ruby code:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686636650247/4a7273fd-a96f-466f-83e2-e6953b9450ad.png" alt="A quote from Yukihiro Matz about beautiful code" class="image--center mx-auto" /></p><h2 id="heading-start-small">Start small</h2><p>1. Open a file</p><ol><li><p>Identify a method that you think is complex or hard to understand</p></li><li><p>Make sure you have a test that covers functionally what that piece of code should do (could also be indirect)</p></li><li><p>Choose one thing that you want to improve: make it smaller or readable or better naming ...</p></li><li><p>Refactor it</p></li><li><p>Ask for a review with at least a focus on readability</p></li></ol><p>Of course if you have more time then there are other strategies to approach refactoring. But in case you don't have too much dedicated time, let's focus this week in doing some bit and pieces of refactoring.</p><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com/"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com/"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="http://rubyandrails.info"><strong>rubyandrails.info</strong></a><strong>.</strong></p>]]></description><link>https://allaboutcoding.ghinda.com/this-week-focus-refactoring</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/this-week-focus-refactoring</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Tue, 13 Jun 2023 08:04:18 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1686646472094/1b926af4-98d3-40ca-bb91-64e3faf16e17.png</cover_image></item><item><title><![CDATA[This week focus: Documentation]]></title><description><![CDATA[<p>Whenever you open a piece of code try to find a place where you can describe how that class or that feature can be used by fellow developers.</p><p>Focus on usage examples or use cases.</p><p><strong>You can start small with a PR description.</strong></p><p>Add some examples of usage in the PR description. This will be an easy start without any pressure, and it can be edited as required based on feedback from your colleagues.</p><p><strong>Add examples using YARD</strong></p><p>Another way is to use YARD structure to write before a class or method directly:</p><pre><code class="lang-ruby"><span class="hljs-comment"># <span class="hljs-doctag">@example</span> How to make this use case work</span><span class="hljs-comment">#    </span><span class="hljs-comment">#    t = http://MyObect.new(...)</span><span class="hljs-comment">#    t.run(email_param: "test")</span><span class="hljs-comment">#</span></code></pre><p>Try to write an example that, when copied/pasted to <code>irb</code> or <code>rails console</code> will work.</p><p>I like this because most IDEs know how to show YARD examples while writing the code and thus you will have examples of usage for your classes while typing.</p><p><strong>Write documentation in markdown format</strong></p><p>If you don't like to write documentation inside your code files, put your documentation outside your code files.</p><p>The easiest is to create a <code>./docs</code> folder inside your project and write in a markdown file. Both Github and Gitlab already know how to render markdown files thus you will have browsable and searchable documentation without any extra effort.</p><p>In case you want something more fancy <a target="_blank" href="https://rust-lang.github.io/mdBook/index.html">mdBook</a> looks very good for providing a way to display this documentation.</p><h3 id="heading-the-best-is-to-start-anywhere">The best is to start anywhere</h3><p>There are maybe other ways to write documentation. Focus this week on this. You will thank yourself in a couple of weeks if you will need to change the code you wrote today if you wrote documentation about it somewhere.</p><hr /><p>Enjoyed this article?</p><p>Join my <a target="_blank" href="https://shortruby.com"><strong>Short Ruby News</strong></a> <strong>newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <a target="_blank" href="https://lintingruby.com"><strong>LintingRuby</strong></a>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <a target="_blank" href="https://rubyandrails.info"><strong>rubyandrails.info</strong></a>.</p>]]></description><link>https://allaboutcoding.ghinda.com/this-week-focus-documentation</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/this-week-focus-documentation</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Mon, 05 Jun 2023 08:04:15 GMT</pubDate></item><item><title><![CDATA[Stay up to date with new Ruby or Rails features]]></title><description><![CDATA[<p>Inspired by this post from Ruby for all:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/rubyforall/status/1660641879170625536">https://twitter.com/rubyforall/status/1660641879170625536</a></div><p> </p><p>Here is one possible way to stay up to date with the latest Ruby features or latest Rails or any other framework changes.</p><h3 id="heading-experiment-with-a-new-feature"><strong>Experiment with a new feature</strong></h3><p>Go to <a target="_blank" href="https://rubyreferences.github.io/rubychanges">https://rubyreferences.github.io/rubychanges</a> and then pick one of the changes introduced in Ruby 3.0, 3.1 or 3.2. Pick the version that matches one of the current projects you are actively working on.</p><p>After choosing one new feature or change, try for a couple of weeks to write code using that feature. Don't force it but also try to reach for it more often.</p><p>Without playing with a new language feature it is very hard to think about cases where it fits and cases where maybe it does not fit.</p><p>Yes, sometimes your colleagues might not accept that in a PR. What I do sometimes is replace the new feature with the old code before submitting the PR.</p><p>You might think, why do this? Because it is important to work with new features in real projects and try to write real solutions.</p><h3 id="heading-draft-a-proposal-to-adopt-a-new-feature">Draft a proposal to adopt a new feature</h3><p>After playing around with a new feature, try to summarise your learnings into a draft proposal to adopt this new feature.</p><p>The draft should have at least 3 sections:</p><ul><li><p><strong>Why this feature?</strong> Where you should describe what this feature brings on, what solutions will it open for you and your team</p></li><li><p><strong>When to use it.</strong> Here you should describe examples of good usage for the new feature. Where does it fit best, and how the code might look when using it</p></li><li><p><strong>When not to use it.</strong> Here you should describe examples of bad usage of the new feature. Where do you think it should not be used and why?</p></li></ul><p>Send this to your colleagues or publish them on your blog. It will be great to contribute this way back to the Ruby community.</p><h3 id="heading-do-the-same-with-new-rails-features-or-hanami-2-or-roda-or-any-other-framework">Do the same with new Rails features or Hanami 2 or Roda or any other framework</h3><p>You can do the same with Rails or Hanami 2 or Roda or any other web framework.</p><ol><li><p>Go to their Github/Gitlab project</p></li><li><p>Checkout the latest PRs merges to <code>main</code></p></li><li><p>Change your Gemfile to point to the <code>main</code> branch</p></li><li><p>Choose one PR that was merged and try to use the changes/features proposed there to implement a solution</p></li></ol><h2 id="heading-share-your-learnings">Share your learnings</h2><p>It would be great to share your insights with the public.</p><p>Write a blog post where you talk about what you learned, how you used a new feature, what problems you encountered and when and how you think it should be used.</p><p>If you don't have a blog, then write a public gist and add it to Github.</p><p>In case you cannot do any of the above just share it via social media.</p><p>It will of course be great if you can combine all: write a blog post and then share it via social media.</p><hr /><p>Enjoyed this article? </p><p>Join my <strong><a target="_blank" href="https://shortruby.com">Short Ruby News</a> newsletter</strong> for weekly Ruby updates. Also, check out my co-authored <strong>book</strong>, <strong><a target="_blank" href="https://lintingruby.com">LintingRuby</a></strong>, for insights on automated code checks. For more Ruby <strong>learning resources</strong>, visit <strong><a target="_blank" href="https://rubyandrails.info">rubyandrails.info</a></strong>.</p>]]></description><link>https://allaboutcoding.ghinda.com/stay-up-to-date-with-new-ruby-or-rails-features</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/stay-up-to-date-with-new-ruby-or-rails-features</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 31 May 2023 08:26:39 GMT</pubDate></item><item><title><![CDATA[Changing the Parent Resource Parameter Key in Rails]]></title><description><![CDATA[<p>Today I wanted to achieve the following:</p><p><strong>Change the key for the parent resource when accessed in the nested resources</strong></p><pre><code class="lang-bash"><span class="hljs-comment"># instead of</span>/collections/:collection_id/entries<span class="hljs-comment"># wanted article_id</span>/collections/:article_id/entries</code></pre><p>I also wanted to have the EntriesController scoped within the <code>Collections</code> module:</p><pre><code class="lang-ruby"><span class="hljs-comment"># instead of </span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EntriesController</span></span>...<span class="hljs-keyword">end</span><span class="hljs-comment"># wanted</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Collections::EntriesController</span></span>...<span class="hljs-keyword">end</span></code></pre><h2 id="heading-what-did-not-worked">What did not worked</h2><p>I first tried to use the <code>param</code> option for the <code>resources</code>. See documentation <a target="_blank" href="https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources">here</a></p><pre><code class="lang-ruby">resources <span class="hljs-symbol">:collections</span>, <span class="hljs-symbol">only:</span> %i(show) <span class="hljs-symbol">param:</span> <span class="hljs-symbol">:article_id</span> <span class="hljs-keyword">do</span>  resources <span class="hljs-symbol">:entries</span>, <span class="hljs-symbol">only:</span> %i(index), <span class="hljs-class"><span class="hljs-keyword">module</span>: :<span class="hljs-title">collections</span></span><span class="hljs-keyword">end</span></code></pre><p>But it does not work.</p><p>If you run <code>rails routes -g collections</code> you will notice the following response:</p><pre><code class="lang-bash"><span class="hljs-comment"># route helper </span>collection_entries<span class="hljs-comment"># HTTP route</span>GET /collections/:collection_article_id/entries<span class="hljs-comment"># controller</span>Collections::EntriesController</code></pre><p>Notice <code>collection_article_id</code> inside the route <code>/collections/:collection_article_id/entries</code> which is not what I wanted:</p><pre><code class="lang-diff"><span class="hljs-deletion">- /collections/:collection_article_id/entries</span><span class="hljs-addition">+ /collections/:article_id/entries</span></code></pre><h2 id="heading-solution-use-as">Solution: use <code>as</code></h2><p>The option <code>as</code> allows to change the route helper and it seems that this way Rails will also change the name of the param key when using nested resources.</p><pre><code class="lang-ruby">resources <span class="hljs-symbol">:collections</span>, <span class="hljs-symbol">only:</span> %i(show) <span class="hljs-symbol">as:</span> <span class="hljs-symbol">:articles</span> <span class="hljs-keyword">do</span>  resources <span class="hljs-symbol">:entries</span>, <span class="hljs-symbol">only:</span> %i(index), <span class="hljs-class"><span class="hljs-keyword">module</span>: :<span class="hljs-title">collections</span></span><span class="hljs-keyword">end</span></code></pre><p>Will generate:</p><pre><code class="lang-bash"><span class="hljs-comment"># route helper </span>article_entries<span class="hljs-comment"># HTTP route</span>GET /collections/:article_id/entries<span class="hljs-comment"># controller</span>Collections::EntriesController</code></pre><p>I am not sure if this is a bug or not in how the <code>param</code> option works.</p>]]></description><link>https://allaboutcoding.ghinda.com/changing-the-parent-resource-parameter-key-in-rails</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/changing-the-parent-resource-parameter-key-in-rails</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 05 May 2023 11:50:45 GMT</pubDate></item><item><title><![CDATA[A method's gravity]]></title><description><![CDATA[<h3 id="heading-what-is-method-gravity">What is method gravity?</h3><p>What I call <strong>Method Gravity</strong> means for me:</p><blockquote><p>The bigger the method the more new lines of code will be added to it.</p></blockquote><p>I noticed this while working on various projects: once a method grows, the chances that the next developer will add more lines to it increase.</p><p>There is a tipping point (like with the gravity of a start) when a method is too big, it will collapse, meaning someone will take it apart and split it into smaller methods.</p><h3 id="heading-what-is-a-long-method-and-what-is-a-short-method">What is a long method, and what is a short method?</h3><p>I am not sure there exists a definition of what is a long method and what is a short method. This is subjective (depending on individual or team preferences).</p><p>I could say this (citing Sandi Metz): anything bigger than 5 lines of code could be considered a long method.</p><p>But I think a better definition could be along the following lines:</p><ul><li><p>If you need to read twice, a method to remember/understand each line of it is long.</p></li><li><p>If you need to read it twice to understand what it does is (probably) too long (or could use some renaming)</p></li></ul><p>Thus <em>a short method can be understood quickly at a glance</em>.</p><h3 id="heading-the-main-problems-with-long-methods">The main problems with long methods</h3><p>There are three main problems:</p><ol><li><p>It makes it easy to break Single Responsibility Principle: a long method tends to do many things, and to summarize it to a main purpose means to keep expanding that goal until it reaches a high complexity</p></li><li><p>It makes it easy to break Cohesion: a long method will tend to do unrelated things resulting in unwanted coupling</p></li><li><p>A long method tends to favor hard-to-read algorithms or too creative naming to avoid collision or variable name re-use</p></li></ol><h3 id="heading-benefits-of-short-methods">Benefits of short methods</h3><ol><li><p>Simplicity</p></li><li><p>Single Responsibility Principle: short methods are easy to focus on one single thing and thus are also easy to describe in a simple way</p></li><li><p>Limits the number of changes: when you have to change something, you can isolate the change to a small method</p></li><li><p>Easy to test: a small method is easy to test</p></li></ol><p>But for me, the most significant benefit for the developer is that using smaller methods forces us to write better names.</p><p>Let's take an example <em>(please read this example as pseudo-code, I will not focus here on language specifics, and the purpose of the example is to show the high-level code design and not focus on specifics)</em></p><p>If you look at the following code, can you say quickly what the result might be?</p><pre><code class="lang-ruby"><span class="hljs-comment"># input = [{ "slug" =&gt; "one_day", "language" =&gt; "en"} =&gt; [ { "id" =&gt; 10, "status" =&gt; "booked" }]]</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform</span><span class="hljs-params">(input)</span></span>  events_hash = input.flat_map { _1.keys }  event_slugs = events_hash.collect { _1[<span class="hljs-string">"slug"</span>] }  events = Event.where(<span class="hljs-symbol">slug:</span> event_slugs)  registrations_hash = input.flat_map {_1.values }.flatten  registration_ids = registrations.collect { _1[<span class="hljs-string">"id"</span>] }  registrations = Registration.where(<span class="hljs-symbol">id:</span> registration_ids)  input.transform_keys! { <span class="hljs-params">|key|</span> events.find { <span class="hljs-params">|e|</span> e.slice(<span class="hljs-symbol">:event_type</span>, <span class="hljs-symbol">:language</span>) == key } }  input.transform_values { <span class="hljs-params">|value|</span> registrations.find { <span class="hljs-params">|r|</span> r.slice(<span class="hljs-symbol">:id</span>, <span class="hljs-symbol">:status</span>) == value} }<span class="hljs-keyword">end</span></code></pre><p>You can probably guess with a bit an effort. And I bet that letting 2 weeks pass and looking at this again, you might still need a small effort to remember.</p><p>What about the following code:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform</span><span class="hljs-params">(input)</span></span>  keys_to_event_objects(input)  values_to_registration_objects(input)<span class="hljs-keyword">end</span></code></pre><p>I think reading this second version of the function would give you a good idea about what <code>transform</code> the method is doing.</p><p>You might wonder what the full code looks like (some spaces and returns were removed to keep the code terse in this article):</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform</span><span class="hljs-params">(input)</span></span>  keys_to_event_objects(input)  values_to_registration_objects(input)<span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">keys_to_event_objects</span><span class="hljs-params">(hash)</span></span> = hash.transform_keys! { event(_1) }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">values_to_registration_objects</span><span class="hljs-params">(hash)</span></span> = hash.transform_values { registration(_1) }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">event</span><span class="hljs-params">(key)</span></span> = events.find { _1.slice(<span class="hljs-symbol">:slug</span>, <span class="hljs-symbol">:language</span>) == key }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">registration</span><span class="hljs-params">(value)</span></span> = registrations.find { _1.slice(<span class="hljs-symbol">:id</span>, <span class="hljs-symbol">:status</span>) == value }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">events</span><span class="hljs-params">(input)</span></span> = @events <span class="hljs-params">||</span>= load_events(from_event_slugs(input))<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">load_events</span><span class="hljs-params">(slugs)</span></span> = Event.where(<span class="hljs-symbol">slug:</span> slugs)<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">from_event_slugs</span><span class="hljs-params">(input)</span></span> = input_keys(input).collect { _1[<span class="hljs-string">"slug"</span>] }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">input_keys</span><span class="hljs-params">(input)</span></span> = input.flat_map { _1.keys }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">registrations</span><span class="hljs-params">(input)</span></span> = @registrations <span class="hljs-params">||</span>= load_registrations(from_registration_ids(input))<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">load_registrations</span><span class="hljs-params">(ids)</span></span> = Registration.where(<span class="hljs-symbol">id:</span> ids)<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">from_registration_ids</span><span class="hljs-params">(input)</span></span> = input_values(input).collect { _1[<span class="hljs-symbol">:id</span>] }<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">input_values</span><span class="hljs-params">(input)</span></span> = input.flat_map { _1.values }.flatten</code></pre><p>This code has a huge advantage: most of the changes you can think of would be limited to a small function. You can achieve the same result with normal methods containing one single line.</p><p>Example of possible changes:</p><ul><li><p>include/eager_load on <code>Event</code> / <code>Registration</code> if more attributes need to be used further down the road, =&gt; change the <code>load_*</code> methods</p></li><li><p>speed up the <code>find_*</code> by ordering records in some specific way (eg, if in general, the input will have the most recent registrations/events) =&gt; change the <code>load_*</code> methods</p></li><li><p>adding/removing keys from the input =&gt; change <code>event</code> or <code>registration</code> methods</p></li><li><p>say the API will decide to return <code>id</code> for events instead of <code>slug</code> this is a breaking change, but still the changes in code are limited to smaller areas =&gt; rename <code>from_event_slugs</code>, change the key inside, and then change in <code>events</code></p></li></ul><p>For all these changes, the <code>transform</code> method does <strong>not</strong> change. This is good because the main algorithm (get hash, map keys to objects, map registration to objects) remains the same.</p><p>With small methods, we achieve what Sandi Metz describes as the purpose of design: <em>reduce the cost of change</em>.</p><h3 id="heading-ideas-to-keep-methods-small">Ideas to keep methods small</h3><p>Some simple things that could help to keep the method gravity small in Ruby:</p><p><strong>Use endless methods</strong></p><pre><code class="lang-ruby"><span class="hljs-comment"># events_controller.rb</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span> = <span class="hljs-title">render</span> <span class="hljs-title">locals</span>: { <span class="hljs-title">event</span>: }</span></code></pre><pre><code class="lang-ruby"><span class="hljs-comment"># time_rules.rb</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">past?</span><span class="hljs-params">(event)</span></span> = event.finish_time &lt;= Time.current</code></pre><p>Why:</p><ul><li>Because once a method is endless, it takes a more significant effort to add another line (you need to make it a normal method)</li></ul><p><strong>Use guard clauses</strong></p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">ensure_access_is_allowed</span></span>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> current_user.admin?  current_user.allowed_to?(<span class="hljs-symbol">:access</span>, event)<span class="hljs-keyword">end</span></code></pre><p>Why:</p><ul><li>Because adding another branch to a guard clause takes a bit of effort, it means transforming the guard clause into a normal <code>if</code> condition</li></ul><p><strong>Use conditional at the end of the statement</strong></p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span></span>  set_context <span class="hljs-keyword">if</span> context_given?  render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span> }<span class="hljs-keyword">end</span></code></pre><p>Why:</p><ul><li>Because also, here, adding another branch means a bit more effort to transform the end of the statement <code>if</code> into a normal <code>if</code></li></ul><h3 id="heading-more-general-advice">More general advice</h3><p>In general, I like the advice from a <a target="_blank" href="https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37755.pdf">paper published by Google</a>, called "Searching for Build Debt: Experiences Managing Technical Debt at Google"</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675946765425/2b0b9f22-2ee8-4b66-b5b9-2d1ca20c185e.png" alt class="image--center mx-auto" /></p><p>So the main idea, if you want to keep the methods small, is to make it hard for you or a colleague to make them bigger in the future. Add a bit of antigravitational force to them.</p><p>The same happens in the example I showed above: using endless methods is a bit hard to add one more line to them. You can still rename them and easily change what each of them is doing.</p><h3 id="heading-in-case-you-like-this-recommendation-from-sandi-metz">In case you like this recommendation from Sandi Metz:</h3><blockquote><p>Methods can be no longer than five lines of code</p></blockquote><p>Then you know what method has always fever than 5 lines of code?</p><p><strong>-&gt; an endless method</strong></p><h3 id="heading-disclaimer">Disclaimer</h3><p>I wrote this as general advice. I agree there are cases when a long method makes sense. Maybe. But that should be an exception.</p><p><em>Apply with care! Moderation is key. Do not abuse!</em></p><p>Even Sandi Metz <a target="_blank" href="https://www.rubypigeon.com/posts/methods-can-be-longer-than-five-lines/">says</a> (regarding her famous rules):</p><blockquote><p>There are actually six rules, and the sixth rule is that you can break any of the first five, as long as you can get your pair to agree. Why is it that were such cargo culters about it? Its like, thats the rule that people forget.</p></blockquote><hr /><h3 id="heading-updates">Updates</h3><ol><li><p>I removed <code>hash = input.pluck(:KEYS).to_h</code> from the initial method and directly called transform_* methods on <code>input</code>. Reason: it does not add any information that is useful for the purpose of the article</p></li><li><p>There are some good comments in this <a target="_blank" href="https://www.reddit.com/r/ruby/comments/10yx6wz/a_methods_gravity/">Reddit thread</a> and I am planning to update the examples in this article. The comments and alternative solutions proposed there at about the code example. But the main idea of my article I think remains the same: a long method will attract more code. </p></li></ol><hr /><p>If you like this type of content, you may want to consider subscribing to my curated newsletter <a target="_blank" href="https://newsletter.shortruby.com">Short Ruby News</a> where I cover weekly Ruby news from around the internet.</p><div class="hn-embed-widget" id="shortruby"></div>]]></description><link>https://allaboutcoding.ghinda.com/a-methods-gravity</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/a-methods-gravity</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 10 Feb 2023 16:12:15 GMT</pubDate></item><item><title><![CDATA[We should adopt and use new Ruby features]]></title><description><![CDATA[<h3 id="heading-a-bit-of-personal-history">A bit of (personal) history</h3><p>I learned Ruby, I think, around 2007, and back then, it was a language pushing boundaries for me. Before encountering Ruby, I worked with Java, C/C++, PHP, and Python, and even dig into the strange world of JS (back then when <a target="_blank" href="https://madrobby.github.io/scriptaculous/">Scriptaculous</a> and <a target="_blank" href="https://en.wikipedia.org/wiki/Prototype_JavaScript_Framework">Prototype.js</a> were the hot JS libraries and jQuery was appearing).</p><p>In the beginning, <em>Ruby looked strange, made people uncomfortable when reading the syntax, and created emotion</em>.</p><p>I remember when I read the first time code written in Ruby (1.8.6) was very strange for me: no <code>;</code>, optional parenthesis, using <code>unless</code>, being able to write <code>if</code> at the end of a statement, the strange lack of <code>for</code>, <code>while</code> , <code>repeat</code> and the continued usage of <code>each</code> this was just the tip of the iceberg along with Rails 1.2 and later on Rails 2.</p><p>Ruby looked very alien compared with other typical languages that I was using. For me, it was a language that created its own category, very close to the English language and with pseudocode.</p><h3 id="heading-where-are-we-now"><strong>Where are we now?</strong></h3><p>In my opinion, we are a bit too conservative with adopting new features. This attitude is good for projects built with Ruby; it comes from experience and wisdom. We tried many things over all these years, and we settled on what works and does not.</p><p>We say Ruby is boring or Rails is boring, and that's good for organizations and their projects.</p><p>At the same time, I feel that we slowed the pace of innovation for code design in the projects built with, Ruby. We are still experimenting, but as far as I see (limited experience of N=1), the adoption rate for new features is slow. There are still features released three years ago (e.g Ruby 2.7 in 2019) for which we still have rules/guidelines against them or limiting usage. See as example numbered params.</p><p>Projects built with Ruby were walking in new territories, playing with how code looks, pushing the language boundaries, and bringing art and beauty into how code looks.</p><p>But then we settled. We wanted to protect the fantastic thing we discovered.</p><p>By protecting it, we are also taking away its light.</p><p>I want Ruby to experiment with more features. And I trust Matz and the other core committers that they still have the same great taste for a great language.</p><h3 id="heading-why-use-new-features-and-language-constructs">Why use new features and language constructs</h3><p>Learning a new language feature is very similar to learning new words, cultural constructions, or expressions in a speaking language.</p><p>Thus my argument for learning and using new features/new language concepts is based on the theory of linguistic relativity and the hypothesis of linguistic determinism.</p><p><em>In the same way, words or the language (we speak) shape our thinking and our reality, programming language constructs or features shape how we define a problem and the solution that we code for that problem.</em></p><p>Fundamentally, when we write code, we model reality into another universe that can be described with a limited/fixed set of words or constructs.</p><p>This modeling is very similar to speaking another language. The more you know from the secondary language (dictionary, grammar, rules, cultural compositions), the better you can express your thoughts. Of course, this goes the same with your maternal language.</p><p>The same goes for knowing a programming language. The more you know (and use) the entire set of language constructs/features, the better you can express your solution.</p><p>Here is an example:</p><ul><li><p>In Ruby - we have <code>&amp;&amp;</code> and <code>||</code>, but we also have <code>and</code> and <code>or</code>. They can be used in various contexts to express different intentions and to control the flow.</p></li><li><p>If you decide only to use <code>&amp;&amp;</code> and <code>||</code>, then how you think about the solution (the algorithm) will be shaped only by how <code>&amp;&amp;</code> and <code>||</code> behave.</p></li><li><p>Thus even if the reality that you want to moderate might be better expressed in some cases with <code>and</code> and <code>or</code> you will force it to fit into <code>&amp;&amp;</code> and <code>||</code>.</p></li></ul><p>Talking specifically about Ruby, there are more examples of new features or changes from Ruby 2.7+ that, for me, are changing the way I write code or at least it changes (sometimes) the way I think about code design:</p><ul><li><a target="_blank" href="https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rdoc.html">Pattern Matching</a></li><li><a target="_blank" href="https://rubyreferences.github.io/rubychanges/2.7.html#numbered-block-parameters">Numbered block params</a></li><li><a target="_blank" href="https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range">Beginless range</a></li><li><a target="_blank" href="https://rubyreferences.github.io/rubychanges/3.0.html#endless-method-definition">Endless methods</a></li><li><a target="_blank" href="https://rubyreferences.github.io/rubychanges/3.0.html#arguments-forwarding--supports-leading-arguments">Arguments forwarding</a></li><li><a target="_blank" href="https://rubyreferences.github.io/rubychanges/3.1.html#moduleprivate-public-protected-and-module_function-return-their-arguments">private, public, protected returning their arguments</a></li><li><a target="_blank" href="https://zverok.space/blog/2023-01-03-data-initialize.html">The new Data class</a></li><li><a target="_blank" href="https://rubyreferences.github.io/rubychanges/3.1.html#kernelload-module-as-a-second-argument">Kernel.load adding accepting new param to evaluate code in context of a module</a></li><li><a target="_blank" href="https://rubyreferences.github.io/rubychanges/3.1.html#values-in-hash-literals-and-keyword-arguments-can-be-omitted">Hash literal value omission</a></li><li><a target="_blank" href="[](https://bugs.ruby-lang.org/issues/18685)">Enumerator.product</a></li></ul><p>And this is not a comprehensive list of all the exciting things that are happening Ruby in the last years. </p><hr /><p>In the end, here are some starting points about linguistic relativity and linguistic determinism:</p><ul><li>Lera Boroditsky, <a target="_blank" href="https://www.scientificamerican.com/author/lera-boroditsky/">How Language Shapes Thought</a>, Scientific American, 2011:</li></ul><blockquote><p>"These are just some of the many fascinating findings of cross-linguistic differences in cognition. But how do we know whether differences in language create differences in thought, or the other way around? The answer, it turns out, is boththe way we think influences the way we speak, but the influence also goes the other way. The past decade has seen a host of ingenious demonstrations establishing that language indeed plays a causal role in shaping cognition. Studies have shown that changing how people talk changes how they think. Teaching people new color words, for instance, changes their ability to discriminate colors. And teaching people a new way of talking about time gives them a new way of thinking about it"</p></blockquote><ul><li>Harriet Joseph Ottenheimer and Judith M.S. Pine, <strong><em>The Anthropology of Language: An Introduction to Linguistic Anthropology,</em> Fourth Edition,</strong> 2018</li></ul><blockquote><p>"What is important to recognize, even more than the idea that you might think or perceive the world differently depending on the language you speak, is that before you can really use a new language comfortably, without thinking about what you are saying, you need to wrap your mind around the new concepts that the new language is presenting to you"</p><p>"According to cognitive linguists, the words we use createand are used withinframes. The idea of frames is similar to the idea of worldview. We view the world through frames. Frames often invoke cultural metaphors, grouping ideas into commonly used phrases. As such, they often invoke an ideology, or a set of ideas we have about the way things should be.</p><p>[...]</p><p>It is more difficult to talk about, and perhaps even to think about, something that you have no frame for in your language. Cognitive scientists call this hypocognition, or lack of the ideas that you need for talking or thinking about something"</p></blockquote><hr /><p>If you like this type of content, then maybe you want to consider subscribing to</p><ul><li>my curated newsletter <a target="_blank" href="https://newsletter.shortruby.com/"><strong>Short Ruby News</strong></a> where I cover weekly Ruby news from all around the internet.</li></ul><iframe src="https://newsletter.shortruby.com/embed" width="800" height="320" style="border:1px solid #EEE"></iframe><ul><li>or to my personal newsletter below</li></ul>]]></description><link>https://allaboutcoding.ghinda.com/we-should-adopt-and-use-new-ruby-features</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/we-should-adopt-and-use-new-ruby-features</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 03 Feb 2023 14:25:20 GMT</pubDate></item><item><title><![CDATA[In defense of Ruby's short-hand syntax]]></title><description><![CDATA[<p><em>This is primarily a response</em> <a target="_blank" href="https://www.reddit.com/r/rails/comments/10f09zp/comment/j4uev23/?context=3"><em>to a Reddit comment</em></a> <em>but I have encountered this line of pushback against short-hand syntax multiple times. Thus I think it is good to write it down and explain my support for short-hand syntax or, in general, for new language features</em></p><p>First: I disagree that shorthand syntax is code golfing. It makes the code shorter, but in my mind, it does not take away readability.</p><p>Second, in support of the short-hand syntax in my argument, my main argument is that people don't like it because they are not familiar with it. It is new thus not used that much in codebases.</p><h2 id="heading-a-metaphor">A metaphor</h2><p>Using shorthand syntax is very similar to, for example, using <code>attr_accessor: var</code> - it means understanding something abstract because we rely on a-priory information that we take for granted in the programming language.</p><p>Taking it for granted means we assume it is readable because we learned the concept, and we can recognize it even when it is described succinctly, and moreover this kind of code is present in many places thus, it is very familiar.</p><p>Here are other examples: <code>flat_map</code> or even <code>map</code> are features that were once new, but we learned how they work, we see them constantly and we now accept them as being readable because we are familiar with them.</p><p>Thus this shorthand syntax hides complexity into a bigger abstraction. The same way <code>attr_accessor</code> is doing.</p><p>I can easily imagine 20 years ago someone saying:</p><blockquote><p>I don't want to use <code>attr_accessor</code> because it is not clear that the object will have <code>def var=</code> and <code>def var</code> methods defined.</p></blockquote><p>But as we become more and more comfortable with the new information (i.e. knowing that when we write<code>attr_accessor</code> we are creating the getters and setters), we accept it as being readable.</p><h2 id="heading-about-short-hand-syntax">About short-hand syntax</h2><p>I think the same goes for this line:</p><pre><code class="lang-ruby">render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span> }</code></pre><p>It is a shorthand of:</p><pre><code class="lang-ruby">render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span> user }</code></pre><p>And the moment we upload/know/recall in our mind that <code>{ user: }</code> means that there is a variable or method named <code>user</code> defined - it is just another way to hide complexity and provide brevity.</p><h2 id="heading-in-defense-of-brevity">In defense of brevity</h2><p>Regarding the idea of readability + Ruby + better code, here the opinions can be subjective.</p><p>Thus I will rely on Matz words from "Treating Code As an Essay":</p><p>First, to lay out a bit the foundation that Matz is talking about readable code as being beautiful code:</p><blockquote><p>Computers can, of course, deal with complexity without complaint, but this is not the case for human beings. Unreadable code will reduce most people's productivity significantly. On the other hand, easily understandable code will increase it. And we see beauty in such code.</p><p>[...]</p><p>beautiful code is really meant to help the programmer be happy and productive. This is the metric I use to evaluate the beauty of a program.</p></blockquote><p>and then here is what he says about what makes code beautiful:</p><blockquote><p>Brevity is one element that helps make code beautiful. As Paul Graham says, "Succinctness is power." In the vocabulary of programming, brevity is a virtue. Because there is a definite cost involved in scanning code with the human eye, programs should ideally contain no unnecessary information.</p><p>[...]</p><p><strong>Brevity can also mean the elimination of redundancy</strong>. Redundancy is defined as the duplication of information. When information is duplicated, the cost of maintaining consistency can be quite high. And because a considerable amount of time can be spent maintaining consistency, redundancy will lower programming productivity.</p></blockquote><h2 id="heading-short-hand-syntax-helps-with-brevity">Short-hand syntax helps with brevity</h2><p>Thus for me, the short-hand syntax is a beautiful example of the elimination of redundancy:</p><p><strong>We don't need to repeat the same name</strong></p><pre><code class="lang-ruby">render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span> user, <span class="hljs-symbol">records:</span> records, <span class="hljs-symbol">page:</span> page }</code></pre><p>and</p><p><strong>We don't need to rely on naming tricks when we don't want to repeat the name like prefixes</strong> <code>get_</code> <code>fetch_</code> ...</p><pre><code class="lang-ruby">render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span> fetch_user, <span class="hljs-symbol">records:</span> fetch_records }</code></pre>]]></description><link>https://allaboutcoding.ghinda.com/in-defense-of-rubys-short-hand-syntax</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/in-defense-of-rubys-short-hand-syntax</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 18 Jan 2023 10:01:14 GMT</pubDate></item><item><title><![CDATA[Refactoring instance variables to local variables in Rails controllers]]></title><description><![CDATA[<p>I like to use as much as possible the new features in Ruby. In this case, I will show how to take advantage of <a target="_blank" href="https://dev.to/baweaver/ruby-3-1-shorthand-hash-syntax-first-impressions-19op">shorthand hash syntax</a> whileThe on changing from instance variables to local variables in controllers and views.</p><h2 id="heading-initial-code">Initial code</h2><p>Say you have the following code in a controller action:</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/controllers/books_controller.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span></span>    @user = user    @books = books    <span class="hljs-keyword">if</span> params[<span class="hljs-symbol">:author</span>].present?      @books = @books.where(<span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>])    <span class="hljs-keyword">end</span>    @books = @books.page(params[<span class="hljs-symbol">:page</span>]) <span class="hljs-keyword">if</span> params[<span class="hljs-symbol">:page</span>].present?  <span class="hljs-keyword">end</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span></span>      @_user <span class="hljs-params">||</span>= User.find(params[<span class="hljs-symbol">:user_id</span>])    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span> = <span class="hljs-title">user</span>.<span class="hljs-title">books</span></span><span class="hljs-keyword">end</span></code></pre><p>Where <code>.search_by_title</code> is defined somewhere on the model (it is not essential for this part).</p><h2 id="heading-code-review-guidelines">Code review guidelines</h2><p>Guidelines that I will follow here:</p><p><strong>The main purpose of an action in the controller is to render a response.</strong></p><p>I like the idea of keeping the code there at a minimum and as close as possible to everything related to <code>render</code>. Any other logic/flows should be in the method.</p><p>Why? SRP and limiting the changes. Here is a short example:</p><blockquote><p>Image you need will need to support anothe type of search, not only by title.</p><p>In this case, why change the <code>index</code> action? Did something changed with regards of what data (thinking about structure, types...) are rendered or how it is rendered? <strong>No</strong>. So there should not be a need to change something in the action itself.</p></blockquote><p><strong>The data between a controller and view should only be transferred via local variables, not instance variables.</strong> This is a bit bigger topic to cover, and I will probably write another article explaining this in detail.</p><p>Briefly, there are three main reasons:</p><ul><li><p>(1) undefined controller instance variables are <code>nil</code> thus will not throw <code>NameError</code> but silently be nil =&gt; causing business logic errors when used in conditionals (a condition if false because the variable is undefined, not because the query returned nil)</p></li><li><p>(2) instance variables leak into all partials referenced, including all nested ones =&gt; it creates direct dependencies to the action in the controller instead of making partials dependent only on the call</p></li><li><p>(3) instance variables can be added by using concerns and callbacks and can quickly become hard to track what instance variable is available in a view and what is the source for that data</p></li></ul><h2 id="heading-code-review">Code Review</h2><p>Considering the guidelines, I can say the following things about the initial code:</p><ul><li><p>The <code>index</code> the method is doing too many =&gt; breaks SRP -&gt; has logic to create Action Record scopes, instantiates some variables from those methods, and paginates if needed</p></li><li><p>The <code>index</code> method uses instance variables to pass data to the view</p></li><li><p>If I need to change the search =&gt; I need to change something in <code>index</code> and it should not be the case because changing the search query (for example, from <code>where</code> to scopes) should not change the way rendering works or the logic in the view.</p></li><li><p>If I need to change the way pagination works =&gt; I need to change something in the <code>index</code> and again, it should not be the case as changing the way pagination works does not change the logic of rendering nor the view logic</p></li></ul><h2 id="heading-refactoring">Refactoring</h2><h3 id="heading-simplify-the-index-method"><strong>Simplify the</strong> <code>index</code> <strong>method</strong></h3><p>It should only call the render and pass along local variables. So it might look like this:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span></span>    render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span>, <span class="hljs-symbol">books:</span> }  <span class="hljs-keyword">end</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span></span>      @_user <span class="hljs-params">||</span>= User.find(params[<span class="hljs-symbol">:user_id</span>])    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span></span>    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>Now <code>index</code> only makes two calls <code>user</code> and <code>books</code> to obtain all data needed, and it will pass that data to the <code>app/views/books/index.html.erb</code> as local variables. This is a very good and simple action method. It does one thing: taking care of rendering and passing the data.</p><p><code>user</code> method exists, and it is simple enough. But I will need to update the <code>books</code> method to return the correct query.</p><p>Also, I am using shorthand hash syntax to write shorter code:</p><pre><code class="lang-diff">def index<span class="hljs-deletion">-    render locals: { user: user, books: books }</span><span class="hljs-addition">+    render locals: { user:, books: }</span>end</code></pre><p>I will apply one more trick:</p><ul><li>Because I don't want other developers or myself in the future to add logic to <code>index</code> I will transform it into an endless method. This will make the method a little bit more closed to change, which is good because now this method is straightforward and brief, and we want to keep it as long as possible in this form.</li></ul><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span> = <span class="hljs-title">render</span> <span class="hljs-title">locals</span>: { <span class="hljs-title">user</span>:, <span class="hljs-title">books</span>: }</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span></span>      @_user <span class="hljs-params">||</span>= User.find(params[<span class="hljs-symbol">:user_id</span>])    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span></span>    <span class="hljs-comment"># TBD</span>    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h3 id="heading-moving-logic-to-books"><strong>Moving logic to</strong> <code>books</code></h3><p><code>books</code> should also be as simple as possible. Based on the initial code, this method should return the list of books based on some criteria:</p><ul><li><p>should be user books</p></li><li><p>filtered by author if <code>params[:author]</code> is present</p></li><li><p>paginate when if <code>params[:page]</code> is present</p></li></ul><p>Thus I can say this method could look something like this:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span></span>  user.books.    <span class="hljs-keyword">then</span> { filter_by_author(_1) }.    <span class="hljs-keyword">then</span> { paginate(_1) }<span class="hljs-keyword">end</span></code></pre><h3 id="heading-single-responsibility-principle">Single Responsibility Principle</h3><p>I also want to keep the satisfying SRP. If I need to change, for example, something related to author search or page parameters without affecting the general algorithm, then I should not need to change this method. Thus logic for search and paginate should live in their methods:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span></span>  user.books.    <span class="hljs-keyword">then</span> { filter_by_author(_1) }.    <span class="hljs-keyword">then</span> { paginate(_1) }<span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter_by_author</span><span class="hljs-params">(books)</span></span>  <span class="hljs-keyword">return</span> books <span class="hljs-keyword">unless</span> params[<span class="hljs-symbol">:author</span>].present?  books.where(<span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>])<span class="hljs-keyword">end</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">paginate</span><span class="hljs-params">(books)</span></span>  <span class="hljs-keyword">return</span> books <span class="hljs-keyword">unless</span> params[<span class="hljs-symbol">:page</span>].present?  books.page(params[<span class="hljs-symbol">:page</span>])<span class="hljs-keyword">end</span></code></pre><p>In practice, I want to make sure that when a change is needed, it should be limited to only the method related to the scope of the change.</p><p>An example of such change could be that I define on Book in a new scope like:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span> &lt; ActiveRecord</span>  scope <span class="hljs-symbol">:sorted_by_author</span>, -&gt; (author) { where(<span class="hljs-symbol">author:</span> author).order(<span class="hljs-symbol">author:</span> <span class="hljs-symbol">:asc</span>, <span class="hljs-symbol">created_at:</span> <span class="hljs-symbol">:desc</span>)}<span class="hljs-keyword">end</span></code></pre><p>To roll out this change to the <code>BooksController</code> I only need to change the <code>filter_by_author</code> method leaving everything else unchanged, thus limiting the scope of the change.</p><h2 id="heading-final-result">Final result</h2><p>Here is how the code looks at the end:</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/controllers/books_controller.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span> = <span class="hljs-title">render</span> <span class="hljs-title">locals</span>: { <span class="hljs-title">user</span>:, <span class="hljs-title">books</span>: }</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span></span>      @_user <span class="hljs-params">||</span>= User.find(params[<span class="hljs-symbol">:user_id</span>])    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span></span>      user.books.        <span class="hljs-keyword">then</span> { filter_by_author(_1) }.        <span class="hljs-keyword">then</span> { paginate(_1) }    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter_by_author</span><span class="hljs-params">(books)</span></span>      <span class="hljs-keyword">return</span> books <span class="hljs-keyword">unless</span> params[<span class="hljs-symbol">:author</span>].present?      books.where(<span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>])    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">paginate</span><span class="hljs-params">(books)</span></span>      <span class="hljs-keyword">return</span> books <span class="hljs-keyword">unless</span> params[<span class="hljs-symbol">:page</span>].present?      books.page(params[<span class="hljs-symbol">:page</span>])    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><h2 id="heading-alternatives">Alternatives</h2><p>There was a good discussion on <a target="_blank" href="https://www.reddit.com/r/rails/comments/10f09zp/refactoring_instance_variables_to_local_variables/">/r/rails</a> about this article with some good points against various things I presented here.</p><p>So here are some alternatives of how the cood looks like:</p><h3 id="heading-move-logic-from-controller-to-books-without-going-deeper">Move logic from controller to <code>books</code> without going deeper</h3><pre><code class="lang-ruby"><span class="hljs-comment"># app/controllers/books_controller.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span> = <span class="hljs-title">render</span> <span class="hljs-title">locals</span>: { <span class="hljs-title">user</span>:, <span class="hljs-title">books</span>: }</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span></span>      @_user <span class="hljs-params">||</span>= User.find(params[<span class="hljs-symbol">:user_id</span>])    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span></span>      @_books <span class="hljs-params">||</span>= <span class="hljs-keyword">begin</span>        user_books = user.books        user_books = user_books.where(<span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>]) <span class="hljs-keyword">if</span> params[<span class="hljs-symbol">:author</span>].present?        user_books = user_books.page(params[<span class="hljs-symbol">:page</span>]) <span class="hljs-keyword">if</span> params[<span class="hljs-symbol">:page</span>].present?        user_books      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>My review of this is that I feel somehow that <code>books</code> is doing too much, but in the same time its scope can be summarised as querying user books based on some conditions.</p><p>Usually when I have to choose between a longer method or multiple shorter methods I choose shorter methods becuase I think it allows the reader to decide if they want to go down to details or if they want to read the code at a higher level.</p><h3 id="heading-using-a-query-object">Using a query object</h3><p>This solution is based on the one provided by <a target="_blank" href="https://www.reddit.com/r/rails/comments/10f09zp/comment/j4wvlmh">markevich's comment</a>:</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/controllers/books_controller.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span> = <span class="hljs-title">render</span> <span class="hljs-title">locals</span>: { <span class="hljs-title">user</span>:, <span class="hljs-title">books</span>: }</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span> = <span class="hljs-title">query_service</span>.<span class="hljs-title">user</span></span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span> = <span class="hljs-title">query_service</span>.<span class="hljs-title">books</span></span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query_service</span></span>      @_query_service <span class="hljs-params">||</span>= UserWithBooksQuery.call(        <span class="hljs-symbol">user_id:</span> params[<span class="hljs-symbol">:user_id</span>],        <span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>],        <span class="hljs-symbol">page:</span> params[<span class="hljs-symbol">:page</span>]      )    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># app/services/user_with_books_query.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserWithBooksQuery</span></span>  UserWithBooksContract = Struct.new(<span class="hljs-symbol">:user</span>, <span class="hljs-symbol">:books</span>, <span class="hljs-symbol">keyword_init:</span> <span class="hljs-literal">true</span>)  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">call</span><span class="hljs-params">(<span class="hljs-symbol">user_id:</span>, <span class="hljs-symbol">author:</span>, <span class="hljs-symbol">page:</span>)</span></span>    user = User.find(user_id)    books = user.books    books = books.where(<span class="hljs-symbol">author:</span> author) <span class="hljs-keyword">if</span> author    books = books.paginate(page) <span class="hljs-keyword">if</span> page    UserWithBooksContract.new(<span class="hljs-symbol">user:</span>, <span class="hljs-symbol">books:</span>)  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>This is a good solution worth exploring if you/your team are using services. I did not present this from the start as I know quite a few teams that are using services in a specific ways (only to call external services), and they use models for everything else. For this article, it does not matter where the logic is hidden.</p><h3 id="heading-procedural-action">Procedural action</h3><p>This proposal was added by <a class="user-mention" href="https://hashnode.com/@katafrakt">Pawe witkowski</a> as a comment to this article and something similar was proposed by <a target="_blank" href="https://www.reddit.com/r/rails/comments/10f09zp/comment/j4w5hiu">elementboarder on /r/rails</a></p><p>And the main idea is that the action method should describe the logic in a procedural way</p><p><strong>Option 1:</strong></p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span></span>    user = User.find(params[<span class="hljs-symbol">:user_id</span>])    books = get_filtered_books    render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span>, <span class="hljs-symbol">books:</span> }  <span class="hljs-keyword">end</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_filtered_books</span></span>      @_books <span class="hljs-params">||</span>= <span class="hljs-keyword">begin</span>        user_books = user.books        user_books = user_books.where(<span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>]) <span class="hljs-keyword">if</span> params[<span class="hljs-symbol">:author</span>].present?        user_books = user_books.page(params[<span class="hljs-symbol">:page</span>]) <span class="hljs-keyword">if</span> params[<span class="hljs-symbol">:page</span>].present?        user_books      <span class="hljs-keyword">end</span>    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>I think this works. The only comment I have here is that it brings the need to invent these <code>get_&lt;name&gt;</code> or <code>fetch_&lt;name&gt;</code> to go eliminate the name collision with other methods or variables. So I think for example naming <code>get_filtered_books</code> to books and using short-hand syntax will remove the need to use the <code>get</code> prefix.</p><p><strong>Option 2</strong></p><pre><code class="lang-ruby"><span class="hljs-comment"># app/controllers/books_controller.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span></span>    user = User.find(params[<span class="hljs-symbol">:user_id</span>])    books = user.books.filter_by(<span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>]).page(params[<span class="hljs-symbol">:page</span>])    render <span class="hljs-symbol">locals:</span> { <span class="hljs-symbol">user:</span>, <span class="hljs-symbol">books:</span> }  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># app/models/books</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span> &lt; ApplicationRecord</span>  scope <span class="hljs-symbol">:filter_by</span>, -&gt; (author) { where(<span class="hljs-symbol">author:</span> author) }<span class="hljs-keyword">end</span></code></pre><p>The move of the query logic to the model makes a lot of sense to me. I did not do that in my refactoring as I started with an example where the author put the logic in the controller and I only wanted to cleanup the controller. But moving it to model is the right way to go. This is also very short so it is a very good candidate for a good solution.</p><h2 id="heading-simplicity">Simplicity</h2><p>If moving the logic to model is an option then I think the following solution is among the simplest and concise ones:</p><pre><code class="lang-ruby"><span class="hljs-comment"># app/controllers/books_controller.rb</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BooksController</span> &lt; ApplicationController</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span> = <span class="hljs-title">render</span> <span class="hljs-title">locals</span>: { <span class="hljs-title">user</span>:, <span class="hljs-title">books</span>: }</span>  private    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span></span>      @_user <span class="hljs-params">||</span>= User.find(params[<span class="hljs-symbol">:user_id</span>])    <span class="hljs-keyword">end</span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">books</span></span>      @_books <span class="hljs-params">||</span>= user.books.filter_by(<span class="hljs-symbol">author:</span> params[<span class="hljs-symbol">:author</span>]).page(params[<span class="hljs-symbol">:page</span>])    <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span><span class="hljs-comment"># app/models/books</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span> &lt; ApplicationRecord</span>  scope <span class="hljs-symbol">:filter_by</span>, -&gt; (author) { where(<span class="hljs-symbol">author:</span> author) }<span class="hljs-keyword">end</span></code></pre><h3 id="heading-conclusion">Conclusion</h3><p>The comments received show the power of code review. It is great to see many more ideas and principles that people use to refactor code and how while having the same objective (make the code maintainable and readable) we can identify different solutions.</p><hr /><p><em>Thanks to</em> <a target="_blank" href="https://www.linkedin.com/in/cristiantone"><em>Cristian Tone</em></a> <em>for reading various drafts of this article and giving me valuable feedback.</em></p><hr /><p>Updates:</p><ol><li><p>As pointed out by pirokar13 <a target="_blank" href="https://www.reddit.com/r/rails/comments/10f09zp/comment/j4wjwly/?context=3">here</a> I had a bug in the <code>then</code> block that returned nil, so I fixed the code. I updated the code to fix this issue</p></li><li><p>I added a whole new section called Alternatives where I added some proposal of code samples that I received from comments on reddit or on this blog sometimes adding my own twist :)</p></li></ol><hr /><p>If you like this type of content, then maybe you want to consider subscribing to</p><ul><li><p>my personal newsletter <a target="_blank" href="https://allaboutcoding.ghinda.com/newsletter">All About Coding</a></p><p>  <em>or</em></p></li><li><p>my curated newsletter <a target="_blank" href="https://newsletter.shortruby.com">Short Ruby News</a> where I cover weekly Ruby news from all around the internet.</p></li></ul>]]></description><link>https://allaboutcoding.ghinda.com/refactoring-instance-variables-to-local-variables-in-rails-controllers</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/refactoring-instance-variables-to-local-variables-in-rails-controllers</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 18 Jan 2023 04:36:49 GMT</pubDate></item><item><title><![CDATA[Code Design Inertia]]></title><description><![CDATA[<p>Code Design Inertia:</p><blockquote><p>The code that will be written in the future will follow the design of the code already written.</p></blockquote><p>Inertia is stronger inside a file =&gt; the code added will follow the design of the code already written.</p><p>So going further with this metaphor, "consistency" is like gravity: the close you follow a guideline the harder will be to see the benefit of other ways to write code.</p><p><a target="_blank" href="https://ruby.social/tags/code">#code</a> <a target="_blank" href="https://ruby.social/tags/design">#design</a> #shorts</p>]]></description><link>https://allaboutcoding.ghinda.com/code-design-inertia</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/code-design-inertia</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 12 Jan 2023 08:49:50 GMT</pubDate></item><item><title><![CDATA[Write tests on all test levels]]></title><description><![CDATA[<p>When thinking about testing, consider the following points: </p><p><strong>You are not writing tests only for yourself</strong> but also for other developers who might join your project. They might come later, and I need to understand the code from a unit/component/object/method perspective, not only from an integration perspective. </p><p><strong>Write unit and integration tests to uncover common scenarios and edge cases</strong>. So that when someone wants to understand some edge cases in the code, they will have fast feedback or can use the tests to document how the code works and why it works that way. This is because tests have context: pre-conditions, input on one side, and expected result and post-conditions. Thus together with the code, the code comments are an integral part of describing a piece of functionality. </p><p>I often hear the idea: Don't write unit tests! They are useless! You have to change them too often when you change the code! I will not go into what good unit tests are or the difference between functional and non-functional/structural unit testing, but only talk about the idea of having them. </p><p><strong>The worst possible scenario is a collaborative project where the team members feel there are not enough tests or that tests are not covering the code correctly. This creates fear of changing the code. </strong>Thus, the code quality will decrease. </p><p>Imagine a scenario where you write a lot of integration tests or system tests that exercise a lot of scenarios so that you consider to have good coverage. Now you want to change a line in a method: </p><ul><li>What tests do you run to have quick feedback about this change? </li><li>Are the ones you choose covering well the cases of how this method should behave? Or did you missed to run one integration test? </li><li>Do you pay the price to run the entire full suite for just changing a line of code in a method? </li></ul><p><strong>There is a concept called Test Levels which is old wisdom learned through a lot of practice. </strong> Before adopting a hard stance against any test level, it would be good advice to understand what they are and, most importantly, to understand what category of bugs each test level can uncover.  Suffice it to say that the tests discovered at one level are usually different than the bugs discovered at another level. It might be that some experts in your field are advocating for not writing unit tests, and maybe for the projects, they are right. </p><p><strong>If you work alone on your project, hands off, do as you like.</strong> Tests or no tests, either way, is a good choice <em>for you</em>. It is a good choice because you support the consequences. Thus, you are entitled to choose to write tests only on the integration level or even not to write any tests. </p><p><strong>When working in a team, please add tests in all test levels: unit, integration, system, and acceptance if your project allows this.</strong></p><p>Don't worry about too many tests, </p><blockquote><p>Or worry, but know that worrying is as effective as trying to solve an algebra equation by chewing Bubble gum</p></blockquote><p>Your colleagues will delete the tests that do not make sense or need to understand why a test fails, thus having <strong>an excellent opportunity to reflect at Chestertons Fence and answer why the code is there.</strong></p><p>Yes, they might spend time understanding tests and then decide after a refactoring/change that it is irrelevant and delete it. This is not a waste of time. This is good, this is learning, and it helps improve the system's quality while expanding the understanding of how it works under the hood.</p><p>As we are here, let me say this: <strong>don't delegate writing tests for later.</strong> It will most probably not happen: I rarely see a couple of days later a PR saying, Adding tests for feature X. </p><p><em>Once a feature is implemented, assume you will move to another feature. So the best time to add tests or think about code quality is when you build the feature. You have the context and focus.</em></p><p><strong>Testing is just another face of making your code easy to debug.</strong>If you care about improving code reliability, tests are an essential component. The same goes if you care about making your code easy to understand; consider tests an integral part of making your code easy to read by the next developer. Add tests on the proper test level where you see fit.</p><p>When thinking about unit testing/component testing, <strong>if you find writing a test for a piece of code hard, then don't be afraid to change the code. </strong>Most probably, the code is too complex and has too many dependencies. The most common solution is to split the code into smaller components. </p><p><strong>Please do not write clever code in tests. Write them in the simplest possible way.</strong> No meta-programming, fancy abstractions (except for the test framework), branches, and conditions. I dare to say don't DRY your tests. Maybe write some data generators or specific assertions to your domain that can be reused.  </p>]]></description><link>https://allaboutcoding.ghinda.com/write-tests-on-all-test-levels</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/write-tests-on-all-test-levels</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Sat, 26 Nov 2022 09:31:42 GMT</pubDate></item><item><title><![CDATA[Alternative ways to freeze a string in Ruby]]></title><description><![CDATA[<p>If you want to freeze strings in Ruby there are at least two ways to do this:</p><p>1) Adding the magic comment at the beginning of the file</p><pre><code class="lang-ruby"><span class="hljs-comment"># frozen_string_literal: true</span></code></pre><p>2) Calling <code>.freeze</code> on the string that you want to freeze</p><pre><code class="lang-ruby">a = <span class="hljs-string">"this is a frozen string"</span>.freezeputs a.frozen? <span class="hljs-comment"># will return true </span>b = <span class="hljs-string">"this is a frozen string"</span>.freezeputs b.frozen? <span class="hljs-comment"># will return true </span>puts a.equal?(b) <span class="hljs-comment"># will return true</span>puts a.object_id == b.object_id <span class="hljs-comment"># true</span></code></pre><p>As you notice <code>a</code> and <code>b</code> seem to be the same object instance. No new objects are instantiated. </p><p>The same happens for symbols: </p><pre><code class="lang-ruby">s = <span class="hljs-symbol">:a_new_symbol_open</span>puts s.frozen? <span class="hljs-comment"># will, of course return true</span>m = <span class="hljs-symbol">:a_new_symbol_open</span>puts m.frozen? <span class="hljs-comment"># will, of course return true</span>puts s.object_id == m.object_id <span class="hljs-comment"># will return true</span></code></pre><h2 id="heading-alternative-ways-to-freeze-a-string">Alternative ways to freeze a String</h2><p>Enter a kind of strange method that can be applied on Strings: <code>-</code></p><p>You might see code that looks like this:</p><pre><code class="lang-ruby">status = -<span class="hljs-string">"global.pending"</span></code></pre><h2 id="heading-string-and-string-methods"><code>String#-@</code> and <code>String#+@</code> methods</h2><p>Let's explore how these methods work. </p><p>Using a string literal will create a new String object every time an assignment takes place. Notice in the following example that the object_id is different between <code>str1</code> and <code>str2</code>:</p><pre><code class="lang-ruby">str1 = <span class="hljs-string">"Normal string"</span>puts <span class="hljs-string">"<span class="hljs-subst">#{str1.object_id}</span>, <span class="hljs-subst">#{str1.frozen?}</span>"</span> <span class="hljs-comment"># 60, false</span>str2 = <span class="hljs-string">"Normal string"</span>puts <span class="hljs-string">"<span class="hljs-subst">#{str2.object_id}</span>, <span class="hljs-subst">#{str2.frozen?}</span>"</span> <span class="hljs-comment"># 80, false</span>puts str1.object_id == str2.object_id <span class="hljs-comment"># false</span></code></pre><h3 id="heading-what-does-using-on-a-string-do">What does using <code>-</code> on a String do?</h3><p>First, <a target="_blank" href="https://docs.ruby-lang.org/en/3.1/String.html#method-i-2D-40">here</a> is the definition of <code>String#-</code></p><blockquote><p>Returns a frozen, possibly pre-existing copy of the string</p><p>The returned String will be deduplicated if it has no instance variables.</p></blockquote><p>Notice in the following example that the strings are frozen and return the same object_id:</p><pre><code class="lang-ruby">str3 = -<span class="hljs-string">"Normal string"</span>puts <span class="hljs-string">"<span class="hljs-subst">#{str3.object_id}</span>, <span class="hljs-subst">#{str3.frozen?}</span>"</span> <span class="hljs-comment"># 100, true</span><span class="hljs-comment"># Here for example, it will return the same object id</span>str4 = -<span class="hljs-string">"Normal string"</span>puts <span class="hljs-string">"<span class="hljs-subst">#{str4.object_id}</span>, <span class="hljs-subst">#{str4.frozen?}</span>"</span> <span class="hljs-comment"># 100, true</span>puts str3.object_id == str4.object_id <span class="hljs-comment"># true</span></code></pre><p>Thus we are not only making the string close to modifications but we are also re-using the same object. </p><h3 id="heading-how-to-unfreeze-such-string">How to unfreeze such string?</h3><p>There is a counter-part method on String: <code>+</code></p><p>It <a target="_blank" href="https://docs.ruby-lang.org/en/3.1/String.html#method-i-2B-40">does</a> the following:</p><blockquote><p>Returns a frozen, possibly pre-existing copy of the string.</p><p>The returned String will be deduplicated as long as it does not have any instance variables set on it.</p></blockquote><pre><code class="lang-ruby">frozen_string = -<span class="hljs-string">"This is a frozen string"</span><span class="hljs-keyword">begin</span>  frozen_string &lt;&lt; <span class="hljs-string">"and it cannot be modified"</span><span class="hljs-keyword">rescue</span> FrozenError =&gt; e  puts e <span class="hljs-comment"># can't modify frozen String: "This is a frozen string"</span><span class="hljs-keyword">end</span> puts <span class="hljs-string">"<span class="hljs-subst">#{frozen_string.object_id}</span>, <span class="hljs-subst">#{frozen_string.frozen?}</span>"</span> <span class="hljs-comment"># 120, true</span>str5 = +frozen_string puts <span class="hljs-string">"<span class="hljs-subst">#{str5.object_id}</span>, <span class="hljs-subst">#{str5.frozen?}</span>"</span>  <span class="hljs-comment"># 140, false</span>str5 &lt;&lt; <span class="hljs-string">" and it can be modified"</span> <span class="hljs-comment"># This is a frozen string and it can be modified</span>puts str5</code></pre><h2 id="heading-exploring-some-interesting-cases">Exploring some interesting cases</h2><p>Hashes with String keys in Ruby are freezing the keys. But are the keys the same object? What about values?</p><pre><code class="lang-ruby">hash1 = { <span class="hljs-string">"Key"</span> =&gt; <span class="hljs-string">"Value"</span> }key1 = hash1.keys[<span class="hljs-number">0</span>]value1 = hash1.values[<span class="hljs-number">0</span>]puts <span class="hljs-string">"H1: <span class="hljs-subst">#{key1.object_id}</span>, <span class="hljs-subst">#{value1.object_id}</span>"</span> <span class="hljs-comment"># 160, 180</span>puts <span class="hljs-string">"H1: <span class="hljs-subst">#{key1.frozen?}</span>, <span class="hljs-subst">#{value1.frozen?}</span>"</span> <span class="hljs-comment"># true, false</span>hash2 = { -<span class="hljs-string">"Key"</span> =&gt; <span class="hljs-string">"Value"</span> }key2 = hash2.keys[<span class="hljs-number">0</span>]value2 = hash2.values[<span class="hljs-number">0</span>]puts <span class="hljs-string">"H2: <span class="hljs-subst">#{key2.object_id}</span>, <span class="hljs-subst">#{value2.object_id}</span>"</span> <span class="hljs-comment"># 160, 200</span>puts <span class="hljs-string">"H2: <span class="hljs-subst">#{key2.frozen?}</span>, <span class="hljs-subst">#{value2.frozen?}</span>"</span> <span class="hljs-comment"># true, false</span>puts key1.equal?(key2) <span class="hljs-comment"># true</span>puts key1.object_id == key2.object_id <span class="hljs-comment"># true</span></code></pre><p>As you can notice, the key has the same object_id. Thus, it is the same object instance. But values have different object_id, so they are not the same object.</p><pre><code class="lang-ruby">puts value1.equal?(value2) <span class="hljs-comment"># false</span>puts value1.object_id == value2.object_id <span class="hljs-comment"># false</span></code></pre><p>So if you want to make also the value the same object as it is the same string literal: </p><pre><code class="lang-ruby">hash3 = { <span class="hljs-string">"Key"</span> =&gt; -<span class="hljs-string">"Value"</span> }key3 = hash2.keys[<span class="hljs-number">0</span>]value3 = hash2.values[<span class="hljs-number">0</span>]puts <span class="hljs-string">"H3: <span class="hljs-subst">#{key3.object_id}</span>, <span class="hljs-subst">#{value3.object_id}</span>"</span> <span class="hljs-comment"># 160, 200</span>puts <span class="hljs-string">"H3: <span class="hljs-subst">#{key3.frozen?}</span>, <span class="hljs-subst">#{value3.frozen?}</span>"</span> <span class="hljs-comment"># true, true</span>hash4 = { <span class="hljs-string">"Key"</span> =&gt; -<span class="hljs-string">"Value"</span> }key4 = hash2.keys[<span class="hljs-number">0</span>]value4 = hash2.values[<span class="hljs-number">0</span>]puts <span class="hljs-string">"H4: <span class="hljs-subst">#{key4.object_id}</span>, <span class="hljs-subst">#{value4.object_id}</span>"</span> <span class="hljs-comment"># 160, 200</span>puts <span class="hljs-string">"H4: <span class="hljs-subst">#{key4.frozen?}</span>, <span class="hljs-subst">#{value4.frozen?}</span>"</span> <span class="hljs-comment"># true, true</span>puts value3.equal?(value4) <span class="hljs-comment"># true</span>puts value3.object_id == value4.object_id <span class="hljs-comment"># true</span></code></pre><h2 id="heading-some-examples-of-using-the-on-strings">Some examples of using the <code>-</code> on strings</h2><p>First when possible, use symbols instead of strings. </p><p>Of course, symbols have a bit more restrictive rules to write, so it might be that for clarity or other reasons you cannot/don't want to use symbols. </p><p>So use <code>-</code> on strings that will probably not have variations, not they will be modified</p><p>Examples:</p><p>1) When writing a custom SQL that might be re-used in some other places</p><pre><code class="lang-ruby">SolarSystem.planets.order(-<span class="hljs-string">"orbit = 'circular' ASC, name ASC"</span>)</code></pre><p>2) When calling an external API where you define the base URL or the path:</p><pre><code class="lang-ruby">base = -<span class="hljs-string">"http://example.com"</span>url = URI.join(base, -<span class="hljs-string">"/foo"</span>)</code></pre><p>3) When adding, for example headers:</p><pre><code class="lang-ruby">header = { <span class="hljs-string">"Content-Type"</span> =&gt; -<span class="hljs-string">"application/x-www-form-urlencoded"</span> }</code></pre><p>4) When you want to log some structured short information:</p><pre><code class="lang-ruby">Rails.logger(-<span class="hljs-string">"job.start.highpriority"</span>)</code></pre>]]></description><link>https://allaboutcoding.ghinda.com/alternative-ways-to-freeze-a-string-in-ruby</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/alternative-ways-to-freeze-a-string-in-ruby</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 13 Oct 2022 02:56:26 GMT</pubDate></item><item><title><![CDATA[How to get class name and module name in Ruby]]></title><description><![CDATA[<h1 id="heading-how-to-get-the-class-name">How to get the class name</h1><p>To get the class name:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span> = <span class="hljs-title">puts</span> <span class="hljs-title">self</span>.<span class="hljs-title">class</span>.<span class="hljs-title">name</span></span><span class="hljs-keyword">end</span>User.new.call <span class="hljs-comment">#=&gt; User</span></code></pre><p>This will work even if the name is namespace inside a module: </p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Authenticated</span></span>  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Visitor</span></span>    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span> = <span class="hljs-title">puts</span> <span class="hljs-title">self</span>.<span class="hljs-title">class</span>.<span class="hljs-title">name</span></span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span>Authenticated::Visitor.new.call <span class="hljs-comment">#=&gt; Authenticated::Visitor</span></code></pre><h1 id="heading-how-to-get-the-module-name">How to get the module name</h1><p>Will not work if you call <code>class.name</code></p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Authenticated</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">call</span> = <span class="hljs-title">puts</span> <span class="hljs-title">self</span>.<span class="hljs-title">class</span>.<span class="hljs-title">name</span></span><span class="hljs-keyword">end</span>Authenticated.call <span class="hljs-comment"># will return Module!!</span></code></pre><p>But Module defines an accessor named <code>name</code></p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Visitors</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">call</span> = <span class="hljs-title">puts</span> <span class="hljs-title">self</span>.<span class="hljs-title">name</span></span><span class="hljs-keyword">end</span>Visitors.call <span class="hljs-comment"># will return Visitors</span></code></pre><p>Yes there is a constant <code>name</code> defined on a Module.</p><p>You can of course redefine that if you want:</p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Guest</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">name</span> = "<span class="hljs-title">Another</span> <span class="hljs-title">name</span>"</span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">call</span> = <span class="hljs-title">puts</span> <span class="hljs-title">self</span>.<span class="hljs-title">name</span></span><span class="hljs-keyword">end</span>Guest.call <span class="hljs-comment"># will return "Another name"</span></code></pre><h1 id="heading-bonus-how-to-get-the-current-method-name">Bonus: How to get the current method name</h1><p>Use <code>__method__</code> for that: </p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Admin</span></span>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span><span class="hljs-params">(...)</span></span> = puts __method_<span class="hljs-number">_</span><span class="hljs-keyword">end</span>Admin.new.call <span class="hljs-comment"># "call"</span></code></pre>]]></description><link>https://allaboutcoding.ghinda.com/how-to-get-class-name-and-module-name-in-ruby</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/how-to-get-class-name-and-module-name-in-ruby</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Thu, 06 Oct 2022 04:07:03 GMT</pubDate></item><item><title><![CDATA[How to work with PostgreSQL enums in Rails 7]]></title><description><![CDATA[<p>Rails 7 introduced a new way to create enums in PostgreSQL, by adding <code>create_enum</code> method. See <a href="https://guides.rubyonrails.org/active_record_postgresql.html#enumerated-types">official documentation here</a>.</p><p>I will explore the following tasks:</p><ol><li>Creating an enum type and adding a column to a model with that enum type</li><li>How to add a new value for the enum</li><li>How to rename a value from enum type</li><li>How to delete a value from enum type</li></ol><p>All examples were tested on <code>PostgreSQL 12</code>, <code>Ruby 3.1.2</code> and <code>Rails 7.1.0.alpha</code></p><h1 id="heading-how-to-create-an-enum-type-for-an-active-record-model">How to create an enum type for an Active Record model</h1><p>The migration will look like this:</p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">up</span></span>  create_enum <span class="hljs-symbol">:user_status</span>, [<span class="hljs-string">"pending"</span>, <span class="hljs-string">"active"</span>, <span class="hljs-string">"archived"</span>]  create_table <span class="hljs-symbol">:users</span>, <span class="hljs-symbol">force:</span> <span class="hljs-literal">true</span> <span class="hljs-keyword">do</span> <span class="hljs-params">|t|</span>    t.enum <span class="hljs-symbol">:status</span>, <span class="hljs-symbol">enum_type:</span> <span class="hljs-string">"user_status"</span>, <span class="hljs-symbol">default:</span> <span class="hljs-string">"pending"</span>, <span class="hljs-symbol">null:</span> <span class="hljs-literal">false</span>  <span class="hljs-keyword">end</span><span class="hljs-keyword">end</span></code></pre><p>The User model might look something like this: </p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> &lt; ActiveRecord::Base</span>  enum <span class="hljs-symbol">status:</span> {    <span class="hljs-symbol">pending:</span> <span class="hljs-string">'pending'</span>,    <span class="hljs-symbol">active:</span> <span class="hljs-string">'active'</span>,    <span class="hljs-symbol">archived:</span> <span class="hljs-string">'archived'</span>,    <span class="hljs-symbol">disabled:</span> <span class="hljs-string">'disabled'</span>,    <span class="hljs-symbol">waiting:</span> <span class="hljs-string">'waiting'</span>  }, <span class="hljs-symbol">_prefix:</span> <span class="hljs-literal">true</span><span class="hljs-keyword">end</span></code></pre><p>And then you can execute things like:</p><pre><code class="lang-ruby">User.status_pending.count <span class="hljs-comment"># returning the number of Users with status pending</span>user = User.firstuser.status_pending? <span class="hljs-comment"># will return true if the status is `pending`</span></code></pre><p>A rollback for this might look like this: </p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">down</span></span>  drop_table <span class="hljs-symbol">:users</span>  execute <span class="hljs-string">&lt;&lt;-SQL    DROP TYPE user_status;  SQL</span><span class="hljs-keyword">end</span></code></pre><h1 id="heading-how-to-add-a-new-value">How to add a new value</h1><p>You can do this by executing the following migration with raw SQL:</p><pre><code class="lang-ruby">disable_ddl_transaction<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">up</span></span>  execute <span class="hljs-string">&lt;&lt;-SQL      ALTER TYPE user_status ADD VALUE IF NOT EXISTS 'disabled' AFTER 'active';    SQL</span><span class="hljs-keyword">end</span></code></pre><p>For creating a rollback, see the section about how to delete a value from an enum. Things are a bit more complicated.</p><h1 id="heading-how-to-rename-an-enum-type-that-a-rails-model-uses">How to rename an enum type that a Rails model uses</h1><p>Let's say you now want to rename <code>pending</code> to <code>waiting</code> and do that in Rails and the database. </p><p>The migration might look something like the following:</p><pre><code class="lang-ruby">disable_ddl_transaction<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">up</span></span>    execute <span class="hljs-string">&lt;&lt;-SQL      ALTER TYPE user_status RENAME VALUE 'pending' TO 'waiting';    SQL</span>    <span class="hljs-comment"># don't forget to change the default status if you renamed the default one</span>    change_column_default <span class="hljs-symbol">:users</span>, <span class="hljs-symbol">:status</span>, <span class="hljs-symbol">from:</span> <span class="hljs-string">'pending'</span>, <span class="hljs-symbol">to:</span> <span class="hljs-string">'waiting'</span><span class="hljs-keyword">end</span></code></pre><h1 id="heading-how-to-delete-a-value-from-an-enum-type">How to delete a value from an enum type</h1><p>This is a more complex operation if you want to delete a value. </p><p>First, you need to do the following two things:</p><ol><li>Make sure you deleted or renamed any records that have the value that you want to delete in their enum column</li><li>Make sure you removed the default that points to the value you want to remove if you have that value as a default</li><li>Please notice that an enum type can be used for multiple tables, so you should check if other tables are using that enum and that specific value</li></ol><p>Then your migration for this might look like this. Say I want to remove <code>waiting</code> status and replace it with <code>pending</code></p><pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">up</span></span>  <span class="hljs-comment"># First, make sure no records are using the status that you want to remove</span>  User.status_waiting.update_all(<span class="hljs-symbol">status:</span> <span class="hljs-string">'pending'</span>)  <span class="hljs-comment"># change default to nil</span>  change_column_default <span class="hljs-symbol">:users</span>, <span class="hljs-symbol">:status</span>, <span class="hljs-literal">nil</span>  execute <span class="hljs-string">&lt;&lt;-SQL    --- Rename the old enum    ALTER TYPE user_status RENAME TO user_status_old;    --- Create the new enum as you will like it to be    CREATE TYPE user_status AS ENUM('pending', 'active', 'archived');    --- Alter the table to update it to use the new enum    ALTER TABLE users ALTER COLUMN status TYPE user_status USING users::text::user_status;    --- Drop the old status    DROP TYPE user_status_old;  SQL</span>  <span class="hljs-comment"># make the default status pending</span>  change_column_default <span class="hljs-symbol">:users</span>, <span class="hljs-symbol">:status</span>, <span class="hljs-string">'pending'</span><span class="hljs-keyword">end</span></code></pre><p> This migration cannot be undone. You can write a rollback to recreate the enum that was initially, but that will not update back the Users that were in waiting. That information is lost.</p><p>If you want to keep that information, then the way to do this is:</p><ol><li>You create a new status column named <code>backup_status</code> that should be of type <code>string</code> (because you probably don't want to create a new enum type, and you don't want to link the backup to the enum type that you are just about to change)</li><li>You create a migration that will copy <code>status</code> to <code>backup_status</code></li><li>You can execute the migration that will remove the value <code>waiting</code> from the <code>user_status</code> enum as described above</li><li>You check if all is fine</li><li>Then you can delete the column <code>backup_status</code></li></ol><hr /><p>If you want to play with the code, I create a single file Rails app here: <a href="https://github.com/lucianghinda/shortrubynewsletter/blob/main/code-summaries/week37/postgres_enums_in_rails_7.rb"><code>postgres_enums_in_rails_7.rb</code></a> </p><hr /><h1 id="heading-resources">Resources</h1><ol><li>https://guides.rubyonrails.org/active_record_postgresql.html#enumerated-types</li><li>https://blog.yo1.dog/updating-enum-values-in-postgresql-the-safe-and-easy-way/</li></ol><hr /><p>If you like this content, follow me on Twitter <a href="https://twitter.com/lucianghinda">@lucianghinda</a>, where I tweet or retweet about Ruby and Ruby web frameworks.</p><p>I also publish a free newsletter with Ruby and Rails fresh content at <a href="https://newsletter.shortruby.com">newsletter.shortruby.com</a>  in case you want to stay up to date with what is happening in Ruby and Rails world.</p>]]></description><link>https://allaboutcoding.ghinda.com/how-to-work-with-postgresql-enums-in-rails-7</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/how-to-work-with-postgresql-enums-in-rails-7</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 21 Sep 2022 07:22:44 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1663612428683/dl0O10B-u.png</cover_image></item><item><title><![CDATA[What is a class in Ruby?]]></title><description><![CDATA[<p>I will try to explain what a class is in some other way, somehow quoting or paraphrasing in some parts what <a target="_blank" href="https://twitter.com/steveklabnik">@steveklabnik</a> said a very long time ago in a presentation about OOP in Ruby.</p><p>Plato had a theory called "A Theory of Forms and Ideas" which is something like this:</p><p>Think about a perfect triangle as described by a mathematical formula. This would be a description of a form or idea of a triangle.</p><p>Plato says this triangle exists in an abstract and perfect state, but it is somehow independent of our minds. These Ideas or Forms live in their own realm, which is not our universe. </p><p>We might try to draw a triangle, but this will be an (imperfect) representation of the triangle in our universe.</p><p>Still, almost no matter how badly we can draw a triangle, anyone can recognize it as a triangle if it has the general shape:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663575805457/r-n-hqdUB.png" alt="image.png" /></p><p>Thus we draw a triangle shape, and even if imperfect, we can work with it as a triangle that works out. We can compose other objects, we can calculate various parts of them, can split them into smaller objects, and much more. </p><p>A class is like a Form or Idea of an object. We think about what object we will like to create, and then in the realm of Forms and Ideas we say a definition of it that usually contains data this ideal object might have and how it will behave. </p><p>We have a notation for this in Ruby: </p><pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Triangle</span></span><span class="hljs-keyword">end</span></code></pre><p><strong>Thus, a class represents the Idea of how we want an object to be.</strong></p><p>Then we instantiate an object that looks like the idea we wanted to describe. But it is not precisely that idea. </p><p>We draw that idea in our universe where now we can move it around, add data to it &amp; so on. </p><pre><code class="lang-ruby">triangle = Triangle.new</code></pre><p>The <code>triangle</code> will behave like the <code>Triangle</code>(the idea) but will not be the Idea itself.</p>]]></description><link>https://allaboutcoding.ghinda.com/what-is-a-class-in-ruby</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/what-is-a-class-in-ruby</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Mon, 19 Sep 2022 08:28:41 GMT</pubDate></item><item><title><![CDATA[How to learn Ruby]]></title><description><![CDATA[<p>I think learning Ruby has three parts:</p><ol><li>OOP + SOLID in Ruby</li><li>Ruby Syntax</li><li>Idiomatic Ruby</li></ol><p>What follows assumes you already know how to program in any other programming language. If this is the first time you are learning to code, then that is another path and I will write about it some day. </p><p>I would start in the following way: </p><ol><li>Ruby Syntax: First, read a bit about the Ruby syntax. You can find a short introduction to Ruby. (shameless plug) I tried to collect a lot of resources <a target="_blank" href="https://ghinda.com/blog/programming/ruby/2021/learning-ruby.html#learning-ruby-where-and-how">here</a>, but you can also find some resources <a target="_blank" href="https://rubyandrails.info">here</a>. Do some small exercises if you feel like it just to get the syntax. </li><li>Watch some videos of Sandi Metz. Here is a <a target="_blank" href="https://www.youtube.com/results?search_query=sandi+metz">list of Sandi Metz videos</a> for that. I recommend starting with <a target="_blank" href="https://www.youtube.com/watch?v=v-2yFMzxqwU">SOLID Object-Oriented Design by Sandi Metz</a>. Some things might make sense, some not. No worries, this is just a kind of priming your way of thinking about how to code in Ruby</li><li>After watching one video go ahead and try to work on some learning project in Ruby. Do the first version of that that should work. Here is an example: Do an ATM simulator in Ruby. Or a Daily Quote CLI that should serve one quote daily. </li><li>Push that project to Github and Gitlab. </li><li>After you do the first version, install Minitest and try to redo that project but TDD style. So first, you will write a test and then make that test pass. Do this on a separate branch and open an MR/PR with it. </li><li>Now, after you rewrote that project with tests, go ahead and rewatch the SOLID Object Oriented</li><li>After watching, go open Github/Gitlab and review your code and try to add comments on what you think should be changed. That PR/MR done previously will help with this. </li><li>Here is how to do the review: First, think about one of the principles you saw explained in the video and read your PR/MR, and add comments there with what to refactor to follow that principle. Then think about the second principle explained and then reread the code and add comments about this second principle. This is called perspective-based review, and it works better when you focus on one thing at a time.</li><li>When finished with your review, go ahead and refactor based on the comments you added there.</li><li>Watch this video <a target="_blank" href="https://www.youtube.com/watch?v=npOGOmkxuio">Rules, by Sandi Metz</a> and then go ahead and do the same process: Do the review and then refactor your code again.</li><li>Then watch this video <a target="_blank" href="https://www.youtube.com/watch?v=DC-pQPq0acs">Aloha Ruby Conf 2012 Refactoring from Good to Great by Ben Orenstein</a> and again do the same process: review + refactoring</li></ol><p>At this point, you will know Ruby Syntax and OOP principles in Ruby. </p><hr /><p>It is time to go deep into idiomatic Ruby: </p><ol><li>I recommend first reading this https://www.poodr.com/</li><li>Then I recommend you to read this https://www.packtpub.com/product/polished-ruby-programming/9781801072724</li></ol><p>You don't need to read those books from start to finish. I recommend reading a chapter and doing a code review, and then refactoring.</p><p>Then you can read some style guides, but you can search and read more of them: </p><ol><li><a target="_blank" href="https://ruby-style-guide.shopify.dev/">Shopify Style Guide</a></li><li><a target="_blank" href="https://github.com/thoughtbot/guides/tree/main/ruby">Thoughtbot Style Guide</a></li><li><a target="_blank" href="https://airbnb.io/projects/ruby/">AirBnB Style Guide</a></li><li><a target="_blank" href="https://github.com/cookpad/styleguide/blob/master/ruby.en.md">Cookpad Style Guide</a></li></ol><p>See which style guide makes sense for you now, and then go ahead and refactor your code to match one of them. Do not try now to mix them, but just take one and refactor your code to follow the guide. </p><p>After doing this, it is time that you start using static analysis tools:</p><ol><li>Install <a target="_blank" href="https://github.com/rubocop/rubocop">Rubocop</a> and see if any of the style guides that you read has a Rubocop configuration. Use it with Rubocop. </li><li>Install <a target="_blank" href="https://github.com/whitesmith/rubycritic">Ruby Critic</a> and run it and try to fix reported issues. </li></ol><p>From now you can start experimenting freely with Ruby and see what you like, what your style is what makes you happy. </p><hr /><p>Follow me on Twitter <a target="_blank" href="https://twitter.com/lucianghinda">@lucianghinda</a> where I share/retweet mostly Ruby and Rails content. </p><p>I also publish a newsletter with Ruby and Rails fresh content at https://newsletter.shortruby.com - in case you want to stay up to date with what is happening in Ruby and Rails world. </p>]]></description><link>https://allaboutcoding.ghinda.com/how-to-learn-ruby</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/how-to-learn-ruby</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 02 Sep 2022 07:03:33 GMT</pubDate></item><item><title><![CDATA[A case for using git worktree]]></title><description><![CDATA[<h1 id="heading-context">Context</h1><p>Let's say I am working on a project named "short-ruby" 😉</p><p>That project is currently located somewhere like </p><p><code>/home/lucian/projects/short-ruby</code> </p><p>Let's say that I am currently working on a complex feature named "automate newsletter".</p><p>This feature has multiple branches:</p><ul><li><code>twitter-integration</code> branched from <code>main</code></li><li><code>substack-integration</code> branched from <code>main</code></li><li><code>collect-tweets</code> branched from <code>automate-n/twitter-integration</code></li></ul><p>To test all this stuff, I am also creating a temp merge branch <code>all</code> where I want to test if all work well together before deciding to integrate them to <code>main</code>. </p><p>For simplicity </p><h1 id="heading-current-task">Current task</h1><p>The first 2 branches are implemented, and I am currently working on <code>collect-tweets</code>.</p><p>Now imagine that while working on this branch, I discover that there is something that needs to be changed, and it is related to integration with Twitter, like I need access to also <code>moments</code>, not only lists. </p><p>This change should be part of <code>twitter-integration</code> branch.</p><h2 id="heading-git-solution-without-git-worktree">Git solution without git worktree</h2><p>What are my options now? </p><h3 id="heading-simple-solution">Simple solution</h3><p>The simplest one is to <code>git stash</code> my current changes from <code>collect-tweets</code> and then <code>git checkout</code> the <code>twitter-integration</code> branch do the changes there, commit them and then come back to <code>collect-tweets</code> and <code>git rebase</code>. </p><p>It might look something like this:</p><pre><code class="lang-bash"><span class="hljs-comment"># work on collect-tweets</span>(short-ruby)$ git stash(short-ruby)$ git checkout twitter-integration<span class="hljs-comment">#... do the work</span>(short-ruby)$ git commit -m <span class="hljs-string">"Added support for querying moments"</span>(short-ruby)<span class="hljs-variable">$git</span> checkout collect-tweets(short-ruby)$ git rebase twitter-integration(short-ruby)$ git stash pop<span class="hljs-comment"># resume previous work on collect-tweets</span></code></pre><h3 id="heading-elegant-but-more-complex-solution">Elegant but more complex solution</h3><p>Of course, another solution would be to commit the changes in the branch <code>collect-tweets</code> and then <code>git cherry-pick</code> them to the correct branch. </p><p>This is an elegant solution, but you have to be careful and remember to make sure your commit was atomic and did not contain anything from the current branch. Of course, you can also fix that with <code>git rebase -i</code>, but still, that is also a solution that works when you always do logical atomic commits. </p><h2 id="heading-more-changes">More changes</h2><p>Now imagine that you resumed work on "collect-tweets" and then the next day, a bug needs to be fixed on <code>main</code> and the bug means changing a method in a class that was refactored in <code>twitter-integration</code></p><p>You will need again to do a series of <code>stash</code>/<code>checkout</code>/<code>rebase</code> that now will also mean multiple git checkout and rebases as you will want to propagate the changes from <code>main</code> to <code>twitter-integration</code> to <code>collect-tweets</code>. </p><p>Probably cannot do cherry picking while you are in <code>twitter-integration</code> or <code>collect-tweets</code> because you refactored that code already so cherry-picking will fail. </p><p>Here I think comes in handy git worktree</p><h2 id="heading-git-worktree">Git worktree</h2><p>Git worktree is a simple command that will map a local folder to a branch.</p><p>Imagine I said I have the project in <code>/home/lucian/projects/short-ruby</code></p><p>Here is what I can do before going into all branches: </p><pre><code class="lang-bash"><span class="hljs-comment"># Rename folder short-ruby to main and create a new folder short-ruby and put main inside</span>(short-ruby)$ <span class="hljs-built_in">cd</span> ... ; mv short-ruby main ;  mkdir short-ruby ; <span class="hljs-built_in">cd</span> short-ruby(short-ruby)$ mv ../main ./ ; <span class="hljs-built_in">cd</span> main(short-ruby/main)$ git checkout main(short-ruby/main)$ git worktree add ../twitter-integration witter-integration(short-ruby/main)$ git worktree add ../substack-integration substack-integration(short-ruby/main)$ git worktree add ../collect-tweets collect-tweets(short-ruby/main)$ git worktree add ../all all</code></pre><p>Now if I execute <code>git worktree list</code> it will show an output like this:</p><pre><code class="lang-bash">/home/lucian/projects/short-ruby/main                   bff36930 [main]/home/lucian/projects/short-ruby/twitter-integration    f840805c [twitter-integration]/home/lucian/projects/short-ruby/substack-integration   8286fd79 [substack-integration]/home/lucian/projects/short-ruby/collect-tweets         52d9bf8c [collect-tweets]/home/lucian/projects/short-ruby/all                    7cc380a4 [all]</code></pre><p>What happen is that it created those folders and inside each one it did a checkout with that specific branch.</p><p>Now my flow will look like this:I will <code>cd short-ruby/collect-tweets</code> and work in that folder everything related to the branch <code>collect-tweets</code>. </p><p>If I need to do a change on <code>twitter-integration</code> branch: </p><pre><code class="lang-bash"><span class="hljs-built_in">cd</span> twitter-integration<span class="hljs-comment"># do the changes</span>git commit -m <span class="hljs-string">"Added support for querying moments"</span><span class="hljs-built_in">cd</span> collect-tweetsgit rebase twitter-integration<span class="hljs-comment"># continue work</span></code></pre><p>Do I need to do some changes on <code>main</code>?</p><p>Simple: </p><pre><code class="lang-bash"><span class="hljs-built_in">cd</span> main<span class="hljs-comment"># do the changes</span>git commit -m <span class="hljs-string">"Bug is fixed"</span><span class="hljs-built_in">cd</span> twitter-integrationgit rebase main<span class="hljs-built_in">cd</span> collect-tweetsgit rebase twitter-integration</code></pre><p>Of course, I can automate rebase on all branches (probably) with something like:</p><pre><code class="lang-bash"><span class="hljs-comment"># rebase-automate-newsletter.sh</span><span class="hljs-built_in">cd</span> ~/projects/short-ruby/main ; git pull<span class="hljs-built_in">cd</span> ~/projects/short-ruby/twitter-integration; git pull ; git rebase main<span class="hljs-built_in">cd</span> ~/projects/short-ruby/substack-integration; git pull ; git rebase main<span class="hljs-built_in">cd</span> ~/projects/short-ruby/collect-tweets; git pull ; rebase twitter-integration</code></pre>]]></description><link>https://allaboutcoding.ghinda.com/a-case-for-using-git-worktree</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/a-case-for-using-git-worktree</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 03 Aug 2022 15:29:46 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1659602631486/cUPXu_Cxa.png</cover_image></item><item><title><![CDATA[Write code that is easy to change]]></title><description><![CDATA[<p>I read <a target="_blank" href="https://gordonc.bearblog.dev/dry-most-over-rated-programming-principle/">this article</a> about DRY and this prompted me to write some thoughts about this. This was a very debated article about what is DRY and what it means to use it or not and then there was a lot of discussion about OOP and good code. </p><p>I will not go into debating that article, but I think there are a couple of things to consider when thinking about how to design our code.</p><p>I will mostly talk thinking about (Ruby and thus OOP), but I think some of the comments I will write below might apply in general to programming in a lot of languages.</p><p>First, we have some principles about what it means to write good code. Could be they are not principles but more guidelines or maybe a kind of philosophy about how to think about code. </p><p>Here is an example: </p><blockquote><p>"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system" - Dave Thomas and Andrew Hunt in The Pragmatic Programmer</p></blockquote><p>This seems like a very good principle to follow. Actually, if you will go ahead and read this amazing book you will see this principle as being named the <em>"DRY principle"</em> like in the <em>"Do not repeat yourself principle"</em>.</p><p>What is the problem with having a principle? The problem with it is that it is hard for someone new to apply it in concrete situations. Thus when people are learning to code it is hard without prior experience to understand this principle and so they stick with the shorter version of it: "Do not repeat yourself". This is easy to remember and being so prescriptive it is easy to follow.  </p><p>I don't think there is a problem with starting to learn to code and follow this principle. </p><p>Based on my experience as a trainer, delivering a lot of technical training (programming, testing, architecture), when learning a hard skill there is a lot of information to process and some of it is very abstract and very hard to internalize and use every day. </p><p>Imagine that you did not encounter yet any bad code nor have you encountered the kind of maintenance issues generated by having duplicate logic or data models for example. It is will be hard to mentally process this principle and then while writing code to be able to distinguish between when to use DRY and when it is ok to let the code WET for a while.</p><p>A senior programmer will probably know this and should be able to decide when to break a principle and what to do to protect their change against introducing failures into the system because of that decision.</p><p>Where I think there is a gap is in between. What should happen on the road from being a novice to being a senior? </p><p>If you are lucky enough you will work in a team where there are already some senior programmers that have enough time to play the role of a mentor and help you do good code/solution/architecture design. </p><p>If you are not benefiting from such help, then breaking unknowingly these principles might create a lot of problems further down the road, most of them having an impact on how quick/easy your app will adapt to new requirements or will adapt to change. </p><p>What can be done is to go back to basics: from time to time revisit these materials about how to do code design. Re-read some of the books from your field, some of them might have revisited/updated editions. I do this from time to time and get new perspectives. Most of the time I look back at the code I wrote 1-2 years ago (and sometimes even a couple of months ago) and find out some bad design that I did. </p><p>Another thing that you can do is to ask for a code review and not only that but when receiving a suggestion, ask "Why?" not with the purpose to debate it but to understand what is the principle behind that suggestion or comment. </p><p>Try to find out timeless and mostly programming language agnostic advice. </p><p>Talk with the elders from your field, watch their videos, and read their books or articles. Even if they are published a while ago, having 30 years of experience coding brings a kind of wisdom that should be taken into account no matter the framework or programming language you are using today. </p><hr /><p>Coming back to the DRY acronym and the actual principle behind it, I think it is worth understanding from where they come and what are they trying to solve. </p><p>The answer is in two amazing books: </p><ol><li><a target="_blank" href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/">The Pragmatic Programmer</a> by Andrew Hunt Hunt and Dave Thomas</li><li><a target="_blank" href="https://sandimetz.com/products">Practical Objected Oriented Design</a> by Sandi Metz</li></ol><p>And it is mostly related to the following let's say axioms: </p><blockquote><p>"Good Design Is Easier to Change Than Bad Design" - Dave Thomas and Andrew Hunt in The Pragmatic Programer </p></blockquote><p>and </p><blockquote><p>"The purpose of design is to allow you to do design later, and its primary goal is to reduce the cost of change." - Sandi Metz in Practical Object-Oriented Design</p></blockquote><p>Thus if you think about it (or just read about this in these books) the purpose of code design is to write code today that will make it easy to change in the future. </p><p>In a way every other principle is derived from this or in a way tries to achieve this (not my idea, read about it in "The Pragmatic Programer"). So is <a target="_blank" href="https://en.wikipedia.org/wiki/SOLID">SOLID</a> or <a target="_blank" href="https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)">GRASP</a> or <a target="_blank" href="https://dannorth.net/2022/02/10/cupid-for-joyful-coding/">CUPID</a> or design patterns. They are all trying in various ways to provide a way for the developer to think about how to design their code to make it easy to change. </p><p>No matter if you agree or disagree with any of these principles, there is no easy way to achieve this at scale and make it easy to teach it. Programming is more close to a mix of art and engineering endeavor than it is a science when implementing apps that deal with the real world. </p><p>Photo source: https://publicdomainreview.org/collection/cycling-art </p>]]></description><link>https://allaboutcoding.ghinda.com/write-code-that-is-easy-to-change</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/write-code-that-is-easy-to-change</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Sun, 10 Jul 2022 16:33:58 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1657470690006/Yscb57hW8.jpg</cover_image></item><item><title><![CDATA[Why participate in a coding competition]]></title><description><![CDATA[<p>Imagine you are taking part in a coding competition. Lets explore what it might look like.</p><p>You found this contest online and registered to participate. On the day of the event, you confirm your presence and, after finishing checking in, you unpack your laptop and find a comfy place to sit.</p><p>You watch the other people in the room before your competition starts. Most of them are focused on their laptops, making last-minute adjustments to sitting positions, to their programming environments, and preparing mentally for what is about to come. Some of them are in small groups, chatting or even laughing to wash away possible nervousness.</p><p>The problems are finally published and you dive into solving them.</p><p>Time passes by and you go back and forth through a series of experiences. There are moments when you feel confident that youve found the best solution. There are also moments of uncertainty, discovery, thinking, and creating strategies. Time passes unaffected by what is happening in the room.</p><p>The big finale is approaching very quickly. You look at the clock and then again at your computer screen. With only a few minutes left, you are not sure if the code you wrote will be enough to win this contest.</p><p>Also, you are now facing a hard choice: <strong>(a)</strong> should you rewrite part of it, but maybe not have time to completely test the code, or <strong>(b)</strong> just continue with the current code and make small, incremental adjustments, while being sure it runs and works? The choice is hard also because you dont know what your opponents did, or what they implemented.</p><hr /><p>The description above might be what you could experience during a programming contest. Or you might experience many other different things, as not all experiences are the same.</p><p>There are a lot of reasons to participate in this kind of event, no matter the experience you have so far or the way you will behave during these events</p><hr /><h2 id="heading-learning-to-write-code-faster">Learning to write code faster</h2><p>Competition is limited in time and usually, there is no way to extend the allowed interval. You are not just solving problems, you also need to do it quickly. Writing code faster will allow you to go through many possible solutions and pick the best one out of them.</p><p>A big part of writing code fast is to intimately know your IDE, especially the shortcuts it provides. Other things that improve coding speed may be having experience with library data structures and methods, using standard best practices when choosing variable names and method structure, structuring code on paper before writing it, etc.</p><h2 id="heading-focus-on-making-your-solution-work">Focus on making your solution work</h2><p>Yes, during a competition you could also focus on writing clear, beautiful code. But the first objective you need to handle is making your code to work. Implement your solution in the fastest way possible so that it works. Only after it works, you could then start to improve it, making it look good and clean. This is a good mindset in software development and very similar to the lean mindset.</p><p>First make something that works and only after that try to improve it.</p><h2 id="heading-improve-problem-solving-skills">Improve problem-solving skills</h2><p>Competitions create a lot of opportunities to exercise generating solutions, testing and validating them, identifying which ones do not fit, fixing them, and rethinking them. It helps a lot if you are not only able to quickly come up with solutions, but you are also able to quickly choose the one that/which best fits some defined criteria.</p><p>All contest problems are made so that they have an optimal solution and this solution can be discovered, implemented, and tested in the allotted time. Notice that solving a problem requires the following sequence of actions: read the problem, understand what it asks from you, mentally model what real-life situation the problem fits to, come up with solution ideas, quickly evaluate them and choose one, and make a plan of how this solution will look like in code, make a plan of what tests it must pass, write the code, fix the bugs, then test it some more. It could be of help if shortly before the contest you go through these steps by solving some random problem, similar to how athletes warm up shortly before a game.</p><h2 id="heading-playing-with-the-programming-language">Playing with the programming language</h2><p>Each contest is an opportunity to better understand your programming language, and to use it as much as possible, in a very pure and direct way. This is a good way for newcomers to learn about their programming language and for experienced developers to dust off some not so often used parts of it. In a contest, you are there facing a problem, having the language as your main tool, with little time to search for another solution or to learn a new API or a new module. You will need to make the most out of what you know and out of your previous experience with that programming language.</p><p>It is tempting to Google things about syntax or solutions to small intermediary problems. However, the time it takes to do so must be carefully considered, as in a contest you have little time to spare.</p><h2 id="heading-bugs-and-bug-fixing">Bugs and bug fixing</h2><p>Testing and bug fixing is much harder in a contest compared to everyday work. You cannot afford to unit test and you really should not use a debugger unless you are sure you can afford the time. The bulk of your testing and bug fixing will be done most likely by simply looking at the code, reading it line by line, and thinking if it makes sense and what bugs could be hidden in that line of code. It is also a good example of trying to focus on the essential and improving your skills of quickly detecting a bug before execution.</p><p>A nice exercise to do when practicing for a contest is to use a site like <a target="_blank" href="https://www.hackerrank.com/">HackerRank</a>, <a target="_blank" href="https://www.codewars.com/">Codewars</a>, <a target="_blank" href="https://www.topcoder.com/">TopCoder</a> and just look at various submitted solutions that pass most but not all test cases. Practice how to quickly spot bugs. This could be done even better if you have a training partner with whom you can review the code.</p><h2 id="heading-solving-problems-with-your-code">Solving problems with your code</h2><p>In the end, a contest is about focusing on the core, the essence of being a programmer: writing code that solves a given problem. No GUI, no API to integrate, not many docs to be read. It is only you and a problem, a keyboard, your favorite IDE, and your mind.</p><p>Coding contests are abundant, there probably is an online contest every single day. This should be a strong argument in favor of treating them like something fun, a training experience instead of a competition. In the end, the one who gains the most out of a given contest is the one who learned and improved the most.</p><h2 id="heading-meeting-like-minded-or-passionate-people">Meeting like-minded (or passionate) people</h2><p>This is the right place to meet people who are passionate about coding, solving problems, optimizations, and about the same programming language as you are. It is the best group in which you can talk about IT, libraries, frameworks, about how to use a language quirk in a specific way.</p><hr /><p>Lets end this (long) article with a bit of very practical advice for winning or at least increasing your chances of winning:</p><h2 id="heading-prepare-a-cheat-sheet">Prepare a cheat sheet</h2><p>It always helps to have some favorite algorithms and tricks printed out on paper. During the contest, you want to spend as little time as possible thinking about algorithms or researching. This list could be from very small, containing only some basic tricks, to extremely large, containing notes about almost all algorithms you ever encountered while training.</p><p>For example, here are three such possible notes (taken from <a target="_blank" href="http://www.geeksforgeeks.org/">www.geeksforgeeks.org</a>):</p><p><a target="_blank" href="https://www.geeksforgeeks.org/merge-sort/">MergeSort</a>(arr[], l, r)  divides input array into two halves, calls itself for the two halves and then merges the two sorted halves.</p><pre><code>If r <span class="hljs-operator">&gt;</span> l  <span class="hljs-number">1.</span> Find the middle point to divide the array into two halves: middle m <span class="hljs-operator">=</span> (l<span class="hljs-operator">+</span>r)<span class="hljs-operator">/</span><span class="hljs-number">2</span>  <span class="hljs-number">2.</span> Call mergeSort <span class="hljs-keyword">for</span> first half: Call mergeSort(arr, l, m)  <span class="hljs-number">3.</span> Call mergeSort <span class="hljs-keyword">for</span> second half: Call mergeSort(arr, m<span class="hljs-operator">+</span><span class="hljs-number">1</span>, r)  <span class="hljs-number">4.</span> Merge the two halves sorted in step <span class="hljs-number">2</span> and <span class="hljs-number">3</span>: Call merge(arr, l, m, r)</code></pre><p><a target="_blank" href="https://www.geeksforgeeks.org/binary-search/">Binary Search</a>  searches a sorted array by repeatedly dividing the search array interval in half.</p><pre><code>1. Compare x <span class="hljs-keyword">with</span> the middle element.<span class="hljs-number">2.</span> <span class="hljs-keyword">If</span> x matches <span class="hljs-keyword">with</span> the middle <span class="hljs-keyword">element</span>, we <span class="hljs-keyword">return</span> the <span class="hljs-keyword">mid</span> index.<span class="hljs-number">3.</span> <span class="hljs-keyword">Else</span> <span class="hljs-keyword">If</span> x <span class="hljs-keyword">is</span> greater <span class="hljs-keyword">than</span> the <span class="hljs-keyword">mid</span> <span class="hljs-keyword">element</span>, <span class="hljs-keyword">then</span> x can <span class="hljs-keyword">only</span> lie <span class="hljs-keyword">in</span> the <span class="hljs-keyword">right</span> half subarray <span class="hljs-keyword">after</span> the <span class="hljs-keyword">mid</span> element. So we recur <span class="hljs-keyword">for</span> the <span class="hljs-keyword">right</span> half.<span class="hljs-number">4.</span> <span class="hljs-keyword">Else</span> (x <span class="hljs-keyword">is</span> smaller) recur <span class="hljs-keyword">for</span> the <span class="hljs-keyword">left</span> half.</code></pre><p><a target="_blank" href="https://www.geeksforgeeks.org/longest-increasing-subsequence/">Longest Increasing Subsequence</a>  find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.</p><pre><code><span class="hljs-number">1.</span> Let arr[<span class="hljs-number">0</span>..n-<span class="hljs-number">1</span>] be the input array and L(i) be the length of the LIS ending at index i such that arr[i] <span class="hljs-keyword">is</span> the last element of the LIS.2. Then, L(i) can be recursively written <span class="hljs-keyword">as</span>: a. L(i) <span class="hljs-operator">=</span> <span class="hljs-number">1</span> <span class="hljs-operator">+</span> max( L(j) ) where <span class="hljs-number">0</span> <span class="hljs-operator">&lt;</span> j <span class="hljs-operator">&lt;</span> i and arr[j] <span class="hljs-operator">&lt;</span> arr[i]; or b. L(i) <span class="hljs-operator">=</span> <span class="hljs-number">1</span>, <span class="hljs-keyword">if</span> no such j exists.3. To find the LIS <span class="hljs-keyword">for</span> a given array, we need to <span class="hljs-keyword">return</span> max(L(i)) where <span class="hljs-number">0</span> <span class="hljs-operator">&lt;</span> i <span class="hljs-operator">&lt;</span> n.</code></pre><hr /><p>This post was originally published on <a target="_blank" href="https://medium.com/metrosystemsro">METRO SYSTEMS Romania</a> publication on Medium in January, 2018.</p>]]></description><link>https://allaboutcoding.ghinda.com/why-participate-in-a-coding-competition</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/why-participate-in-a-coding-competition</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Wed, 11 May 2022 07:39:06 GMT</pubDate></item><item><title><![CDATA[Resources to learn Ruby on Rails 7]]></title><description><![CDATA[<p>I shared this first as a thread on Twitter but I think I want to document it also here:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/lucianghinda/status/1518594459189104640">https://twitter.com/lucianghinda/status/1518594459189104640</a></div><p> </p><h1 id="heading-courses-and-trainings">Courses and Trainings</h1><p><a target="_blank" href="https://www.hotrails.dev">https://www.hotrails.dev</a> by <a target="_blank" href="https://twitter.com/alexandre_ruban">Alexandre Ruban</a> is an online free course (as text) where you will create a full CRUD web app in Rails 7 and add <a target="_blank" href="https://turbo.hotwired.dev">Turbo</a> and <a target="_blank" href="stimulus.hotwired.dev">Stimulus</a></p><p><a target="_blank" href="https://davidcolby.gumroad.com/l/hotwired-ats/68y8c5c">Hotwired ATS: Modern, full-stack Rails 7 development</a> by <a target="_blank" href="https://mobile.twitter.com/davidcolbyatx">David Colby</a> is a paid full course teaching Rails 7 while building an Application Tracking System to recruitment.</p><p><a target="_blank" href="https://youtube.com/playlist?list=PLdTytUiloS16epXsqHswpCUMND_rksjr4">SupeRails Videos</a> by <a target="_blank" href="https://twitter.com/yarotheslav">Yaroslav Shmarov</a> is a great collection of videos showing how to build features with Hotwire in Rails. You should browse the entire <a target="_blank" href="https://www.youtube.com/c/SupeRails/videos">Youtube channel</a> as it has great Rails videos.</p><p><a target="_blank" href="https://gorails.com/series/hotwire-rails">GoRails Hotwire</a> by <a target="_blank" href="https://twitter.com/excid3">Chris Oliver</a> is a collection of video tutorials each one explaining various possibilities for Hotwire. While you are there I suggest making an account on <a target="_blank" href="https://gorails.com">GoRails</a> - the best-organized resource for learning basic and advanced Rails.</p><p><a target="_blank" href="https://store.afomera.dev/learn-hotwire">Learn Hotwire by Building a Forum</a> by <a target="_blank" href="https://twitter.com/afomera">Andrea Fomera</a> is a course where you will learn Rails 7 and Hotwire by building a forum. Each lesson is short and easy to follow and it builds up a nice web app.</p><p><a target="_blank" href="https://pragmaticstudio.com/courses/hotwire-rails">Hotwire for Rails Developers</a> by <a target="_blank" href="https://twitter.com/pragmaticstudio">Pragmatic Studio</a> is a full course about Rails 7 and Hotwire with a focus on developers that already know Rails but want to speed up learning Hotwire.</p><p><a target="_blank" href="https://www.youtube.com/playlist?list=PLBhH0uX92r6oiwiLBjdE-3NNsyRqyLAV9">Ruby on Rails - Hotwire</a> by <a target="_blank" href="https://twitter.com/chalmagean">Cezar Halmagean</a> has short videos teaching specific topics about Rails and Hotwire. Good explanation, very short videos teaching one single thing at a time.</p><p><a target="_blank" href="https://youtube.com/playlist?list=PLcKahasNsPxS-Y9yvmOHMOn9Uei6nzYiz">Recreate Notion in Ruby on Rails</a> by <a target="_blank" href="https://twitter.com/drnic">Dr Nic</a> where he tries to recreate Notion in Rails with Hotwire.</p><h1 id="heading-resource-catalogs">Resource Catalogs</h1><p>Here are two places trying to organise Ruby on Rails resources:</p><ul><li><p><a target="_blank" href="https://www.rubyandrails.info">Ruby &amp; Ruby on Rails</a> by <a target="_blank" href="https://twitter.com/nleonid">Leonid</a></p></li><li><p><a target="_blank" href="https://rubyhunt.dev">Find all ruby courses and books in one place</a> by <a target="_blank" href="https://twitter.com/hoppergeegee">Hopper Gee</a></p></li></ul><h1 id="heading-communities">Communities</h1><p>On Twitter there are some big communities that anyone can join:</p><ul><li><p><a target="_blank" href="https://twitter.com/i/communities/1498390597530537984">Rails Community</a> with over 2k members</p></li><li><p><a target="_blank" href="https://twitter.com/i/communities/1497828731548844034">Ruby Community</a> with over 600 members</p></li><li><p><a target="_blank" href="https://twitter.com/i/communities/1501872318225784832">Dry-rb community</a> with over 200 members</p></li></ul><h1 id="heading-discord-and-slack">Discord and Slack</h1><p><a target="_blank" href="https://discord.com/invite/CUuYVH7Qa9">The Spicy Web</a> by <a target="_blank" href="https://twitter.com/jaredcwhite">Jared White</a> is a <em>"chat room all about indie content publishing &amp; web development"</em>.</p><p><a target="_blank" href="https://discord.com/invite/Kx3VABnB">Kasper helps your Ruby&amp;Rails</a> by <a target="_blank" href="https://twitter.com/kaspth">Kasper Timm Hansen</a> is a chat room with focused dicussions about development and great suggestions about code architecture, solutions, code quality.</p><p><a target="_blank" href="https://discord.com/invite/CcqkHnuyUK">Ruby InfoSec</a>by <a target="_blank" href="https://twitter.com/postmodern_mod3">postmodern</a> is a chat room focused on Ruby &amp; InfoSec: <em>"The goal of this server is to help answer questions, educate others, highlight other Ruby InfoSec projects, and counter anti-Ruby FUD with information"</em></p><p><a target="_blank" href="https://www.rubyonrails.link">Ruby on Rails Link</a> is one of the biggest online Ruby community.</p><p><a target="_blank" href="https://rubydevs.herokuapp.com">Ruby Developers</a> is another Slack group for Ruby developers.</p><h2 id="heading-ruby-and-rails-info">Ruby and Rails info</h2><p>If you want to discover more resources about Ruby and Rails I recommend to visit <a target="_blank" href="https://rubyandrails.info">https://rubyandrails.info</a> that I maintain and try to add fresh resources as soon as they appear.</p><p>You should also subscribe to Short Ruby Newsletter at <a target="_blank" href="https://newsletter.shortruby.com">newsletter.shortruby.com</a>. It is a Monday morning summary of the articles, discussions, and news from the Ruby community. I watch a series of places like Twitter, ruby.socia<a target="_blank" href="http://ruby.social">l</a>, Reddit, Linkedin, Dev.to, Ruby LibHunt, and other news sources.</p><hr /><p><em>If you know any resources about learning Ruby on Rails 7 with Hotwire or any online communities for Ruby please let me know in a comment below or via twitter</em> <a target="_blank" href="https://twitter.com/lucianghinda"><em>@lucianghinda</em></a></p>]]></description><link>https://allaboutcoding.ghinda.com/resources-to-learn-ruby-on-rails-7</link><guid isPermaLink="true">https://allaboutcoding.ghinda.com/resources-to-learn-ruby-on-rails-7</guid><dc:creator><![CDATA[Lucian Ghinda]]></dc:creator><pubDate>Fri, 29 Apr 2022 03:38:29 GMT</pubDate></item></channel></rss>