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.
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
: Suchkonfigurationapp/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 🦄!