<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nikolay Sverchkov</title>
    <description>The latest articles on DEV Community by Nikolay Sverchkov (@ssnickolay).</description>
    <link>https://dev.to/ssnickolay</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%2F104278%2Ffd7e10cf-4e29-416f-83de-b9fc64685e29.png</url>
      <title>DEV Community: Nikolay Sverchkov</title>
      <link>https://dev.to/ssnickolay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ssnickolay"/>
    <language>en</language>
    <item>
      <title>Normalization, Consistency, and Clowne</title>
      <dc:creator>Nikolay Sverchkov</dc:creator>
      <pubDate>Mon, 10 Jun 2019 12:30:35 +0000</pubDate>
      <link>https://dev.to/evilmartians/normalization-consistency-and-clowne-44mn</link>
      <guid>https://dev.to/evilmartians/normalization-consistency-and-clowne-44mn</guid>
      <description>&lt;p&gt;One year ago &lt;a href="https://evilmartians.com/chronicles/clowne-clone-ruby-models-with-a-smile" rel="noopener noreferrer"&gt;we introduced&lt;/a&gt; a new flexible gem for cloning complex models, &lt;a href="https://clowne.evilmartians.io" rel="noopener noreferrer"&gt;Clowne&lt;/a&gt;, with the support for the two most popular Ruby ORMs: ActiveRecord and Sequel.&lt;/p&gt;

&lt;p&gt;Clowne was extracted from a large-scale Rails project we (&lt;a href="https://evilmartians.com" rel="noopener noreferrer"&gt;Evil Martians&lt;/a&gt;) were working on at that time. We were using it to clone the core record (aka &lt;em&gt;God object&lt;/em&gt;) of the app—a single cloning operation could affect more than thirty different models. You can imagine how many relations existed between them and why we had to build a new gem to help us.&lt;/p&gt;

