<?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: Abdelkader Boudih</title>
    <description>The latest articles on DEV Community by Abdelkader Boudih (@seuros).</description>
    <link>https://dev.to/seuros</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%2F766706%2F3d9f585d-7da2-4e5f-a994-9e0c8b4db410.png</url>
      <title>DEV Community: Abdelkader Boudih</title>
      <link>https://dev.to/seuros</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seuros"/>
    <language>en</language>
    <item>
      <title>Finding the Road Back Home: Building a Clean PostGIS Gem for Rails 8</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Sat, 31 May 2025 13:58:51 +0000</pubDate>
      <link>https://dev.to/seuros/finding-the-road-back-home-building-a-clean-postgis-gem-for-rails-8-21ai</link>
      <guid>https://dev.to/seuros/finding-the-road-back-home-building-a-clean-postgis-gem-for-rails-8-21ai</guid>
      <description>&lt;p&gt;&lt;em&gt;Or: How I Finally Stopped Having Monkeys in My Codebase&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After years of wrestling with spatial data in Rails, my codebase had become a literal zoo—complete with monkeys swinging through monkey patches, chaotic adapter inheritance trees, and compatibility disasters that made upgrades feel like playing Jenga with gorillas. But I've finally escaped the zoo by creating &lt;code&gt;activerecord-postgis&lt;/code&gt;, a clean, Rails 8-friendly PostGIS adapter gem. Here’s the real story behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Welcome to the Jungle
&lt;/h2&gt;

