<?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: Nikolai Privalov</title>
    <description>The latest articles on DEV Community by Nikolai Privalov (@nikolaiprivalov).</description>
    <link>https://dev.to/nikolaiprivalov</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%2F301982%2Faf34d53d-3f38-48b9-8ace-fb81387cc972.png</url>
      <title>DEV Community: Nikolai Privalov</title>
      <link>https://dev.to/nikolaiprivalov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikolaiprivalov"/>
    <language>en</language>
    <item>
      <title>Parsing huge XML in Ruby with Sax Parser. Still relevant</title>
      <dc:creator>Nikolai Privalov</dc:creator>
      <pubDate>Tue, 03 Mar 2020 15:57:36 +0000</pubDate>
      <link>https://dev.to/cybergizer/sax-parsing-in-ruby-still-actual-1k9i</link>
      <guid>https://dev.to/cybergizer/sax-parsing-in-ruby-still-actual-1k9i</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is written by &lt;a href="https://dev.to/nikolaiprivalov"&gt;Nikolai Privalov&lt;/a&gt;, graduate of &lt;a href="https://rubizza.com"&gt;Rubizza Survival Camp&lt;/a&gt; during his work on the first commercial project.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task in hand&lt;/li&gt;
&lt;li&gt;Parsing of an XML element&lt;/li&gt;
&lt;li&gt;Nested elements parsing&lt;/li&gt;
&lt;li&gt;Non-nested concurrent elements parsing&lt;/li&gt;
&lt;li&gt;Insert optimization&lt;/li&gt;
&lt;li&gt;Results&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The task in hand
&lt;/h2&gt;

&lt;p&gt;Every web-developer once in a while faces the problem of parsing price lists and catalogs for another e-commerce site. And there are common strategies to deal with price updates, depending on the way you integrate with your supplier. &lt;/p&gt;

&lt;p&gt;In my case I was working on a b2b solution, which by default has a low number of visitors. The catalog is an XML that contains all kinds of possible products, categories and vendors and it is updated once a month. When you read this file with the Nokogiri default (DOM) parser, it creates a tree structure with all branches and leaves. It allows you to easily navigate through it via css/xpath selectors.&lt;/p&gt;

&lt;p&gt;The only problem is that if you read the whole file into memory, it takes a significant amount of RAM. It is really ineffective to pay for a server if you need this RAM once a month. Since I don't need to navigate through the tree structure, but just replicate all the needed data into the database, the best option is to use SAX parser. &lt;/p&gt;

&lt;p&gt;SAX parser works a little bit differently. It uses an &lt;code&gt;event-based&lt;/code&gt; approach. It doesn't parse or create a tree structure. Instead, it reads file line by line and notifies you about any occurrence of opening or closing tag.&lt;/p&gt;

&lt;p&gt;This communication between the caller app and the parser is usually carried out by the use of the callback functions. In other words, SAX parser passes through the XML document sequentially, and notifies you on the encounters of the tracked events(document start, document end, element start(tag opening), element end(tag closing), etc...), that you can react on by executing some event-specific code. That approach allows interpreter not to store everything in RAM except the one and only line, where tag has been opened or closed.&lt;/p&gt;

&lt;p&gt;Now, presumably, you are working on a &lt;code&gt;ruby&lt;/code&gt; application, and use ActiveRecord to access the database, that has pre-created tables with associations, closely matching the nesting structure of your XML-document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;CATALOG&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;VENDOR&lt;/span&gt; &lt;span class="na"&gt;MAN_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;MAN_NAME=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;UNITS&lt;/span&gt; &lt;span class="na"&gt;UNT_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;UNT_NAME=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;UNT_PACK_QTTY=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;UNITS&lt;/span&gt; &lt;span class="na"&gt;UNT_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;UNT_NAME=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;UNT_PACK_QTTY=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;PRODUCTS&lt;/span&gt; &lt;span class="na"&gt;SER_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;SER_NAME=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;STY_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;PRODUCT_FAMILY&lt;/span&gt; &lt;span class="na"&gt;PRF_CONVERT=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRF_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ITEM&lt;/span&gt; &lt;span class="na"&gt;PRD_ADDITION=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_AXIS=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_CHECKDIGIT=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_COLOR=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_CONVERT=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_CYLINDER=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_DESCRIPTION=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_POWER=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_UPC_CODE=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_COLOR_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ITEM&lt;/span&gt; &lt;span class="na"&gt;PRD_ADDITION=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_AXIS=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_CHECKDIGIT=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_COLOR=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_CONVERT=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_CYLINDER=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_DESCRIPTION=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_POWER=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_UPC_CODE=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;PRD_COLOR_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        ...
        &lt;span class="nt"&gt;&amp;lt;UNITS_AVAILABILITY&lt;/span&gt; &lt;span class="na"&gt;UNT_ID=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/PRODUCT_FAMILY&amp;gt;&lt;/span&gt;
      ...
    &lt;span class="nt"&gt;&amp;lt;/PRODUCTS&amp;gt;&lt;/span&gt;
    ...
  &lt;span class="nt"&gt;&amp;lt;/VENDOR&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/CATALOG&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's consider an XML document, that represents a catalog of items(&lt;code&gt;&amp;lt;ITEM/&amp;gt;&lt;/code&gt;) structured by the product families(&lt;code&gt;&amp;lt;PRODUCT_FAMILY/&amp;gt;&lt;/code&gt;), by products(&lt;code&gt;&amp;lt;PRODUCTS/&amp;gt;&lt;/code&gt;), and by vendors(&lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt;). The data for parsing can be found within the attributes of the corresponding elements, while the elements themselves are hierarchically nested in each other. In the catalog, there could be multiple vendors, in the vendor - multiple products, etc. There is also a &lt;code&gt;&amp;lt;UNITS_AVAILABILITY/&amp;gt;&lt;/code&gt; element, that uniquely matches one of the &lt;code&gt;&amp;lt;UNITS/&amp;gt;&lt;/code&gt; elements by the &lt;code&gt;UNT_ID&lt;/code&gt; attribute, and characterizes available package type for the product family &lt;em&gt;(In fact, not for the family, but for the products instead, due to UNT_ID attributes being identical within a particular products)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OHIjS3Kt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3wlrcema3gtenv61hm2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OHIjS3Kt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3wlrcema3gtenv61hm2t.png" alt="table structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tables of the following structure have been prepared in the database. The column names are mostly similar to the attribute names of the XML elements, and the nesting hierarchical structure is somewhat simplified. Here the &lt;code&gt;vendors&lt;/code&gt; table matches the &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element, &lt;code&gt;products&lt;/code&gt; - &lt;code&gt;&amp;lt;PRODUCTS/&amp;gt;&lt;/code&gt;,&lt;code&gt;&amp;lt;UNITS/&amp;gt;&lt;/code&gt;, &lt;code&gt;items&lt;/code&gt; - &lt;code&gt;&amp;lt;PRODUCT_FAMILY/&amp;gt;&lt;/code&gt;,&lt;code&gt;&amp;lt;ITEM/&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing of an XML element
&lt;/h2&gt;

