Bei ActiveRecord geht es ein wenig um Rechen, Relationen und Indizes

    Ich möchte Ihnen etwas über die Schmerzen erzählen: über die Arbeit mit AR im Allgemeinen und mit Relation im Besonderen; warnen Sie vor Standard-Gartenprodukten, die leicht Ihr Leben ruinieren und den Code langsam und gefräßig machen können. Die Erzählung basiert auf Rails 3.2 und ActiveRecord. Rails 4 hat natürlich viele neue und nützliche Dinge, aber Sie müssen immer noch darauf umsteigen, und das Fundament ist das gleiche.

    Dieses Material ist größtenteils für Anfänger gedacht, da es für den Autor sehr schmerzhaft ist, den Inhalt ganzer Tabellen in Form von ActiveRecord-Objekten und anderen Gliedmaßen in den Speicher zu extrahieren, wenn er AR verwendet . Für Entwickler, die Zen gelernt haben, ist es unwahrscheinlich, dass das Thema von Nutzen ist. Sie können nur helfen und ergänzen es mit ihren eigenen Beispielen und Verbesserungen.



    Wie oft haben sie auf der Welt wiederholt ...


    Wenn Sie mit Relation (und mit einem beliebigen ActiveRecord- Objekt im Allgemeinen) arbeiten, müssen Sie eine Sache klar darstellen: An welchem ​​Punkt „materialisieren“ wir die Auswahl, dh an welchem ​​Punkt hören wir auf, die SQL-Abfrage zu erstellen. Mit anderen Worten: Wenn Daten abgerufen werden und wir mit der Verarbeitung im Speicher fortfahren. Warum ist das wichtig? Ja, weil es umständlich ist:

    Product.all.find{|p| p.id == 42}
    

    Es kann einen Server aufhängen, den gesamten RAM aufheben und viele weitere schmutzige Tricks ausführen. Und das Gleiche, aber mit anderen Worten:

    Product.find(42)
    

    wird schnell und ohne Folgen arbeiten. Ebenso finden und finden - es ist nicht das Gleiche! Warum? Ja, denn im ersten Fall sagen wir Product.all und schoss sich in den Fuß, weil sie alle den Inhalt der Tabelle bedeutet entfernen Produkte für jede Zeile und Build - AR-Objekt, eine Reihe von ihnen schaffen und sicherlich für ihn gehen den Fund , die eine Klassenmethode ist Array (im Allgemeinen stammt find von Enumerable , dies sind jedoch bereits Details). Im zweiten Fall ist alles viel besser: find ist eine AR-Methode und wurde für die Suche nach pk entwickelt . Das heißt, wir generieren eine Anfrage

    SELECT * FROM products WHERE products.id = 42;
    

    Wir führen es aus, wir erhalten eine Zeile und alles.

    Was ist gut und was ist schlecht?


    Nachdem wir nun herausgefunden haben, warum die Arbeit mit AR eine große Verantwortung darstellt, wollen wir herausfinden, wie wir uns nicht in den Fuß schießen sollen. Das ist ganz einfach: Sie müssen die Methoden verwenden, die uns AR zur Verfügung stellt. Dies sind: where, select, pluck, includes, joins, scoped, unscoped, find_each und einige mehr, die in der Dokumentation oder im benachbarten Hub zu finden sind . Was man jedoch besser nicht verwenden sollte, ist eine sehr schwierige und gleichzeitig sehr einfache Aufzählung: Es ist nicht wünschenswert, alles andere zu verwenden, da fast die gesamte verbleibende Methodenvielfalt Relation in Array mit all seinen Konsequenzen umwandelt.

    Einfache Rezepte


    Jetzt werde ich ein paar Standard- und nicht sehr einfache Designs nennen, die das Leben erleichtern, aber oft vergessen werden. Aber bevor ich dem Leser eine Frage stelle: Denken Sie an die Funktion has_many. Überlegen Sie, welche Parameter Sie kennen und welche Sie aktiv verwenden. Listen Sie sie in Ihrem Kopf auf, zählen Sie sie ... und jetzt lautet die Frage: Wissen Sie, wie viele von ihnen wirklich sind?

    Die antwort
    24 Stücke in Rails3 und 12 in Rails4. Der Unterschied in 12 Teilen wird durch Methoden wie where, group usw. sowie Methoden für die Arbeit mit reinem SQL gemacht, die in Rails4 in einem Block und nicht in einem Hash übergeben werden.

    Warum habe ich das gefragt? Ja, um Ihr Niveau möglichst genau einschätzen zu können und zu behaupten, wenn Sie die meisten Optionen kennen, ist es unwahrscheinlich, dass Ihnen Folgendes neues Wissen einbringt. Diese Einschätzung ist sehr bedingt, daher lieber Leser, ärgere dich nicht sehr, wenn es dir lächerlich / unhaltbar / seltsam / etc vorkam (unbedingt hervorheben).

    Anzahl der Rezepte


    Also, jetzt gehen wir in Ordnung. Über update_attributes und update_attribute kennen alle (oder alle?). Die erste - massiv aktualisiert Felder mit dem Aufruf von Validierungen und Rückrufen. Nichts interessantes. Die zweite Option - überspringt alle Überprüfungen, startet Rückrufe, kann jedoch nur den Wert eines ausgewählten Felds aktualisieren (für einige Benutzer ähnelt Speichern (Überprüfen: Falsch) eher dem Wert ). Aber aus irgendeinem Grund vergessen sie oft update_column und update_all . Diese Methode überspringt sowohl Validierungen als auch Rückrufe und schreibt ohne Vorspiel direkt in die Datenbank.

    Rezept Nummer zwei


    Die Kommentare erinnerten an die wunderbare Berührungsmethode . Sie vergessen ihn auch oft und schreiben so etwas

    @product.updated_at = DateTime.now
    @product.save
    

    oder

    @product.update_attribute(:updated_at, DateTime.now)
    

    Obwohl es für solche Zwecke einfacher ist, dies zu tun:

    @product.touch(:updated_at)  # в данном случае параметр можно опустить
    

    Darüber hinaus verfügt touch über einen eigenen after_touch- Rückruf sowie die Option : touch ist in der belong_to- Methode vorhanden .

    So iterieren Sie richtig


    Der Hub hat bereits über find_each gesprochen, aber ich kann es nur noch einmal erwähnen, weil die Konstruktionen

    product.documents.map{…}
    

    und sie sind isomorph, etwas mehr als überall zu finden. Bei herkömmlichen Iteratoren, die für Relation verwendet werden, gibt es nur ein Problem : Sie ziehen alles auf einmal aus der Datenbank. Und das ist schrecklich. Im Gegensatz dazu führt find_each standardmäßig 1000 Teile gleichzeitig und das ist in Ordnung!

    UPD: Wie in den Kommentaren erwähnt, werden alle Methoden, die nicht explizit auf raw-sql projiziert werden, an to_a delegiert, weshalb die gesamte Abfrage in den Speicher abgerufen wird und die Arbeit damit nicht mehr auf der DB-Seite, sondern auf der Ruby-Seite stattfindet.

    Tipp zu default_scope


    Wickeln Sie den Inhalt von default_scope in einen Block. Ein Beispiel:

    default_scope where(nullified: false)  # плохо!
    default_scope { where(nullified: false) }  # хорошо
    

    Was ist der unterschied Die erste Option wird direkt beim Serverstart ausgeführt, und wenn sich das ungültig gemachte Feld nicht in der Datenbank befindet, startet der Server nicht. Gleiches gilt für Migrationen - sie werden nicht bestanden, da kein Feld vorhanden ist, das wir höchstwahrscheinlich nur hinzufügen möchten. Im zweiten Fall wird der Block aufgrund der Tatsache, dass Ruby faul ist, nur ausgeführt, wenn auf das Modell zugegriffen wird, und die Migrationen werden normal ausgeführt.

    Has_many durch


    Ein weiterer häufiger Patient ist

    product.documents.collect(&:lines).flatten
    

    Hier hat das Produkt viele Dokumente, die viele Zeilen haben. Es kommt häufig vor, dass Sie alle Zeilen aller Dokumente zum Produkt abrufen möchten. Und in diesem Fall erzeugen sie die obige Konstruktion. In diesem Fall können wir die Durchgangsoption für Beziehungen zurückrufen und Folgendes für das Produkt tun:

    has_many :lines, through: documents
    

    und dann ausführen

    product.lines
    

    Es fällt sowohl klarer als auch effizienter aus.

    Ein bisschen über JOIN


    Denken Sie in Fortsetzung des Themas Joins an Includes . Was ist das Besondere daran? Ja, das ist LEFT JOIN . Sehr oft sehe ich, dass Links- / Rechts-Joins explizit geschrieben sind

    joins("LEFT OUTER JOIN wikis ON wiki_pages.wiki_id=wikis.id")
    

    es funktioniert sicherlich auch, aber reines SQL in RoR war schon immer in Ungnade gefallen.

    Ohne von der Registrierkasse abzuweichen, müssen wir Sie auch an den Unterschied in den Werten zwischen Verknüpfungen und wo erinnern, wenn sie zusammen verwendet werden. Angenommen, wir haben eine Benutzertabelle und verschiedene Entitäten, z. B. Produkte, haben ein Feld author_id und eine Autorenbeziehung , unter der sich eine Benutzertabelle befindet .

    has_one :author,
    	     class: 'User',
    	     foreign_key: 'author_id'   # не обязательно, но для наглядности
    

    Der folgende Code für einen solchen Fall an die Arbeit ist nicht zu sein

    products.joins(:author).where(author: {id: 42})
    

    Warum? Denn in Joins ist der Name des Relationalen angegeben, welcher der Joinim ist, und in welchem ​​die Bedingung auf den Tisch auferlegt ist und wir sagen müssen

    where(users: {id: 42})
    

    Dies kann durch explizite Angabe von 'AS author' in der Verknüpfung vermieden werden, es handelt sich jedoch wiederum um reines SQL.

    Betrachten Sie als Nächstes die Verknüpfungen aus einem anderen Blickwinkel. Was auch immer wir uns anschließen, wir landen bei Objekten der Klasse, mit denen alles begann:

    Product.joins(:documents, :files, :etc).first
    

    In diesem Fall erhalten wir das Produkt unabhängig von der Anzahl der Verknüpfungen. Dieses Verhalten stört einige, da sie Felder von verknüpften Tabellen erhalten möchten. Und sie beginnen, die gleiche Anfrage von der anderen Seite zu stellen: Dokumente zu nehmen, sie mit Produkten zu verbinden, reines SQL zu schreiben, um mit anderen Entitäten zu kommunizieren, im Allgemeinen ein Fahrrad zu erfinden, wenn der richtige und logische Code am Anfang geschrieben wurde. Deshalb erinnere ich mich an die Grundlage:

    Product.joins(:documents, :files, :etc).where(...).pluck('documents.type')
    

    Hier erhalten wir ein Array mit dem gewünschten Feld aus der Datenbank. Vorteile: Mindestanforderungen, es werden keine AR-Objekte erstellt. Nachteile: In Rails 3 akzeptiert Rupfen nur 1 (einen) Parameter und hier ist es

    pluck('documents.type', 'files.filename', 'files.path')
    

    kann nur in Rails 4 durchgeführt werden.

    Beziehungen aufbauen


    Wenden wir uns nun der Überlegung zu, mit dem Aufbau von Beziehungen zu arbeiten. Im Allgemeinen ist alles ganz einfach:

    product.documencts.build(type: 'article', etc: 'etc').lines.build(content: '...')
    

    Nach dem Aufruf von product.save werden alle Verknüpfungen mit Validierungen, Präferenzen und Kurtisanen gespeichert. Diese freudige Handlung hat eine Einschränkung: All dies ist gut, wenn das Produkt nicht schreibgeschützt ist und / oder es keine anderen Lagerungsbeschränkungen gibt. In solchen Fällen arrangieren viele einen Garten ähnlich dem Garten mit Verknüpfungen im obigen Beispiel. Das heißt, sie erstellen ein Dokument , binden es an das Produkt und erstellen die Zeilen für das Dokument. Es stellt sich ein schiefes und standardmäßiges Verhalten heraus, das normalerweise mit dem Produkt zur Fehlerbehandlung zusammenhängtdas ____ funktioniert nicht. Daher ist das alles im Anhang sofort mit Krücken und Fehlern umrahmt, und es stellt sich als ziemlich böse heraus. Was ist in diesem Fall zu tun? Wir müssen uns an Autosave erinnern und verstehen, wie es funktioniert. Ohne auf Details einzugehen, sage ich, dass er an Rückrufen arbeitet . Daher gibt es eine Möglichkeit, die Beziehungen für das oben beschriebene Produkt beizubehalten:

    product.autosave_associated_records_for_documents
    

    In diesem Fall wird das Dokument gespeichert, seine Rückrufe werden aufgerufen, um die Zeilen usw. zu speichern.

    Ein paar Worte zu Indizes


    Zuletzt muss man über Indizes sagen, weil viele aufgrund von Problemen auf der Basis von Indizes mit dem Kopf gegen feste Objekte stießen. Ich entschuldige mich sofort dafür, dass ich mich in die Reihe der ActiveRecord- und Datenbankfunktionen eingemischt habe, aber für meine persönliche Meinung: Sie können nicht gut mit AR arbeiten, ohne zu wissen, was gerade auf der Datenbankseite passiert.

    Problem eins


    Aus irgendeinem Grund sind sich viele sicher, dass die Reihenfolge von Relation nicht davon abhängt, welche Spalte wir sortieren. Eine Variation dieses Missverständnisses ist das mangelnde Verständnis des Unterschieds zwischen Ordnungsrelation und Ordnungsarray . Aus diesem Grund können Sie default_scope mit einem Warrant für das VARCHAR-Feld und Fragen im Geiste begegnen : „Warum wird Ihre Seite so langsam geladen ? Dort werden nur ein paar Datensätze aus der Datenbank abgerufen! “ Das Problem hierbei ist, dass die Standardsortierung verdammt teuer ist, wenn wir keinen Index für diese Spalte haben. Standardmäßig sortiert AR nach pk . Es passiert, wenn wir es tun

    Products.first
    

    Aber pk hat fast immer einen Index und es gibt keine Probleme. Aber wenn wir sagen, welche Reihenfolge (: name) bei jedem Aufruf des Modells verwendet wird, beginnen die Probleme.
    Als Referenz : Wenn Sie "auf den Fingern" erklären, findet beim Sortieren nach einer indizierten Spalte keine echte Sortierung statt, sie ist bereits in der Datenbank vorhanden und die Daten werden sofort in der richtigen Reihenfolge gesendet.

    Zweites Problem


    Zusammengesetzte Indizes. Nicht jeder kennt sie und ein noch kleinerer Personenkreis weiß, warum sie gebraucht werden. Kurz gesagt, ein zusammengesetzter Index ist ein Index, der auf zwei oder mehr Datenbankfeldern basiert. Wo kann es nützlich sein? Zwei gebräuchliche Orte, um es zu verwenden:
    • polymorphe Assoziationen
    • Zwischen-Viele-zu-Viele-Beziehungstabelle.
    Über polymorphe Verbindungen wurde hier berichtet . Oft ist es für sie praktisch, einen zusammengesetzten Index zu erstellen. Hier ist ein leicht aktualisiertes Beispiel von off.manula :

    class CreatePictures < ActiveRecord::Migration
      def change
        create_table :pictures do |t|
          t.string  :name
          t.integer :imageable_id
          t.string  :imageable_type
          t.timestamps
        end
        add_index :pictures, [:imageable_id, :imageable_type] # вот он составной индекс
      end
    end
    

    Hier einige Worte zum Unterschied zwischen einem regulären Index und einem zusammengesetzten Index. Ich werde nicht weiter auf Details eingehen, da das Thema für einen separaten Hub bestimmt ist. Außerdem wurde schon alles vor mir gemalt.
    Nun zur Zwischenverbindungstabelle. Bekannte HBTM . In einigen Fällen ist es hier angebracht , einen zusammengesetzten Index für assemblies_parts zu veröffentlichen (siehe den HBTM- Link ). Wir müssen uns jedoch daran erinnern, dass die Reihenfolge der Felder in einem zusammengesetzten Index bekannt ist. Details hier .

    Problem drei


    "Indizes werden überall benötigt!" Es ist nicht so üblich, aber es verursacht schreckliche Bremsen bei allem und jedem. Es muss beachtet werden, dass der Index kein Allheilmittel ist und eine Geschwindigkeit von x10 bis x100 garantiert, sondern ein Werkzeug, das an den richtigen Stellen verwendet werden muss und nicht über dem Kopf schwenkt und in jedes Loch schiebt. Hier hier können Sie über die Arten von Indizes lesen, und hier können Sie erfahren , warum sie in der Regel benötigt werden.

    Für sim alles


    Vielen Dank für das Lesen bis zum Ende. Für Tippfehler und Ungenauigkeiten schreiben Sie mir auf PM, ich werde es gerne beheben. Ich würde mich auch freuen, wenn Sie Ihre „schmerzhaften“ und Ihre Erfahrungen darüber teilen, woran Sie sich erinnern müssen und was in verschiedenen Situationen während der Entwicklung besser anzuwenden ist.

    Nur registrierte Benutzer können an der Umfrage teilnehmen. Bitte komm rein .

    Was möchten Sie am liebsten im nächsten Hub sehen?

    • 47,1% Weitere Informationen zu ActiveRecord und der Datenbank (Details zu Indizes, Zuordnungsoptionen usw.) 42
    • 25,8% Interessantes über Ruby und seine Funktionen (zum Beispiel über inject, succ und andere) 23
    • 24,7% Schlechte Ratschläge (Teile von Code und Funktionen, für die ich dem Autor einen Nagel in den Kopf schlagen möchte) 22
    • 2,2% besitzen (in den Kommentaren schreiben) 2

    Jetzt auch beliebt: