Suche…


Wiederholen Sie sich nicht (TROCKEN)

Um den Code sauber zu halten, folgt Rails dem DRY-Prinzip.

Es geht darum, wann immer möglich, so viel Code wie möglich wiederzuverwenden, anstatt ähnlichen Code an mehreren Stellen zu duplizieren (z. B. unter Verwendung von Partials). Dies reduziert Fehler , hält Ihren Code sauber und setzt das Prinzip ein , Code einmal zu schreiben und anschließend wiederzuverwenden. Das Aktualisieren von Code an einer Stelle ist einfacher und effizienter als das Aktualisieren mehrerer Teile desselben Codes. Dadurch wird Ihr Code modularer und robuster.

Auch Fat Model, Skinny Controller ist DRY, weil Sie den Code in Ihr Modell schreiben und im Controller nur den Aufruf ausführen, z.

# Post model
scope :unpublished, ->(timestamp = Time.now) { where('published_at IS NULL OR published_at > ?', timestamp) } 


# Any controller
def index
    ....
    @unpublished_posts = Post.unpublished
    ....
end

def others
    ...
    @unpublished_posts = Post.unpublished
    ...
end

Dies führt auch zu einer API-gesteuerten Struktur, bei der interne Methoden verborgen werden und Änderungen durch das Übergeben von Parametern auf eine API-Art erzielt werden.

Konvention über Konfiguration

In Rails finden Sie Controller, Ansichten und Modelle für Ihre Datenbank.

Um den Bedarf an umfangreichen Konfigurationen zu reduzieren, implementiert Rails Regeln, um die Arbeit mit der Anwendung zu erleichtern. Sie können Ihre eigenen Regeln definieren, aber für den Anfang (und für später) ist es eine gute Idee, sich an die von Rails angebotenen Konventionen zu halten.

Diese Konventionen beschleunigen die Entwicklung, halten Ihren Code übersichtlich und lesbar und ermöglichen Ihnen eine einfache Navigation in Ihrer Anwendung.

Konventionen senken auch die Eintrittsbarrieren für Anfänger. Es gibt so viele Konventionen in Rails, von denen ein Anfänger nicht einmal etwas wissen muss, sondern nur aus Unwissenheit Nutzen ziehen kann. Es ist möglich, großartige Anwendungen zu erstellen, ohne zu wissen, warum alles so ist, wie es ist.

Zum Beispiel

Wenn Sie über eine Datenbanktabelle mit der Primärschlüssel- id orders verfügen, wird das übereinstimmende Modell als order und der Controller, der die gesamte Logik verarbeitet, heißt orders_controller . Die Ansicht ist in verschiedene Aktionen aufgeteilt: Wenn der Controller eine new und eine edit hat, gibt es auch eine new und eine edit .

Zum Beispiel

Um eine App zu erstellen, führen Sie einfach rails new app_name . Auf diese Weise werden ungefähr 70 Dateien und Ordner generiert, die die Infrastruktur und Grundlage für Ihre Rails-App bilden.

Es enthält:

  • Ordner für Ihre Modelle (Datenbankebene), Controller und Ansichten
  • Ordner zum Halten von Komponententests für Ihre Anwendung
  • Ordner zum Speichern von Web-Assets wie Javascript- und CSS-Dateien
  • Standarddateien für HTTP 400-Antworten (dh Datei wurde nicht gefunden)
  • Viele andere

Fettes Modell, dünner Controller

„Fat Model, Skinny Controller“ bezieht sich darauf, wie die M- und C-Teile von MVC ideal zusammenarbeiten. Jede nicht-antwortbezogene Logik sollte in das Modell aufgenommen werden, idealerweise in einer schönen, überprüfbaren Methode. Inzwischen ist der "Skinny" Controller einfach eine schöne Schnittstelle zwischen Ansicht und Modell.

In der Praxis kann dies eine Reihe verschiedener Arten von Refactoring erfordern, aber es kommt auf eine Idee: Durch das Verschieben einer beliebigen Logik, die nicht die Reaktion auf das Modell (anstelle des Controllers) ist, haben Sie nicht nur die Wiederverwendung befördert wo möglich, aber Sie haben es auch ermöglicht, Ihren Code außerhalb des Kontexts einer Anfrage zu testen.