&lt;p&gt;In this article, we are going to use Nokogiri::XML::SAX for parsing.&lt;br&gt;
(&lt;a href="https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/SAX/"&gt;https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/SAX/&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;According to the manual let's create a subclass of the &lt;code&gt;Nokogiri::XML::SAX::Document&lt;/code&gt;, and implement the methods that correspond with the events we want to track(start_document, start_element, end_element, end_document). After that let's create an instance of the parser and feed it our XML file to parse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a subclass of Nokogiri::XML::SAX::Document and implement&lt;/span&gt;
&lt;span class="c1"&gt;# the events we care about:&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogFilter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;XML&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SAX&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_document&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_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;end_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;end_document&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;# Create our parser&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;XML&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SAX&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Parser&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="no"&gt;CatalogFilter&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="c1"&gt;# Send some XML to the parser&lt;/span&gt;
&lt;span class="n"&gt;parser&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="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'./Catalog.xml)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When the parser encounters an element, the &lt;code&gt;start_element&lt;/code&gt; method is being called with two arguments: name - element's name, attrs - an array of the element's attributes. The addition of the &lt;code&gt;case-when&lt;/code&gt; statement into the &lt;code&gt;start_element&lt;/code&gt; method allows us to react to the start of a &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'we are in &amp;lt;VENDOR/&amp;gt; element'&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;As a result, we get &lt;code&gt;we are in &amp;lt;VENDOR/&amp;gt; element&lt;/code&gt; output on every &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element encounter.&lt;/p&gt;

&lt;p&gt;In theory, when a &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element is encountered, we want to create a corresponding record in the &lt;code&gt;vendors&lt;/code&gt; table via the ActiveRecord. To do that, we modify our &lt;code&gt;case-when&lt;/code&gt; accordingly and also convert the &lt;code&gt;attrs&lt;/code&gt; array into a hash, to access the attribute values by keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="no"&gt;Vendor&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_NAME'&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;h2&gt;
  
  
  Nested elements parsing
&lt;/h2&gt;

&lt;p&gt;To create records in the &lt;code&gt;products&lt;/code&gt; table on a &lt;code&gt;&amp;lt;PRODUCTS/&amp;gt;&lt;/code&gt; element encounter, let's change our &lt;code&gt;case-when&lt;/code&gt; statement in a similar way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="no"&gt;Vendor&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_NAME'&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTS'&lt;/span&gt;
    &lt;span class="no"&gt;Product&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_NAME'&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;In the current state of our parser, the records of the &lt;code&gt;vendors&lt;/code&gt; table on the encounter of the &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; elements and the records of the &lt;code&gt;product&lt;/code&gt; table on the encounter of the &lt;code&gt;&amp;lt;PRODUCTS/&amp;gt;&lt;/code&gt; elements are already being created. However, these two tables are linked by the association of (1 - *), and the foreign key of &lt;code&gt;vendor_id&lt;/code&gt; in the records of the &lt;code&gt;products&lt;/code&gt; table is needed to be set. To do that, we assign a Vendor class object, that we create via ActiveRecord, to a CatalogFilter class instance variable, so we can add a &lt;code&gt;vendor_id&lt;/code&gt; foreign key to the &lt;code&gt;products&lt;/code&gt; table records in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="vi"&gt;@vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Vendor&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_NAME'&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTS'&lt;/span&gt;
    &lt;span class="vi"&gt;@vendor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;products&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_NAME'&lt;/span&gt;&lt;span class="p"&gt;]&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;The &lt;code&gt;@vendor&lt;/code&gt; variable gets reassigned every time the parser encounters a &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element, and it's value is relevant while we are inside that element. Since we also have a callback, when the parser finds a closing tag, we can explicitly &lt;code&gt;reset&lt;/code&gt; the value of the &lt;code&gt;@vendor&lt;/code&gt; variable in the &lt;code&gt;end_element&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;end_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="vi"&gt;@vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&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;As a result, on the exit out of the &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element, the &lt;code&gt;end_element&lt;/code&gt; function is called, the &lt;code&gt;@vendor&lt;/code&gt; variable gets assigned a &lt;code&gt;nil&lt;/code&gt;, and therefore the Vendor class object becomes non-accessible outside of the &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element. After that, we proceed to create records of the &lt;code&gt;items&lt;/code&gt; table in the same manner, keeping in mind that we are going to need the &lt;code&gt;&amp;lt;PRODUCT_FAMILY/&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;ITEM/&amp;gt;&lt;/code&gt; elements attributes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="vi"&gt;@vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Vendor&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_NAME'&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTS'&lt;/span&gt;
    &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@vendor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;products&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_NAME'&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;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCT_FAMILY'&lt;/span&gt;
    &lt;span class="vi"&gt;@prf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;basecurve: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRF_BASECURVE'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;diameter: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRF_DIAMETER'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRF_ID'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'ITEM'&lt;/span&gt;
    &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;prf_basecurve: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:basecurve&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prf_diameter: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:diameter&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prf_id: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_addition: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_ADDITION'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_axis: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_AXIS'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_color: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_COLOR'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_cylinder: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_CYLINDER'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_description: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_DESCRIPTION'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_id: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_power: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_POWER'&lt;/span&gt;&lt;span class="p"&gt;]&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;Here, we use &lt;code&gt;@prf&lt;/code&gt; variable to store the attributes of the &lt;code&gt;&amp;lt;PRODUCT_FAMILY/&amp;gt;&lt;/code&gt; element as a hash, which is available during the parsing of all the inner &lt;code&gt;&amp;lt;ITEM/&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;

&lt;p&gt;Let's edit our &lt;code&gt;end_element&lt;/code&gt; method in regards to the newly assigned instance variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;end_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="vi"&gt;@vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTS'&lt;/span&gt;
    &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCT_FAMILY'&lt;/span&gt;
    &lt;span class="vi"&gt;@prf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&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;
  
  
  Non-nested concurrent elements parsing
&lt;/h2&gt;

&lt;p&gt;At this stage we already create the necessary database records during the sequential parsing of the XML document. &lt;/p&gt;

