TL;DR

Wir sehen die Suche als einen entscheidenden Punkt für Nutzererlebnis und Engagement. Das activerecord-mysql-search Gem bietet eine leistungsstarke und einfache Lösung zur Implementierung einer Volltextsuche in Ruby-on-Rails Apps unter Nutzung der nativen MySQL-Funktionalitäten. Es bietet eine schnelle, skalierbare und flexible Suche ohne die Komplexität von externen Abhängigkeiten.

Auf Github ansehen

Häufige Textsuchprobleme in Rails

Wenn wir uns Suchimplementierungen in Rails Anwendungen anschauen, greifen viele standardmäßig auf ActiveRecord und SQL-Abfragen mit dem LIKE- Operator zurück. Dieser Ansatz wirkt simpel und schnell, bringt jedoch einen entscheidenden Nachteil mit sich: Er skaliert nicht gut.

LIKE-Abfragen nutzen keine Volltext-Indizes, scannen ganze Tabellen und sind bei zehntausenden oder hunderttausenden Datensätzen langsam.

  • Standard-LIKE trifft nur exakte Teilstring-Treffer, bietet keine Relevanzbewertung und unterstützt keine Morphologie oder Synonyme
  • LIKE ignoriert sprachliche Nuancen und unterstützt keine Wortformen
  • Suche über mehrere Felder, Assoziationen, Filter und Sortierungen führt schnell zu unübersichtlichem, schwer wartbarem Code

Mit wachsendem Datenvolumen wird die Suche langsamer und die Datenbanklast kritisch.

Unser Schmerz verhindert vielleicht auch deinen

Unsere Hauptgründe, weshalb wir uns der Volltextsuche zugewandt haben:

  • Wir benötigten eine Lösung, die mit den Systemen unserer Kunden skaliert
  • Wir wollten eine wartbare Struktur, die von jedem im Team übernommen werden kann
  • Geschwindigkeit ist ein entscheidender Faktor

Nach der Integration unserer Volltextsuche in diverse Projekte und zahlreichen Erfahrungswerten aus Trial-and-Error entstand das activerecord-mysql-search Gem. Unser Ziel war, Rails-Entwicklern nicht nur einen weiteren Wrapper für FULLTEXT zu bieten, sondern ein Werkzeug, das sich nahtlos integriert, den Wartungsaufwand reduziert und das eingebaute Potential von MySQL nutzt.

Elasticsearch ist mächtig, klar. Aber für die meisten Projekte Overkill: zu komplex, zu kostenintensiv und viel mehr, als nötig ist, wenn man bereits MySQL nutzt.

Key Features

  • Nutzt MySQL-FULLTEXT-Indizes für schnelle und relevante Suche über große Datensätze.
  • Suchdaten werden automatisch aktualisiert, wenn sich Objekte ändern. Flexible Strategien: synchron oder per Hintergrund-Indexierung via ActiveJob.
  • Definiere, welche Felder indiziert werden sollen — einfache Felder, verschachtelte Assoziationen, Datumsangaben, Kalenderwochen oder eigene Transformationen via Proc: alles deklarativ in den Quellklassen.
  • Erstelle separate Suchspalten für verschiedene Benutzerrollen (z. B. Käufer/Verkäufer/Admin): entscheidend für Multi-Tenant-Apps und Datenschutz.
  • Enthält Generatoren für schnelle Einrichtung, Migrations- und Rake-Tasks für Massen-Reindexierung und Scopes für Suche — alles auf die Rails-Art.
  • Indizierte Felder, Formatierungen und Quellen sind in eigene Quellklassen ausgelagert: Das erhöht Transparenz und Wartbarkeit.

Unser Unterschied zu anderen Ansätzen

  • Komplexe SQL- und Arel-Magie bleibt verborgen: Du arbeitest mit vertrauten Scopes und Ruby-Klassen.
  • Tools wie synchrone/hintergrundgestützte Indexierung, geplante Tasks und SQL-Trigger sorgen dafür, dass Suchdaten stets aktuell sind.
  • Quellklassen erlauben es, Felder, Assoziationen oder benutzerdefinierte Indexierungsregeln schnell hinzuzufügen, ohne Migrations- oder SQL-Code anzupassen.
  • Indiziere beliebige assoziierte Daten parallel zur Hauptentität, z. B. Produktbewertungen oder zugehörige Bestellungen.
  • Mehrspaltige Architektur sichert Datenschutz — verschiedene Nutzer sehen unterschiedliche Ergebnisse, und Indizes speichern keine unnötigen Daten.
  • Unser Volltext-Suchansatz lässt sich in großen Anwendungen in wenigen Stunden integrieren.

Volltextsuche in 5 Minuten

Wir haben activerecord-mysql-search so gestaltet, dass die Integration möglichst schnell und „Rails-like“ erfolgt. So geht’s:

Schritt 1: Gem installieren

Füge deinem Gemfile hinzu:

gem 'activerecord-mysql-search'

Run:

bundle install

Schritt 2: Konfiguration und Migrationen generieren

Ausführen:

rails generate mysql:search:install

Dies erzeugt:

  • config/initializers/mysql_search.rb: Suchkonfiguration
  • app/models/search_index.rb: Modell für die Suchindizes
  • eine Migration für die Suchindex-Tabelle.

Schritt 3: Migration ausführen

rails db:migrate

Schritt 4: Suche im Modell aktivieren

Füge in deinem Modell (z. B. Article) hinzu:

