<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Databases on Nelson Figueroa</title><link>https://nelson.cloud/categories/databases/</link><description>Recent content in Databases on Nelson Figueroa</description><image><title>Nelson Figueroa</title><url>https://nelson.cloud/apple-touch-icon.png</url><link>https://nelson.cloud/categories/databases/</link></image><language>en</language><lastBuildDate>Thu, 29 Oct 2026 00:00:00 -0700</lastBuildDate><atom:link href="https://nelson.cloud/categories/databases/index.xml" rel="self" type="application/rss+xml"/><item><title>Enums Can Be Enforced at the Database Level in PostgreSQL</title><link>https://nelson.cloud/enums-can-be-enforced-at-the-database-level-in-postgresql/?ref=rss</link><pubDate>Thu, 29 Oct 2026 00:00:00 -0700</pubDate><guid>https://nelson.cloud/enums-can-be-enforced-at-the-database-level-in-postgresql/?ref=rss</guid><description>A PostgreSQL column can be restricted to a predefined set of values using an enumerated type (enum).</description><content:encoded><![CDATA[<p>PostgreSQL databases support having 

<a href="https://en.wikipedia.org/wiki/Enumerated_type" target="_blank" rel="noopener">enumerated type</a> (enum) columns so that the column only accepts one of a predefined set of values. I&rsquo;ve worked with 

<a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping" target="_blank" rel="noopener">ORMs</a> like 

<a href="https://guides.rubyonrails.org/active_record_basics.html" target="_blank" rel="noopener">Active Record</a> from 