&lt;p&gt;But looking at the document nesting structure we may also acknowledge the requirement of the &lt;code&gt;&amp;lt;UNITS/&amp;gt;&lt;/code&gt; element attributes addition to each of the &lt;code&gt;products&lt;/code&gt; records. However, it becomes known much later, the attributes of which exactly &lt;code&gt;&amp;lt;UNITS/&amp;gt;&lt;/code&gt; element are needed to be used. We can pick the correct &lt;code&gt;&amp;lt;UNITS/&amp;gt;&lt;/code&gt; element only during the parsing of the &lt;code&gt;&amp;lt;UNITS_AVAILABILITY/&amp;gt;&lt;/code&gt; element as we acquire the &lt;code&gt;UNT_ID&lt;/code&gt; attribute value.&lt;/p&gt;

&lt;p&gt;To solve this problem let's make a use of the @units array into which, as we parse the &lt;code&gt;&amp;lt;UNITS/&amp;gt;&lt;/code&gt; elements, we push the hashes containing these elements attribute values. Later on, we select the correct hash to use from the @units array by the UNT_ID attribute value.&lt;/p&gt;

&lt;p&gt;Let's add additional &lt;code&gt;case-when&lt;/code&gt; condition to the &lt;code&gt;start_element&lt;/code&gt; method in order to parse &lt;code&gt;&amp;lt;UNITS/&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;UNITS_AVAILABILITY/&amp;gt;&lt;/code&gt; elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'UNITS'&lt;/span&gt;
    &lt;span class="vi"&gt;@units&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_NAME'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;lens_quantity: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_LENS_QTTY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'UNITS_AVAILABILITY'&lt;/span&gt;
    &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@units&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_ID'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="vi"&gt;@product&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;unit_id: &lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;unit_name: &lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;unit_lens_quantity: &lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:lens_quantity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="o"&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;Of course, the &lt;code&gt;@units&lt;/code&gt; array has to be previously defined before we can even use it, and it also needs to be cleared on the end of the &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element, outside of which its content is irrelevant.&lt;/p&gt;

&lt;p&gt;It would be valid to define the &lt;code&gt;@units&lt;/code&gt; array on the encounter of the &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element in the &lt;code&gt;start_element&lt;/code&gt; method, but, as an example, we are going to implement the &lt;code&gt;start_document&lt;/code&gt; method and define the &lt;code&gt;@units&lt;/code&gt; array in it at the very start of the document parsing. Also on the exit out of the &lt;code&gt;&amp;lt;VENDOR/&amp;gt;&lt;/code&gt; element the @units array is being flushed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_document&lt;/span&gt;
  &lt;span class="vi"&gt;@units&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;end_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
    &lt;span class="vi"&gt;@vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="vi"&gt;@units&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
  &lt;span class="o"&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;After making the final changes our parser is officially ready and it is already capable of filling the database with the relevant records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Insert optimization
&lt;/h2&gt;

&lt;p&gt;This article is based on the production case, and the real catalog contained tens of &lt;code&gt;vendors&lt;/code&gt;, hundreds of &lt;code&gt;products&lt;/code&gt;, and millions of &lt;code&gt;items&lt;/code&gt; table records. Currently our parser creates records one at a time, and while it does not lead to any kind of problems for the &lt;code&gt;vendors&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt;, the creation of the couple of millions &lt;code&gt;items&lt;/code&gt; records one by one will consume a significant amount of time, like hours or even days.&lt;/p&gt;