&lt;p&gt;We open-sourced Clowne once it seemed to be a feature-complete solution with all the flexibility and customizability we intended (following the principles from the &lt;a href="https://gemcheck.evilmartians.io" rel="noopener noreferrer"&gt;GemCheck&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;But, as it usually happens, &lt;em&gt;the business&lt;/em&gt; had another point of view: requests for new, sophisticated features arrived, and we had to reconsider our cloning architecture.&lt;/p&gt;

&lt;p&gt;Today, I would like to share some of the problems that forced us to start working on the new (&lt;a href="https://github.com/palkan/clowne/releases/tag/v1.0.0" rel="noopener noreferrer"&gt;1.0&lt;/a&gt;) version of the &lt;code&gt;clowne&lt;/code&gt; gem and discuss some questions around database schema, e.g., data &lt;em&gt;normalization&lt;/em&gt; and &lt;em&gt;consistency&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the beginning, there was a database
&lt;/h2&gt;

&lt;p&gt;One of the tasks a backend developer working on a web application faces is the database schema design. Database schema describes how we store our &lt;em&gt;business data model&lt;/em&gt;, and for many situations, there could be multiple ways of doing that. How to know which one to choose? How to compare or evaluate them?&lt;/p&gt;

&lt;p&gt;Let's take a look back into history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalization is our friend
&lt;/h2&gt;

&lt;p&gt;In 1970 a true landmark paper for the future of databases came out: &lt;a href="https://dl.acm.org/citation.cfm?id=362685" rel="noopener noreferrer"&gt;“A Relational Model of Data for Large Shared Data Banks."&lt;/a&gt; In this paper, English computer scientist &lt;a href="https://en.wikipedia.org/wiki/Edgar_F._Codd" rel="noopener noreferrer"&gt;Edgar F. Codd&lt;/a&gt; described a relational model which later formed the basis of the SQL language development.&lt;/p&gt;

&lt;p&gt;The integral part of this relational model is the process of structuring database relations. Edgar F. Codd called this process &lt;em&gt;Database Normalization&lt;/em&gt;, and nowadays there exist six major concepts of normalization: the first normal form (1NF), the second normal form (2NF), etc.&lt;/p&gt;

&lt;p&gt;It's worth noting that compliance with each normal form is harder to achieve than with the previous one. For example, to satisfy the 1NF you just need to follow the rule that each attribute (column) contains only atomic (indivisible) values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Breaks 1NF&lt;/th&gt;
&lt;th&gt;Satisfies 1NF&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;


&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Game&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;God of War&lt;/td&gt;
&lt;td&gt;PS4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Red Dead Redemption 2&lt;/td&gt;
&lt;td&gt;PS4, Xbox One&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;




&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;

&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Game&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;God of War&lt;/td&gt;
&lt;td&gt;PS4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Red Dead Redemption 2&lt;/td&gt;
&lt;td&gt;PS4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Red Dead Redemption 2&lt;/td&gt;
&lt;td&gt;Xbox One&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;




&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt; &lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And to understand the last one, 6NF, in-depth knowledge of relational algebra is required 👨‍🎓.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is normalization important?
&lt;/h2&gt;

&lt;p&gt;The primary goal of database normalization is to reduce data redundancy and improve data integrity. An additional advantage of normalization is an increase in data consistency. From &lt;a href="https://www.geeksforgeeks.org/database-normalization-normal-forms/" rel="noopener noreferrer"&gt;the simple examples&lt;/a&gt; of normalization violation you may notice that the basic normalization technique is the decomposition of database schema: divide larger tables into smaller ones and link them using relationships.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: Database normalization process is &lt;em&gt;progressive&lt;/em&gt;, which means that if you have achieved a certain level of normalization, your schema also satisfies all previous levels (e.g., if the database has 4NF, it also has 1NF, 2NF, and 3NF).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi0h20n9z3n6rxzbzrnwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi0h20n9z3n6rxzbzrnwl.png" alt="https://en.wikipedia.org/wiki/Database_normalization#Normal_forms"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Denormalization is also our friend, though cunning
&lt;/h2&gt;

&lt;p&gt;Another technique we often use is &lt;em&gt;denormalization&lt;/em&gt;. As the name suggests, it's orthogonal to the normalization process of duplicating data in the database, usually, for the sake of performance (&lt;em&gt;conscious&lt;/em&gt; way) or to fix some existing schema issues (&lt;em&gt;legacy&lt;/em&gt; way).&lt;/p&gt;

&lt;p&gt;For example, in PostgreSQL we can use unstructured types like &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;jsonb&lt;/code&gt;, and &lt;code&gt;array&lt;/code&gt; to duplicate the data and improve the read operations performance by getting rid of multiple &lt;code&gt;JOIN&lt;/code&gt;-s. This approach is compelling, and in this case, a violation of normalization is justified.&lt;/p&gt;

&lt;p&gt;On the other hand, real-world applications show us that database designs aren’t always perfect: a project’s requirements change fast, we lose sight of something, we have legacy problems left by the previous developers, or we have too much data to use the &lt;em&gt;right solution&lt;/em&gt; (=normalization).&lt;/p&gt;

&lt;p&gt;Unfortunately, normalization errors are not rare, and we will show you how easy it is to make a mistake while choosing the right solution. Let's practice!&lt;/p&gt;

&lt;h2&gt;
  
  
  Selling products
&lt;/h2&gt;

&lt;p&gt;Let’s say that you are working for a business that wants to use your application to sell its products. We’ll start with a simple database structure that we used in the &lt;a href="https://evilmartians.com/chronicles/clowne-clone-ruby-models-with-a-smile" rel="noopener noreferrer"&gt;first article&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fkmc8xzn82eqsylwp3nmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fkmc8xzn82eqsylwp3nmu.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's add a new feature that allows discounts on orders:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F54mx26befjq7uohsqnnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F54mx26befjq7uohsqnnz.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our code to calculate the order's total price looks like this:&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;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:order_items&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:discounts&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total&lt;/span&gt;
    &lt;span class="n"&gt;total_discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:percent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;PriceCalculator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;total_discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_cents&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;class&lt;/span&gt; &lt;span class="nc"&gt;PriceCalculator&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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;This design is straightforward and satisfies all the normal forms. Let's see how to implement cloning of the &lt;code&gt;Order&lt;/code&gt; model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remember the Clowne
&lt;/h2&gt;

&lt;p&gt;Since we already know about the powerful Ruby cloning library called &lt;a href="https://clowne.evilmartians.io" rel="noopener noreferrer"&gt;Clowne&lt;/a&gt;, we decide to go with it for this feature:&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;class&lt;/span&gt; &lt;span class="nc"&gt;OrderCloner&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Clowne&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cloner&lt;/span&gt;
  &lt;span class="n"&gt;include_associations&lt;/span&gt; &lt;span class="ss"&gt;:order_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:discounts&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# using:&lt;/span&gt;
&lt;span class="c1"&gt;# clone = OrderCloner.call(Order.first)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just a single line of code and we can clone an order with the associated records. Easy-peasy, isn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyone makes mistakes
&lt;/h2&gt;

&lt;p&gt;Your product grows, and one day you receive a new feature request from &lt;em&gt;the business&lt;/em&gt;: "We want to be able to give discounts not only on the order total but on specific order items, too."&lt;/p&gt;

&lt;p&gt;Hm, we already have the &lt;code&gt;Discount&lt;/code&gt; model in our app, so we just need to change it to work with &lt;code&gt;OrderItems&lt;/code&gt;, too:&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;class&lt;/span&gt; &lt;span class="nc"&gt;AddOrderItemIdToDiscounts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:discounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:order_item_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&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;And update the &lt;code&gt;OrderItem#total&lt;/code&gt; method:&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;class&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:discount&lt;/span&gt; &lt;span class="c1"&gt;# new &lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total&lt;/span&gt;
    &lt;span class="c1"&gt;# was: count * price_cents&lt;/span&gt;
    &lt;span class="no"&gt;PriceCalculator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuuadex27n2xdzmakn9ep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuuadex27n2xdzmakn9ep.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We haven't changed the &lt;code&gt;OrderCloner&lt;/code&gt; class, and at first glance, everything looks good:&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;# prepare order&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="n"&gt;order_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;total_price: &lt;/span&gt;&lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;percent: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# for all order&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;percent: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order_item_id: &lt;/span&gt;&lt;span class="n"&gt;order_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# for order_item&lt;/span&gt;

&lt;span class="c1"&gt;# check clone order result&lt;/span&gt;
&lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OrderCloner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;
&lt;span class="nb"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_record&lt;/span&gt;
&lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Being satisfied with this concise and elegant solution, you deploy the code to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angry customers 👿
&lt;/h2&gt;

&lt;p&gt;Days go by. You calmly work on new tasks, and one day, a bug report comes to your mailbox—users are complaining about incorrect order prices. But you haven't touched the order price calculation code for a long, long time!&lt;/p&gt;

&lt;p&gt;You check the reported orders, and it looks like the price is correct, why are users saying the opposite?&lt;/p&gt;

&lt;p&gt;Then you notice that all the "incorrect orders" are &lt;em&gt;clones&lt;/em&gt;, so you decide to run a console and check the prices manually:&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;# are used instances from previous code snippet&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 14400  // ((100 * 2) * 0.9) * 0.8&lt;/span&gt;
&lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 16000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcn0vtqn479tdrcyw8ykx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcn0vtqn479tdrcyw8ykx.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to normalization
&lt;/h2&gt;

&lt;p&gt;Let’s see what the final &lt;code&gt;discounts&lt;/code&gt; table looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"discounts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"order_id"&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"order_item_id"&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"percent"&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are only four columns, and yet we are breaking the 3NF. How?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A table satisfying the third normal form (3NF) is a table in 2NF that &lt;strong&gt;has no transitive dependencies&lt;/strong&gt; (&lt;a href="https://en.wikipedia.org/wiki/Database_normalization#Satisfying_3NF" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's take a look at the dependencies we have in our &lt;code&gt;discounts&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;• order_id      -&amp;gt; order_item_id # thought the `order_items` table
                                 # (order_items.order_id -&amp;gt; order_items.id)
• order_item_id -&amp;gt; id
• order_id      -&amp;gt; id # transitive dependency!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dependency between &lt;code&gt;order_item_id&lt;/code&gt; and &lt;code&gt;order_id&lt;/code&gt; is defined outside of the &lt;code&gt;discounts&lt;/code&gt; table (because &lt;code&gt;OrderItem&lt;/code&gt; belongs to &lt;code&gt;Order&lt;/code&gt; via the &lt;code&gt;order_items&lt;/code&gt; table) and, of course, it doesn’t depend on the &lt;code&gt;discounts&lt;/code&gt; primary key. So, we duplicated this dependency, and that's what could be causing inconsistency in the data (which happens during the cloning operation):&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="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discount&lt;/span&gt;
&lt;span class="n"&gt;cloned_discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discount&lt;/span&gt;

&lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;cloned_discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;

&lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_item_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;cloned_discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_item_id&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true // sic!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The discount in the cloned record and in the original both refer to the same &lt;code&gt;order_item&lt;/code&gt; because we copied the &lt;code&gt;order_item_id&lt;/code&gt; attribute as is. How can we avoid this problem and fix it?&lt;/p&gt;

&lt;h3&gt;
  
  
  (The best) option № 1: satisfy 3NF
&lt;/h3&gt;

&lt;p&gt;Let's &lt;em&gt;normalize&lt;/em&gt; our table—decompose into two tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"order_discounts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"order_id"&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"percent"&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"order_items_discounts"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"order_item_id"&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"percent"&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="c1"&gt;# Orders::Discount placed in app/models/orders/discount.rb&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:discounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Orders::Discount'&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="c1"&gt;# OrderItems::Discount placed in app/models/order_items/discount.rb&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:discount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'OrderItems::Discount'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, these tables look very similar; and, yes, we need to change our cloning code to handle two new associations (and DRY-lovers' eyes might start twitching at this point 🧐). That's the price we pay to keep our data consistent: it would be impossible to allow incorrect discount rows by design. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not use Active Record polymorphic associations for this pretty similar &lt;code&gt;Discount&lt;/code&gt; models? There is a good reason why: polymorphic associations always break normal forms and sacrifice database consistency. See &lt;a href="http://seejohncode.com/2011/01/13/polymorphic-relationships-are-bad/" rel="noopener noreferrer"&gt;Don't use polymorphic associations for critical data&lt;/a&gt; for more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even though this option is the best one (and we advise you to always approach it this way first), sometimes it's not applicable: for example, if you're using &lt;code&gt;jsonb&lt;/code&gt; columns to store denormalized data (to improve querying performance by avoiding multiple &lt;code&gt;JOIN&lt;/code&gt;-s). Another example, as we said in the very beginning, is working with a legacy database with a large amount of data, when it's easy to shoot yourself in the foot while changing the schema.&lt;/p&gt;

&lt;p&gt;Let’s consider the alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option № 2: patch at the application level
&lt;/h3&gt;

&lt;p&gt;In our project, we had a very complex cloning case (more than thirty models involved) and some &lt;em&gt;historical&lt;/em&gt; problems with the database schema.&lt;/p&gt;

&lt;p&gt;That's why we fel back to the second option: solve the inconsistency at the application level (i.e., write some Ruby code).&lt;/p&gt;

&lt;p&gt;We relied on some business model-specific heuristics to restore the relations:&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;class&lt;/span&gt; &lt;span class="nc"&gt;CentralSuperFixer&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;clone_discount&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# wrong_order_item produced by clone operation&lt;/span&gt;
      &lt;span class="n"&gt;wrong_order_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clone_discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_item&lt;/span&gt; 
      &lt;span class="c1"&gt;# use product_id attribute as indirect sign&lt;/span&gt;
      &lt;span class="c1"&gt;# to find the correct order_item&lt;/span&gt;
      &lt;span class="n"&gt;correct_order_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
         &lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;wrong_order_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;# fix clone_discount&lt;/span&gt;
      &lt;span class="n"&gt;clone_discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_attributes!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order_item_id: &lt;/span&gt;&lt;span class="n"&gt;correct_order_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# call cloner and fixer altogether&lt;/span&gt;
&lt;span class="nb"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OrderCloner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:persist!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_record&lt;/span&gt;
&lt;span class="no"&gt;CentralSuperFixer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach has many cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not 100% accurate (e.g., when we have two order_items with the same &lt;code&gt;product_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Hard to maintain both the application code and tests (easy to forget to cover the edge cases)&lt;/li&gt;
&lt;li&gt;It just looks ugly :(&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option № 3: patch at the database level
&lt;/h3&gt;

&lt;p&gt;Relational databases (e.g., PostgreSQL and MySQL) do not allow us to add a constraint to check values from the remote table (otherwise we could restrict &lt;code&gt;discounts&lt;/code&gt; rows values based on the &lt;code&gt;order_items&lt;/code&gt; table data) but we can add the &lt;a href="https://postgrespro.com/docs/postgresql/9.6/sql-createtrigger" rel="noopener noreferrer"&gt;custom triggers&lt;/a&gt; and implement pretty much any logic with their help.&lt;/p&gt;

&lt;p&gt;We're not going to dig deep into this topic: it has its cons, too (e.g., testing, performance overhead) and, in general, it's not recommended to implement business logic at the database level.&lt;/p&gt;

&lt;p&gt;You can find an example of such a trigger on &lt;a href="https://stackoverflow.com/a/27107221" rel="noopener noreferrer"&gt;StackOverflow&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option № 4: use the latest version of Clowne :)
&lt;/h3&gt;

&lt;p&gt;We went with the second option first and later decided to extract from the application code and add to the &lt;code&gt;clowne&lt;/code&gt; gem.&lt;/p&gt;

&lt;p&gt;Finding a better, generalized, solution and API took some time, and eventually, we came up with the two new features: the &lt;a href="http://clowne.evilmartians.io/docs/after_persist.html" rel="noopener noreferrer"&gt;after_persist&lt;/a&gt; callback and a &lt;a href="http://clowne.evilmartians.io/docs/clone_mapper.html" rel="noopener noreferrer"&gt;mapper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's see them in action!&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;# We only need to update the `DiscountCloner`&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiscountCloner&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Clowne&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cloner&lt;/span&gt;
  &lt;span class="n"&gt;after_persist&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;origin_discount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clone_discount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
    &lt;span class="c1"&gt;# mapper allows you to get the origin of any record involved in the&lt;/span&gt;
    &lt;span class="c1"&gt;# cloning process;&lt;/span&gt;
    &lt;span class="c1"&gt;# we don't need to apply our specific heuristics anymore&lt;/span&gt;
    &lt;span class="n"&gt;order_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin_discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clone_discount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order_item_id: &lt;/span&gt;&lt;span class="n"&gt;order_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# let's try to run our cloner&lt;/span&gt;
&lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OrderCloner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="c1"&gt;# by default cloner call is not wrapped into a transaction&lt;/span&gt;
  &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;cloned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_record&lt;/span&gt;

&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;cloned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true // yay!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all! Since v1.0 Clowne &lt;em&gt;remembers&lt;/em&gt; the connection between all the original and cloned records during the cloning operation, and a &lt;code&gt;:mapper&lt;/code&gt; in the &lt;code&gt;after_persist&lt;/code&gt; callback provides the &lt;code&gt;#clone_of&lt;/code&gt; method to get this information. No more custom patches (e.g., like the ones we used in &lt;code&gt;CentralSuperFixer&lt;/code&gt;)!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: It wasn't possible to add this feature without introducing a minor breaking change. See the &lt;a href="http://clowne.evilmartians.io/docs/from_v02_to_v10.html" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; if you use an older Clowne version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here we have described only one way of using the new features, there are other uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;after_persist&lt;/code&gt; callback allows you to manipulate with the just-cloned object after it has been persisted&lt;/li&gt;
&lt;li&gt;the mapper data can be used as the log of cloning operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The updated, more flexible, architecture would also allow us to ship new awesome features in the future. There is always room for improvement in OSS projects!&lt;/p&gt;

&lt;h2&gt;
  
  
  Who wins?
&lt;/h2&gt;

&lt;p&gt;We can say without a doubt that keeping data consistent and &lt;em&gt;normalized&lt;/em&gt; is the best option. However, normalization is not a silver bullet (&lt;em&gt;spoiler: there are no silver bullets&lt;/em&gt;), and sometimes we are forced to turn to other, &lt;em&gt;darker&lt;/em&gt;, sides.&lt;/p&gt;

&lt;p&gt;Keep your mind open, evaluate all the possibilities, and try to fix the database architecture problems (if any) as early as possible!&lt;/p&gt;




&lt;p&gt;Read more dev articles on &lt;a href="https://evilmartians.com/chronicles" rel="noopener noreferrer"&gt;https://evilmartians.com/chronicles&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>gems</category>
      <category>normalization</category>
    </item>
  </channel>
</rss>