&lt;p&gt;If you’ve handled spatial data in Rails, you know the pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monkey Patches Everywhere&lt;/strong&gt;: ActiveRecord was mutated beyond recognition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inheritance Chaos&lt;/strong&gt;: Adapter hierarchies tangled beyond belief.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module Madness&lt;/strong&gt;: Gems battling each other through aggressive method prepends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade Nightmares&lt;/strong&gt;: Rails upgrades—from 6.1 to 8—left everything broken.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My own code was a guilty example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Monkey patch central&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveRecord::ConnectionAdapters::PostgreSQLAdapter&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;SpatialMixin&lt;/span&gt;
  &lt;span class="n"&gt;prepend&lt;/span&gt; &lt;span class="no"&gt;YetAnotherSpatialPatch&lt;/span&gt;

  &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:old_create_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create_table&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Terrifying spatial logic&lt;/span&gt;
    &lt;span class="n"&gt;old_create_table&lt;/span&gt;&lt;span class="p"&gt;(&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, it worked—if you ignored the fragility and constant fear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails 8: Déjà Vu All Over Again
&lt;/h2&gt;

&lt;p&gt;When Rails 8 hit, upgrading was brutally hard—just like Rails 6.1 had been. Each weekend I spent wrestling monkeys felt increasingly wasted. I realized: why keep patching when I could rebuild?&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebuilding: Not as Simple as it Sounds
&lt;/h2&gt;

&lt;p&gt;Starting fresh didn’t mean I’d just rename &lt;code&gt;st_point&lt;/code&gt; to &lt;code&gt;spatial_pt&lt;/code&gt; and watch users suffer. Sure, I love clean code, but developers need stability and clear APIs—not surprises from my whims.&lt;/p&gt;

&lt;p&gt;With AI shaping documentation, naming APIs oddly is a fast route to frustration. OSS developers (myself included) work at His Majesty’s pleasure—unpaid, unsponsored, no fancy funding. Writing docs was tedious, but now, with AI, I just jot down ideas and let ChatGPT, Claude, or Gemini handle formatting. It's like hosting "AI’s Got Talent," voting on which version makes the semifinals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Way: Rails-Friendly and Stable
&lt;/h2&gt;

&lt;p&gt;The new gem, &lt;code&gt;activerecord-postgis&lt;/code&gt;, offers:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Rails-Compatible Extensions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;PostgreSQLAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PostGIS&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AdapterExtensions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:st_point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;adapter: :postgresql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:active_record_postgresqladapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectionAdapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PostGIS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Column Methods Done Right
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Clear and precise migrations&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:locations&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;st_point&lt;/span&gt; &lt;span class="ss"&gt;:location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;geographic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;st_polygon&lt;/span&gt; &lt;span class="ss"&gt;:boundary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;geographic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Fully Integrated Rails Type System
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Spatial&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;srid: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;has_z: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;has_m: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;geographic: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;geo_type: &lt;/span&gt;&lt;span class="s2"&gt;"point"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;srid: &lt;/span&gt;&lt;span class="n"&gt;srid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;has_z: &lt;/span&gt;&lt;span class="n"&gt;has_z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;has_m: &lt;/span&gt;&lt;span class="n"&gt;has_m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;geographic: &lt;/span&gt;&lt;span class="n"&gt;geographic&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;h3&gt;
  
  
  4. Reliable Schema Dumping
&lt;/h3&gt;

&lt;p&gt;No more schema mysteries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"locations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;st_point&lt;/span&gt; &lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;geographic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;st_polygon&lt;/span&gt; &lt;span class="s2"&gt;"boundary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;geographic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;srid: &lt;/span&gt;&lt;span class="mi"&gt;4326&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Note on the Past
&lt;/h2&gt;

&lt;p&gt;The old adapters weren’t inherently bad—they were forks of the standard PostgreSQL adapter with spatial types. I myself participated in early monkey patching—not due to lack of skill, but because ActiveRecord wasn't mature enough then to handle such mutations. No bashing the original devs; they did their best with what they had.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned (Again, the Hard Way)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Work With Rails&lt;/strong&gt;: Going against Rails always makes life harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable APIs Matter&lt;/strong&gt;: Exotic naming frustrates users and AI tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Helps Documentation&lt;/strong&gt;: Writing docs is now dramatically easier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-World Testing&lt;/strong&gt;: Gems should thrive in actual apps, not just isolated tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Road Ahead
&lt;/h2&gt;

&lt;p&gt;Future plans include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advanced PostGIS features&lt;/li&gt;
&lt;li&gt;Performance enhancements&lt;/li&gt;
&lt;li&gt;Richer documentation (AI-assisted, naturally)&lt;/li&gt;
&lt;li&gt;Spatial data validations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Coming Home
&lt;/h2&gt;

&lt;p&gt;After endless monkey-patching, returning to clean code feels liberating. If you're managing spatial data in Rails, embrace the new, stable approach—your future self (and sanity) will thank you.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;activerecord-postgis&lt;/code&gt; is ready on GitHub—clean, stable, monkey-free.&lt;/p&gt;

&lt;p&gt;Yes, this article is redacted with AI assistance—not generated. Not because I'm farming views, but because English is not my native language.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>postgis</category>
      <category>spatialdata</category>
    </item>
    <item>
      <title>Building ActiveCypher: When Ruby Learns to Speak Graph</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Sun, 25 May 2025 23:47:32 +0000</pubDate>
      <link>https://dev.to/seuros/building-activecypher-when-ruby-learns-to-speak-graph-49a8</link>
      <guid>https://dev.to/seuros/building-activecypher-when-ruby-learns-to-speak-graph-49a8</guid>
      <description>&lt;h1&gt;
  
  
  Building ActiveCypher: When Ruby Learns to Speak Graph
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Sol 1&lt;/strong&gt;: ActiveCypher started as one of many frameworks. When you build frameworks for a living, you learn to think differently. Not "how do I solve this problem?" but "how do I solve this category of problems?"&lt;/p&gt;

&lt;p&gt;The vision: a universal Cypher translator. If it speaks modern Cypher, we speak back. No legacy support. Your 2012 database with its quirky syntax? Use AI to migrate it or hire a real developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 42&lt;/strong&gt;: Learning graph databases was... educational. Every YouTube short made it look simple. Every documentation page told a different story. Half were 404s. The other half wanted my email for a webinar about enterprise solutions.&lt;/p&gt;

&lt;p&gt;Meanwhile, that one developer who memorized the entire OpenCypher spec was waiting to roast anyone who dared implement it wrong. Framework builders live in fear of that person. They keep us honest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 73&lt;/strong&gt;: First working version. String concatenation everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@query_string&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"MATCH &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked. Sometimes. But frameworks cannot be fragile. When someone builds their business on your code, "sometimes" is not acceptable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 156&lt;/strong&gt;: The real education came from studying great software. Not tutorials or blog posts, but actual code. How does Arel handle complexity? How does ActiveRecord stay extensible? What makes a framework survive decades instead of months?&lt;/p&gt;

&lt;p&gt;The answer was always the same: architecture over features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 234&lt;/strong&gt;: Enter the AST. Abstract Syntax Trees are how real compilers work. If it was good enough for GCC, it was good enough for ActiveCypher.&lt;/p&gt;

&lt;p&gt;Every Cypher clause became a node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;AST&lt;/span&gt;
  &lt;span class="c1"&gt;# The simplest node - just a value&lt;/span&gt;
  &lt;span class="no"&gt;LiteralNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit_literal_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Clean, focused, single responsibility&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LimitNode&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ClauseNode&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:expression&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expression&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean. Predictable. Extensible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 301&lt;/strong&gt;: Clause ordering. MATCH before WHERE. WHERE before RETURN. Simple rules, but how to encode them?&lt;/p&gt;

&lt;p&gt;I spent an afternoon choosing numbers. Not random numbers. Not sequential numbers. The right numbers. Numbers that would live in the codebase forever, silent guardians of query correctness.&lt;/p&gt;

&lt;p&gt;Framework design is full of these moments. Small decisions that echo through time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 342&lt;/strong&gt;: The compiler emerged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visit_limit_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'LIMIT '&lt;/span&gt;
  &lt;span class="n"&gt;render_expression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expression&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;visit_literal_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;param_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;register_parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"$&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;param_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Each node knew how to render itself. The tree structure made invalid queries impossible. This is what separates frameworks from scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 423&lt;/strong&gt;: Production deployment. ActiveCypher now sits between our applications and both Memgraph and Neo4j. Like a couples therapist, it helps them communicate despite their differences.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Cyrel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Cyrel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:Person&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Cyrel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;return_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Same query, two databases&lt;/span&gt;
&lt;span class="n"&gt;memgraph_adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_cypher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;neo4j_adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_cypher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sol 456&lt;/strong&gt;: Someone asked about supporting an older Cypher syntax. I pointed them to our philosophy: latest protocols only. The graph database world moves fast. Supporting legacy means moving slow.&lt;/p&gt;

&lt;p&gt;Harsh? Maybe. But frameworks must choose: support everything poorly or support the future well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 512&lt;/strong&gt;: The AST design proved portable. When we need more speed, the same tree structure will work in Go. Or Rust. Or yes, even COBOL (though that remains a theoretical exercise).&lt;/p&gt;

&lt;p&gt;The numbers I chose for clause ordering? They travel with the design. Some decisions transcend language boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 567&lt;/strong&gt;: Framework building is lonely work. No pull requests on experimental code. No contributors until it proves itself. Just you, the spec, and the fear of that one developer who knows every edge case.&lt;/p&gt;

&lt;p&gt;But when it works? When production systems rely on your abstraction? When complex becomes simple? That is why we build frameworks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 580&lt;/strong&gt; (4 months ago): RedisGraph announced its deprecation. "Official support for RedisGraph will end on January 31, 2025. After this date, commands will be disabled on Redis Enterprise Cloud, and no further support will be provided across platforms."&lt;/p&gt;

&lt;p&gt;The architecture of Redis was never ready to receive all that graph data. I had to remove the RedisGraph adapter. Another vendor drops out. This is why frameworks must be vendor-agnostic. Databases come and go. Standards endure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sol 640&lt;/strong&gt; (2 months ago): Ruby developers started complaining. The neo4j driver had stopped working in its recent version. Vendor lock-in strikes again. It reminded me of the ElasticSearch gem story, where they nuked the adapter to prevent OpenSearch usage.&lt;/p&gt;

&lt;p&gt;I contacted the Memgraph team. They were different. They believed in standards. They wanted developers to have choices.&lt;/p&gt;

&lt;p&gt;This reinforced my vision: something standard. Something where an AI assistant would not give conflicting solutions because two vendors decided to diverge. One Cypher. Many databases. No politics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Log Entry&lt;/strong&gt;: ActiveCypher runs in production. It handles real queries for real applications. It speaks fluent Cypher to multiple databases. It refuses to support your legacy syntax.&lt;/p&gt;

&lt;p&gt;More articles will come as I learn more. Framework building is never done. There is always another edge case, another optimization, another database claiming Cypher compatibility.&lt;/p&gt;

&lt;p&gt;But for now, ActiveCypher does what it promised. It helps Ruby speak graph. Cleanly. Reliably. With carefully chosen numbers ensuring everything happens in the right order.&lt;/p&gt;

&lt;p&gt;Even if those numbers have their own secret significance.&lt;/p&gt;

&lt;p&gt;Especially then.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>ast</category>
      <category>activecypher</category>
      <category>graphdatabases</category>
    </item>
    <item>
      <title>Smarter RAG Systems with Graphs</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Tue, 06 May 2025 18:47:18 +0000</pubDate>
      <link>https://dev.to/seuros/smarter-rag-systems-with-graphs-4bg</link>
      <guid>https://dev.to/seuros/smarter-rag-systems-with-graphs-4bg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: So You Want Your LLM to Stop Guessing
&lt;/h2&gt;

&lt;p&gt;Everyone’s buzzing about Retrieval-Augmented Generation (RAG) like it’s the second coming of AI engineering. And honestly? It kinda is. Your large language model is excellent at confidently making things up—because it doesn’t &lt;em&gt;actually&lt;/em&gt; know anything beyond whatever data it was last trained on (hello, 2023).&lt;/p&gt;

&lt;p&gt;That’s where RAG comes in: bolting on real, external knowledge so your LLM stops hallucinating and starts &lt;em&gt;reasoning&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But here’s the thing: knowledge isn’t just about facts. It’s about how things &lt;em&gt;connect&lt;/em&gt;. And when relationships matter, you don’t want a flat file or a relational torture device. You want a graph.&lt;/p&gt;

&lt;p&gt;Enter the graph database. Specifically: &lt;strong&gt;Memgraph&lt;/strong&gt;—a real-time, in-memory graph database that feels like it was built by actual humans for other actual humans, not for a distributed cluster of suffering.&lt;/p&gt;

&lt;p&gt;And now, before someone @ me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am not a Memgraph employee.&lt;br&gt;&lt;br&gt;
I’m not paid by Memgraph.&lt;br&gt;&lt;br&gt;
I still stick Post-its on my fridge and call it &lt;em&gt;MemFridge&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
I'm not being forced by Memgraph to write this article after I pushed the Epstein files into their trial database.&lt;br&gt;&lt;br&gt;
I'm not locked in a basement in Zagreb, Croatia—though if you squint, Maghreb kind of sounds like it.  &lt;/p&gt;

&lt;p&gt;I'm actually writing this while nursing a sore throat and reflecting on whether the AI apocalypse will arrive before I find decent cough syrup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’m just a person who builds with RAG systems, has trust issues with stale data, and wants tools that don’t make me cry.&lt;/p&gt;

&lt;p&gt;So let’s get into why Memgraph is great for building fast, intelligent, less-fake AI systems—and how it fits into a modern RAG stack without making you sell your soul to Kubernetes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Graph Databases in RAG: Where They Fit
&lt;/h2&gt;

&lt;p&gt;Let’s sketch out a typical RAG architecture:&lt;/p&gt;

&lt;p&gt;Your vector DB fetches semantically relevant chunks. Your graph DB like Memgraph injects &lt;em&gt;contextual relationships&lt;/em&gt;, hierarchy, metadata, and freshness. Combine both, and your LLM isn’t just guessing—it’s &lt;em&gt;understanding&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Link authors, topics, and papers in a knowledge graph to avoid hallucinations.&lt;/li&gt;
&lt;li&gt;Show product recommendations in context ("people who viewed this also viewed...").&lt;/li&gt;
&lt;li&gt;Trace real-time fraud patterns across entities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vector search gives you "what." Graph search gives you "why." And Memgraph gives it to you in milliseconds before the user attention span is over.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm1ndtc7b55672u7pt4j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm1ndtc7b55672u7pt4j.png" alt="Graph vs Vector" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Memgraph’s Engine: Performance Meets Simplicity
&lt;/h2&gt;

&lt;p&gt;From a user's perspective, Memgraph provides modes for different scenarios:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. In-Memory Transactional Mode (Default)
&lt;/h3&gt;

&lt;p&gt;Need speed? This is the Sonic Boom mode.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores all data in RAM.&lt;/li&gt;
&lt;li&gt;Blazing-fast traversals for multi-hop queries.&lt;/li&gt;
&lt;li&gt;Perfect for low-latency, high-throughput RAG pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think real-time recommendations, fraud detection, or live knowledge graphs.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. In-Memory Analytical Mode
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Optimized for running complex analytical queries and algorithms.&lt;/li&gt;
&lt;li&gt;Great for heavy-duty graph computations, batch processing, and analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. On-Disk Transactional Mode (Experimental)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Data stored primarily on disk to handle larger graphs.&lt;/li&gt;
&lt;li&gt;Currently experimental, with lower query performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use this mode cautiously—ideal if your graph outgrows RAM, but be aware of the trade-offs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming (Always Fresh Data)
&lt;/h3&gt;

&lt;p&gt;Streaming isn't technically a storage mode—it's your data ingestion method, compatible with any of the above modes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct Kafka and Pulsar integrations.&lt;/li&gt;
&lt;li&gt;Ingest new data as it arrives.&lt;/li&gt;
&lt;li&gt;Keeps your knowledge graph updated without painful rebuilds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine your knowledge graph evolving as users interact. Your RAG system stays fresh. Just stream, merge, respond.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7g63bsvfcnyz1ealdkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7g63bsvfcnyz1ealdkm.png" alt="Multi-Mode Engine" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Developer Experience: The Stuff That Actually Matters
&lt;/h2&gt;

&lt;p&gt;Memgraph is built like someone actually thought about the person using it. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cypher support&lt;/strong&gt;: Same query language as Neo4j. No learning curve if you’ve touched a graph before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototype-level support for &lt;a href="https://github.com/seuros/activecypher" rel="noopener noreferrer"&gt;ActiveCypher&lt;/a&gt;&lt;/strong&gt;: A lightweight ORM inspired by ActiveRecord for Rubyists who like danger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memgraph Lab&lt;/strong&gt;: A GUI that doesn’t make your eyes bleed. Visualize, explore, debug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs that don’t insult you&lt;/strong&gt;: Actually helpful. Actually updated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser-based Playground&lt;/strong&gt;: No install. Just run and play.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You know how most databases feel like they were built by a committee in 1997 in LaTeX?&lt;br&gt;&lt;br&gt;
Memgraph feels like it was built by someone who’s built an actual app before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtpkyt2hpxb4x6t6n8an.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtpkyt2hpxb4x6t6n8an.png" alt="Developer Experience" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Query Examples: Because Words Are Cheap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get related topics in 2 hops&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;topic:&lt;/span&gt;&lt;span class="n"&gt;Concept&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt; &lt;span class="s1"&gt;'Graph Databases'&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:RELATED_TO&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;other.name&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="ss"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Concept&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationGraphNode&lt;/span&gt;
  &lt;span class="n"&gt;property&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;type: :string&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Inserting a concept&lt;/span&gt;
&lt;span class="no"&gt;Concept&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;name: &lt;/span&gt;&lt;span class="s2"&gt;"Vector Embeddings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integration Into a RAG Stack
&lt;/h2&gt;

&lt;p&gt;You can slot Memgraph into your AI stack easily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Combine with vector DBs like pgvector or even use Memgraph's built-in &lt;a href="https://memgraph.com/docs/querying/vector-search" rel="noopener noreferrer"&gt;vector search&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Serve via REST, WebSockets, or Bolt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extend with custom graph procedures (in your language of choice, because sanity matters).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to update your graph as new documents are embedded? Use streaming integrations.&lt;br&gt;&lt;br&gt;
Want to prioritize certain paths based on weights? Use Cypher and built-in graph algorithms.&lt;br&gt;&lt;br&gt;
Want to sleep? Use snapshots and WALs with in-memory modes, but remember—data still lives in RAM.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Could Go Wrong? (Spoiler: Not Much)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Graph too big for RAM?&lt;/strong&gt; You could try the experimental on-disk mode, but expect lower performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Need high availability?&lt;/strong&gt; Memgraph supports &lt;a href="https://memgraph.com/docs/clustering/replication" rel="noopener noreferrer"&gt;replication&lt;/a&gt; and &lt;a href="https://memgraph.com/docs/clustering/high-availability" rel="noopener noreferrer"&gt;High Availability&lt;/a&gt; (Enterprise Edition).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Want vector-native features?&lt;/strong&gt; Memgraph's built-in vector search works well for smaller embeddings. For huge embeddings, pair with a dedicated vector DB (preferably one that doesn’t have a 9-figure marketing budget).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not magic. It’s just designed like it’s 2025 and not 1995.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought: Just Try It
&lt;/h2&gt;

&lt;p&gt;If you’re building RAG systems or doing anything graph-related, and you want your queries fast, your setup sane, and your dev experience not soul-crushing—give Memgraph a spin.&lt;/p&gt;

&lt;p&gt;Try it for free. It runs in Docker. It has a Playground. It might even make you like databases again. They have a cloud solution that doesn't force you to hire that guy who knows "Kubernetes" after watching a Fireship video.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/" rel="noopener noreferrer"&gt;https://memgraph.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphdb</category>
      <category>rag</category>
      <category>vectordatabase</category>
      <category>llm</category>
    </item>
    <item>
      <title>Prompts in MCP: Preloaded Sanity, Not Just Fancy Slash Commands</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Tue, 06 May 2025 12:52:30 +0000</pubDate>
      <link>https://dev.to/seuros/prompts-in-mcp-preloaded-sanity-not-just-fancy-slash-commands-4b76</link>
      <guid>https://dev.to/seuros/prompts-in-mcp-preloaded-sanity-not-just-fancy-slash-commands-4b76</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP Prompts aren’t just templates. They’re server-defined, user-discoverable scripts designed to prevent your AI from doing exactly what it would do if left to its own devices: improvise badly. Prompts let you say, &lt;em&gt;"Hey AI, do this very specific thing. And please don’t freestyle it into a motivational poem or a crime."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎭 What &lt;em&gt;Are&lt;/em&gt; Prompts (and Why Should You Care)?
&lt;/h2&gt;

&lt;p&gt;Prompts in MCP are like scene instructions for your AI. Without them, the model shows up like an unpaid improv actor—confident, enthusiastic, and terrifyingly unsupervised.&lt;/p&gt;

&lt;p&gt;With prompts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You say: "Summarize this changelog."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It doesn’t: "Rephrase it as a love letter from a tired developer to their codebase."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You say: "Generate a git commit."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It doesn’t: "Write a haiku about file deletions."&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg9esoe4soi44sabctvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg9esoe4soi44sabctvb.png" alt="Prompt Delivery Console" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Anatomy of a Prompt (or, How to Stop Guessing)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generate-git-message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write a concise Git commit message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"changes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No guessing. No passive-aggressive comments from the model. Just a prompt, an argument, and an obedient response like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;"Fix off-by-one error in loop. Stop breaking things, Jerry."&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay, maybe still passive-aggressive. But helpful.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔎 Prompt Discovery (But Not By the AI, Calm Down)
&lt;/h2&gt;

&lt;p&gt;Prompts aren’t discovered by the LLM. Let’s squash that fantasy right now.&lt;/p&gt;

&lt;p&gt;Here’s how it really works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;client&lt;/strong&gt; calls &lt;code&gt;prompts/list&lt;/code&gt; from the MCP server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It decides what prompts to surface in the UI (like slash commands, menus, or keyboard shortcuts).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When a user selects one, the client fetches it via &lt;code&gt;prompts/get&lt;/code&gt;, fills in the arguments, and passes it along to the AI as pre-structured messages.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI doesn’t &lt;em&gt;decide&lt;/em&gt; which prompts to use. It doesn’t go browsing. It doesn’t have agency. It gets what it’s given—like a helpful chatbot on a tight leash.&lt;/p&gt;

&lt;p&gt;The session is maintained by the &lt;strong&gt;client&lt;/strong&gt;, not the LLM. The LLM doesn’t even know what a session is unless you tell it. It just lives in the bubble the client builds around it, like a very powerful goldfish.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Context Injection (a.k.a. Prompts With Homework)
&lt;/h2&gt;

&lt;p&gt;Let’s say you want your AI to analyze logs &lt;strong&gt;and&lt;/strong&gt; review a code file. Don’t just paste it all into the text field like a digital junk drawer. Use a structured prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file:///main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text/x-python"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then follow up with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Now compare that to this server log that looks like it’s been cursed.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your AI will know what’s going on, and for once, it won’t hallucinate that your log is a philosophical riddle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q77pn86w75wr17zedqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q77pn86w75wr17zedqh.png" alt="Context Injection Station" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🤹 Multi-Turn Prompts: When You Want a Conversation, Not a Monologue
&lt;/h2&gt;

&lt;p&gt;A basic prompt says: &lt;em&gt;“Write a commit message.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;multi-turn&lt;/strong&gt; prompt says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What's the bug?"&lt;/p&gt;

&lt;p&gt;"What have you tried?"&lt;/p&gt;

&lt;p&gt;"How many times have you cried about it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These interactions guide the user and the model like a therapist gently walking you back from the edge of another systemd rant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdeft9qw7rv8i5mpgx5pj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdeft9qw7rv8i5mpgx5pj.png" alt="Multi-Turn Workflow Theater" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Prompt Security: Protect Users from Themselves (and Your LLM)
&lt;/h2&gt;

&lt;p&gt;Let’s be honest, if a user can pass custom input to a prompt, they will find a way to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inject something malicious&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Break the formatting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ask the AI if it's "feeling okay today" and then have a 2,000-token spiral about the nature of free will&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;So sanitize. Validate. Rate-limit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just because it's a prompt doesn't mean it's safe. This isn't a spa day—it’s a potential shell command disguised as a “file analysis request.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlzxw6uluzkypkoje9om.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlzxw6uluzkypkoje9om.png" alt="Prompt Security Briefing" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 Real Examples (for the Delightfully Deranged)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔹 &lt;code&gt;debug-error&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"debug-error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Input: &lt;code&gt;"Cannot read property 'length' of undefined"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Output: &lt;code&gt;"Ah, JavaScript. Undefined chaos again. Let’s walk through the fire together."&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🔹 &lt;code&gt;generate-apology-email&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generate-apology-email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incident"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Input: &lt;code&gt;"Deployed broken code to production on Friday at 4:55pm"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"Dear team, I have made choices. None of them good. Here's how I plan to fix the chaos I unleashed..."&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🔹 &lt;code&gt;name-my-startup&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name-my-startup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"idea"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Input: &lt;code&gt;"AI-powered cat feeder with mood tracking"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Output: &lt;code&gt;"Purrlytics™ — Data-Driven Feline Satisfaction" (You're welcome.)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ✅ Conclusion: Prompts Keep You Honest
&lt;/h2&gt;

&lt;p&gt;MCP Prompts are the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AI that works with context, structure, and sanity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And AI that responds to "summarize this changelog" with "Here’s a poem about endings"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They let you scale functionality, standardize interactions, and—most importantly—prevent your AI from becoming a dangerously confident improv artist with API keys.&lt;/p&gt;

&lt;p&gt;So next time someone says “we don’t need structured prompts,” just remember: that’s how you end up with an AI that flirts with your infrastructure monitor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fad4o2xeu11ukuqekq1qs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fad4o2xeu11ukuqekq1qs.png" alt="Dumb Prompt Graveyard" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>prompts</category>
      <category>llm</category>
    </item>
    <item>
      <title>If You Think MCP Is Just a Tool Registry, You’re Missing the Point</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Tue, 06 May 2025 12:05:26 +0000</pubDate>
      <link>https://dev.to/seuros/if-you-think-mcp-is-just-a-tool-registry-youre-missing-the-point-5982</link>
      <guid>https://dev.to/seuros/if-you-think-mcp-is-just-a-tool-registry-youre-missing-the-point-5982</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If you think Model Context Protocol (MCP) is just a boring tool registry, think again. It’s more like giving your AI a Swiss Army knife—with a concierge who hands over exactly the right tool at exactly the right moment. Each AI session is isolated, reducing chaos and preventing leaks, and everything is logged like a black box flight recorder. &lt;br&gt;
In short: MCP is not a dumb proxy. It’s structured autonomy with context-aware safeguards.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠 The Swiss Army Knife Myth
&lt;/h2&gt;

&lt;p&gt;Let’s get one thing straight: &lt;strong&gt;MCP is not just a static tool registry&lt;/strong&gt;. Sure, it lets your AI discover available functions or APIs—but calling it a registry is like calling a Swiss Army knife “just some blades.”&lt;/p&gt;

&lt;p&gt;What sets MCP apart is its context-driven, session-specific dynamic tooling. Your AI doesn't sift through a buffet of random tools—it gets a curated menu based on who the user is, what the session needs, and what permissions apply.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Admins might get one toolset.&lt;/li&gt;
&lt;li&gt;Guests get a safer, restricted subset.&lt;/li&gt;
&lt;li&gt;Your AI doesn’t need to memorize every API ahead of time or rely on brittle if-else spaghetti. It discovers tools &lt;em&gt;at runtime&lt;/em&gt;, adapting in real-time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6exliewrg1un68e1zcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6exliewrg1un68e1zcg.png" alt="Concierge AI Tool Delivery Panel" width="800" height="533"&gt;&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 One Session, One Sandbox: Isolation That Actually Works
&lt;/h2&gt;

&lt;p&gt;Each session under MCP runs in a clean, isolated environment. Think of it like astronauts in separate Mars habitats. If one habitat gets messed up, it doesn't affect the others.&lt;/p&gt;

&lt;p&gt;Each session has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own memory and context
&lt;/li&gt;
&lt;li&gt;Its own standard input/output
&lt;/li&gt;
&lt;li&gt;Its own execution state
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means tools triggered in one session can’t leak into another. One user’s wild API experiment won’t ruin someone else’s workflow or expose sensitive data. It also makes debugging beautifully simple—no session-crossing nonsense to worry about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cwbzoxxvsm2dtdpg1bo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cwbzoxxvsm2dtdpg1bo.png" alt="Session Isolation Mars Habitat Grid" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📼 Session Logs: JSON-RPC as Black Box Recorder
&lt;/h2&gt;

&lt;p&gt;MCP sessions include structured &lt;strong&gt;logging&lt;/strong&gt; of all JSON-RPC calls, which turns each session into a transparent, replayable story. You’ll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What tools were discovered
&lt;/li&gt;
&lt;li&gt;Which were invoked
&lt;/li&gt;
&lt;li&gt;The exact inputs and responses
&lt;/li&gt;
&lt;li&gt;Any glorious, flaming errors
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t just for post-mortems when something catches fire. These logs are gold for proactive monitoring, too. Set alerts. Spot suspicious patterns. Roll back or quarantine tools if needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3or27eajt3e0h8rs38m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3or27eajt3e0h8rs38m.png" alt="MCP Logging Terminal" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 Dumb Proxies: Don’t Be That Person
&lt;/h2&gt;

&lt;p&gt;Now, let’s talk about what &lt;em&gt;not&lt;/em&gt; to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A dumb proxy&lt;/strong&gt; is when you simply expose all your tools to the AI with no context, no control, and no oversight. You just wire everything up and hope for the best. The model tries every tool it sees, errors explode like fireworks, and you learn very quickly what &lt;em&gt;not&lt;/em&gt; to do.&lt;/p&gt;

&lt;p&gt;You might think:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Why not just let the AI figure it out? It’s smart!”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Right. And toddlers are good at juggling chainsaws.&lt;/p&gt;

&lt;p&gt;Without isolation, permissions, or dynamic context, you’re setting up a system where the AI might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete important data
&lt;/li&gt;
&lt;li&gt;Spam endpoints with bad input
&lt;/li&gt;
&lt;li&gt;Leak internal notes to public channels
&lt;/li&gt;
&lt;li&gt;Order 1,000 pizzas because you said you were hungry. (True story—probably.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP, by contrast, enforces proper boundaries and only surfaces the tools that make sense in a given moment. It’s like replacing your toddler-chainsaw problem with a trained sous-chef who knows exactly when to use the paring knife.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf8np509d8dxa9pnyhgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf8np509d8dxa9pnyhgi.png" alt="Dumb Proxy Disaster Control Room" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Conclusion: Stop Calling It a Registry
&lt;/h2&gt;

&lt;p&gt;MCP is not a registry. It’s not a dump truck of APIs. And it’s definitely not a dumb proxy.&lt;/p&gt;

&lt;p&gt;It’s a dynamic, context-aware protocol that acts like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Swiss Army knife with an expert concierge
&lt;/li&gt;
&lt;li&gt;A Mars-grade isolated sandbox per session
&lt;/li&gt;
&lt;li&gt;A JSON-RPC black box for every decision your AI makes
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you treat it like just another plugin system, you’re missing the point—and probably setting yourself up for a fun little disaster.&lt;/p&gt;

&lt;p&gt;So the next time someone tells you MCP is “just a registry,” go ahead and roll your eyes. Then explain, calmly, that you’d rather not hand a toddler a loaded API ever again.&lt;/p&gt;

&lt;p&gt;For more in-depth info, check out the &lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;official MCP documentation&lt;/a&gt;. Happy hacking.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Rise of 'Vibe Packages' in Open Source Development</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Sat, 12 Apr 2025 14:35:10 +0000</pubDate>
      <link>https://dev.to/seuros/the-rise-of-vibe-packages-in-open-source-development-40kg</link>
      <guid>https://dev.to/seuros/the-rise-of-vibe-packages-in-open-source-development-40kg</guid>
      <description>&lt;h2&gt;
  
  
  When AI-Driven Speed Meets Open Source Ecosystems
&lt;/h2&gt;

&lt;p&gt;In the world of open source, a strange new trend has emerged: the rise of &lt;em&gt;“vibe packages.”&lt;/em&gt; These are libraries, SDKs, and API wrappers that pop up almost overnight – often crafted by enthusiastic junior developers riding high on AI code generators. They appear in droves on GitHub and package registries, tackling every API or service imaginable.&lt;/p&gt;

&lt;p&gt;At first glance, this sounds like a win for developer productivity and open source abundance. Who wouldn’t want more code available to solve problems? But scratch the surface, and you’ll find a Wild West of unlicensed copy-paste code, half-baked implementations, and forgotten repositories. In this post, we’ll explore what vibe packages are, why they’re spreading (especially in niche programming languages), and how the community is reacting – with equal parts excitement and eye-rolling.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Are "Vibe Packages"?
&lt;/h2&gt;

&lt;p&gt;“Vibe package” isn’t an official term – it’s a tongue-in-cheek label for the flood of quickly published libraries generated more for the &lt;em&gt;vibe&lt;/em&gt; of coding than for any real production need. &lt;/p&gt;

&lt;p&gt;These packages are often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created by junior developers with the help of AI coding tools.&lt;/li&gt;
&lt;li&gt;Thin wrappers around APIs with minimal (or no) testing.&lt;/li&gt;
&lt;li&gt;Rebranded copies of other projects, sometimes with the original comments and todos still in place.&lt;/li&gt;
&lt;li&gt;Missing licenses or published with inappropriate ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? A sea of packages that look shiny but are often brittle, shallow, or ethically dubious.&lt;/p&gt;




&lt;h2&gt;
  
  
  Niche Language Ecosystems: From Curated to Cluttered
&lt;/h2&gt;

&lt;p&gt;The impact is especially pronounced in smaller ecosystems like Fortran, Nim, or even Elixir. These languages used to have few but carefully maintained libraries. Now? API wrappers and SDKs appear for every new hot service—whether or not there’s a real need.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A wave of unofficial OpenAI SDKs popped up in Dart, Zig, Rust, even Fortran.&lt;/li&gt;
&lt;li&gt;Some projects are just AI-generated regurgitations of API docs.&lt;/li&gt;
&lt;li&gt;Entire structures are copy-pasted across languages with little adaptation or review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where once developers had to build something from scratch, now they have to sift through clones and half-formed libraries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Case Study: The API Wrapper Gold Rush
&lt;/h2&gt;

&lt;p&gt;When OpenAI released their API, unofficial wrappers flooded GitHub in days. Dozens of "OpenAI for X" libraries emerged, often indistinguishable from each other and rarely production-ready.&lt;/p&gt;

&lt;p&gt;One example: a Fortran wrapper for OpenAI's API. It was impressive on paper—but under the hood, it was a minimal HTTP layer with zero error handling and no community engagement. &lt;/p&gt;

&lt;p&gt;Other wrappers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Skipped licensing.&lt;/li&gt;
&lt;li&gt;Borrowed structure and code directly from existing SDKs.&lt;/li&gt;
&lt;li&gt;Were generated, posted, and abandoned all in a weekend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? A cluttered ecosystem that confuses users, duplicates effort, and disincentivizes original maintainers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Community Reactions and Consequences
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Original Maintainers: &lt;em&gt;Why Bother?&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;For those who spent months building thoughtful libraries, it’s disheartening to see a thin GPT-generated wrapper get more stars. It leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Burnout.&lt;/li&gt;
&lt;li&gt;Reluctance to publish early versions.&lt;/li&gt;
&lt;li&gt;Extra work policing licenses and clones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Junior Devs: &lt;em&gt;Confidence on Shaky Ground&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Publishing a library with AI’s help feels empowering—but it can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;False confidence in one's skills.&lt;/li&gt;
&lt;li&gt;Poor understanding of the codebase.&lt;/li&gt;
&lt;li&gt;Ethical gray areas when listing it on a resume.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI is a great assistant—but claiming generated code as your own without understanding it invites trouble down the line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Companies: &lt;em&gt;Dependency Roulette&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;An engineer finds a new wrapper that looks solid and integrates it into production. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No tests.&lt;/li&gt;
&lt;li&gt;No maintenance.&lt;/li&gt;
&lt;li&gt;Security holes or leaks appear months later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many vibe packages don’t survive real-world stress.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seasoned Devs: &lt;em&gt;Holding Back&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Some are choosing not to release unless the project feels “enterprise-ready,” just to avoid being lumped in with low-effort clones.&lt;/p&gt;

&lt;p&gt;Others take the mentorship route: filing issues, offering feedback, or guiding enthusiastic newcomers into creating sustainable packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Navigating the Vibe Package Era
&lt;/h2&gt;

&lt;p&gt;We’re in a golden age of code generation—but it’s also the noisiest. Developers of all skill levels are publishing more, faster. That’s not inherently bad. But if quantity outpaces quality, we risk drowning in mediocrity.&lt;/p&gt;

&lt;p&gt;If you’re a junior dev:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish, but learn what you’re publishing.&lt;/li&gt;
&lt;li&gt;Take feedback seriously.&lt;/li&gt;
&lt;li&gt;Use AI as a stepping stone, not a crutch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re a maintainer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lead by example.&lt;/li&gt;
&lt;li&gt;Offer guidance, not gatekeeping.&lt;/li&gt;
&lt;li&gt;Consider new strategies for building and maintaining community trust.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're part of a team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vet your dependencies.&lt;/li&gt;
&lt;li&gt;Check for licenses, commits, tests, and maintainers.&lt;/li&gt;
&lt;li&gt;Don’t trust the README at face value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI has made it easier to ship. But it’s still up to us to &lt;strong&gt;build wisely&lt;/strong&gt;, publish &lt;strong&gt;responsibly&lt;/strong&gt;, and guide others in doing the same.&lt;/p&gt;

&lt;p&gt;Because in the end, &lt;em&gt;not all that glitters is gold&lt;/em&gt;. Sometimes it’s just polished copy-paste. And the best vibe? One that lasts.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>LLMs and the Ossification of APIs: Are We Stuck with Prehistoric Patterns?</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Fri, 11 Apr 2025 12:48:10 +0000</pubDate>
      <link>https://dev.to/seuros/llms-and-the-ossification-of-apis-are-we-stuck-with-old-patterns-2mh6</link>
      <guid>https://dev.to/seuros/llms-and-the-ossification-of-apis-are-we-stuck-with-old-patterns-2mh6</guid>
      <description>&lt;h2&gt;
  
  
  When AI Coding Assistants Influence API Design in Ruby
&lt;/h2&gt;

&lt;p&gt;The Ruby community has always valued elegant API design and idiomatic code. But recently, a new factor is subtly influencing how our APIs evolve: AI coding assistants. Tools like GitHub Copilot and ChatGPT have become our AI pair programmers, trained on millions of lines of existing Ruby code. They excel at recognizing and reproducing common coding patterns.&lt;/p&gt;

&lt;p&gt;While this speeds up development, it also introduces an unintended side effect: API designs are becoming "fossilized" into their current form, as deviations from the norm are discouraged by the very tools meant to help us.&lt;/p&gt;

&lt;p&gt;In this post, I'll explore how Large Language Models (LLMs) and AI code assistants can create inertia around API design. I focus on how these tools reinforce existing patterns, making any novel or unconventional API feel "wrong" or hard to reason about, even if it's technically correct.&lt;/p&gt;

&lt;p&gt;The goal isn't to vilify AI helpers (they're incredibly useful! I argue with Grok and Claude on a daily basis) but to raise awareness of this subtle problem and encourage reflection in the Ruby community.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rise of AI Pair Programmers in Ruby
&lt;/h2&gt;

&lt;p&gt;AI-assisted development has quickly gone from novelty to normalcy. GitHub Copilot, ChatGPT, Claude and similar tools are now integrated into editors such as VSCode, Cursor, Zed, Windsurf, JetBrains and Notepad (with a subscription), offering autocomplete or even generating entire blocks of code.&lt;/p&gt;

&lt;p&gt;These assistants are trained on vast repositories of open-source code, which means they've learned the dominant patterns and idioms of languages. For Ruby, they know Ruby on Rails conventions, popular gem APIs, style guide preferences, and so on.&lt;/p&gt;

&lt;p&gt;This has obvious benefits: they can produce code that looks right at a glance. For standard tasks, the AI's output often adheres to community style and best practices because it's copying from what most developers do. It's like having an encyclopedic junior developer who always codes in a textbook style.&lt;/p&gt;

&lt;p&gt;However, this strength is also a weakness. LLMs are inherently conservative in that they regurgitate patterns from training data. When faced with something new—be it a cutting-edge library, a new framework, or an unconventional API design—these tools struggle.&lt;/p&gt;

&lt;p&gt;I recently tested &lt;a href="https://replit.com/~" rel="noopener noreferrer"&gt;Replit&lt;/a&gt; to see if it supports a UI framework I like, &lt;a href="https://flowbite.com" rel="noopener noreferrer"&gt;Flowbite&lt;/a&gt;. The LLM acknowledged it was familiar with Flowbite, but then simply aliased &lt;a href="https://ui.shadcn.com" rel="noopener noreferrer"&gt;shadcn&lt;/a&gt; in the import statement and proceeded with its previous patterns.&lt;/p&gt;

&lt;p&gt;AI code assistants are superb followers of convention. But what happens when we need to break convention or update it? This is where API ossification comes in. Developers might unwittingly start treating the AI's suggestions as the path of least resistance, sticking to familiar patterns to keep the AI (and by extension themselves) comfortable.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Ossification: When Patterns Become a Prison
&lt;/h2&gt;

&lt;p&gt;In software, "ossification" refers to something becoming rigid and resistant to change. For APIs, ossification means the design and usage patterns harden such that breaking away from them is very difficult. &lt;/p&gt;

&lt;p&gt;Historically, API ossification is a concern in long-lived protocols (like TCP/IP) where entrenched assumptions make evolution difficult. Now we're seeing a form of it in application-level code, reinforced by AI training.&lt;/p&gt;

&lt;p&gt;If we only ever accept what the AI suggests, we risk reinforcing designs that may not be optimal, just common. Our APIs stop evolving; they simply repeat the past. How does this manifest in day-to-day coding?&lt;/p&gt;

&lt;p&gt;First, deviations feel "incorrect." When we encounter or attempt a different approach that the AI isn't expecting, it often won't autocomplete it nicely. This lack of affirmation can make a developer second-guess the approach. We've grown used to that little Copilot gray text completing our thoughts. If it doesn't appear (or shows something off-base), we might assume we're doing something wrong. The result? We retreat to a pattern that Copilot recognizes, because its suggestions give a subconscious nod of approval.&lt;/p&gt;

&lt;p&gt;Second, tooling feedback reinforces the norm. It's not just the AI autocompletion. Our linters, formatters, and other static analysis tools are built with certain expectations. They might flag code that, while valid, is unusual. When our tools constantly underline something in yellow or throw warnings, it wears on our confidence in that code.&lt;/p&gt;

&lt;p&gt;Third, perceived correctness can override actual correctness. We humans are highly visual and context-driven. If code looks like what we're used to, we instinctively trust it more. Conversely, code that looks "odd" is suspect. AI suggestions amplify this—if the AI writes a snippet that looks polished, we assume it's correct. If we write something ourselves that the AI didn't suggest, and especially if it triggers lint warnings, we start to doubt ourselves, maybe even start considering a career in farming or fishing.&lt;/p&gt;

&lt;p&gt;The danger is that perceived correctness (following familiar patterns) overrides technical correctness. An API design could be perfectly valid or even superior, but if it doesn't resemble what we've seen before, developers may resist it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case Study: When Official APIs Defy Convention – Anthropic's Ruby SDK
&lt;/h2&gt;

&lt;p&gt;A recent example of new-vs-old style friction comes from the Anthropic API (the company behind Claude). For a while, Ruby developers had access to Anthropic's AI via an unofficial gem by &lt;a href="https://github.com/alexrudall" rel="noopener noreferrer"&gt;Alex Rudall&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This community gem, initially called anthropic (later ruby-anthropic), was built in a very Ruby-ish way. It followed conventions that Rubyists expect: configuration via a block, plain old Ruby objects, and using Faraday for HTTP under the hood. Faraday is the de facto standard HTTP client adapter in Ruby, and the gem's documentation explicitly references Faraday::Errors, confirming it uses Faraday for network calls. This choice made sense—Faraday is widely used, integrates with middleware for logging or retries, and any Rubyist using the gem would feel right at home with its behavior.&lt;/p&gt;

&lt;p&gt;Fast forward to now: Anthropic released an official Ruby SDK (packaged as the anthropic gem, with Alex Rudall even donating the gem name to them).&lt;/p&gt;

&lt;p&gt;Official support is great news, but the API design and implementation of the official SDK took a lot of Rubyists by surprise. Instead of following the patterns set by the unofficial gem (and other Ruby API wrappers), the official SDK introduced a new client API that feels influenced by other languages like Go or Java. It was decided to reduce dependency.&lt;/p&gt;

&lt;p&gt;For instance, it doesn't use Faraday at all—it comes with its own HTTP adapter and connection pooling system, more like what you'd see in a Go HTTP client or a Java OkHttp setup, rather than a typical Ruby script that just uses Faraday to allow adapter selection. The README notes that each client manages its own HTTP connection pool for concurrency, a concept more common in lower-level languages or explicitly concurrency-oriented Ruby code.&lt;/p&gt;

&lt;p&gt;The surface API also has subtle differences. The unofficial gem had a very idiomatic usage, e.g., using class methods like &lt;code&gt;Anthropic.messages.create(...)&lt;/code&gt; with hash options, similar to how something like the Stripe Ruby SDK or ActiveRecord might work. The official SDK, by contrast, might require instantiating a client (&lt;code&gt;Anthropic::Client.new&lt;/code&gt;) and then calling methods on it (e.g., &lt;code&gt;client.messages.create(...)&lt;/code&gt;). It also introduced a typed interface (using Sorbet RBI/RBS for type checking).&lt;/p&gt;

&lt;p&gt;From a purely technical standpoint, there's nothing wrong with the official SDK's approach. In fact, it might offer performance and reliability benefits (like thread safety and fewer GC churn by reusing HTTP connections).&lt;/p&gt;

&lt;p&gt;Today, however, such changes come with a new kind of friction: our AI copilots and established habits push back against the unfamiliar design. A developer trying out the official Anthropic SDK might encounter a few pain points:&lt;/p&gt;

&lt;p&gt;First, muscle memory &amp;amp; AI suggestions. If they used the older gem (or even the OpenAI Ruby gem, which is similar in style), they might start coding by habit. They type &lt;code&gt;Anthropic.&lt;/code&gt; and perhaps Copilot suggests &lt;code&gt;Anthropic.messages.create(...)&lt;/code&gt; because that pattern was common in the unofficial SDK. But in the official SDK, maybe you need to do &lt;code&gt;client = Anthropic::Client.new&lt;/code&gt; first and use that instance.&lt;/p&gt;

&lt;p&gt;Second, deviation from community tools. Not using Faraday means the official SDK might not seamlessly fit into middleware stacks that Ruby devs use (like instrumenting Faraday for logging or metrics). Meanwhile, an AI tool likely has seen tons of Faraday usage and zero of whatever internal HTTP client Anthropic uses, so it won't suggest how to add a logging interceptor or retry logic on this new client.&lt;/p&gt;

&lt;p&gt;Third, cognitive dissonance in style. Ruby developers pride themselves on APIs that read naturally. A non-idiomatic style stands out. If the official client has method names or patterns that feel imported from Java/Go, a developer might initially think "am I using this right? It feels clunky."&lt;/p&gt;

&lt;p&gt;The end result is that developers may hesitate to adopt the official SDK or fully embrace its new patterns. Some might stick with the older community gem because it's familiar and all their tools support it. Others might use the official one but internally wrap it in something that feels more like Faraday to satisfy their own sense of normalcy—you can see that with 100 implementations around MCP specification in JavaScript with &lt;a href="http://mcp-framework.com" rel="noopener noreferrer"&gt;mcp-framework&lt;/a&gt; taking the lead&lt;/p&gt;

&lt;h2&gt;
  
  
  Case Study: When AI and Linters Mislead – The &lt;code&gt;render&lt;/code&gt; Method Confusion
&lt;/h2&gt;

&lt;p&gt;It's not only big SDK designs that face this pattern inertia; even small design choices in a gem can cause confusion when they collide with established meanings. I ran into this firsthand with a gem I authored called &lt;code&gt;action_mcp&lt;/code&gt;. This gem allows building prompt/response flows and "tools" for LLMs in a Rails application following the MCP spec.&lt;/p&gt;

&lt;p&gt;In this domain, it made perfect sense for me to define a method called &lt;code&gt;render&lt;/code&gt; – because the gem is about rendering AI responses (text, images, audio, resources, etc.) as the output of an LLM tool or prompt. So inside an &lt;code&gt;ActionMCP::Tool&lt;/code&gt; class or prompt, you might see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# inside a tool's perform method&lt;/span&gt;
&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"Calculating &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; + &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"The sum is &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What could be more reasonable? The method name &lt;code&gt;render&lt;/code&gt; perfectly describes what it does: it takes content (or an error or image) and renders it into the response that will be sent back via the LLM.&lt;/p&gt;

&lt;p&gt;However, the moment I started using this in a Rails project with GitHub Copilot enabled and RuboCop linting my code, I realized I'd stepped on a linguistic landmine. Both Copilot and RuboCop seemed to assume &lt;code&gt;render&lt;/code&gt; meant ActionController's render method.&lt;/p&gt;

&lt;p&gt;In Rails, &lt;code&gt;render&lt;/code&gt; is a heavily used method to render views or JSON, etc., within controller actions. My gem's &lt;code&gt;render&lt;/code&gt; is something entirely different (it doesn't render a view; it produces an AI message), but the tools don't know that context.&lt;/p&gt;