Schauen wir uns ein einfaches Beispiel an. Angenommen, Sie haben folgenden Code:

def index
  @published_posts = Post.where('published_at <= ?', Time.now)
  @unpublished_posts = Post.where('published_at IS NULL OR published_at > ?', Time.now)
end

Sie können es so ändern:

def index
  @published_posts = Post.published
  @unpublished_posts = Post.unpublished
end

Dann können Sie die Logik in Ihr Post-Modell verschieben, wo es wie folgt aussehen könnte:

scope :published, ->(timestamp = Time.now) { where('published_at <= ?', timestamp) }
scope :unpublished, ->(timestamp = Time.now) { where('published_at IS NULL OR published_at > ?', timestamp) }

Hüten Sie sich vor default_scope

default_scope enthält default_scope , um ein Modell standardmäßig automatisch default_scope .

class Post
  default_scope ->{ where(published: true).order(created_at: :desc) }
end

Der obige Code wird Beiträge bereitstellen, die bereits veröffentlicht werden, wenn Sie eine Abfrage für das Modell ausführen.

Post.all # will only list published posts 

Dieser Spielraum wirkt zwar harmlos, hat aber mehrere versteckte Nebeneffekte, die Sie möglicherweise nicht möchten.

default_scope und order

Da Sie im default_scope eine order default_scope , wird die aufrufende order bei Post als zusätzliche Bestellung hinzugefügt, anstatt die Standardeinstellung zu überschreiben.

Post.order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."created_at" DESC, "posts"."updated_at" DESC

Dies ist wahrscheinlich nicht das gewünschte Verhalten. Sie können dies überschreiben, indem Sie die order aus dem Geltungsbereich ausschließen

Post.except(:order).order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."updated_at" DESC

default_scope und Modellinitialisierung

Wie bei jedem anderen ActiveRecord::Relation default_scope den Standardzustand von Modellen, die von ihm initialisiert werden.

In dem obigen Beispiel hat Post standardmäßig where(published: true) festgelegt, sodass neue Modelle von Post ebenfalls festgelegt werden.

Post.new # => <Post published: true>

unscoped

default_scope kann nominell gelöscht werden, unscoped zuerst unscoped wird. Dies hat jedoch auch Nebenwirkungen. Nehmen Sie zum Beispiel ein STI-Modell:

class Post < Document
  default_scope ->{ where(published: true).order(created_at: :desc) }
end

Standardmäßig Abfragen für Post werden scoped werden , um type - Spalten mit 'Post' . unscoped wird dies jedoch zusammen mit Ihrem eigenen default_scope . Wenn Sie also unscoped verwenden, unscoped Sie dies ebenfalls berücksichtigen.

Post.unscoped.where(type: 'Post').order(updated_at: :desc)

unscoped und Model Associations

Betrachten Sie eine Beziehung zwischen Post und User

class Post < ApplicationRecord
  belongs_to :user
  default_scope ->{ where(published: true).order(created_at: :desc) }
end

class User < ApplicationRecord
  has_many :posts
end

Indem Sie einen einzelnen User , können Sie die dazugehörigen Beiträge sehen:

user = User.find(1)
user.posts
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' AND "posts"."user_id" = ? ORDER BY "posts"."created_at" DESC [["user_id", 1]]

Sie möchten jedoch den default_scope aus der posts Relation unscoped , sodass Sie nicht mit einem Bereich versehene unscoped

user.posts.unscoped
SELECT "posts".* FROM "posts"

Dadurch werden die user_id Bedingung sowie der default_scope .

Ein Anwendungsbeispiel für default_scope

Trotzdem gibt es Situationen, in denen die Verwendung von default_scope vertretbar ist.

Stellen Sie sich ein Mehrmandanten-System vor, bei dem mehrere Subdomains von derselben Anwendung aus mit isolierten Daten bedient werden. Eine Möglichkeit, diese Isolation zu erreichen, ist default_scope . Die Nachteile in anderen Fällen werden hier zu einem Nachteil.

