DEV Community

Abdelkader Boudih
Abdelkader Boudih

Posted on

NoFlyList: Custom Tag Screening with NoFlyList

Tag parsing seems simple until you handle real user input.

Let's explore how NoFlyList's transformers handle messy tag data submitted.

Basic Setup

rails generate no_fly_list:transformer
Enter fullscreen mode Exit fullscreen mode

Default transformer:

module ApplicationTagTransformer
  module_function

  def parse_tags(tags)
    if tags.is_a?(Array)
      tags
    else
      tags.split(separator).map(&:strip).compact
    end
  end

  def recreate_string(tags)
    tags.join(separator)
  end

  def separator
    ','
  end
end
Enter fullscreen mode Exit fullscreen mode

Real-World Examples

Hashtag Transformer

module HashtagTransformer
  module_function

  def parse_tags(tags)
    return tags if tags.is_a?(Array)

    tags.scan(/#[\w-]+/).map { |tag| tag.delete('#') }
  end

  def recreate_string(tags)
    tags.map { |tag| "##{tag}" }.join(' ')
  end
end

class Post < ApplicationRecord
  include NoFlyList::TaggableRecord

  has_tags :hashtags, transformer: HashtagTransformer
end

post = Post.new(content: "Check out #rails #ruby #webdev")
post.hashtags_list = post.content  # Extracts: ["rails", "ruby", "webdev"]
Enter fullscreen mode Exit fullscreen mode

Multi-Language Transformer

module MultiLangTransformer
  module_function

  def parse_tags(tags)
    return tags if tags.is_a?(Array)

    tags.split('|').map do |tag_pair|
      lang, tag = tag_pair.split(':').map(&:strip)
      "#{lang}:#{tag}"
    end
  end

  def recreate_string(tags)
    tags.join(' | ')
  end
end

class Article < ApplicationRecord
  has_tags :keywords, transformer: MultiLangTransformer
end

article.keywords_list = "en:ruby | es:rubí | fr:rubis"
Enter fullscreen mode Exit fullscreen mode

Hierarchical Tag Transformer

module CategoryTransformer
  module_function

  def parse_tags(tags)
    return tags if tags.is_a?(Array)

    tags.split('>').map(&:strip)
  end

  def recreate_string(tags)
    tags.join(' > ')
  end
end

class Product < ApplicationRecord
  has_tags :categories, transformer: CategoryTransformer
end

product.categories_list = "Electronics > Computers > Laptops"
Enter fullscreen mode Exit fullscreen mode

Custom Normalization

module NormalizedTransformer
  module_function

  def parse_tags(tags)
    tags = tags.split(',') unless tags.is_a?(Array)

    tags.map do |tag|
      tag.strip
         .downcase
         .gsub(/[^a-z0-9\s-]/, '')  # Remove special chars
         .gsub(/\s+/, '-')          # Spaces to hyphens
    end.uniq.compact
  end

  def recreate_string(tags)
    tags.join(', ')
  end
end

class Photo < ApplicationRecord
  has_tags :labels, transformer: NormalizedTransformer
end

photo.labels_list = "Nature Photos, WILDLIFE shots, Outdoor-Photography"
# Transforms to: ["nature-photos", "wildlife-shots", "outdoor-photography"]
Enter fullscreen mode Exit fullscreen mode

Context-Specific Transformers

Mix transformers based on tag context:

class Article < ApplicationRecord
  include NoFlyList::TaggableRecord

  has_tags :categories, transformer: CategoryTransformer
  has_tags :hashtags, transformer: HashtagTransformer
  has_tags :keywords, transformer: MultiLangTransformer
end
Enter fullscreen mode Exit fullscreen mode

Testing Transformers

class TransformerTest < ActiveSupport::TestCase
  test "hashtag parsing" do
    input = "#ruby #rails doing #testing"
    expected = ["ruby", "rails", "testing"]
    assert_equal expected, HashtagTransformer.parse_tags(input)
  end

  test "hierarchical parsing" do
    input = "Tech > Software > Tools"
    expected = ["Tech", "Software", "Tools"]
    assert_equal expected, CategoryTransformer.parse_tags(input)
  end
end
Enter fullscreen mode Exit fullscreen mode

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.

Part4

Top comments (0)