&lt;p&gt;GitHub Copilot's reaction: Whenever I wrote a &lt;code&gt;render(&lt;/code&gt; call in my tool class, Copilot kept trying to autocomplete it with typical controller options – e.g., it would suggest things like &lt;code&gt;render json: ...&lt;/code&gt; or &lt;code&gt;render template: ...&lt;/code&gt; because it's seen thousands of controllers do that. It might even try to close the method with an &lt;code&gt;end&lt;/code&gt; early, thinking I was in a controller action rendering and done.&lt;/p&gt;

&lt;p&gt;RuboCop's reaction: RuboCop also got confused. It might have rules around controllers using &lt;code&gt;render&lt;/code&gt; properly. I saw warnings about the arguments to &lt;code&gt;render&lt;/code&gt; or the context of &lt;code&gt;render&lt;/code&gt; because RuboCop assumed I was calling &lt;code&gt;ActionController::Base#render&lt;/code&gt; incorrectly.&lt;/p&gt;

&lt;p&gt;The combination of Copilot's misguided suggestions and RuboCop's warnings created a strong impression that I was doing something "bad," even though I knew logically that &lt;code&gt;ActionMCP::Tool#render&lt;/code&gt; was fine. I caught myself momentarily considering, "Should I rename that method to something else, just to avoid these hassles?"&lt;/p&gt;

