<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Bevin Hernandez</title>
    <description>The latest articles on Forem by Bevin Hernandez (@bevinh).</description>
    <link>https://forem.com/bevinh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F96586%2F1d8f6f71-9aa2-4bc3-9ee3-ec347548504e.jpeg</url>
      <title>Forem: Bevin Hernandez</title>
      <link>https://forem.com/bevinh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bevinh"/>
    <language>en</language>
    <item>
      <title>Elemental Ruby: A Better Way to Organize Rails Applications</title>
      <dc:creator>Bevin Hernandez</dc:creator>
      <pubDate>Mon, 16 Dec 2024 20:08:40 +0000</pubDate>
      <link>https://forem.com/bevinh/elemental-ruby-a-better-way-to-organize-rails-applications-5p0</link>
      <guid>https://forem.com/bevinh/elemental-ruby-a-better-way-to-organize-rails-applications-5p0</guid>
      <description>&lt;p&gt;Every Rails developer has had that moment. You open a controller, and there it is: hundreds of lines of tangled business logic staring back at you. In my case, it was a reports controller that had grown to over 300 lines, mixing date parsing, filtering, business logic, and export handling into an incomprehensible mess. It didn't have tests, and the former developer had left the company — and I, the new hire, was being asked to change the functionality.&lt;/p&gt;

&lt;p&gt;Let me show you something that resembles it without actually showing the old code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="c1"&gt;# 300 lines of mixed concerns:&lt;/span&gt;
  &lt;span class="vi"&gt;@date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
  &lt;span class="vi"&gt;@report_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;default_report_type&lt;/span&gt;

  &lt;span class="c1"&gt;# Complex date logic&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@view_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Day"&lt;/span&gt;
    &lt;span class="c1"&gt;# date calculations&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="vi"&gt;@view_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Week"&lt;/span&gt;
    &lt;span class="c1"&gt;# more date calculations&lt;/span&gt;
  &lt;span class="c1"&gt;# ... several more conditions&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Business logic mixed with filtering&lt;/span&gt;
  &lt;span class="vi"&gt;@records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;records&lt;/span&gt;
  &lt;span class="vi"&gt;@records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;complex_conditions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Complex business rules&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@report_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Summary"&lt;/span&gt;
    &lt;span class="c1"&gt;# 50 lines of summary logic&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="vi"&gt;@report_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Detail"&lt;/span&gt;
    &lt;span class="c1"&gt;# 50 more lines of detail logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Export logic mixed in&lt;/span&gt;
  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;csv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;# complex CSV generation }&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To say I was afraid to change it was an understatement. To boot, the method was deeply nested, and writing tests for it as it was with it's endlessly branching logic would be a feat that I wasn't up to. I was in need of another pattern. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Traditional Approaches
&lt;/h2&gt;

&lt;p&gt;So, I tried the standard Rails patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  Fat Models
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Report&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_summary&lt;/span&gt;
    &lt;span class="c1"&gt;# Move logic to model&lt;/span&gt;
    &lt;span class="c1"&gt;# Now model is huge instead of controller - not a solution&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Concerns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ReportGeneration&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="c1"&gt;# Move logic to concern&lt;/span&gt;
  &lt;span class="c1"&gt;# Now complexity is just hidden - not a solution&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Service Objects
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateReportService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# Move logic to service&lt;/span&gt;
    &lt;span class="c1"&gt;# End up with explosion of services, &lt;/span&gt;
    &lt;span class="c1"&gt;# and functional programming methods in an OO language/app &lt;/span&gt;
    &lt;span class="c1"&gt;# just never felt right&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these approaches helped a bit, but they didn't quite solve the fundamental problem: the code wasn't organized around the actual business concepts it represented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Elemental Ruby
&lt;/h2&gt;

&lt;p&gt;Elemental Ruby is a pattern that emerged from the intersection of several powerful ideas. &lt;/p&gt;

&lt;p&gt;I wanted a lot in a solution - it had to be extensible, follow good design principles, be easily testable, maintainable, and to the extent possible, follow Rails conventions. &lt;/p&gt;

&lt;p&gt;So I drew from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brad Frost's Atomic Design (breaking complex systems into fundamental units)&lt;/li&gt;
&lt;li&gt;Sandi Metz's teaching about small, focused objects&lt;/li&gt;
&lt;li&gt;Domain-Driven Design's bounded contexts&lt;/li&gt;
&lt;li&gt;Rails' convention over configuration&lt;/li&gt;
&lt;li&gt;SOLID design principles &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core idea is simple: organize your code around the fundamental elements of your business domain, using Ruby's natural namespacing capabilities.&lt;/p&gt;

&lt;p&gt;Let's see how our reports controller evolves:&lt;/p&gt;