class Article < ApplicationRecord
  include MySQL::Search::Searchable
  belongs_to :news_digest
end

Schritt 5: Index-Schema definieren (Source-Klasse)

Erstelle app/search_sources/article_source.rb:

class ArticleSource < MySQL::Search::Source
  schema content: {
    title: :text,
    content: :text,
    type: ->(value) { I18n.t("article.types.#{value}") },
    news_digest: {
      title: :text,
      published_at: [:date, :calendar_week]
    }
  }
end

Schritt 6: Bestehende Daten indizieren

rails mysql:search:reindex

Schritt 7: Suche in Controllern oder Services verwenden

results = Article.full_text_search("Ruby on Rails")

Das war’s! Nutzer erhalten jetzt schnelle, skalierbare und relevante Suchergebnisse.

Erweiterte Szenarien: Mehrspaltige Suche für Rollen und Kontexte

Echte Projekte benötigen selten nur „Einzelspalten-Suche“. Geschäftslogik erfordert oft, verschiedenen Nutzern unterschiedliche Daten zu zeigen, flexible Filter und Datenschutz zu garantieren. activerecord-mysql-search unterstützt das von Haus aus. Beispiel: Kunden, Verkäufer und Admins benötigen jeweils ihre eigene „Weltansicht“. Das Gem erlaubt separate Indizes pro Rolle:

class ProductSource < MySQL::Search::Source
  schema content: {
    name: :text,
    description: :text,
    brand: :text,
    reviews: { content: :text, rating: :text }
  },
  seller_extra: {
    sku: :text,
    internal_notes: :text,
    supplier: { name: :text, contact_info: :text },
  },
  admin_extra: {
    created_by: { name: :text, email: :text }
  }
end

Füge Spalten und Indizes zu SearchIndex hinzu:

class ExtraContentForSearchIndices < ActiveRecord::Migration[7.1]
  def change
    add_column :search_indices, :seller_extra, :text
    add_column :search_indices, :admin_extra, :text

    add_index :search_indices, [:content, :seller_extra], type: :fulltext
    add_index :search_indices, [:content, :seller_extra, :admin_extra], type: :fulltext
  end
end

Jetzt suchen Verkäufer mit:

results = Product.full_text_search("Ruby on Rails", search_column: [:content, :seller_extra])

Admins verwenden:

results = Product.full_text_search("Ruby on Rails", search_column: [:content, :seller_extra, :admin_extra])

So sind Suchkontexte komplett getrennt. Es ist nicht nötig, kombinierte Indizes zu erstellen, sondern einfach unterschiedliche Spalten und separate Indizes pro Rolle zu nutzen.

Was, wenn Methoden ActiveRecord-Callbacks nicht auslösen?

Verwendung von #update_column und ähnlichen Methoden, die keine ActiveRecord-Callbacks triggern, kann zur Desynchronisation des Suchindex führen. Lösung: nutze #update oder #save, damit die Indizes aktuell bleiben. Soll dies nicht möglich sein, bietet das Gem folgendes Werkzeug:

Hier verlässt sich das Gem auf die Spalte updated_at. Du kannst das Aktualisieren dieser Spalte der Datenbank per Trigger überlassen. Erstelle eine Migration mit dem Generator:

rails generate mysql:search:create_triggers

Diese Migration erzeugt in jeder Tabelle einen Trigger, der updated_at bei Änderungen aktualisiert, und fügt einen Monkey-Patch zur ActiveRecord-#timestamps-Methode in Migrations hinzu (um künftige Tabellen automatisch auszustatten). Damit lässt sich Suchindex-Relevanz mit einem oder mehreren dieser Werkzeuge pflegen:

  • Rake-Task rails mysql:search:actualize[1.hour] — prüft und aktualisiert Indizes periodisch, synchronisiert sie mit dem aktuellen Datenbank-Status. Lässt sich per Cron ausführen.
  • MySQL::Search::Jobs::ScheduledUpdaterJob — ein Background-Job, der Indizes regelmäßig prüft und aktualisiert. Beispiel für Solid Queue:
  # config/recurring.yml
  actualize_search_indices:
    class: MySQL::Search::Jobs::ScheduledUpdaterJob
    args: [:daily]
    schedule: every day at noon
-	Vollständige Reindexierung via Rake-Task `mysql:search:reindex` . Wenn du Indizes komplett auffrischen möchtest, z. B. nach Migrationen oder Schema-Änderungen. In diesem Fall sind Trigger nicht erforderlich.

Warum dieses Gem einfach funktioniert: für dich, deine Nutzer und dein Team

Es ist schnell, leicht zu benutzen und für modernes Rails gebaut. Keine Black-Boxes, kein Vendor-Lock-in, sondern saubere, quelloffene Suche, die deine Daten und deine Zeit respektiert. Wir haben es entwickelt, weil wir genug hatten von umständlicher, überentwickelter Suche. Battle-getestet, geformt durch echten Schmerz und bereits dabei, Teams schneller zu machen. Open-Source und gut dokumentiert.

Suche ist essentiell, darf aber keine Last sein. activerecord-mysql-search bietet Rails-Entwicklern eine einfache, mächtige Möglichkeit, schnelle und flexible Suche direkt auf MySQL aufzubauen.

Probier es aus, integriere es in deine App und lass uns wissen, was du denkst. Fragen, Feedback oder Ideen sind immer willkommen.

Stay in shape 🦄!