&lt;p&gt;That right there is the ossification pressure in action! The AI and lint tools were nudging me to rename or redesign a perfectly valid API in my gem, purely because it clashed with a more common meaning of &lt;code&gt;render&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This demonstrates how LLMs can make correct code feel incorrect. The code looked like something else to the AI, so it kept telling me to change it. If I didn't have confidence in my intent, I might have listened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Challenge: Balancing Innovation and Expectation
&lt;/h2&gt;

&lt;p&gt;These case studies underline a new challenge for Ruby (and likely all programming ecosystems): How do we continue to innovate in API design under the watchful eye of AI "mentors" that learned from the pre-history? It's a strange question to face. In the past, introducing a new library or pattern required convincing other developers; now, it seems we also have to convince the AIs (indirectly, by retraining or by user feedback) – or work around them. Maybe I should become an AI DEV advocate and spend my days tweeting motivational text until Grok learns it.&lt;/p&gt;

&lt;p&gt;Let's break down what's happening:&lt;/p&gt;

&lt;p&gt;First, developer expectations are AI-shaped. As developers, our expectations are now partly formed by what AI assistants deem likely. If every time you type code, Copilot suggests a certain completion, you begin to expect that pattern in your code. When an API deviates from that pattern, it's jarring. The Anthropic SDK story is a case in point: the community might have been ready to accept a new approach, but their expectations were anchored by the unofficial gem and similar libraries.&lt;/p&gt;

&lt;p&gt;Second, toolchain feedback loops. Linters and other automated feedback tools encode rules – sometimes flexible, sometimes rigid. When those rules collide with a new design, they produce warnings or errors. We have multiple layers of "automated opinions" now: the AI that writes the code and the linter that reviews it. If both are trained on the same corpus of existing code, they'll likely agree on what's normal. So a new API design gets flagged from two sides, doubling the developer's impression that it's wrong.&lt;/p&gt;

&lt;p&gt;Third, "looks correct" vs "works correctly." If an API design itself is different, an AI might produce code that looks right but actually targets a different API. For example, had I not known better, I might accept Copilot's suggestion to call &lt;code&gt;render json: ...&lt;/code&gt; in an ActionMCP tool – which would totally not do what was intended, because my &lt;code&gt;render&lt;/code&gt; doesn't take a &lt;code&gt;:json&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;Fourth, misleading documentation &amp;amp; learning resources. Many of us learn via googling error messages or copying code from StackOverflow. If the community is slow to write about a new API, AI might surface older documentation or Q&amp;amp;A that no longer applies. For instance, developers searching for "Anthropic Ruby API example" might find blog posts or Q&amp;amp;As related to the unofficial gem (with Faraday) and use those, not realizing the official SDK works differently.&lt;/p&gt;