&lt;h3&gt;
  
  
  First Evolution: Service Objects
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="vi"&gt;@date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DateParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;
  &lt;span class="vi"&gt;@report_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ReportTypeSelector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:type&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;
  &lt;span class="vi"&gt;@date_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DateRangeCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:view_type&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;
  &lt;span class="vi"&gt;@records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RecordFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;

  &lt;span class="vi"&gt;@data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ReportGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="vi"&gt;@report_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;records: &lt;/span&gt;&lt;span class="vi"&gt;@records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;date_range: &lt;/span&gt;&lt;span class="vi"&gt;@date_range&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;

  &lt;span class="n"&gt;respond_with_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better, but our business concepts are still scattered across multiple service objects. We've traded one form of complexity for another.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Evolution: Elemental Ruby
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="vi"&gt;@date_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DateRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;view_type: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:view_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="vi"&gt;@report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;account: &lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;

  &lt;span class="vi"&gt;@data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;records: &lt;/span&gt;&lt;span class="no"&gt;Reports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;date_range: &lt;/span&gt;&lt;span class="vi"&gt;@date_range&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;respond_with_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business concepts are now clear and organized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/reports/date_range.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Reports::DateRange&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;view_type&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="vi"&gt;@date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@view_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view_type&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_date&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="vi"&gt;@view_type&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"Day"&lt;/span&gt;
      &lt;span class="vi"&gt;@date&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"Week"&lt;/span&gt;
      &lt;span class="vi"&gt;@date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginning_of_week&lt;/span&gt;
    &lt;span class="c1"&gt;# etc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;header&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="vi"&gt;@view_type&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"Day"&lt;/span&gt;
      &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;l&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format: :long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"Week"&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%B %e'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%B %e'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# etc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Works Better
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Clear Organization&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each element represents a clear business concept&lt;/li&gt;
&lt;li&gt;Related functionality stays together&lt;/li&gt;
&lt;li&gt;Easy to find code by thinking about the domain&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Better Testing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elements have clear responsibilities&lt;/li&gt;
&lt;li&gt;Dependencies are explicit&lt;/li&gt;
&lt;li&gt;Tests follow business concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Easier to Change&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes tend to affect single elements&lt;/li&gt;
&lt;li&gt;New features have clear homes&lt;/li&gt;
&lt;li&gt;Less risk of unintended consequences&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Better for Teams&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New developers can understand the domain&lt;/li&gt;
&lt;li&gt;Clear boundaries between different parts&lt;/li&gt;
&lt;li&gt;Natural organization for dividing work&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How This Aligns with SOLID
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Single Responsibility Principle&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each element handles one aspect of the domain&lt;/li&gt;
&lt;li&gt;Reports::DateRange only handles date-related concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open/Closed Principle&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New behavior can be added by creating new elements&lt;/li&gt;
&lt;li&gt;Existing elements remain unchanged&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Liskov Substitution Principle&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elements use composition over inheritance&lt;/li&gt;
&lt;li&gt;Avoid inheritance hierarchies entirely&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Interface Segregation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elements expose only related methods&lt;/li&gt;
&lt;li&gt;Clients depend only on what they need&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dependency Inversion&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elements depend on abstractions&lt;/li&gt;
&lt;li&gt;Implementation details stay private&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When to Use Elements
&lt;/h2&gt;

&lt;p&gt;Good candidates for elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex models (User, Account, Project)&lt;/li&gt;
&lt;li&gt;Code that clusters around business concepts&lt;/li&gt;
&lt;li&gt;Functionality that grows together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't create elements for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple models (Tag, Category)&lt;/li&gt;
&lt;li&gt;Single methods&lt;/li&gt;
&lt;li&gt;Technical concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Look for clusters of related methods in your models&lt;/li&gt;
&lt;li&gt;Identify business concepts in your controllers&lt;/li&gt;
&lt;li&gt;Create namespaced classes for each concept&lt;/li&gt;
&lt;li&gt;Move logic gradually&lt;/li&gt;
&lt;li&gt;Let your tests guide you&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Elemental Ruby isn't a silver bullet, but it provides a clear path for organizing Rails applications around business concepts. It builds on Rails conventions while adding structure that helps applications grow sustainably.&lt;/p&gt;

&lt;p&gt;The next time you open a controller and feel that familiar dread, remember: there's a better way to organize your code. Break it down into its elements, and let your business domain guide you.&lt;/p&gt;




&lt;p&gt;This is the first in a series exploring Elemental Ruby. Next time we'll look at identifying elements in legacy code.&lt;/p&gt;

&lt;p&gt;What patterns have you found for organizing Rails applications as they grow? Have you tried similar approaches? Let me know in the comments!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
