DEV Community

Abdelkader Boudih
Abdelkader Boudih

Posted on

2

NoFlyList: How NoFlyList Optimizes Tag Queries

Database-Specific Strategies

NoFlyList automatically detects your database type and uses optimized queries:

class Product < ApplicationRecord
  include NoFlyList::TaggableRecord
  has_tags :categories
end

# This generates different SQL for each database
Product.with_any_categories("electronics", "gaming")
Enter fullscreen mode Exit fullscreen mode

PostgreSQL Optimization

PostgreSQL query:

# Using array operators and CTE for better performance
SELECT "products".*
FROM "products"
WHERE "products"."id" IN (
  SELECT DISTINCT "products"."id"
  FROM products
  INNER JOIN "product_taggings" ON "product_taggings"."taggable_id" = "products"."id"
  INNER JOIN "product_tags" ON "product_tags"."id" = "product_taggings"."tag_id"
  WHERE "product_taggings"."context" = 'category'
  AND "product_tags"."name" = ANY(ARRAY['electronics', 'gaming'])
)
Enter fullscreen mode Exit fullscreen mode

MySQL Optimization

MySQL query:

# Using FIND_IN_SET and subqueries
SELECT `products`.*
FROM `products`
WHERE `products`.`id` IN (
  SELECT `products`.`id`
  FROM products
  INNER JOIN `product_taggings` ON `product_taggings`.`taggable_id` = `products`.`id`
  INNER JOIN `product_tags` ON `product_tags`.`id` = `product_taggings`.`tag_id`
  WHERE `product_taggings`.`context` = 'category'
  AND `product_tags`.`name` IN ('electronics', 'gaming')
  GROUP BY `products`.`id`
)
Enter fullscreen mode Exit fullscreen mode

SQLite Optimization

SQLite query:

# Optimized for SQLite's simpler query planner
SELECT "products".*
FROM "products"
WHERE "products"."id" IN (
  SELECT "products"."id"
  FROM products
  INNER JOIN product_taggings ON product_taggings.taggable_id = products.id
  INNER JOIN product_tags ON product_tags.id = product_taggings.tag_id
  WHERE product_taggings.context = 'category'
  AND product_tags.name IN ('electronics', 'gaming')
)
Enter fullscreen mode Exit fullscreen mode

Complex Queries

# Finding products with ALL specified tags
Product.with_all_categories("electronics", "gaming")

# PostgreSQL uses:
SELECT "products".*
FROM "products"
WHERE "products"."id" IN (
  SELECT "products"."id"
  FROM products
  INNER JOIN "product_taggings" ON "product_taggings"."taggable_id" = "products"."id"
  INNER JOIN "product_tags" ON "product_tags"."id" = "product_taggings"."tag_id"
  WHERE "product_taggings"."context" = 'category'
  AND "product_tags"."name" IN ('electronics', 'gaming')
  GROUP BY "products"."id"
  HAVING COUNT(DISTINCT "product_tags"."name") = 2
)

# Finding products without specific tags
Product.without_any_categories("discontinued")

# Finding products with exact tag set
Product.with_exact_categories(["electronics", "gaming"])
Enter fullscreen mode Exit fullscreen mode

Performance Tips

  1. Index Optimization:
class CreateProductTags < ActiveRecord::Migration[7.2]
  def change
    add_index :product_tags, :name
    add_index :product_taggings, [:taggable_id, :taggable_type, :context]
  end
end
Enter fullscreen mode Exit fullscreen mode

Unlike AATO, the gem support multiple database connections and mixed adapters.

  1. Counter Cache:
class Product < ApplicationRecord
  has_tags :categories, counter_cache: true
end
Enter fullscreen mode Exit fullscreen mode
  1. Eager Loading:
# Efficient loading of products with their tags
Product.includes(:categories)
       .with_any_categories("electronics")
Enter fullscreen mode Exit fullscreen mode

Debugging Queries

Use query logging to see optimizations:

# config/environments/development.rb
config.active_record.verbose_query_logs = true

# In console
Product.with_any_categories("electronics").explain
Enter fullscreen mode Exit fullscreen mode

Common Patterns

  1. Category Trees:
Product.with_all_categories("electronics")
       .with_any_categories("gaming", "professional")
Enter fullscreen mode Exit fullscreen mode
  1. Exclusions:
Product.with_any_categories("electronics")
       .without_any_categories("discontinued", "clearance")
Enter fullscreen mode Exit fullscreen mode
  1. Exact Matching:
Product.with_exact_categories(["gaming", "electronics"])
Enter fullscreen mode Exit fullscreen mode

Each pattern generates optimized SQL based on your database.
If you know a better query, feel free to open a pull request on the adapter query.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series