&lt;p&gt;The sum of these pressures is that API designers might feel discouraged from deviating too far from established patterns. If you're a Ruby gem author in 2025, you have to wonder: will my target audience be comfortable with this design, or will their AI tools rebel and tag me persona non grata? Do I pick the novel approach that might be better, or do I stick with conventions so that adoption is smoother?&lt;/p&gt;

&lt;p&gt;It's almost like we have a new stakeholder in API design, not just the end-user and the maintainer, but the AI intermediary that needs to "get it" for the end-user to have a good experience. For the Ruby community, which historically has embraced DSLs and unconventional but elegant solutions, this is a cultural shift.&lt;/p&gt;

&lt;p&gt;Imagine if something as innovative as Rails itself, with all its domain-specific magic, were introduced today. Would Copilot constantly fight DHH while he was typing out &lt;code&gt;has_many :through =&amp;gt; ...&lt;/code&gt; in 2004? Would linters scream about missing class definitions for methods created by &lt;code&gt;method_missing&lt;/code&gt;? (They often do, even now.) Yet Rails succeeded in part because the community was willing to learn a new paradigm.&lt;/p&gt;

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

&lt;p&gt;AI assistance in coding is here to stay, and overall, it's a positive development for productivity. But it comes with the subtle side effect of reinforcing the status quo. APIs and coding styles could ossify, not because of technical limitations, but because deviation triggers confusion – in our tools and then in ourselves.&lt;/p&gt;

&lt;p&gt;As Ruby developers and library authors, we should be mindful of this dynamic. The next time an official SDK or a new gem comes along with a bold design choice, check if any resistance you feel is due to genuine technical critique or just the discomfort of the unfamiliar amplified by AI-trained expectations.&lt;/p&gt;