class ApplicationRecord < ActiveRecord::Base
  def self.inherited(subclass)
    super

    return unless subclass.superclass == self
    return unless subclass.column_names.include? 'tenant_id'

    subclass.class_eval do
      default_scope ->{ where(tenant_id: Tenant.current_id) }
    end
  end
end

Alles, was Sie tun müssen, ist, Tenant.current_id zu einem frühen Tenant.current_id in der Anforderung Tenant.current_id , und jede Tabelle, die tenant_id enthält, wird automatisch ohne zusätzlichen Code festgelegt. Durch das Instanziieren von Datensätzen wird automatisch die Mandanten-ID übernommen, unter der sie erstellt wurden.

Das Wichtigste an diesem Anwendungsfall ist, dass der Gültigkeitsbereich einmal pro Anforderung festgelegt wird und sich nicht ändert. Die einzigen Fälle , die Sie benötigen unscoped hier sind Spezialfälle wie Hintergrund Arbeitnehmer , die außerhalb eines Ersuchens Umfang ausgeführt werden .

Du wirst es nicht brauchen (YAGNI)

Wenn Sie über ein Feature „YAGNI“ (Sie werden es nicht brauchen) sagen können, sollten Sie es besser nicht implementieren. Durch die Fokussierung auf Einfachheit kann viel Entwicklungszeit eingespart werden. Die ohnehin vorhandene Implementierung solcher Funktionen kann zu Problemen führen:

Probleme

Übertechnik

Wenn ein Produkt komplizierter ist als es sein muss, ist es überentwickelt. Normalerweise werden diese "noch nicht verwendeten" Funktionen niemals in der vorgesehenen Art und Weise verwendet, in der sie geschrieben wurden, und müssen umgestaltet werden, wenn sie jemals verwendet werden. Vorzeitige Optimierungen, insbesondere Leistungsoptimierungen, führen häufig zu Designentscheidungen, die sich in der Zukunft als falsch herausstellen.

Code aufgebläht

Code Bloat bedeutet unnötig komplizierten Code. Dies kann beispielsweise durch Abstraktion, Redundanz oder fehlerhafte Anwendung von Entwurfsmustern geschehen. Die Codebasis wird schwer verständlich, verwirrend und teuer in der Wartung.

Feature Kriechen

Feature Creep bezieht sich auf das Hinzufügen neuer Features, die über die Kernfunktionalität des Produkts hinausgehen und zu einer unnötig hohen Komplexität des Produkts führen.

Lange Entwicklungszeit

Die Zeit, die zum Entwickeln notwendiger Merkmale verwendet werden könnte, wird aufgewendet, um nicht benötigte Merkmale zu entwickeln. Die Lieferung dauert länger.

Lösungen

KISS - Mach es einfach, dumm

KISS zufolge funktionieren die meisten Systeme am besten, wenn sie einfach gestaltet sind. Einfachheit sollte ein vorrangiges Designziel sein, um die Komplexität zu reduzieren. Dies kann erreicht werden, indem zum Beispiel das Prinzip der Einzelverantwortung befolgt wird.

YAGNI - Du wirst es nicht brauchen

Weniger ist mehr. Denken Sie über jedes Feature nach, ist es wirklich nötig? Wenn Sie sich vorstellen können, dass es sich um YAGNI handelt, lassen Sie es weg. Es ist besser, es zu entwickeln, wenn es benötigt wird.

Kontinuierliches Refactoring

Das Produkt wird ständig verbessert. Mit Refactoring können wir sicherstellen, dass das Produkt nach bester Vorgehensweise erstellt wird und nicht zu einer Patch-Arbeit degeneriert.

Domänenobjekte (keine fetten Modelle)

"Fat Model, Skinny Controller" ist ein sehr guter erster Schritt, skaliert jedoch nicht, sobald Ihre Codebase zu wachsen beginnt.

Denken wir über die Einzelverantwortung von Modellen nach. Was ist die alleinige Verantwortung von Modellen? Soll es Geschäftslogik geben? Soll es eine nicht antwortbezogene Logik geben?

Nein. Ihre Verantwortung besteht darin, mit der Persistenzschicht und ihrer Abstraktion umzugehen.

Geschäftslogik sowie jede nicht antwortbezogene Logik und nicht persistente Logik sollte in Domänenobjekte verankert sein.