<a href="https://rubyonrails.org/" target="_blank" rel="noopener">Ruby on Rails</a>. Using Active Record, I could work with database columns that only allowed a certain set of values. I always thought that was only possible due to the ORM layer abstracting away these things, but that&rsquo;s not the case. Enums can be enforced at the database level (at least with PostgreSQL).</p>
<p>I&rsquo;ll demonstrate on a PostgreSQL container with a simple table. I&rsquo;ll be using Podman. You can also use Docker to follow along, just replace <code>podman</code> with <code>docker</code> in the commands.</p>
<p>Make sure Podman/Docker is running, then run the following to get a running PostgreSQL container locally:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">podman</span> run <span class="no">-d</span> <span class="no">--name</span> enum-demo <span class="no">-e</span> <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span>postgres docker.io/library/postgres:18</span></span></code></pre></div><p>Then exec into the running container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">podman</span> <span class="nb">exec</span> <span class="no">-it</span> enum-demo psql <span class="no">-U</span> postgres
</span></span><span class="line"><span class="cl"><span class="go">psql (18.6 (Debian 18.6-1.pgdg13+2))
</span></span></span><span class="line"><span class="cl"><span class="go">Type &#34;help&#34; for help.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">postgres=#
</span></span></span></code></pre></div><p>Now we can start setting up the table to try out enums. First, we&rsquo;ll create the enum type itself. It&rsquo;s going to be a type just like <code>INTEGER</code> or <code>TEXT</code> are types in a database. I&rsquo;m thinking of CI/CD pipelines to use as an example, so the enum will be called <code>pipeline_status</code> and it&rsquo;ll only accept the values <code>pending</code>, <code>running</code>, <code>succeeded</code>, and <code>failed</code>. Run this SQL query in the container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TYPE</span><span class="w"> </span><span class="n">pipeline_status</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">ENUM</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;pending&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;running&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;succeeded&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;failed&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL should return:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CREATE TYPE</span></span></code></pre></div><p>Now let&rsquo;s create a simple table that makes use of the enum. The table will be called <code>pipeline_runs</code> and it&rsquo;ll have two columns: <code>branch</code> of type <code>TEXT</code>, and <code>status</code> which uses our enum type <code>pipeline_status</code>. Run this in the container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">branch</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">status</span><span class="w"> </span><span class="n">pipeline_status</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL should return:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CREATE TABLE</span></span></code></pre></div><p>Now let&rsquo;s try inserting a row. This SQL query inserts a row where <code>branch</code> is <code>main</code> and <code>status</code> is one of the four predefined enum values (<code>running</code>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;running&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL replies:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">INSERT 0 1</span></span></code></pre></div><p>We can check for the newly created row:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="p">;</span></span></span></code></pre></div><p>You should see:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> branch | status
</span></span><span class="line"><span class="cl">--------+---------
</span></span><span class="line"><span class="cl"> main   | running
</span></span><span class="line"><span class="cl">(1 row)</span></span></code></pre></div><p>Now let&rsquo;s try inserting a row with an enum value that is not a part of the values we defined. We&rsquo;ll try inserting <code>cancelled</code> for the <code>status</code> column. We should get an error.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;cancelled&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL should say:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ERROR:  invalid input value for enum pipeline_status: &#34;cancelled&#34;
</span></span><span class="line"><span class="cl">LINE 1: ...TO pipeline_runs (branch, status) VALUES (&#39;main&#39;, &#39;cancelled...
</span></span><span class="line"><span class="cl">                                                             ^</span></span></code></pre></div><p>The <code>cancelled</code> value isn&rsquo;t one of <code>('pending', 'running', 'succeeded', 'failed')</code>, so PostgreSQL gives us an error. PostgreSQL itself is enforcing enums! No ORMs here.</p>
<p>Note that the values in the enum are case sensitive. So inserting <code>Pending</code> instead of <code>pending</code> will fail:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Pending&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL returns the error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ERROR:  invalid input value for enum pipeline_status: &#34;Pending&#34;
</span></span><span class="line"><span class="cl">LINE 1: ...TO pipeline_runs (branch, status) VALUES (&#39;main&#39;, &#39;Pending&#39;)...
</span></span><span class="line"><span class="cl">                                                             ^</span></span></code></pre></div><p>Another caveat is that inserting <code>NULL</code> as the value for <code>status</code> is still allowed unless the column is also created with <code>NOT NULL</code>.</p>
<p>Let&rsquo;s say we actually want <code>cancelled</code> to be a part of the enum. We can modify the enum like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">ALTER</span><span class="w"> </span><span class="k">TYPE</span><span class="w"> </span><span class="n">pipeline_status</span><span class="w"> </span><span class="k">ADD</span><span class="w"> </span><span class="n">VALUE</span><span class="w"> </span><span class="s1">&#39;cancelled&#39;</span><span class="p">;</span></span></span></code></pre></div><p>PostgreSQL responds with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ALTER TYPE</span></span></code></pre></div><p>Now that <code>cancelled</code> is a valid value, we can try inserting again and we won&rsquo;t get an error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;cancelled&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL responds with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">INSERT 0 1</span></span></code></pre></div><p>We should have two rows total now. We can double check:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="p">;</span></span></span></code></pre></div><p>You should see:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> branch |  status
</span></span><span class="line"><span class="cl">--------+-----------
</span></span><span class="line"><span class="cl"> main   | running
</span></span><span class="line"><span class="cl"> main   | cancelled
</span></span><span class="line"><span class="cl">(2 rows)</span></span></code></pre></div><p>We successfully added a value to the <code>pipeline_status</code> enum and used it in a new row.</p>
<p>One final note: there isn&rsquo;t a clean way to remove a value from an enum. You can add values, but you would need to rebuild the enum if you want to remove any values. Plan ahead!</p>
<p>Now that the demo is over, exit the PostgreSQL prompt by running:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">\q</span></span></code></pre></div><p>Then clean up the container and, optionally, the image:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">podman</span> rm <span class="no">-f</span> enum-demo
</span></span><span class="line"><span class="cl"><span class="go">enum-demo
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">podman</span> rmi docker.io/library/postgres:18
</span></span><span class="line"><span class="cl"><span class="go">Untagged: docker.io/library/postgres:18
</span></span></span><span class="line"><span class="cl"><span class="go">Deleted: 07ee628d2e71f8956c85fc5466906a6d4038cf00f6db07f067d810ef8fec8161
</span></span></span></code></pre></div><p>We created an enum type with predefined values, tried inserting both valid and invalid values, and it was all enforced at the database level with no abstractions from an ORM!</p>
]]></content:encoded></item><item><title>Uniqueness Constraint Between Two Columns in Rails</title><link>https://nelson.cloud/uniqueness-constraint-between-two-columns-in-rails/?ref=rss</link><pubDate>Tue, 11 May 2021 00:00:00 -0700</pubDate><guid>https://nelson.cloud/uniqueness-constraint-between-two-columns-in-rails/?ref=rss</guid><description>Add a uniqueness constraint between two columns in Ruby on Rails.</description><content:encoded><![CDATA[<p>Ruby on Rails allows us to define uniqueness between two database table columns (i.e. two model attributes). At the time of this writing, I couldn&rsquo;t find official Rails documentation that shows how to do this in both the migration and the model, hence this post.</p>
<h2 id="defining-a-uniqueness-constraint-in-the-migration">Defining a Uniqueness Constraint in the Migration</h2>
<p>Creating a uniqueness constraint in a migration requires us to add an index on two columns/attributes. The key here is to place the attributes in an array and set them to be unique as a pair. The line in the migration looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">t</span><span class="o">.</span><span class="n">index</span> <span class="o">[</span><span class="ss">:attribute</span><span class="p">,</span> <span class="ss">:another_attribute</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span></span></span></code></pre></div><p>Here&rsquo;s a more realistic example. Assume the following:</p>
<ul>
<li>We have an existing <code>Account</code> model</li>
<li>We want to create a new table for a <code>Statement</code> model.</li>
<li>An account can have many statements</li>
<li>A statement belongs to one account</li>
<li>We want a maximum of 1 statement per date, per account (which means we need the <code>date</code> and <code>account_id</code> attributes to be unique together)</li>
</ul>
<p>To accomplish the above, we add indexes to <code>date</code> and <code>account_id</code> and set them to be unique as a pair.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CreateStatements</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="o">[</span><span class="mi">6</span><span class="o">.</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_table</span> <span class="ss">:statements</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">references</span> <span class="ss">:account</span><span class="p">,</span> <span class="ss">foreign_key</span><span class="p">:</span> <span class="kp">true</span><span class="p">,</span> <span class="ss">index</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">date</span> <span class="ss">:date</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># date and account_id are unique as a pair</span>
</span></span><span class="line hl"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">index</span> <span class="o">[</span><span class="ss">:date</span><span class="p">,</span> <span class="ss">:account_id</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div>
<p>Now an Account can have many Statements, but only a maximum of 1 per date. Without this constraint, an existing statement with today&rsquo;s date would prevent any other statements from being created today, even for separate accounts.</p>
<p>Uniqueness constraints can also be added after a table has been created. All we need to do is add the indexes to both attributes like before. Also, instead of using <code>t.index</code> we use <code>add_index</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">AddUniqueIndexToStatements</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="o">[</span><span class="mi">6</span><span class="o">.</span><span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span>
</span></span><span class="line"><span class="cl">    <span class="n">add_index</span> <span class="ss">:statements</span><span class="p">,</span> <span class="o">[</span><span class="ss">:account_id</span><span class="p">,</span> <span class="ss">:date</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div><h2 id="model-validation">Model Validation</h2>
<p>Now that the constraint is set in the database, we can also add it in the model. To do that, we add a uniqueness validation of an attribute and scope it to another attribute. For example, in the Statement model previously mentioned it looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">validates</span> <span class="ss">:date</span><span class="p">,</span> <span class="ss">uniqueness</span><span class="p">:</span> <span class="p">{</span> <span class="ss">scope</span><span class="p">:</span> <span class="ss">:account_id</span><span class="p">,</span> <span class="ss">message</span><span class="p">:</span> <span class="s2">&#34;Statement already exists for this date.&#34;</span> <span class="p">}</span></span></span></code></pre></div><p>And that&rsquo;s it! The model&rsquo;s attributes are now unique as a pair and validated at both the database layer and model layer.</p>
]]></content:encoded></item><item><title>Handling Decimal Precision in Rails</title><link>https://nelson.cloud/handling-decimal-precision-in-rails/?ref=rss</link><pubDate>Mon, 22 Mar 2021 00:00:00 -0700</pubDate><guid>https://nelson.cloud/handling-decimal-precision-in-rails/?ref=rss</guid><description>Configure Rails decimal columns with precision and scale to prevent rounding errors.</description><content:encoded><![CDATA[<p>Ruby on Rails allows us to specify how precise we want decimals to be by defining precision and scale in database migrations. Rails also provides a way of adding front-end validation to forms that accept decimal values. I&rsquo;ll be using an <code>expense</code> model that lets a user track expense amounts as an example. I&rsquo;ll also point out differences between SQLite and PostgreSQL in regards to saving decimals beyond constraints.</p>
<h2 id="migration">Migration</h2>
<p>The migration for an expense model that has a decimal field looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CreateExpenses</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="o">[</span><span class="mi">6</span><span class="o">.</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_table</span> <span class="ss">:expenses</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">decimal</span> <span class="ss">:amount</span><span class="p">,</span> <span class="ss">precision</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="ss">scale</span><span class="p">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">timestamps</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div><p>Notice that we&rsquo;re specifying <code>precision</code> and <code>scale</code> in the decimal column:</p>
<ul>
<li>Precision is the total number of digits in the number, both before and after the decimal point.</li>
<li>Scale is the number of digits after the decimal.</li>
</ul>
<p>So this field will take a decimal value up to 999.99.</p>
<h2 id="model">Model</h2>
<p>For the expense model, we want to add validations so that the amount is positive and is less than 1000. This is based on the precision and scale we defined in the migration. We can also add validation to take into account decimal places by using a regular expression that allows values up to 999.99.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Expense</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
</span></span><span class="line"><span class="cl">  <span class="n">validates</span> <span class="ss">:amount</span><span class="p">,</span> <span class="ss">numericality</span><span class="p">:</span> <span class="p">{</span> <span class="ss">greater_than_or_equal_to</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">less_than</span><span class="p">:</span> <span class="no">BigDecimal</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">3</span><span class="p">)</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">                     <span class="nb">format</span><span class="p">:</span> <span class="p">{</span> <span class="ss">with</span><span class="p">:</span> <span class="sr">/\A\d{1,3}(\.\d{1,2})?\z/</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div><h2 id="controller">Controller</h2>
<p>For the expense model, we have a standard controller. We don&rsquo;t need to do anything here in regards to decimal precision. I&rsquo;m showing this for completeness of the example.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ExpensesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@expenses</span> <span class="o">=</span> <span class="vi">@user</span><span class="o">.</span><span class="n">expenses</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">new</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@expense</span> <span class="o">=</span> <span class="no">Expense</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@expense</span> <span class="o">=</span> <span class="no">Expense</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">expense_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="vi">@expense</span><span class="o">.</span><span class="n">save</span>
</span></span><span class="line"><span class="cl">      <span class="n">flash</span><span class="o">[</span><span class="ss">:notice</span><span class="o">]</span> <span class="o">=</span> <span class="s1">&#39;Expense created&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="n">redirect_to</span><span class="p">(</span><span class="n">expenses_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">flash</span><span class="o">[</span><span class="ss">:alert</span><span class="o">]</span> <span class="o">=</span> <span class="vi">@expense</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">full_messages</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">&#39;, &#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">render</span><span class="p">(</span><span class="s1">&#39;new&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div><h2 id="erb-form">ERB Form</h2>
<p>In the form, we need to use <code>step</code> to add front-end validation and to be able to accept decimal values in the field.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-erb" data-lang="erb"><span class="line"><span class="cl"><span class="cp">&lt;%=</span> <span class="n">form_with</span> <span class="ss">model</span><span class="p">:</span> <span class="vi">@expense</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="p">{</span><span class="ss">controller</span><span class="p">:</span> <span class="s1">&#39;expenses&#39;</span><span class="p">,</span> <span class="ss">action</span><span class="p">:</span> <span class="s1">&#39;create&#39;</span><span class="p">}</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="cp">%&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="cp">&lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">label</span> <span class="ss">:amount</span> <span class="cp">%&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="cp">&lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">number_field</span> <span class="ss">:amount</span><span class="p">,</span> <span class="ss">step</span><span class="p">:</span> <span class="mi">0</span><span class="o">.</span><span class="mo">01</span><span class="p">,</span> <span class="ss">class</span><span class="p">:</span> <span class="s1">&#39;input&#39;</span> <span class="cp">%&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="cp">&lt;%=</span> <span class="n">f</span><span class="o">.</span><span class="n">submit</span> <span class="s2">&#34;Add&#34;</span><span class="p">,</span> <span class="ss">class</span><span class="p">:</span> <span class="s1">&#39;button is-primary&#39;</span> <span class="cp">%&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span></span></span></code></pre></div><p>In this form, <code>step: 0.01</code> is the same as specifying a scale of 2 in the database. Decimal values will only be accepted if they have two decimal places and are in increments of 0.01. (If we had specified <code>step: 0.05</code>, then values accepted would have to be in increments of 0.05, such as 1, 1.05, and 1.10). Without <code>step</code>, the form would only take whole numbers without decimals.</p>
<p>Thanks to our model validation, a user won&rsquo;t be able to submit values like <code>555.555</code> even if they were clever enough to skip front-end validation.</p>
<h2 id="sqlite-vs-postgresql-validation">SQLite vs PostgreSQL Validation</h2>
<p>Let&rsquo;s say we didn&rsquo;t have any front-end or model validations. How would the database handle decimal inputs that exceed both precision and scale? We can do some experiments in the Rails console.</p>
<h3 id="exceeding-scale-constraints">Exceeding Scale Constraints</h3>
<p>First, we&rsquo;ll try the value <code>555.555</code>, which exceeds the scale of 2.</p>
<p><strong>Rails console with SQLite:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">rails</span> c
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e = Expense.new(amount: 555.555)
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; #&lt;Expense id: nil, amount: 0.55556e3, created_at: nil, updated_at: nil&gt;
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e.save!
</span></span></span><span class="line"><span class="cl"><span class="go">  TRANSACTION (0.0ms)  begin transaction
</span></span></span><span class="line"><span class="cl"><span class="go">  Expense Create (0.6ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES (?, ?, ?)  [[&#34;amount&#34;, 555.56], [&#34;created_at&#34;, &#34;2021-03-22 09:49:35.881478&#34;], [&#34;updated_at&#34;, &#34;2021-03-22 09:49:35.881478&#34;]]
</span></span></span><span class="line"><span class="cl"><span class="go">  TRANSACTION (1.1ms)  commit transaction
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; true
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e.amount
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; 0.55556e3
</span></span></span></code></pre></div><p>After saving a new expense with the amount <code>555.555</code>, the resulting amount is <code>0.55556e3</code>, or <code>555.56</code>. The database rounded our input since it was set to a scale of 2 in the migration.</p>
<p><strong>Rails console with PostgreSQL:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">rails</span> c
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e = Expense.new(amount: 555.555)
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; #&lt;Expense id: nil, amount: 0.55556e3, created_at: nil, updated_at: nil&gt;
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e.save!
</span></span></span><span class="line"><span class="cl"><span class="go">  D, [2021-03-23T01:47:26.928433 #1] DEBUG -- :   TRANSACTION (0.4ms)  BEGIN
</span></span></span><span class="line"><span class="cl"><span class="go">  D, [2021-03-23T01:47:26.929505 #1] DEBUG -- :   Expense Create (0.7ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES ($1, $2, $3) RETURNING &#34;id&#34;  [ [&#34;amount&#34;, &#34;555.56&#34;], [&#34;created_at&#34;, &#34;2021-03-23 01:47:26.927355&#34;], [&#34;updated_at&#34;, &#34;2021-03-23 01:47:26.927355&#34;]]
</span></span></span><span class="line"><span class="cl"><span class="go">  D, [2021-03-23T01:47:26.931487 #1] DEBUG -- :   TRANSACTION (1.6ms)  COMMIT
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; true
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e.amount
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; 0.55556e3
</span></span></span></code></pre></div><p>PostgreSQL behaves the same way and rounds to two decimal places if the scale is exceeded.</p>
<h3 id="exceeding-precision-constraints">Exceeding Precision Constraints</h3>
<p>Next we&rsquo;ll try the value <code>123456.01</code>, which exceeds the precision of 5.</p>
<p><strong>Rails console with SQLite:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">rails</span> c
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e = Expense.new(amount: 123456.01)
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; #&lt;Expense id: nil, amount: 0.12346e6, created_at: nil, updated_at: nil&gt;
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e.save!
</span></span></span><span class="line"><span class="cl"><span class="go">  TRANSACTION (0.1ms)  begin transaction
</span></span></span><span class="line"><span class="cl"><span class="go">  Expense Create (0.8ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES (?, ?, ?)  [[&#34;amount&#34;, 123460.0], [&#34;created_at&#34;, &#34;2021-03-23 02:10:09.951895&#34;], [&#34;updated_at&#34;, &#34;2021-03-23 02:10:09.951895&#34;]]
</span></span></span><span class="line"><span class="cl"><span class="go">  TRANSACTION (0.5ms)  commit transaction
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; true
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e.amount
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; 0.12346e6
</span></span></span></code></pre></div><p>Interestingly, SQLite saves the value <code>123456.01</code> incorrectly as <code>123460.0</code> with no errors whatsoever. This is undesired behavior because it defeats the point of defining a precision in the first place.</p>
<p><strong>Rails console with PostgreSQL:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">rails</span> c
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e = Expense.new(amount: 123456.01)
</span></span></span><span class="line"><span class="cl"><span class="go"> =&gt; #&lt;Expense id: nil, amount: 0.12346e6, created_at: nil, updated_at: nil&gt;
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">e.save!
</span></span></span><span class="line"><span class="cl"><span class="go">  D, [2021-03-23T01:54:25.490193 #1] DEBUG -- :   TRANSACTION (0.5ms)  BEGIN
</span></span></span><span class="line"><span class="cl"><span class="go">  D, [2021-03-23T01:54:25.491212 #1] DEBUG -- :   Expense Create (0.7ms)  INSERT INTO &#34;expenses&#34; (&#34;amount&#34;, &#34;created_at&#34;, &#34;updated_at&#34;) VALUES ($1, $2, $3) RETURNING &#34;id&#34;  [[&#34;amount&#34;, &#34;123460.0&#34;], [&#34;created_at&#34;, &#34;2021-03-23 01:54:25.489022&#34;], [&#34;updated_at&#34;, &#34;2021-03-23 01:54:25.489022&#34;]]
</span></span></span><span class="line"><span class="cl"><span class="go">  D, [2021-03-23T01:54:25.491842 #1] DEBUG -- :   TRANSACTION (0.4ms)  ROLLBACK
</span></span></span><span class="line"><span class="cl"><span class="go">  Traceback (most recent call last):
</span></span></span><span class="line"><span class="cl"><span class="go">          1: from (irb):17:in `&lt;main&gt;&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">  ActiveRecord::RangeError (PG::NumericValueOutOfRange: ERROR:  numeric field overflow)
</span></span></span><span class="line"><span class="cl"><span class="go">  DETAIL:  A field with precision 5, scale 2 must round to an absolute value less than 10^3.
</span></span></span></code></pre></div><p>PostgreSQL rejects the value and shows us an error telling us that the number does not fit in with the precision and scale constraints. This is preferable to storing an incorrect value.</p>
<p>While Rails offers several ways of validating decimals, it&rsquo;s still important to choose the correct database to handle decimals.</p>
]]></content:encoded></item></channel></rss>