&lt;p&gt;We should strive to remain critical thinkers: sometimes the "weird" new way might actually be an improvement, and we shouldn't dismiss it outright because our AI buddy hasn't seen it before. On the flip side, when designing APIs, it's worth considering how autodidactic tools perceive your API. It sounds odd, but you might ask, "If a newbie relies on Copilot to use my library, will it help or hinder?"&lt;/p&gt;

&lt;p&gt;This doesn't mean never breaking convention, but it means you may need to educate both the community and the AI. That could be through very clear documentation, examples (so that they hopefully get into the training data), or even collaborating with lint tool authors to create custom rules that understand your DSL.&lt;/p&gt;

&lt;p&gt;Ultimately, awareness is the first step. The Ruby ecosystem has thrived on expressiveness and innovation. By recognizing the ossification pressure from AI, we can take steps to ensure we don't become stuck in old ways. With conscious effort, we can have the best of both worlds: enjoying AI assistants for the routine stuff, while keeping our minds open to new ideas that break the mold.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>api</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Rails Version Management with Rails AppVersion</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Thu, 02 Jan 2025 21:14:58 +0000</pubDate>
      <link>https://dev.to/seuros/rails-version-management-best-practices-with-rails-appversion-1h6b</link>
      <guid>https://dev.to/seuros/rails-version-management-best-practices-with-rails-appversion-1h6b</guid>
      <description>&lt;p&gt;Rails AppVersion provides a standard way to handle version and environment information in Rails applications. It eliminates the need for custom version management solutions while providing useful conventions for error tracking, caching, and deployment verification.&lt;/p&gt;

&lt;p&gt;Key Features&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version information through &lt;code&gt;Rails.application.version&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Environment management via &lt;code&gt;Rails.application.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Version-aware response headers&lt;/li&gt;
&lt;li&gt;Cache key generation&lt;/li&gt;
&lt;li&gt;Console environment information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basic Setup&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"rails_app_version"&lt;/span&gt;

&lt;span class="c1"&gt;# Terminal&lt;/span&gt;
&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="ss"&gt;:version:config&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"1.0.0"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;VERSION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common Use Cases&lt;/p&gt;

&lt;p&gt;Error Tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache Management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"index-page-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_cache_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:index&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;Version Headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/app_version.yml&lt;/span&gt;
&lt;span class="na"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;version_header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;X-Staging-Version&lt;/span&gt;
      &lt;span class="na"&gt;environment_header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;X-Staging-Environment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VersionTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"version information"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="s2"&gt;"1.2.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="s2"&gt;"1-2-3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_cache_key&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;Version Management Options&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;VERSION File (Recommended):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;YAML Configuration:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= Rails.root.join('VERSION').read.strip rescue '0.0.0' %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= Rails.root.join('REVISION').read.strip rescue (`git rev-parse HEAD`.strip rescue '0') %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accessing Version Details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;major&lt;/span&gt;      &lt;span class="c1"&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minor&lt;/span&gt;      &lt;span class="c1"&gt;# =&amp;gt; 2&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;      &lt;span class="c1"&gt;# =&amp;gt; 3&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full&lt;/span&gt;       &lt;span class="c1"&gt;# =&amp;gt; "1.2.3 (abc123de)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment Management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;               &lt;span class="c1"&gt;# =&amp;gt; "staging"&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;staging?&lt;/span&gt;      &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gem is available at &lt;a href="https://github.com/seuros/rails_app_version" rel="noopener noreferrer"&gt;https://github.com/seuros/rails_app_version&lt;/a&gt; under the MIT License.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NoFlyList: How NoFlyList Optimizes Tag Queries</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Fri, 20 Dec 2024 14:55:07 +0000</pubDate>
      <link>https://dev.to/seuros/noflylist-how-noflylist-optimizes-tag-queries-3hbd</link>
      <guid>https://dev.to/seuros/noflylist-how-noflylist-optimizes-tag-queries-3hbd</guid>
      <description>&lt;h2&gt;
  
  
  Database-Specific Strategies
&lt;/h2&gt;

&lt;p&gt;NoFlyList automatically detects your database type and uses optimized queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# This generates different SQL for each database&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;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"gaming"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  PostgreSQL Optimization
&lt;/h2&gt;

&lt;p&gt;PostgreSQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using array operators and CTE for better performance&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="no"&gt;DISTINCT&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
  &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
  &lt;span class="no"&gt;INNER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"taggable_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
  &lt;span class="no"&gt;INNER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"product_tags"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"product_tags"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"tag_id"&lt;/span&gt;
  &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"context"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'category'&lt;/span&gt;
  &lt;span class="no"&gt;AND&lt;/span&gt; &lt;span class="s2"&gt;"product_tags"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'electronics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gaming'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  MySQL Optimization
&lt;/h2&gt;

&lt;p&gt;MySQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using FIND_IN_SET and subqueries&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="sb"&gt;`products`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="sb"&gt;`products`&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="sb"&gt;`products`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;` IN (
  SELECT `&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;`
  FROM products
  INNER JOIN `&lt;/span&gt;&lt;span class="n"&gt;product_taggings&lt;/span&gt;&lt;span class="sb"&gt;` ON `&lt;/span&gt;&lt;span class="n"&gt;product_taggings&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="n"&gt;taggable_id&lt;/span&gt;&lt;span class="sb"&gt;` = `&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;`
  INNER JOIN `&lt;/span&gt;&lt;span class="n"&gt;product_tags&lt;/span&gt;&lt;span class="sb"&gt;` ON `&lt;/span&gt;&lt;span class="n"&gt;product_tags&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;` = `&lt;/span&gt;&lt;span class="n"&gt;product_taggings&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="n"&gt;tag_id&lt;/span&gt;&lt;span class="sb"&gt;`
  WHERE `&lt;/span&gt;&lt;span class="n"&gt;product_taggings&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="sb"&gt;` = 'category'
  AND `&lt;/span&gt;&lt;span class="n"&gt;product_tags&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="sb"&gt;` IN ('electronics', 'gaming')
  GROUP BY `&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;`
)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SQLite Optimization
&lt;/h2&gt;

&lt;p&gt;SQLite query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Optimized for SQLite's simpler query planner&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
  &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
  &lt;span class="no"&gt;INNER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;product_taggings&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;product_taggings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;taggable_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
  &lt;span class="no"&gt;INNER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;product_tags&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;product_tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product_taggings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tag_id&lt;/span&gt;
  &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;product_taggings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'category'&lt;/span&gt;
  &lt;span class="no"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;product_tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'electronics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gaming'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Complex Queries
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Finding products with ALL specified tags&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;with_all_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"gaming"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# PostgreSQL uses:&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
  &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
  &lt;span class="no"&gt;INNER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"taggable_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
  &lt;span class="no"&gt;INNER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"product_tags"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"product_tags"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"tag_id"&lt;/span&gt;
  &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"product_taggings"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"context"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'category'&lt;/span&gt;
  &lt;span class="no"&gt;AND&lt;/span&gt; &lt;span class="s2"&gt;"product_tags"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'electronics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gaming'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;GROUP&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
  &lt;span class="no"&gt;HAVING&lt;/span&gt; &lt;span class="no"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DISTINCT&lt;/span&gt; &lt;span class="s2"&gt;"product_tags"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Finding products without specific tags&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;without_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"discontinued"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Finding products with exact tag set&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;with_exact_categories&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"gaming"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Index Optimization:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateProductTags&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:product_tags&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;add_index&lt;/span&gt; &lt;span class="ss"&gt;:product_taggings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:taggable_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:taggable_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:context&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;Unlike AATO, the gem support multiple database connections and mixed adapters.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Counter Cache:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Eager Loading:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Efficient loading of products with their tags&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;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Debugging Queries
&lt;/h2&gt;

&lt;p&gt;Use query logging to see optimizations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/environments/development.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose_query_logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# In console&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;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Patterns
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Category Trees:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_all_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"gaming"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"professional"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Exclusions:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"discontinued"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"clearance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Exact Matching:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_exact_categories&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"gaming"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each pattern generates optimized SQL based on your database.&lt;br&gt;
If you know a better query, feel free to open a pull request on the adapter query.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NoFlyList: Custom Tag Screening with NoFlyList</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Fri, 20 Dec 2024 14:47:13 +0000</pubDate>
      <link>https://dev.to/seuros/noflylist-custom-tag-screening-with-noflylist-553j</link>
      <guid>https://dev.to/seuros/noflylist-custom-tag-screening-with-noflylist-553j</guid>
      <description>&lt;p&gt;Tag parsing seems simple until you handle real user input. &lt;/p&gt;

&lt;p&gt;Let's explore how NoFlyList's transformers handle messy tag data submitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;no_fly_list&lt;/span&gt;&lt;span class="ss"&gt;:transformer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default transformer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ApplicationTagTransformer&lt;/span&gt;
  &lt;span class="kp"&gt;module_function&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;tags&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:strip&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;compact&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;recreate_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&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;separator&lt;/span&gt;
    &lt;span class="s1"&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;
  
  
  Real-World Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hashtag Transformer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;HashtagTransformer&lt;/span&gt;
  &lt;span class="kp"&gt;module_function&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/#[\w-]+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#'&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;def&lt;/span&gt; &lt;span class="nf"&gt;recreate_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&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;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:hashtags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;transformer: &lt;/span&gt;&lt;span class="no"&gt;HashtagTransformer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s2"&gt;"Check out #rails #ruby #webdev"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hashtags_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;  &lt;span class="c1"&gt;# Extracts: ["rails", "ruby", "webdev"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Language Transformer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MultiLangTransformer&lt;/span&gt;
  &lt;span class="kp"&gt;module_function&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'|'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag_pair&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag_pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:strip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;recreate_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&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;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:keywords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;transformer: &lt;/span&gt;&lt;span class="no"&gt;MultiLangTransformer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keywords_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en:ruby | es:rubí | fr:rubis"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hierarchical Tag Transformer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;CategoryTransformer&lt;/span&gt;
  &lt;span class="kp"&gt;module_function&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:strip&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;recreate_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' &amp;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;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;transformer: &lt;/span&gt;&lt;span class="no"&gt;CategoryTransformer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;categories_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Electronics &amp;gt; Computers &amp;gt; Laptops"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom Normalization
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;NormalizedTransformer&lt;/span&gt;
  &lt;span class="kp"&gt;module_function&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;
         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[^a-z0-9\s-]/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Remove special chars&lt;/span&gt;
         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\s+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# Spaces to hyphens&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compact&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;recreate_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&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;class&lt;/span&gt; &lt;span class="nc"&gt;Photo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;transformer: &lt;/span&gt;&lt;span class="no"&gt;NormalizedTransformer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;labels_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Nature Photos, WILDLIFE shots, Outdoor-Photography"&lt;/span&gt;
&lt;span class="c1"&gt;# Transforms to: ["nature-photos", "wildlife-shots", "outdoor-photography"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Context-Specific Transformers
&lt;/h2&gt;

&lt;p&gt;Mix transformers based on tag context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;transformer: &lt;/span&gt;&lt;span class="no"&gt;CategoryTransformer&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:hashtags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;transformer: &lt;/span&gt;&lt;span class="no"&gt;HashtagTransformer&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:keywords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;transformer: &lt;/span&gt;&lt;span class="no"&gt;MultiLangTransformer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Transformers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransformerTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"hashtag parsing"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"#ruby #rails doing #testing"&lt;/span&gt;
    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"testing"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;HashtagTransformer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"hierarchical parsing"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Tech &amp;gt; Software &amp;gt; Tools"&lt;/span&gt;
    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Tech"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Software"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Tools"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CategoryTransformer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&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;Remember: Transformers are your first line of defense against messy tag data. Like TSA agents, they ensure only properly formatted tags make it through to your system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/seuros/noflylist-how-noflylist-optimizes-tag-queries-3hbd"&gt;Part4&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NoFlyList: Choosing Between Polymorphic and Model-Specific Tags</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Fri, 20 Dec 2024 14:39:04 +0000</pubDate>
      <link>https://dev.to/seuros/noflylist-choosing-between-polymorphic-and-model-specific-tags-4phh</link>
      <guid>https://dev.to/seuros/noflylist-choosing-between-polymorphic-and-model-specific-tags-4phh</guid>
      <description>&lt;p&gt;Let's build a blog platform to understand when to use polymorphic vs model-specific tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create our models&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="ss"&gt;:text&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="no"&gt;Video&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;no_fly_list&lt;/span&gt;&lt;span class="ss"&gt;:install&lt;/span&gt;      &lt;span class="c1"&gt;# For polymorphic tags&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;no_fly_list&lt;/span&gt;&lt;span class="ss"&gt;:tagging&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;no_fly_list&lt;/span&gt;&lt;span class="ss"&gt;:tagging&lt;/span&gt; &lt;span class="no"&gt;Video&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scenario 1: Shared Categories
&lt;/h2&gt;

&lt;p&gt;Articles and videos share the same category system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Uses shared tags table&lt;/span&gt;
    &lt;span class="ss"&gt;restrict_to_existing: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Video&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;restrict_to_existing: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Usage&lt;/span&gt;
&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&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;title: &lt;/span&gt;&lt;span class="s2"&gt;"Rails Tips"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;categories_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"programming, tutorials"&lt;/span&gt;

&lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Video&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;title: &lt;/span&gt;&lt;span class="s2"&gt;"Rails Setup Guide"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;categories_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"programming, beginners"&lt;/span&gt;

&lt;span class="c1"&gt;# Find all programming content across both models&lt;/span&gt;
&lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"programming"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"programming"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scenario 2: Model-Specific Tags
&lt;/h2&gt;

&lt;p&gt;Articles have technical requirements, videos have equipment details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:technical_requirements&lt;/span&gt;  &lt;span class="c1"&gt;# Model-specific&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Video&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:equipment_used&lt;/span&gt;  &lt;span class="c1"&gt;# Model-specific&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Usage&lt;/span&gt;
&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;technical_requirements_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ruby-3.2, rails-7.2"&lt;/span&gt;
&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equipment_used_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sony-a7iii, rode-mic"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mixed Usage
&lt;/h2&gt;

&lt;p&gt;Combine both approaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Shared&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:technical_requirements&lt;/span&gt;         &lt;span class="c1"&gt;# Specific&lt;/span&gt;
  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:author_notes&lt;/span&gt;                   &lt;span class="c1"&gt;# Specific&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Find articles by shared categories and specific requirements&lt;/span&gt;
&lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"programming"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_all_technical_requirements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ruby-3.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Database Structure
&lt;/h2&gt;

&lt;p&gt;Polymorphic tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application_tags&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:application_tags&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# application_taggings&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:application_taggings&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:tag&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:taggable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:context&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Model-specific tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# article_tags&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:article_tags&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# article_taggings&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:article_taggings&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:tag&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:article&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:context&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Use polymorphic tags when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tags need to be shared across models&lt;/li&gt;
&lt;li&gt;You want centralized tag management&lt;/li&gt;
&lt;li&gt;Tags require validation across all models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use model-specific tags when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tags are unique to the model&lt;/li&gt;
&lt;li&gt;You want simpler queries&lt;/li&gt;
&lt;li&gt;Tags don't need to be shared&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;Polymorphic tags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One query across multiple tables&lt;/li&gt;
&lt;li&gt;Slower for large datasets&lt;/li&gt;
&lt;li&gt;More complex indexes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Model-specific tags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct table access&lt;/li&gt;
&lt;li&gt;Faster for single-model queries&lt;/li&gt;
&lt;li&gt;Simpler indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/seuros/noflylist-custom-tag-screening-with-noflylist-553j"&gt;Part3&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NoFlyList : How NoFlyList Got Cleared for Production</title>
      <dc:creator>Abdelkader Boudih</dc:creator>
      <pubDate>Fri, 20 Dec 2024 14:36:41 +0000</pubDate>
      <link>https://dev.to/seuros/noflylist-how-noflylist-got-cleared-for-production-3cgf</link>
      <guid>https://dev.to/seuros/noflylist-how-noflylist-got-cleared-for-production-3cgf</guid>
      <description>&lt;p&gt;As the maintainer of &lt;a href="https://rubygems.org/gems/acts-as-taggable-on" rel="noopener noreferrer"&gt;Acts-as-Taggable-On&lt;/a&gt; (AATO), I started seeing patterns of technical debt and maintenance challenges around 2018. While AATO served its purpose well, its legacy codebase began showing signs of strain, especially with modern Rails applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Birth of NoFlyList
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rubygems.org/gems/no_fly_list" rel="noopener noreferrer"&gt;NoFlyList&lt;/a&gt; wasn't born in a weekend hackathon, it evolved over years of real-world testing across multiple production applications. &lt;/p&gt;

&lt;p&gt;The name comes from how the TSA tags passengers and bags for extra screening. &lt;br&gt;
Much like the TSA's tagging system, this gem manages and validates tags. &lt;br&gt;
And unless you're actually building TSA software, you won't have any naming conflicts in your codebase.&lt;/p&gt;

&lt;p&gt;The initial concept emerged from  a protocol and platform for sustainable, traceable and resilience supply chains, where the constraints of AATO became evident and ultimately lead me to the development of a more robust, flexible solution.&lt;/p&gt;

&lt;p&gt;Let's walk through a practical example of how NoFlyList handles one of these real-world scenarios.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TaggableRecord&lt;/span&gt;

  &lt;span class="n"&gt;has_tags&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# Shared across models&lt;/span&gt;
    &lt;span class="ss"&gt;restrict_to_existing: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Only allow predefined categories&lt;/span&gt;
    &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;         &lt;span class="c1"&gt;# Track usage statistics&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple configuration represents years of learning about what teams actually need in production. Each option addresses specific pain points discovered in real applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polymorphic tags came from the platform needing to share categories across products and services.&lt;/li&gt;
&lt;li&gt;Tag restrictions emerged from a content management system requiring controlled vocabularies.&lt;/li&gt;
&lt;li&gt;Counter cache was added after seeing performance issues in high-traffic situations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Database-Specific Optimizations
&lt;/h2&gt;

&lt;p&gt;One feature that took significant real-world testing was the database-specific query strategies. Different applications had different scaling challenges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# PostgreSQL-optimized query&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;with_all_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"gaming"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"wireless"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bluetooth"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# MySQL-specific optimizations, model is using another connection.&lt;/span&gt;
&lt;span class="no"&gt;MySqlProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_any_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"discontinued"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"clearance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Infrastructure
&lt;/h2&gt;

&lt;p&gt;The test helpers was build from actual testing patterns i use across multiple applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;NoFlyList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestHelper&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"product categorization"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;assert_taggable_record&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;
    &lt;span class="n"&gt;assert_tagging_context&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&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;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;While NoFlyList is now ready for public use, i'm still extracting and refining features based on real-world usage patterns. Future releases will include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional query optimizations&lt;/li&gt;
&lt;li&gt;Enhanced migration tools&lt;/li&gt;
&lt;li&gt;More granular caching options&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gem is stable and production-tested, but i'm continuously identifying features that could benefit the broader Rails community.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/contriboss/no_fly_list" rel="noopener noreferrer"&gt;The repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/seuros/noflylist-choosing-between-polymorphic-and-model-specific-tags-4phh"&gt;Part2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