Domänenobjekte sind Klassen, die nur eine Verantwortung in der Domäne des Problems haben. Lassen Sie Ihre Klassen für die von ihnen gelösten Probleme " Scream Their Architecture ".

In der Praxis sollten Sie nach dünnen Modellen, dünnen Ansichten und dünnen Controllern streben. Die Architektur Ihrer Lösung sollte nicht durch das von Ihnen gewählte Framework beeinflusst werden.

Zum Beispiel

Angenommen, Sie sind ein Marktplatz, der Ihren Kunden eine feste Provision von 15% über Stripe berechnet. Wenn Sie eine feste Provision von 15% berechnen, ändert sich Ihre Provision abhängig von der Höhe der Bestellung, da Stripe 2,9% + 30 ¢ berechnet.

Der Betrag, den Sie als Provision berechnen, sollte amount*0.15 - (amount*0.029 + 0.30) : amount*0.15 - (amount*0.029 + 0.30) .

Schreiben Sie diese Logik nicht in das Modell:

# app/models/order.rb
class Order < ActiveRecord::Base
  SERVICE_COMMISSION = 0.15
  STRIPE_PERCENTAGE_COMMISSION = 0.029
  STRIPE_FIXED_COMMISSION = 0.30

  ...

  def commission
    amount*SERVICE_COMMISSION - stripe_commission  
  end

  private

  def stripe_commission
    amount*STRIPE_PERCENTAGE_COMMISSION + STRIPE_FIXED_COMMISSION
  end
end

Sobald Sie eine neue Zahlungsmethode integriert haben, können Sie diese Funktionalität innerhalb dieses Modells nicht skalieren.

Sobald Sie beginnen, mehr Geschäftslogik zu integrieren, verliert Ihr Order Objekt den Zusammenhalt .

Bevorzugen Sie Domain-Objekte, wobei die Berechnung der Provision vollständig von der Verantwortung für persistierende Aufträge abhängt:

# app/models/order.rb
class Order < ActiveRecord::Base
  ...
  # No reference to commission calculation
end

# lib/commission.rb
class Commission
  SERVICE_COMMISSION = 0.15

  def self.calculate(payment_method, model)
    model.amount*SERVICE_COMMISSION - payment_commission(payment_method, model)  
  end

  private

  def self.payment_commission(payment_method, model)
    # There are better ways to implement a static registry,
    # this is only for illustration purposes.
    Object.const_get("#{payment_method}Commission").calculate(model)
  end
end

# lib/stripe_commission.rb
class StripeCommission
  STRIPE_PERCENTAGE_COMMISSION = 0.029
  STRIPE_FIXED_COMMISSION = 0.30

  def self.calculate(model)
    model.amount*STRIPE_PERCENTAGE_COMMISSION
      + STRIPE_PERCENTAGE_COMMISSION
  end
end

# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)
    @order.commission = Commission.calculate("Stripe", @order)
    ...
  end
end

Die Verwendung von Domänenobjekten hat die folgenden architektonischen Vorteile:

  • Der Komponententest ist extrem einfach, da keine Fixtures oder Factorys erforderlich sind, um die Objekte mit der Logik zu instanziieren.
  • arbeitet mit allem, was die Meldung akzeptiert amount .
  • hält jedes Domänenobjekt klein, mit klar definierten Verantwortlichkeiten und höherer Kohäsion.
  • Skaliert leicht mit neuen Zahlungsmethoden durch Hinzufügen, nicht Modifizieren .
  • stoppt die Tendenz, in jeder Ruby on Rails-Anwendung ein ständig wachsendes User zu haben.

Ich persönlich mag Domänenobjekte in lib . Wenn Sie dies tun, denken Sie daran, es zu autoload_paths hinzuzufügen:

# config/application.rb
config.autoload_paths << Rails.root.join('lib')

Sie können auch bevorzugen, Domänenobjekte nach dem Befehls- / Abfragemuster aktionsorientierter zu erstellen. In einem solchen Fall empfiehlt es sich, diese Objekte in app/commands zu platzieren, da alle app Unterverzeichnisse automatisch zum Autoload-Pfad hinzugefügt werden.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow