Auslösbar - ereignisorientierte Logik für ActiveRecord-Modelle

    Einer der Haupttrends von Rails besteht derzeit darin, die Rolle von ActiveRecord-Klassen in der Anwendung zu überdenken: Von nun an sollten Modelle Klassen werden, die für die Arbeit mit der Datenbank verantwortlich sind, und nicht mehr eine Ansammlung von Abfragen, Zuordnungen, Validierungen, Domänenmethoden und Präsentationsmethoden. Trotz der riesigen Modelle verschiebt sich ein Teil der Domänenlogik immer noch zu anderen Teilen der Anwendung, und dies erschwert das Verständnis erheblich. In vielen Anwendungen werden viele Aktionen ausgeführt, wenn Ereignisse auftreten, während ActiveRecord :: Callbacks verwendet wird . Dieses Juwel ist ein Versuch, die Beschreibung von Geschäftsregeln für ActiveRecord-Modelle zu überdenken.

    Also auslösbarist ein Juwel für die Beschreibung von ereignisgesteuerten Geschäftsregeln auf hoher Ebene. Regeln können sowohl im Kontext der Modellklasse deklariert als auch in einer separaten Datei daraus entnommen werden. Derzeit umfasst die Bibliothek die Implementierung von zwei Arten von Regeln: Trigger und Automatisierung.

    Auslöser


    Ein Trigger ist eine Regel, die ein Ereignis, eine Ausführungsbedingung und eine Aktion enthält. Beispielsweise müssen wir nach der Registrierung SMS an neue Benutzer senden, sofern der Benutzer dem Empfang von Nachrichten von uns zugestimmt hat. Deklarieren Sie einen einfachen Trigger:

    User.trigger name: 'SMS to user', on: :after_create, if: { receives_sms: true } do
      SmsGateway.send_welcome_sms(phone_number)
    end
    

    So funktioniert es: Dem Modell wird ein spezieller Callback hinzugefügt, der die Ausführung aller deklarierten Trigger einleitet, wenn die Bedingung erfüllt ist. Aktion (Block do ) wird im Rahmen des Modells erfolgen. Da diese Regel basierend auf ActiveRecord :: Callbacks implementiert wird, wird dieselbe Liste von Ereignissen verwendet ( before_save , after_create usw.). Die Mitteilung übermittelten Regeln optionaler Attribut Name , kann es zum Beispiel verwendet werden, um die Regeln für den Betrieb anzumelden.

    DSL-Einschränkungen


    Die Bedingung kann auf zwei Arten definiert werden, die erste über das integrierte DSL. In diesem Fall ist der if- Wert ein Hash. Um einem Feld eine Einschränkung aufzuerlegen, müssen Sie seinen Namen als Schlüssel verwenden, und der Wert ist ein Hash mit Bedingungen. Im obigen Beispiel wird eine Kurzform des Vergleichs verwendet. In der vollständigen Form können Sie die Bedingung {receive_sms: {is: true}} verwenden . Die folgenden einfachen Bedingungen sind derzeit verfügbar:

    TypVolle FormKurzform
    Wert{field: {is :: value}}{field :: value}
    Zugehörigkeit{Status: {in: [: offen ,: akzeptiert]}}{Status: [: offen ,: akzeptiert]}
    Ablehnung{field: {is_not :: value}
    Mehr{Feld: {Größerer_Then :: Wert}
    Weniger{field: {less_then :: value}
    Existenz{field: {exists: true}

    Zusätzlich ist eine Kombination von Bedingungen durch und und oder verfügbar :

    { and: [{ field1: :value1 }, { field2: :value2 }] }
    { or: [{ field1: :value1 }, { field2: :value2 }] }
    

    Wenn Sie die Zuordnungsprüfung (derzeit von DSL nicht unterstützt) oder einen anderen komplizierten Fall verwenden müssen, können Sie die zweite Methode verwenden - die Lambda-Bedingung. In diesem Fall ist der if- Wert ein Block, während der Modellkontext im Block gespeichert wird. Beispiel:

    User.trigger on: :after_create, if: { receives_sms? && payments.count > 0 } do
      send_welcome_sms
    end
    


    Aktionen


    Wir haben zuvor Aktionen mit dem do- Block angekündigt. In Fällen, in denen die gleichen Aktionen von Objekten unterschiedlicher Klassen ausgeführt werden, kann die Codeduplizierung mit unserer eigenen Aktionsklasse vermieden werden. Um dies zu tun, müssen Sie aus der Klasse vererbt werden auslösbare :: Aktionen :: Aktion und implementieren eine einzige Methode def run_for (Objekt ,, rule_name)! , Das das erste argementom Objekt sein wird, die den Trigger ausgeführt wird , und die zweite - der Name der Regel (im Attribut übergeben Namen finden oben).
    Kehren wir zum Beispiel mit dem Senden von SMS zurück. Angenommen, Kunden können im System registriert werden ( Kundenklasse ), die nach der Registrierung auch eine SMS erhalten müssen. Erstellen Sie eine neue Aktionsklasse und lösen Sie Folgendes aus:

    class SendWelcomeSms < Triggerable::Actions::Action
      def run_for! object, trigger_name
        SmsGateway.send_welcome_sms(object.phone_number)
      end
    end
    User.trigger on: :after_create, if: { receives_sms: true }, do: :send_welcome_sms
    Customer.trigger on: :after_create, if: {
      and: [{ receives_sms: true }, { active: true}]
    }, do: :send_welcome_sms
    


    Automatisierung


    Automatisierung - Ausführen einer ausstehenden Aktion, wenn die Bedingungen erfüllt sind. Lassen Sie uns beispielsweise eine Nachricht nicht sofort, sondern nach 24 Stunden an die Benutzer senden. Die Automatisierung sieht folgendermaßen aus:

    User.automation name: 'SMS to user', if: { created_at: { after: 24.hours }, receives_sms: true } do: :send_welcome_sms
    

    Unterscheidet die Anzeige ausgelöst werden :
    1. Keine Angabe Ereignis ( auf )
    2. Die Bedingungen Blockausführungszeit angegeben ( die vor und der nach )
    3. Lambda-Ausdrücke verboten

    Wie es funktioniert: für avtomatsy Notwendigkeit , jeden Motor für die Organisation der geplanten Aufgaben verbinden ( B. wann immer ) und stellen Sie sicher, dass die Automation Engine startet: Triggerable :: Engine.run_automations (interval) , wobei interval das Zeitintervall zwischen den Taskstarts ist. Beim Start wird für jede Automatisierung eine Anforderung an die Datenbank ausgeführt, die auf den deklarierten Bedingungen basiert (daher funktionieren Lambda-Bedingungen nicht), und für die ausgewählten Modelle wird eine Aktion ausgeführt.Die angekündigten Aktionen werden nicht genau nach dem angegebenen Zeitraum, sondern nach dem Intervall ausgeführt!

    Anstelle einer Schlussfolgerung


    Sie können mehr über das Herstellen einer Verbindung mit der Anwendung und vieles mehr auf dem Github lesen (und sich auch die Quelle ansehen!). Warten auf Fragen, Kritik und Feedback in den Kommentaren.

    UPD: Eine der möglichen Anwendungen dieser Lösung ist das Erstellen einer Schnittstelle zum Deklarieren von Benutzerregeln (wie in Zendesk), dh mögliche Aktionen sind fest codiert, und es ist möglich, diese Aktionen auf Modellen unter den vom Benutzer ausgewählten Bedingungen auszuführen. Der Unterschied zur Verwendung von ActiveRecord :: Observer besteht darin, Automatisierungen ähnlich wie Trigger zu deklarieren.

    Jetzt auch beliebt: