<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Evgeniy Demin on Medium]]></title>
        <description><![CDATA[Stories by Evgeniy Demin on Medium]]></description>
        <link>https://medium.com/@evgeniydemin?source=rss-ba351205e070------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Z_4mOaihL69mNjOvgWpHYw.jpeg</url>
            <title>Stories by Evgeniy Demin on Medium</title>
            <link>https://medium.com/@evgeniydemin?source=rss-ba351205e070------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 11 May 2026 09:52:23 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@evgeniydemin/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Never cared much about Thread-safety in Ruby when I should have]]></title>
            <link>https://evgeniydemin.medium.com/never-cared-much-about-thread-safety-in-ruby-when-i-should-have-121c39d89f32?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/121c39d89f32</guid>
            <category><![CDATA[rails]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Sat, 03 Jan 2026 08:18:07 GMT</pubDate>
            <atom:updated>2026-01-03T08:18:07.746Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cCbGWKSHL6nMfw4vxEErLQ.png" /><figcaption>Ruby-Threads</figcaption></figure><p>Five years ago, I made a gem <a href="https://github.com/djezzzl/n1_loader">N1Loader</a> that helps prevent N+1 issues by providing a simple DSL for handling loading in batches.</p><p>It is trivial, works perfectly fine as a standalone, or with ActiveRecord. It even has an integration with <a href="https://github.com/DmitryTsepelev/ar_lazy_preload">ArLazyPreload</a> to eliminate any need for manual preloading whatsoever. Although I don’t want to praise it anymore cause I already did it a <a href="https://evgeniydemin.medium.com/automatic-preloading-in-rails-the-dream-that-came-true-80ed4982ce2d">couple</a> of <a href="https://evgeniydemin.medium.com/stop-using-eager-loading-in-your-rails-application-use-this-instead-b837f0246033">times</a>.</p><p>Not so long ago, I received a message from an ex-colleague that they had to do a monkey-patch to fix a thread-safety issue with N1Loader. My first reaction was immediate curiosity. First, I was surprised they used the gem at all. Yes, I know it’s good, but it still makes my imposter syndrome slightly shake and fight back. But then came the real thing: a use case where the code doesn’t work. Gladly, in a good, old-fashioned way by throwing errors.</p><p>The case was related to GraphQL implemented with WebSockets, so the concurrency was happening there. But you don’t need such a specific case to meet the problem.</p><p>For a while, ActiveRecord has asynchronous loading functionality, such as:</p><pre>@users = User.where(active: true).load_async</pre><p>The code above uses threads underthehood, and here is where I made a mistake.</p><p>You see, the original implementation has nothing more than a simple hash that is being filled with objects (all at once), then distributed across relative objects. First of all, Hash is not a thread-safe structure in Ruby. Moreover, the loading function wasn’t good either.</p><pre>def loaded<br>  return @loaded if @loaded<br><br>  check_arguments!<br><br>  @loaded = {}<br>  if respond_to?(:single) &amp;&amp; elements.size == 1<br>    fulfill(elements.first, single(elements.first))<br>  elsif elements.any?<br>    perform(elements)<br>  end<br><br>  @loaded<br>end</pre><p>Sharp eyes can notice that the function has one more issue, apart from missing thread safety. In case you find more, please let me know or contribute!</p><p>About thread-safety, threads can simultaneously start loading, and at least two things may happen:</p><ul><li>They did full multiple loading, resulting in extensive resource usage (no bug, but no performance gain).</li><li>One set @loaded variable, so others got it with no values yet set inside.</li></ul><p>The second case is what raised the team’s concerns, because with an empty loaded object, gem raises an error which says “Something went wrong” as preloading wasn’t executed at all. (Don’t mistreat a loaded nil object vs nothing).</p><p>There are not many options to fix it, but gladly, we don’t need many. Mutex is a known mechanism to allow concurrent code to be executed in a single thread at a time. However, in its simple form, it may cause a deadlock. Therefore, we need a so-called re-entrant locking mechanism, which is implemented by the core Ruby module: Monitor and MonitorMixin.</p><p>So the new<strong> thread-safe</strong> implementation would be the following:</p><pre>def loaded<br>  return @loaded if @already_loaded<br><br>  # comes from MonitorMixin<br>  synchronize do<br>    non_thread_safe_loaded unless @already_loaded<br>  end<br><br>  @loaded<br>end<br><br>def non_thread_safe_loaded<br>  return @loaded if @already_loaded<br><br>  check_arguments!<br><br>  @loaded = {}<br>  if respond_to?(:single) &amp;&amp; elements.size == 1<br>    fulfill(elements.first, single(elements.first))<br>  elsif elements.any?<br>    perform(elements)<br>  end<br><br>  @already_loaded = true<br>  @loaded<br>end</pre><p>Note that we check for @already_loaded two times. The first time, we do it before init locking mechanism in case the data was already loaded. This way, we save some redundant costs. The second time, in case more than one thread starts actual loading, and other threads are waiting, we won’t load data more than once as soon as the lock is freed.</p><blockquote>Notice also that we have now introduced a @already_loaded variable. It helps us solve the second problem I briefly mentioned: <em>idempotency</em>. Before, if the code during execution perform fails, the @loaded would be set to an empty hash regardless, so no more errors would be thrown on subsequent calls.</blockquote><p>By the way, I was surprised to notice that the actual CPU-wise cost of having Monitor within this code is close to zero. <strong><em>Nice!</em></strong></p><p>Are you thinking about thread-safety when working on your projects?</p><p><em>In case you need me:</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=121c39d89f32" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Another way to use GitHub’s codeowners feature, or how to collaborate in big teams effectively]]></title>
            <link>https://evgeniydemin.medium.com/another-way-to-use-githubs-codeowners-feature-or-how-to-collaborate-in-big-teams-effectively-55fdd1dea9f6?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/55fdd1dea9f6</guid>
            <category><![CDATA[code]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Fri, 29 Aug 2025 12:02:27 GMT</pubDate>
            <atom:updated>2025-08-29T12:02:27.669Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TH0zcnnyEfSTzHhStp8mNA.png" /><figcaption>Code ownership example between multiple teams</figcaption></figure><p>GitHub’s <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners">codeowners feature</a> helps with better control over changes for big teams collaborating on the same projects. And with the recent growth in popularity of monolith and monorepo architectures, the use of the codeowners feature has also increased.</p><blockquote>Not a long time ago, I wrote a <a href="https://github.com/djezzzl/database_schema_ownership">tool</a> and an <a href="https://evgeniydemin.medium.com/database-schema-ownership-c3b20a6abd2d">article</a> about database schema ownership in the Ruby on Rails world. Now, I want to discuss something much broader and language-agnostic.</blockquote><p>Let’s start from the beginning.</p><p>GitHub’s CODEOWNERS feature allows you to mark files of your repository to be owned by a specific group of engineers; therefore, anytime a new PR is opened that changes the file, that group will be asked for approval.</p><p>Imagine having tens of engineers working closely on a single repository, each making numerous changes simultaneously. The feature helps split ownership in a way that only the most experienced with a particular piece of code needs to review the proposed changes.</p><p>This approach has many benefits, including, but not limited to:</p><ul><li>You get better feedback from the most experienced reviewer</li><li>You don’t ping everybody, so creating fewer distractions for them</li><li>The development cycle is generally shorter, as everybody involved knows the domain</li></ul><p>Of course, there are also disadvantages to overusing this feature, such as poor domain knowledge sharing within the project, which can be mitigated with different practices, for example, ownership rotation.</p><p>Here, let’s focus on another problem that emerges from the feature limitation: you can only assign a whole file. Check an example below.</p><pre># Whole docs folder is owned by @doc-team<br>docs/ @doc-team<br><br># File with some logic written in Ruby is owned by @core-team<br>app/converter.rb @core-team<br><br># This is not possible to assign lines from 80-120 to a @support-team<br>app/converter.rb:80-120 @support-team</pre><p>This makes sense, as otherwise, if it were on a per-line basis, you would need to update the ownership almost every time you touch the file. I doubt that would be convenient.</p><p>Now, the problem that you can guess or have already is the following:</p><blockquote>The more collaboration you have between teams (domains), the more files have multiple owners.</blockquote><pre># File with some logic written in Ruby is owned by @core-team, @support-team, and @testing-team<br>app/converter.rb @core-team @support-team @testing-team</pre><p>Why would that be a problem?</p><p>Well, because in some cases, we come back to the point where we started: many engineers are notified about the change requests they shouldn’t be notified about.</p><p>This happens all the time. Let’s check out the example.</p><ol><li>The core team has created a Ruby file converter.rb that does some business logic.</li><li>Then, the support team has decided to log specific steps of that logic into an internal logging system, which they own.</li><li>Later, the testing team wrapped a minor function in A/B testing for their feature based on the file.</li></ol><p>Now, with the straightforward usage of the feature, anytime any team makes a change, regardless of how small, it will require the approval of all teams.</p><p>However, most of the time, only a specific function of the file is changed due to the way teams collaborate:</p><ul><li>The core team will change its business logic.</li><li>The support team might consider adding or removing logging steps.</li><li>And so, the testing team will either finish or adjust the A/B testing.</li></ul><p>Now, I hope you clearly see the problem I am trying to highlight here, and you follow the solution I implicitly exposed in the previous paragraph.</p><p>There is nothing innovative or new in the principle I would like to share; however, I see many still have issues with that. So, here it is.</p><blockquote>Don’t own the code, own the functions.</blockquote><p>Sounds simple, but what does it mean, considering the example I shared above?</p><p>So the goal is not to assign converter.rb a file to a particular team, though we still can to some extent, but to adequately write test files that we assign to specific teams.</p><pre># Let&#39;s say a file is generally owned by @core-team<br>app/converter.rb @core-team<br><br># Business logic related tests owned by @core-team<br>tests/converter_tests.rb @core-team<br><br># Tests cover that expected logging steps are triggered<br>tests/converter_logging_tests.rb @support-team<br><br># Tests cover A/B testing<br>tests/converter_ab_testing_tests.rb @testing-team</pre><p>The naming pattern (splitting a single test file into multiple) can vary. It can be anything as long as everybody follows it, so it’s easy to navigate within the repository.</p><p>Now, with the functions above, only the core team will be notified of any change to converter.rb the file. Other teams will be asked for approval only if their functions were adjusted.</p><p>Yes, the approach can be riskier if tests are poor; however, with good test coverage and overall code quality, it provides <strong>better flexibility</strong> for working within large teams.</p><p>Questions to you:</p><ul><li>What approach do you apply?</li><li>Would you try a different one?</li></ul><p><em>Consider </em><strong><em>subscribing</em></strong><em> to me! <br>And don’t forget to share your thoughts on the topic.</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><p><em>The </em><strong><em>donation</em></strong><em> supports me in continuing my open-source work and blogging:</em></p><ul><li><a href="https://github.com/sponsors/djezzzl">GitHub Sponsor</a></li><li><a href="https://opencollective.com/database_consistency">Open Collective</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=55fdd1dea9f6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Database Schema Ownership]]></title>
            <link>https://evgeniydemin.medium.com/database-schema-ownership-c3b20a6abd2d?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3b20a6abd2d</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[rails]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Tue, 19 Nov 2024 06:13:16 GMT</pubDate>
            <atom:updated>2024-11-19T06:13:16.958Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PZHU2eJqgytwY5iK_Eo5rg.png" /></figure><p>It’s been a while since my last article. Now, feeling fresh and energetic, I want to highlight a problem that has been bothering me for quite some time. As a bonus, I have created a solution right away that you can leverage in your projects.</p><p>I have over a decade of experience working in big companies with hundreds of contributors to the same projects daily. Apart from the many consequences of such workflow, there is one that I want to discuss today.</p><p>Let’s say we have Project X with a database attached. We store information about our users in this database. From a business perspective, team A owns the code about users, while other teams have their own domains. Now, assume another team needs to extend users’ data. Usually, the team needs to contact team A and ask for their opinion on the subject because they own the user’s domain. Unfortunately, engineers can sometimes overlook such changes because of a lack of automation.</p><p>There is a nice feature from GitHub called GitHub Codeowners to manage the collaborative work of big teams. It basically helps you automatically assign particular teams to be owners of specific <strong>files</strong> in your project, so every time a file is changed, the team will be pinned to review the pull request.</p><p>Unfortunately, the same cannot be done on a per-line basis; therefore, in worlds like Ruby on Rails, where a single file represents the whole database schema (schema.rb or structure.sql), we cannot use such a feature.</p><p>But what if we split the file into multiple files per table and assign responsive teams to those files? This way, any change applied to a schema would be appropriately reflected, and the right team would be asked about leaving a review for a pull request.</p><p>This is where <a href="https://github.com/djezzzl/database_schema_ownership">DatabaseSchemaOwnership</a> comes in handy. So far, it only works with <a href="https://github.com/rails/rails/tree/main/activerecord">ActiveRecord</a>, but it can be extended easily. Check the repo for more dive-in examples, and I hope you will find it helpful.</p><p><em>Please consider </em><strong><em>subscribing </em></strong><em>or</em><strong><em> adding me in social networks!</em></strong></p><p><em>And don’t forget to share what you think about the topic. Just write me a message. I would be happy to discuss the topic or anything else with you!</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3b20a6abd2d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Surprising breaking change during Rails upgrade in sprockets]]></title>
            <link>https://evgeniydemin.medium.com/breaking-change-during-rails-upgrade-introduced-by-david-heinemeier-hansson-80262129c7f1?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/80262129c7f1</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[rails]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Thu, 10 Aug 2023 17:53:43 GMT</pubDate>
            <atom:updated>2023-08-15T11:29:41.747Z</atom:updated>
            <content:encoded><![CDATA[<p>I have noticed one strange behavior about how Rails serves static files. It looks like a <strong>breaking change</strong> because I’m upgrading <strong>Rails 5.2</strong> to <strong>6.1</strong>. After spending some time debugging it, I decided to share this short story with you.</p><p><em>P.S. It isn’t the first unexpected issue I encountered during the Rails upgrade. I recommend you look at these stories:</em></p><ul><li><a href="https://evgeniydemin.medium.com/rails-upgrade-led-to-ruby-bug-f1289d118a85"><em>Bug with refinement</em></a></li><li><a href="https://betterprogramming.pub/the-mysterious-time-to-datetime-conversion-unraveling-rubys-unexpected-behavior-5b081f278cf1"><em>Time vs. DateTime difference</em></a></li><li><a href="https://evgeniydemin.medium.com/rails-upgrade-why-its-hard-and-a-single-trick-on-simplifying-it-1a0a2f2a05aa"><em>Non-trivial upgrade from Classic to Zeitwerk loader</em></a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BkzUncBxE59NKt9d" /><figcaption>Photo by <a href="https://unsplash.com/@introspectivedsgn?utm_source=medium&amp;utm_medium=referral">Erik Mclean</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In development and test environments, it’s a typical pattern to serve static files by Rails. In my case, with Rails 5.2, I could easily access all the images through the server. However, with Rails 6.1, I noticed that some of the files can’t be found.</p><p>I couldn’t understand why this behavior had changed. I would be okay if all of them were missing, but this wasn’t the case. Let’s get into specifics.</p><p>We have three icons served (I narrowed down the number for you to describe the problem):</p><ul><li>something.svg</li><li>something-supercool.svg</li><li>anything-supercool.svg</li></ul><p>All of these are served fine with Rails 5.2. However, one of them is failing with Rails 6.1. Would you like to guess which one?</p><p>And the answer is: something-supercool.svg</p><p>Now, you might ask yourself, why is that? I was surprised and confused. Although I found a quick workaround by playing with names to make it work, the intense feeling of solving the puzzle was tempting.</p><p>Unfortunately, the stack trace of the error was very poor.</p><pre>ActionController::RoutingError (No route matches [GET] &quot;/something-supercool.svg&quot;) </pre><p>After a quick lookup, I saw this error is coming from <a href="https://github.com/rails/rails/blob/6-1-stable/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L33">ActionDispatch::DebugExceptions</a> middleware.</p><pre>def call(env)<br>  request = ActionDispatch::Request.new env<br>  _, headers, body = response = @app.call(env)<br><br>  if headers[&quot;X-Cascade&quot;] == &quot;pass&quot;<br>    body.close if body.respond_to?(:close)<br>    raise ActionController::RoutingError, &quot;No route matches [#{env[&#39;REQUEST_METHOD&#39;]}] #{env[&#39;PATH_INFO&#39;].inspect}&quot;<br>  end<br><br>  response<br>rescue Exception =&gt; exception<br>  # ...<br>end</pre><p>So now we need to find out when we set headers[&quot;X-Cascade&quot;] to &quot;pass&quot;. After several debug points and a little clue where to look, we come to <a href="https://github.com/rails/sprockets/blob/main/lib/sprockets/server.rb#L171">Sprockets</a>.</p><pre># Returns a 404 Not Found response tuple<br>def not_found_response(env)<br>  if head_request?(env)<br>    [ 404, { &quot;content-type&quot; =&gt; &quot;text/plain&quot;, &quot;content-length&quot; =&gt; &quot;0&quot;, &quot;x-cascade&quot; =&gt; &quot;pass&quot; }, [] ]<br>  else<br>    [ 404, { &quot;content-type&quot; =&gt; &quot;text/plain&quot;, &quot;content-length&quot; =&gt; &quot;9&quot;, &quot;x-cascade&quot; =&gt; &quot;pass&quot; }, [ &quot;Not found&quot; ] ]<br>  end<br>end</pre><p>Indeed, Sprockets can’t find the icon. Let’s add a few more lines from the <a href="https://github.com/rails/sprockets/blob/main/lib/sprockets/server.rb#L27">middleware’s call method</a> for the whole picture.</p><pre>case status<br># ...<br>when :not_found<br>  logger.info &quot;#{msg} 404 Not Found (#{time_elapsed.call}ms)&quot;<br>  not_found_response(env)</pre><p>The status is defined here:</p><pre>if asset.nil?<br>  status = :not_found<br>elsif fingerprint &amp;&amp; asset.etag != fingerprint<br>  status = :not_found<br># ...</pre><p>And this is how we load the asset variable.</p><pre>path = full_path # In our case &quot;something-supercool.svg&quot; <br><br># Strip fingerprint<br>if fingerprint = path_fingerprint(path)<br>  path = path.sub(&quot;-#{fingerprint}&quot;, &#39;&#39;)<br>end<br><br># ...<br><br># Look up the asset.<br>asset = find_asset(path)<br><br># Fallback to looking up the asset with the full path.<br># This will make assets that are hashed with webpack or<br># other js bundlers work consistently between production<br># and development pipelines.<br>if asset.nil? &amp;&amp; (asset = find_asset(full_path))<br>  if_match = asset.etag if fingerprint<br>  fingerprint = asset.etag<br>end</pre><p>You may already find that we modify the path to eliminate fingerprint.</p><pre>def path_fingerprint(path)<br>  path[/-([0-9a-zA-Z]{7,128})\.[^.]+\z/, 1]<br>end</pre><p>Looking at this Regexp, we can say that something-supercool.svg path becomes just something.svg.</p><p>Okay, we understand this now, but …</p><p>Why would anything-supercool.svg work if we cut the path there too?</p><p>Let’s look through the flow together.</p><ol><li>change the path from anything-supercool.svg to anything.svg</li><li>try to load asset and it fails as there is no such file</li><li>try to load asset with full original path and it succeeds</li></ol><p>In the case of something-supercool.svg, the flow is different.</p><ol><li>change the path to something.svg</li><li>try to load asset and it succeeds as there is a file something.svg (another one)</li><li>define status as :not_found because asset.etag is not equal to extracted fingerprint</li></ol><p>Now, we have a complete picture of what is happening and why.</p><p>Last question remains, what has changed since Rails 5.2?</p><p>In Rails 5.2, Sprocket had a different Regexp to extract the fingerprint.</p><pre>def path_fingerprint(path)<br>  path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]<br>end</pre><p>And this breaking change was introduced by David Heinemeier Hansson (<a href="https://medium.com/u/54bcbf647830">DHH</a>) with <a href="https://github.com/rails/sprockets/pull/726">this PR</a> to ensure compatibility with esbuilds’ base32 digests.</p><p>Unfortunately, I couldn’t find any mentions of this breaking change in the Changelog, although I see that some people had the same experience.</p><ul><li><a href="https://github.com/rails/rails/issues/9788">https://github.com/rails/rails/issues/9788</a></li><li><a href="https://github.com/rails/sprockets/issues/749">https://github.com/rails/sprockets/issues/749</a></li></ul><h3>Conclusion</h3><p>This whole story reminded me of my recent conclusion during migration <a href="https://evgeniydemin.medium.com/rails-upgrade-why-its-hard-and-a-single-trick-on-simplifying-it-1a0a2f2a05aa">from Classic to Zeitwerk loader</a>.</p><blockquote>Ruby on Rails is a world of convention over configuration, and apparently, prefers underscores in file names.</blockquote><p>Thank you for your time!</p><p><em>Please consider </em><strong><em>subscribing </em></strong><em>or</em><strong><em> adding me in social networks!</em></strong></p><p><em>And don’t forget to share what you think about the topic. Just write me a message. I would be happy to discuss the topic or anything else with you!</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=80262129c7f1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rails upgrade led to Ruby bug]]></title>
            <link>https://evgeniydemin.medium.com/rails-upgrade-led-to-ruby-bug-f1289d118a85?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/f1289d118a85</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[ruby-on-rails-development]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Fri, 30 Jun 2023 15:01:59 GMT</pubDate>
            <atom:updated>2023-06-30T15:01:59.668Z</atom:updated>
            <content:encoded><![CDATA[<p>Have you ever seen Ruby bugs?</p><p>I recently did. Surprisingly, it happened during my Rails upgrade for a giant monolith.</p><p>My first impression was confusion.</p><p>I have never seen Ruby bugs before. But Ruby is another program that great engineers built. Nevertheless, it still could have some minor issues for edge cases.</p><p>So it happened to me, and I would like to share this short story on how I found it.</p><p><em>P.S. If you are considering upgrading the Rails version of your application, I encourage you to read my </em><a href="https://medium.com/@evgeniydemin/rails-upgrade-why-its-hard-and-a-single-trick-on-simplifying-it-1a0a2f2a05aa"><em>other short article</em></a><em> with valuable advice that could help you avoid many issues I had.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C1KXXjj5QCA8quVd3nIGaw.png" /><figcaption>Ruby on Rails logo</figcaption></figure><p>In large code bases, times when you might want to change something’s behavior, are generally speaking higher. But in some rare cases, you might even want to monkey patch something only for specific contexts rather than globally. Having these strict requirements in mind, you may recall one of the least used features of Ruby: <strong>Refinements</strong>.</p><p>We used refinements to monkey-patch Kernel#puts method for specific lexical contexts where the function has slightly different behavior.</p><p><em>P.S. Let’s take this case as it is.</em></p><p>In terms of the code, the below is a minimal example.</p><pre>module Refinement<br>  refine Kernel do<br>    def puts(*args)<br>      # We change the behavior<br>      super(2)<br>    end<br>  end<br>end<br><br>module Environment<br>  using Refinement<br><br>  puts(1) # =&gt; 2<br>end</pre><p>The code above works as expected with Rails 5.2. <br>However, it stops working with Rails 6.1.</p><p><em>How is this possible? How could Rails break Ruby functionality?</em></p><p>It turns out the bug is already there; Rails just exposes it. So the difference is that <strong>Rails 6 patches</strong> <strong>Kernel</strong> with two classes via prepend.</p><pre> Kernel.ancestors<br>=&gt; [ActiveSupport::ForkTracker::CoreExtPrivate, ActiveSupport::ForkTracker::CoreExt, Kernel]</pre><p>And this is the reason that causes this bug to be exposed. Here you have a minimal reproducible example.</p><pre>module Prepender<br>end<br><br>module Refinement<br>  refine Kernel do<br>    def puts(*args)<br>      super(2)<br>    end<br>  end<br>end<br><br># The line below affects the behavior of Refinement<br>#<br>Kernel.prepend(Prepender)<br>#<br># If it is commented out, then we have an expected output of 2<br># Otherwise, refinement is ignored, and output is 1<br><br>module Environment<br>  using Refinement<br><br>  puts(1)<br>end</pre><p>The bug has been found in <strong>Ruby 2.7.7</strong>. However, in Ruby 3, it seems it was fixed. I didn’t try to find a release note, but I feel it was a known case.</p><p><em>Please consider </em><strong><em>subscribing </em></strong><em>or</em><strong><em> adding me in social networks!</em></strong></p><p><em>And don’t forget to share what you think about the topic. Just write me a message. I would be happy to discuss the topic or anything else with you!</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f1289d118a85" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rails upgrade: why it’s hard and a single trick on simplifying it..]]></title>
            <link>https://evgeniydemin.medium.com/rails-upgrade-why-its-hard-and-a-single-trick-on-simplifying-it-1a0a2f2a05aa?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/1a0a2f2a05aa</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[rails]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Tue, 27 Jun 2023 14:34:41 GMT</pubDate>
            <atom:updated>2023-06-27T16:03:48.547Z</atom:updated>
            <content:encoded><![CDATA[<h3>Rails upgrade: why it’s hard and a single trick on simplifying it.</h3><p>I’m working on a Rails upgrade for a giant monolith application. It’s been a while already, and while I’m encountering many issues trying to jump from 5.2+ right to 6.1, I want to share some of the stories.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C1KXXjj5QCA8quVd3nIGaw.png" /><figcaption>Ruby on Rails logo</figcaption></figure><p>You may know that <a href="https://gist.github.com/fxn/271deed5a0772a5967c3aa00a8306746">Rails 6 has introduced</a> a new application loader: <a href="https://github.com/fxn/zeitwerk">Zeitwerk</a>. There are <a href="https://guides.rubyonrails.org/classic_to_zeitwerk_howto.html">some guidelines</a> and <a href="https://github.com/fxn/zeitwerk#introduction">incredibly well-put documentation</a> on the gem itself, so you shouldn’t be lost. But yet, for enormous projects with hundreds of engineers working together, there is something that you won’t find obvious.</p><p>I have prepared for you a minimal example to outline the problem. So, without further ado, let’s get started.</p><p>Imagine your project has two modules/domains/packages or whatever you call it that two teams/engineers work on simultaneously. In our example, their names will be Discord and Skype. And let’s say that every package has its own class to deal with API.</p><pre>module Discord<br>  class Api<br>    # code here<br>  end<br>end</pre><pre>module Skype<br>  class API<br>    # code here<br>  end<br>end</pre><p>You may notice that the Discord package has Api class while Skype has API. If you’re suspicious, that’s good; however, please consider that everything is alright so far for Rails 5.2 with classic loader. And because there is no automation to prevent such naming from happening (for pure Rails applications), teams may want to have their names even though they are inconsistent with others.</p><p>Now, you decided to upgrade your application (massive monolith, I remind you) to Rails 6.1. You are even brave enough to turn on Zeitwerk immediately because you know that classic loader will be killed anyway, so why not deal with this problem right now?</p><p>After resolving all dependency conflicts, you try to start the project console.</p><p>You see that Discord::Api can be resolved fine, but Skype::API is failing.</p><pre>Discord::Api<br>=&gt; Discord::Api</pre><pre>Skype::API<br>Traceback (most recent call last):<br>        1: from (irb):2<br>NameError (uninitialized constant Skype::API)<br>Did you mean?  Skype::Api<br>               Api</pre><p>After looking for a few documents, which I mentioned above, and even <a href="https://twitter.com/fxn/status/1671243689467715589">chatting on Twitter with the Zeitwerk author</a>, you realize it’s a good tool, and you can <a href="https://guides.rubyonrails.org/v6.0.2.1/autoloading_and_reloading_constants.html#customizing-inflections">override an inflector</a> to map different packages correctly.</p><p>You may end up with code like that:</p><pre># config/initializers/zeitwerk.rb<br>class CustomInflector &lt; Zeitwerk::Inflector<br>  CUSTOM = {<br>    &#39;discord/api.rb&#39; =&gt; &#39;Api&#39;,<br>    &#39;skype/api.rb&#39; =&gt; &#39;API&#39;<br>  }<br><br>  def camelize(basename, abspath)<br>    custom = CUSTOM.find { |(custom, _)| abspath.ends_with?(custom) }&amp;.last<br><br>    custom || super<br>  end<br>end<br><br>Rails.autoloaders.each do |loader|<br>  loader.inflector = CustomInflector.new<br>end</pre><p>This code allows us to change the inflection for file names based on their absolute path. In our example, discord/api.rb expects to have an Api constant, while slack/api.rb expects to define an API constant.</p><p><em>P.S. We don’t need a custom mapping for </em><em>Discord, but I added it for visibility.</em></p><p>Let’s try it out in the console now.</p><pre>Discord::Api<br>=&gt; Discord::Api</pre><pre>Skype::API<br>=&gt; Skype::API</pre><p>Both constants are accessible without any issues.</p><p>Now, you only need to handle every failing case for your giant application by adding a custom mapping based on its absolute path. Unfortunately, that’s not enough; there is a surprise waiting.</p><p>One of your failing cases was a controller like the one below.</p><pre>module API<br>  class BasesController &lt; ApplicationController<br>    def show<br>      head :ok<br>    end<br>  end<br>end</pre><p>And you know how to fix it already, right? So you added a custom mapping too:</p><pre>class CustomInflector &lt; Zeitwerk::Inflector<br>  CUSTOM = {<br>    &#39;discord/api.rb&#39; =&gt; &#39;Api&#39;,<br>    &#39;skype/api.rb&#39; =&gt; &#39;API&#39;,<br>    &#39;controllers/api&#39; =&gt; &#39;API&#39;, # controllers under API scope<br>  }<br><br>  def camelize(basename, abspath)<br>    custom = CUSTOM.find { |(custom, _)| abspath.ends_with?(custom) }&amp;.last<br><br>    custom || super<br>  end<br>end<br><br>Rails.autoloaders.each do |loader|<br>  loader.inflector = CustomInflector.new<br>end</pre><p>And the constant is accessible fine in the console:</p><pre>API::BasesController<br>=&gt; API::BasesController</pre><p>But here is a twist, it won’t work still!</p><p>As soon as you would try to reach that controller action via any of its routes, it will fail with:</p><pre>uninitialized constant Api Did you mean? API</pre><p>This happens due to the following piece of code from ActionPack (responsible for routing/controllers).</p><pre># gems/actionpack-6.1.7.3/lib/action_dispatch/http/request.rb<br>def controller_class_for(name)<br>  if name<br>    controller_param = name.underscore<br>    const_name = controller_param.camelize &lt;&lt; &quot;Controller&quot;<br>    begin<br>      ActiveSupport::Dependencies.constantize(const_name)<br>    rescue NameError =&gt; error<br>      if error.missing_name == const_name || const_name.start_with?(&quot;#{error.missing_name}::&quot;)<br>        raise MissingController.new(error.message, error.name)<br>      else<br>        raise<br>      end<br>    end<br>  else<br>    PASS_NOT_FOUND<br>  end<br>end</pre><p>So, when a router tries to find a corresponding controller for a path, it uses the camelize method.</p><p>And as you can see, it is converted to Api, not API.</p><pre>&quot;api/bases&quot;.camelize<br>=&gt; &quot;Api::Bases&quot;</pre><p>To fix it, you have to adjust ActiveSupport::Inflector:</p><pre>ActiveSupport::Inflector.inflections(:en) do |inflect|<br>  inflect.acronym &#39;API&#39;<br>end</pre><p>And after that, camelize will work out for you as required:</p><pre>&quot;api/bases&quot;.camelize<br>=&gt; &quot;API::Bases&quot;</pre><p>As well as the controller will respond adequately to a request.</p><p><em>P.S. This is true that ActiveSupport::Inflector has to have the same acronym defined for Rails 5.2. The point is that it </em><strong><em>isn’t enough</em></strong><em> to have only updated Zeitwerk inflection because it’s responsible for loading only. However, with a classic loader, you could have </em><strong><em>only one </em></strong><em>adjusted.</em></p><p>Now, you may wonder if we have the same “double” situation with API and Api for controllers, then, we have a problem, we would need to monkey patch ActiveSupport inflection to distinguish between different prefixes/namespaces to decide how to convert the string.</p><p>However, I don’t think we should go further than we already did.</p><p>I wanted to show you <strong>how many problems can be brought by certain decisions when the Rails convention is not honored</strong>.</p><p>All of the above can be avoided if you don’t have constant names requiring custom inflection.</p><p><em>Please consider </em><strong><em>subscribing </em></strong><em>or</em><strong><em> adding me in social networks!</em></strong></p><p><em>And don’t forget to share what you think about the topic. Just write me a message. I would be happy to discuss the topic or anything else with you!</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1a0a2f2a05aa" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Mysterious Time-to-DateTime Conversion: Unraveling Ruby’s Unexpected Behavior]]></title>
            <link>https://medium.com/better-programming/the-mysterious-time-to-datetime-conversion-unraveling-rubys-unexpected-behavior-5b081f278cf1?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/5b081f278cf1</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Mon, 22 May 2023 12:25:03 GMT</pubDate>
            <atom:updated>2023-05-22T15:04:23.298Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*qio1yP9YiGzc_40SZYFSXQ.png" /><figcaption>Unexpected conversion from Time to DateTimef</figcaption></figure><p>I recently stumbled upon a perplexing discrepancy while converting Time to DateTime using the #to_datetime method. Intrigued by this unexpected behavior, I delved into an investigation to unravel its root cause. I’ll share my findings in this blog post with you, as I believe it’s worth your attention.</p><h3>Introduction</h3><p>I’m currently engaged in the challenging task of upgrading a massive Ruby on Rails monolith. This unique experience has allowed me to dive into unfamiliar territory, learn new things, and engage with numerous stakeholders.</p><p>Amid fixing a failing spec on the new Rails version, I stumbled upon the following behavior:</p><pre># ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [arm64-darwin22]<br><br>time = Time.new(1,1,1,0)<br>=&gt; 0001-01-01 00:00:00 +0100<br><br>time.to_datetime<br>=&gt; #&lt;DateTime: 0001-01-03T00:00:00+01:00 ((1721425j,82800s,0n),+3600s,2299161j)&gt;</pre><p>I couldn’t help but wonder why there was a 2-day shift when converting Time to DateTime. Although I found a quick fix for my specs, the behavior continued to intrigue me, compelling me to dig deeper.</p><h3>Investigation</h3><p>Initially, I wasn’t sure where to begin. I started by refreshing my knowledge of the differences between Time and DateTime in Ruby’s documentation:</p><p><a href="https://ruby-doc.org/core-2.7.0/Time.html">Time</a></p><blockquote><a href="https://ruby-doc.org/core-2.7.0/Time.html">Time</a> is an abstraction of dates and times. <a href="https://ruby-doc.org/core-2.7.0/Time.html">Time</a> is stored internally as the number of seconds with fraction since the <em>Epoch</em>, January 1, 1970 00:00 UTC.<br>…<br>Since Ruby 1.9.2, <a href="https://ruby-doc.org/core-2.7.0/Time.html">Time</a> implementation uses a signed 63 bit integer, Bignum or <a href="https://ruby-doc.org/core-2.7.0/Rational.html">Rational</a>. The integer is a number of nanoseconds since the <em>Epoch</em> which can represent 1823-11-12 to 2116-02-20. When Bignum or <a href="https://ruby-doc.org/core-2.7.0/Rational.html">Rational</a> is used (before 1823, after 2116, under nanosecond), <a href="https://ruby-doc.org/core-2.7.0/Time.html">Time</a> works slower as when integer is used.</blockquote><p><a href="https://ruby-doc.org/stdlib-2.7.0/libdoc/date/rdoc/DateTime.html">DateTime</a></p><blockquote>A subclass of <a href="https://ruby-doc.org/stdlib-2.7.0/libdoc/date/rdoc/Date.html">Date</a> that easily handles date, hour, minute, second, and offset. <a href="https://ruby-doc.org/stdlib-2.7.0/libdoc/date/rdoc/DateTime.html">DateTime</a> does not consider any leap seconds, does not track any summer time rules.</blockquote><p>I <strong>quickly</strong> scrolled those documentations, then googled a bit, but, unfortunately, I couldn’t find anything related to the situation.</p><p>Therefore, I decided to go the “hard way” and opened a Ruby source code.</p><pre>static VALUE<br>time_to_datetime(VALUE self)<br>{<br>    VALUE y, sf, nth, ret;<br>    int ry, m, d, h, min, s, of;<br><br>    y = f_year(self);<br>    m = FIX2INT(f_mon(self));<br>    d = FIX2INT(f_mday(self));<br><br>    h = FIX2INT(f_hour(self));<br>    min = FIX2INT(f_min(self));<br>    s = FIX2INT(f_sec(self));<br>    if (s == 60) <br>        s = 59;<br><br>    sf = sec_to_ns(f_subsec(self));<br>    of = FIX2INT(f_utc_offset(self));<br><br>    decode_year(y, -1, &amp;nth, &amp;ry);<br><br>    ret = d_complex_new_internal(cDateTime,<br>                                 nth, 0,<br>                                 0, sf,<br>                                 of, GREGORIAN,<br>                                 ry, m, d,<br>                                 h, min, s,<br>                                 HAVE_CIVIL | HAVE_TIME);<br>    {<br>        get_d1(ret);<br>        set_sg(dat, DEFAULT_SG);<br>    }<br>    return ret;<br>}</pre><p>From top to bottom, a few things got my attention:</p><ul><li>decode_year — It deals with year-related operations differently from other attributes like month and day, although our issue pertains more to the “day” aspect.</li><li>GREGORIAN — This keyword provides a clue for the following line of investigation.</li><li>DEFAULT_SG — It is defined as #define DEFAULT_SG ITALY.</li></ul><p>Armed with these findings, I revisited the Time and DateTime documentation and finally uncovered an explanation that shed light on the reason behind the behavior.</p><blockquote>An optional argument, the day of calendar reform (start), denotes a Julian day number, which should be 2298874 to 2426355 or negative/positive infinity. The default value is Date::ITALY (2299161=1582-10-15).<br>…<br>Since Ruby’s <a href="https://ruby-doc.org/stdlib-2.7.0/libdoc/date/rdoc/Time.html">Time</a> class implements a <a href="http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar">proleptic Gregorian calendar</a> and has no concept of calendar reform, there’s no way to express this with <a href="https://ruby-doc.org/stdlib-2.7.0/libdoc/date/rdoc/Time.html">Time</a> objects.</blockquote><p>There is also an excellent explanation of when to use what.</p><p><em>P.S. It’s important to say that this paragraph was just a few blocks from the beginning, meaning my eyes simply overlooked it when I was “very quickly” scrolling through the page.</em></p><h3>Conclusion</h3><p>Dealing with time, especially in the context of time zones, is always challenging and prone to bugs. This investigation has highlighted an additional consideration before claiming that our code functions as expected. We can make informed decisions and write more robust code by understanding the intricacies of Ruby’s Time and DateTime conversions.</p><p>As for the conversion, it can be done with .gregorian to have the expected value.</p><pre>require &#39;time&#39;<br><br>time = Time.new(1,1,1,0)<br>=&gt; 0001-01-01 00:00:00 +0100<br><br>time.to_datetime<br>=&gt; #&lt;DateTime: 0001-01-03T00:00:00+01:00 ((1721425j,82800s,0n),+3600s,2299161j)&gt;<br><br>time.to_datetime.gregorian<br>=&gt; #&lt;DateTime: 0001-01-01T00:00:00+01:00 ((1721425j,82800s,0n),+3600s,-Infj)&gt;</pre><p>Thanks for reading.</p><p><em>Please consider </em><strong><em>subscribing!</em></strong></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5b081f278cf1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/the-mysterious-time-to-datetime-conversion-unraveling-rubys-unexpected-behavior-5b081f278cf1">The Mysterious Time-to-DateTime Conversion: Unraveling Ruby’s Unexpected Behavior</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Rails Controller to Business Action]]></title>
            <link>https://evgeniydemin.medium.com/from-rails-controller-to-business-action-d1ccabdc1ae2?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1ccabdc1ae2</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Tue, 11 Apr 2023 13:15:22 GMT</pubDate>
            <atom:updated>2023-04-11T13:15:22.404Z</atom:updated>
            <content:encoded><![CDATA[<p>Since <a href="https://github.com/rails/rails/commit/9541977e049d236564f34cf58660b61e154ccb32#diff-03dcb66024937683b2f2f7e4194794cab43df052b1c5be5f24df1363638cb6cd">the beginning of Ruby on Rails</a>, there have been no built-in solutions for organizing controllers and business logic. I witness engineers/companies still doing the same <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> controllers that Rails scaffolding shows them to do.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/976/1*K4V9pASNuj5FbkMr5i9xsQ.png" /><figcaption>Level up your controllers’ actions by extracting business logic.</figcaption></figure><p>While this might be a good approach in some cases, in my experience, most times, it should be done differently. Today, I would like to discuss this issue with you and what are the available solutions.</p><p>Let’s start by looking at an action you might have in your plain controller. I’ve made it based on the <a href="https://github.com/rails/rails/blob/d6715c72c50255ccc2106ddbe10e453c636c3e4a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt">scaffolding template</a>. I’ve also removed unnecessary parts to keep the focus on the subject.</p><pre>class UsersController &lt; ApplicationController<br>  # ...<br><br>  def update<br>    if @user.update(user_params)<br>      # render success<br>    else<br>      # render failure<br>    end<br>  end<br>  <br>  private<br>  <br>  def user_params<br>    params.require(:user).permit(:email, :full_name)<br>  end<br>end</pre><p>As always, everything has its pros and cons. We can emphasize that this code is straightforward to read.</p><p><em>But what can be done better here?</em></p><p>Before jumping into that, let’s define a business action in software engineering.</p><p><em>Please take a moment to stop and guess how you would describe it before reading forward.</em></p><blockquote>In software engineering, a “business action” typically refers to a unit of code that implements a specific business function or process. These actions are often designed to perform related tasks in a larger business workflow.</blockquote><blockquote>For example, let’s say you are building an e-commerce website. One of the business actions in your code might be to process a customer’s order. This action would involve several steps, such as checking the availability of products, calculating the total cost of the order, updating inventory levels, and generating an order confirmation for the customer.</blockquote><blockquote>Each step could be implemented as a separate function or method, and the overall business action would tie them together into a cohesive process. By breaking down the larger business workflow into smaller, more manageable code units, you can create more modular, reusable software that is easier to maintain and update over time.</blockquote><blockquote>Overall, designing business actions in software engineering aims to create code that reflects the underlying business logic and workflows, which can be easily adapted to business requirements or environment changes.</blockquote><p>After familiarizing yourself, I believe it would be fair to say that the @user.update(...) call is an implicit business action. In this context, its purpose is to update the user’s email and full name.</p><p><em>Where is the promised issue, then?</em></p><p>I will describe several below.</p><h4>Code isolation and reusability</h4><p>In software development, it’s important to avoid duplicating business logic and instead extract it into reusable code units.</p><p>You probably have multiple places where you need to execute the same business logic. It could be data migrations, background jobs, or other APIs like GraphQL. While copying and pasting business logic into multiple places may seem convenient, this approach is prone to bugs. It can make it difficult to maintain and update the codebase over time.</p><p>Even if you don’t currently have other consumers for the business logic, extracting it into a separate code unit can help ensure consistency and avoid errors in the future.</p><p>Honestly, it isn’t the controller’s responsibility to handle business logic. Controllers are already having a lot of responsibilities. Extracting business logic from controllers can help improve the modularity and testability of your code by allowing you to focus on testing specific units of functionality in isolation.</p><h4>Concrete business meaning</h4><p>When designing software, it’s important to ensure that the codebase is organized in a way that reflects the concrete business meaning of the application.</p><p>It’s easy to lose the concrete business meaning of the action when you have generic, widely open update actions. I saw such actions growing in size to an enormous number of accepted attributes where it becomes hard to say what it does, how, and when it’s being used.</p><p>Let’s update the action to accept just eight fields.</p><pre>def update_params<br>  params.require(:user).permit(:email, :full_name, :address, :phone,<br>                               :balance, :default_currency, <br>                               :last_signed_at,<br>                               :last_order_id)  </pre><p>It’s only eight fields (I have seen 30+), but it’s hard to tell what it’s going on here for me.</p><p><em>Don’t get me wrong, it’s obvious that it just updates the user’s fields, but can you anticipate the flows/business actions?</em></p><p>If you look at the code above one more time, you can see that newlines split the list of attributes in a specific way. Continuing the example, this business action has four fully isolated consumers. For me, it’s a clue that an action should be split into four pieces representing their concrete meanings.</p><pre>def update_personal_information<br>  personal_information = <br>    params.require(:user).permit(:email, :full_name, :address, :phone)<br>  <br>  if @user.update(personal_information)<br>    # ...<br>  else <br>    # ...<br>  end<br>end<br><br>def update_billing<br>  billing = params.require(:user).permit(:balance, :default_currency)<br><br>  if @user.update(billing)<br>    # ..<br>  else<br>    # ..<br>  end<br>end<br><br># and separate for :last_signed_at <br># one for :last_order_id</pre><p>The concrete-focused meaning is essential for understanding how the application generally behaves. It’s also much easier to extend its functionality. We are going to elaborate on this in the next bullet point.</p><h4>Business functionality</h4><p>Business functionality represents a list of things that business action does. So far, every business action above has described a single function — a user update.</p><p>Usually, it’s quite the opposite. In actual live applications, business actions consist of multiple steps. For example, I saw actions doing 20+ different things.</p><p>Let’s look at the instance where a user updates personal information. Probably, you don’t want to allow this change without sending a notification letter. So the resulting business action would look something like this:</p><pre>def update_personal_information<br>  personal_information = <br>    params.require(:user).permit(:email, :full_name, :address, :phone)<br>  <br>  if @user.update(personal_information)<br>    # Send a notification letter to the user<br>    NotificationLetter.to(user).send! # pseudo code<br>    # ...<br>  else <br>    # ...<br>  end<br>end</pre><p>And the presented flows can grow tremendously. It’s crucial to support every step appropriately, considering its nature. They can be sync or async, revertable or not, critical or optional, etc.</p><p>I hope I provided enough reasons why <strong>putting the logic into the controller</strong> is a <strong>bad idea</strong>.</p><p><em>Please let me know if it didn’t convince you. I would be happy to discuss it further.</em></p><p>In summary, building a robust DSL for business actions can help solve the issues we’ve discussed, such as <strong>reusability</strong>, <strong>transparency</strong>, and <strong>scalability</strong>.</p><p>While several existing solutions are available, such as <a href="https://github.com/toptal/granite">Granite</a>, <a href="https://github.com/BookingSync/operations">Operations</a>, <a href="https://github.com/trailblazer/trailblazer">Trailblazer</a>, and <a href="https://github.com/AaronLasseigne/active_interaction">ActiveInteraction</a>, you can always create your custom wrapper to control the required features.</p><p>Overall, I hope this article has emphasized treating your business logic as a valuable unit that deserves attention and care. By doing so, you’ll reap the benefits of a more efficient and maintainable codebase.</p><p><em>If you have any other alternatives to the ones mentioned here or would like to share your experience with these tools, please feel free to reach out.</em></p><p><strong>Thank you for reading!</strong></p><p><em>Please consider </em><strong><em>subscribing!</em></strong></p><p><em>And don’t forget to share what you think about the topic. Just write me a message. I would be happy to discuss the topic or anything else with you!</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1ccabdc1ae2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automatic preloading in Rails: the dream that came true.]]></title>
            <link>https://evgeniydemin.medium.com/automatic-preloading-in-rails-the-dream-that-came-true-80ed4982ce2d?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/80ed4982ce2d</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[rails]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Tue, 14 Mar 2023 14:41:06 GMT</pubDate>
            <atom:updated>2023-03-14T14:41:06.139Z</atom:updated>
            <content:encoded><![CDATA[<p>Recently, I published an article about “<a href="https://medium.com/@evgeniydemin/stop-using-eager-loading-in-your-rails-application-use-this-instead-b837f0246033">Stop using manual preloading in your Rails application; use this instead.</a>” Many people were interested, but I failed to explain <strong>the ultimate solution</strong>. Considering that I still see posts about ActiveRecord includes, I want to elaborate on the idea deeply.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/581/1*kn1MI1althbmEdrSpogYmA.jpeg" /><figcaption>Generated on <a href="https://imgflip.com/memegenerator">https://imgflip.com/memegenerator</a></figcaption></figure><p><em>If you haven’t read the original article, please do so. However, this is not required to understand the subject.</em></p><p><a href="https://evgeniydemin.medium.com/stop-using-eager-loading-in-your-rails-application-use-this-instead-b837f0246033">Stop using manual preloading in your Rails application; use this instead.</a></p><p>Without further ado, let’s get into the topic.</p><p><em>Skip the next section if you are well familiar with N+1 issues.</em></p><p>I’m sure you’ve all heard of N+1 issues here. In case you don’t, putting it simply, I would say:</p><blockquote>“The code executes many similar inefficient database queries/HTTP requests/complex calculations.”</blockquote><p>Most often, in the Ruby on Rails world, it’s all about database queries so we will stop on that part. Ruby on Rails offers a <a href="https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations">built-in solution</a> for dealing with N+1 issues regarding fetching associations.</p><p>Now, let’s look at the example.</p><pre>class User &lt; ActiveRecord::Base<br>  has_many :accounts<br>end<br><br>class Account &lt; ActiveRecord::Base<br>  has_many :contacts<br>end<br><br>class Contact &lt; ActiveRecord::Base<br>end</pre><p>Somewhere in the code, you want to show your users’ accounts’ contacts.</p><pre>users = User.all <br><br>users.each do |user|<br>  user.accounts.each do |account|<br>    p account.contact<br>  end<br>end</pre><p><em>If you didn’t spot the issue yet, please stop here for a moment, look carefully at the code above, and try to find it.</em></p><p>The problem is that for every user, there will be a query to a database to fetch accounts; moreover, for every account then, there will be a query to fetch its contacts.</p><p>As you can see, this chain of calls can grow as a snowball, leading to hundreds or even thousands of requests.</p><p>Rails’ solution is to use <a href="https://guides.rubyonrails.org/active_record_querying.html#includes">includes</a> at the very beginning of the chain. The fixed code would look like this:</p><pre>users = User.includes(accounts: :contacts).all<br># ...</pre><p>This code will preload all needed data in <strong>only three queries:</strong> all users, their accounts and contacts.</p><p><em>If you have ever worked with </em><em>includes before, please pause here and remember what you didn’t like about it.</em></p><p>To keep the post concise and focused on <strong>the elegant solution</strong> we are looking at soon, I will shortly share mine:</p><ul><li>you have to accurately and manually keep your beginning point <strong>consistent</strong> with the rest during the execution. If you no longer need down-the-road contacts, you better update includes, too; otherwise, you load extra data for no reason. If you need more data, let’s say referrals, you must update includes, or you get another N+1 issue. The effort required for consistency depends on how much the execution trace is spread along the project, but it isn’t trivial.</li><li>includes fetches all the data immediately. Sometimes, we need to show information under the conditions. Following our case, what if contacts should only be displayed for primary accounts? It’s possible to do partial includes by directly calling ActiveRecord::Associations::Preloader, but this way isn’t convenient nor recommended by the guidelines.</li></ul><p>Now, when we recall the N+1 problem and its most-popular Rails-way solution for that, let’s look at <strong>the proposed standard</strong>.</p><p>The Ruby on Rails framework is all about a convention and fast delivery. The goal is to focus closely on the business rather than technical aspects. With this in mind, let’s look at the fix provided by includes.</p><p>When I look at it, I wonder: if the only thing needed to avoid the N+1 problem is to type includes with specified associations, why can’t Rails do that for me? Is it possible?</p><p>Gladly, it is! It’s already production-proven and awaits you at zero integration cost.</p><ol><li>Add gem &quot;ar_lazy_preload” to your Gemfile.</li><li>Enable auto-preloading globally ArLazyPreload.config.auto_preload = true</li><li>Remove redundant includes</li></ol><p><em>Chain your loading with </em><em>.preload_associations_lazily If you don’t want to enable auto-preloading globally. For example, </em><em>User.preload_associations_lazily.all. Any consequential association loading on every </em><em>user (and following loaded records) won’t create an N+1 problem.</em></p><p>Let’s investigate what it does to avoid the N+1 problem. I will also show you when it doesn’t work as a bonus.</p><p><em>Want to read more topics about #ruby and #rails?</em> Please <strong>join</strong> my network:</p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><p>There are two main parts that make auto-preloading work:</p><ul><li><a href="https://github.com/DmitryTsepelev/ar_lazy_preload/blob/master/lib/ar_lazy_preload/contexts/base_context.rb">ArLazyPreload Context</a> (it has two implementations: one for globally enabled preloading and another for preloading through .preload_associations_lazily)</li><li><a href="https://github.com/DmitryTsepelev/ar_lazy_preload/blob/master/lib/ar_lazy_preload/active_record/relation.rb">ArLazyPreload Relation</a> patch</li></ul><p>Context is an object that stores metadata in with every ActiveRecord instance. It’s stored in .lazy_preload_context instance method. The most important metadata is the list of sibling records.</p><p>Sibling records are the records of the same class fetched in the same query and conceptually treated as similar records.</p><pre>users = User.preload_associations_lazily.first(5)<br># Records are siblings/Share single Context<br>users.map(&amp;:lazy_preload_context).uniq.count == 1<br><br>other_users = User.preload_assocations_lazily.first(5)<br># Records are siblings/Share single Context<br>other_users.map(&amp;:lazy_preload_context).uniq.count == 1<br><br># But &quot;users&quot; aren&#39;t siblings with &quot;other_users&quot; <br>users.first.lazy_preload_context != other_users.first.lazy_preload_context</pre><p>In the code above, instances in users and other_users groups are sibling records among their groups, but the two groups are not siblings.</p><p><em>Context also keeps track of a tree of already preloaded associations, but this is unnecessary to understand the main point.</em></p><p><a href="https://github.com/DmitryTsepelev/ar_lazy_preload/blob/master/lib/ar_lazy_preload/active_record/relation.rb">Relation</a> patches ActiveRecord::Relation class to look into the Context when deciding on loading the association. It will preload the association <strong>in one query for all records</strong> in the context if it exists. New records are properly distributed per referencing instance and cached there as if they would be manually preloaded with includes. After that, it assigns the context to loaded records, <strong>keeping them as siblings</strong> so they won’t produce the N+1 problem too.</p><p>Does that mean we don’t need to think about the N+1 at all?</p><p><em>Well, yes and no.</em></p><p>The answer depends on how well you know how includes is working. For example, please, look at the code below and think if it has the N+1 issue.</p><pre>users = User.includes(:accounts).all<br><br>users.each do |user|<br>  user.accounts.where(primary: true) do |account|<br>    p account<br>  end<br>end</pre><p>The answer is <strong>yes</strong>.</p><p><em>I leave it to you to understand why because this is very important and would help you avoid many pitfalls.</em></p><p>There are two main quick solutions:</p><p>First way is to replace .where with in-memory Ruby filtering by select { |account| account.primary == true }. However, I don’t recommend you this way as it is inefficient.</p><p>The second way is to create a new scoped association in User model and use that instead.</p><pre>class User &lt; ActiveRecord::Base<br>  has_many :primary_accounts, -&gt; { where(primary: true) }<br>end<br><br>users = User.includes(:primary_accounts)<br><br>users.each do |user|<br>  user.primary_accounts do |account|<br>    p account<br>  end<br>end</pre><p><em>This approach works well. However, it is very negotiable due to software design aspects; therefore, it may or may not be accepted in your project.</em></p><p>The same pattern comes when using auto-preloading. You can’t chain associations without declaring a new scoped association to avoid the N+1 issue. It’s simple to do, though.</p><p>That’s about it for auto-preloading in ActiveRecord (Rails default ORM). I hope this time I did better in explaining how it works and why you should start using it.</p><p><em>Please consider </em><strong><em>subscribing!</em></strong></p><p><em>And don’t forget to share what you think about the topic. Do you consider N+1 issues to be important in your projects? I would be happy to hear from you.</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=80ed4982ce2d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop using manual preloading in your Rails application; use this instead.]]></title>
            <link>https://evgeniydemin.medium.com/stop-using-eager-loading-in-your-rails-application-use-this-instead-b837f0246033?source=rss-ba351205e070------2</link>
            <guid isPermaLink="false">https://medium.com/p/b837f0246033</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[rails]]></category>
            <dc:creator><![CDATA[Evgeniy Demin]]></dc:creator>
            <pubDate>Tue, 28 Feb 2023 11:36:04 GMT</pubDate>
            <atom:updated>2023-03-01T13:17:01.171Z</atom:updated>
            <content:encoded><![CDATA[<p>It’s a popular internet recommendation to use <a href="https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations">preloading in Ruby on Rails</a> applications to eliminate <a href="https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping">the N+1 query problem</a>. It may seem like a blessing initially, but it has drawbacks. Gladly, there is a much better solution I want to share with you.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/918/1*RRKDoqNIzR6MD7WcE-Ml1g.png" /><figcaption>No more includes</figcaption></figure><p>Before we dive into the <strong><em>ultimate</em></strong> <strong><em>solution</em></strong>, let’s quickly explore the current approach’s downsides.</p><p><em>If you know what you don’t like about preloading, scroll right to the last section for a much better solution.</em></p><p>Imagine we have the following query somewhere in a controller or any other place according to your architecture.</p><pre>User.includes(:account, :payments, referral: :user)</pre><p>This will execute several SQL queries.</p><pre>SELECT * FROM users<br>SELECT * FROM accounts WHERE user_id = ?<br>SELECT * FROM payments WHERE user_id = ?<br>SELECT * FROM referrals WHERE user_id = ?<br>SELECT * FROM users WHERE id = ?</pre><p><em>What do you think is wrong with that?</em></p><h3>Potential unnecessary loading</h3><p>What if we need to show referrals only for users without an account?</p><pre>- if user.acccount.nil?<br>  = user.referral.user.full_name</pre><p>With initial includes, we will query referrals and users tables even when the block is never reached, e.g., all users are missing an account. This brings an unnecessary loading that we would like to avoid as well.</p><p>Existing approaches to resolve the issue take a lot of work to maintain.</p><pre>users = User.includes(:account, :payments)<br><br># Check if any user has an account<br>if users.any?(&amp;:account)<br>  # Keep in mind that this class changed its API in ActiveRecord 7.<br>  ActiveRecord::Association::Preloader.new(records: users, associations: {referral: :user}).call<br>end</pre><p>So now, you must keep the consistency of your condition logic when you load and present the data. Even with extracting the logic and reusing it in both places, it’s still hard to ensure consistency as more places could be involved later.</p><h3>Manual maintenance of zero N+1</h3><p>Assuming that no code is unchanged by fixing bugs, refactoring or developing new features, you must keep your eyes sharp on N+1.</p><p>For example, one day, you want to show users’ countries on the view. So you adjust the view, and with the help of tools like <a href="https://github.com/flyerhzm/bullet">bullet</a>, you find a new N+1 issue. You go back to the loading logic and update it.</p><pre>User.includes(:account, :payments, :country)</pre><p><em>“The job is done”</em> — you think. No more N+1, so you are free to take on another task.</p><p>While it may look like the case, unfortunately, it isn’t always true. What if the magic tool didn’t spot N+1? What if your testing/development data wasn’t producing N+1? There are many reasons for what could go wrong.</p><p>One more frequent thing is keeping preloading when it isn’t needed anymore. Let’s say one day you decided to stop showing account information. So an engineer updates the view by removing a couple of lines.</p><p>Now again, a lot of things may happen:</p><ul><li>you don’t have an automatic tool to find redundant preloading</li><li>you are afraid to remove preloading as it might be used for another place</li><li>you were focused on business value and forget to think about the technical part</li></ul><p>While reasoning I mentioned can be questionable by some, my main point is simple: <strong><em>why not make developing easier having the same quality/performance</em>?</strong></p><p><em>Do you like the topic so far?</em> I would be <strong>delighted</strong> if you subscribe:</p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><p>It’s time for <strong>the ultimate solution</strong> I promised at the beginning of the article.</p><p>When we first query for users, we could bind them with <strong>a shareable context</strong> so that every user from the group knows the whole batch at any moment.</p><pre>users = User.all<br><br>users.each { |user| user.batch_group = users }</pre><p>Now, all we need to do before querying the associations is to preload them for every object in the batch.</p><pre>unless user.association(:account).loaded?<br>  ActiveRecord::Association::Preloader.new(records: user.batch_group, associations: :account).call<br>end<br><br>user.account</pre><p>The idea is so trivial and yet so powerful. Without a surprise, tools are available for you out of the box, so you don’t need to think about preloading anymore.</p><ul><li><a href="https://github.com/DmitryTsepelev/ar_lazy_preload">ArLazyPreload</a></li><li><a href="https://github.com/salsify/goldiloader">Goldiloader</a></li></ul><p>With them, you don’t need ever to remember about includes.</p><p><em>It just works!</em></p><p>The configuration is simple (steps for ArLazyPreload):</p><ul><li>add the gem to your Gemfile (gem ‘ar_lazy_preload’)</li><li>enable it globally (ArLazyPreload.config.auto_preload = true)</li><li>that’s it.</li></ul><p>I <strong>encourage</strong> you to <strong>check them out</strong> and <strong>give them a try</strong>. I believe you will like it a lot!</p><p><em>Thank you for your time! Please </em><strong><em>subscribe</em></strong><em> for further topics about </em><strong><em>#ruby</em></strong><em> and </em><strong><em>#rails</em></strong><em>.</em></p><ul><li><a href="https://evgeniydemin.medium.com/subscribe">Medium</a></li><li><a href="https://www.linkedin.com/in/evgeniydemin/">LinkedIn</a></li><li><a href="https://twitter.com/EvgeniyDemin/">Twitter</a></li><li><a href="https://github.com/djezzzl">GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b837f0246033" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>