&lt;p&gt;The simplest solution to that is the insertion of records in batches of some reasonable size. To accomplish that the &lt;code&gt;insert_all&lt;/code&gt; method of the ActiveRecord can be used. It accepts an array of the attribute hashes and creates multiple records in the database via single INSERT SQL request, bypassing the ActiveRecord objects creation callbacks. Therefore instead of the Item.create on the encounter of the &lt;code&gt;&amp;lt;ITEM/&amp;gt;&lt;/code&gt; element, we form attribute hashes and push them into the &lt;code&gt;@items&lt;/code&gt; array, as it was previously done with the &lt;code&gt;@units&lt;/code&gt; array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;start_document&lt;/span&gt;
  &lt;span class="vi"&gt;@units&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="vi"&gt;@items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'ITEM'&lt;/span&gt;
    &lt;span class="vi"&gt;@items&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;prf_basecurve: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:basecurve&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prf_diameter: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:diameter&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prf_id: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_addition: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_ADDITION'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_axis: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_AXIS'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_color: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_COLOR'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_cylinder: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_CYLINDER'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_description: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_DESCRIPTION'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_id: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;prd_power: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_POWER'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;product_id: &lt;/span&gt;&lt;span class="vi"&gt;@product&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;# ← here we pass product_id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&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;Now, at the end of the &lt;code&gt;&amp;lt;ITEM/&amp;gt;&lt;/code&gt; element we may check if the @items array size has reached some set &lt;code&gt;BATCH_SIZE&lt;/code&gt; value, and if so, the batch insert of the records based on the accumulated attribute hashes takes place with the clearing of the &lt;code&gt;@items&lt;/code&gt; array right after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;end_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'ITEM'&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;
      &lt;span class="n"&gt;item_batch_insert&lt;/span&gt;
      &lt;span class="vi"&gt;@items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;item_batch_insert&lt;/span&gt;
  &lt;span class="no"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@items&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;Bear in mind, that at the end of the document parsing there could be still some amount of elements left in the @items array, and if the array size is less than a BATCH_SIZE the &lt;code&gt;item_batch_insert&lt;/code&gt; is not being called, and that last set of records is not being created in the database. To counteract that, we call &lt;code&gt;item_batch_insert&lt;/code&gt; one more time at the end of the document parsing in the &lt;code&gt;end_document&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;end_document&lt;/span&gt;
  &lt;span class="n"&gt;item_batch_insert&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Here is the final result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;CatalogFilter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;XML&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SAX&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&lt;/span&gt;
  &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_document&lt;/span&gt;
    &lt;span class="vi"&gt;@units&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="vi"&gt;@items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
      &lt;span class="vi"&gt;@vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Vendor&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MAN_NAME'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'UNITS'&lt;/span&gt;
      &lt;span class="vi"&gt;@units&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_NAME'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;lens_quantity: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_LENS_QTTY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTS'&lt;/span&gt;
      &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@vendor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;products&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;abbr: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_NAME'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;ser_day_per_lens: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SER_DAY_PER_LENS'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCT_FAMILY'&lt;/span&gt;
      &lt;span class="vi"&gt;@prf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;basecurve: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRF_BASECURVE'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;diameter: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRF_DIAMETER'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRF_ID'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'ITEM'&lt;/span&gt;
      &lt;span class="vi"&gt;@items&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;prf_basecurve: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:basecurve&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prf_diameter: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:diameter&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prf_id: &lt;/span&gt;&lt;span class="vi"&gt;@prf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prd_addition: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_ADDITION'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prd_axis: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_AXIS'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prd_color: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_COLOR'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prd_cylinder: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_CYLINDER'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prd_description: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_DESCRIPTION'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prd_id: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;prd_power: &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PRD_POWER'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;product_id: &lt;/span&gt;&lt;span class="vi"&gt;@product&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;when&lt;/span&gt; &lt;span class="s1"&gt;'UNITS_AVAILABILITY'&lt;/span&gt;
      &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@units&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'UNT_ID'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="vi"&gt;@product&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;unit_id: &lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;unit_name: &lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;unit_lens_quantity: &lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:lens_quantity&lt;/span&gt;&lt;span class="p"&gt;]&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;def&lt;/span&gt; &lt;span class="nf"&gt;end_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'ITEM'&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;
        &lt;span class="n"&gt;item_batch_insert&lt;/span&gt;
        &lt;span class="vi"&gt;@items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCT_FAMILY'&lt;/span&gt;
      &lt;span class="vi"&gt;@prf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'PRODUCTS'&lt;/span&gt;
      &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'VENDOR'&lt;/span&gt;
      &lt;span class="vi"&gt;@vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
      &lt;span class="vi"&gt;@units&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
    &lt;span class="n"&gt;en&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;end_document&lt;/span&gt;
    &lt;span class="n"&gt;item_batch_insert&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;item_batch_insert&lt;/span&gt;
    &lt;span class="no"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@items&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="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;XML&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SAX&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Parser&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="no"&gt;CatalogFilter&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;parser&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="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'./Catalog.xml)'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;P.S. Are we done? Almost. Instead of creating new objects from scratch all the time, you might want to check if they already exist in your database. In that case you can use &lt;code&gt;first_or_create&lt;/code&gt; method of ActiveRecord. But you can not use that strategy for SQL bulk insertion. Instead, use &lt;code&gt;upsert_all&lt;/code&gt; method for that. And since &lt;code&gt;insert_all&lt;/code&gt; and &lt;code&gt;upsert_all&lt;/code&gt; bypass all the callbacks it also skips setting timestamps to a record. Please check &lt;a href="https://github.com/rails/rails/issues/35493"&gt;https://github.com/rails/rails/issues/35493&lt;/a&gt; this thread to deal with possible problems.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>xml</category>
      <category>nokogiri</category>
    </item>
  </channel>
</rss>
