Ruby on Rails: Wo werden globale Konstanten definiert?

213

Ich fange gerade mit meiner ersten Ruby on Rails-Webanwendung an. Ich habe eine Reihe verschiedener Modelle, Ansichten, Controller und so weiter.

Ich möchte einen guten Ort finden, um Definitionen von wirklich globalen Konstanten festzuhalten, die für meine gesamte App gelten. Insbesondere gelten sie sowohl für die Logik meiner Modelle als auch für die Entscheidungen, die in meinen Ansichten getroffen wurden. Ich kann keinen trockenen Ort finden, an dem diese Definitionen sowohl für alle meine Modelle als auch für alle meine Ansichten verfügbar sind .

Um ein bestimmtes Beispiel zu nennen, möchte ich eine Konstante COLOURS = ['white', 'blue', 'black', 'red', 'green']. Dies wird überall verwendet, sowohl in Modellen als auch in Ansichten. Wo kann ich es an nur einem Ort definieren, damit es zugänglich ist?

Was ich versucht habe:

  • Konstante Klassenvariablen in der Datei model.rb, mit denen sie am meisten verknüpft sind, z @@COLOURS = [...]. Aber ich konnte keinen vernünftigen Weg finden, es so zu definieren, dass ich in meine Ansichten schreiben kann, Card.COLOURSanstatt etwas Klobiges wie Card.first.COLOURS.
  • Eine Methode am Modell, so etwas wie def colours ['white',...] end- das gleiche Problem.
  • Eine Methode in application_helper.rb - das mache ich bisher, aber auf die Helfer kann nur in Ansichten zugegriffen werden, nicht in Modellen
  • Ich glaube, ich habe etwas in application.rb oder environment.rb ausprobiert, aber diese scheinen nicht wirklich richtig zu sein (und sie scheinen auch nicht zu funktionieren).

Gibt es einfach keine Möglichkeit, etwas zu definieren, auf das sowohl von Modellen als auch von Ansichten aus zugegriffen werden kann? Ich meine, ich weiß, dass Modelle und Ansichten getrennt sein sollten, aber in einigen Bereichen wird es sicherlich Zeiten geben, in denen sie auf dasselbe domänenspezifische Wissen verweisen müssen.

AlexC
quelle
Ich weiß zu schätzen, dass dies WIRKLICH spät ist, aber für andere Leser frage ich mich, warum Sie sie nicht einfach in Ihrem Modell definiert und Ihre Controller verwendet haben, um sie an Ihre Ansichten weiterzugeben. Auf diese Weise haben Sie eine sauberere Trennung von Bedenken - anstatt Abhängigkeiten zwischen Controller / Ansicht UND Modell / Ansicht zu erstellen.
Tom Tom
2
@ TomTom: Übergeben Sie diese Konstanten an jede Ansicht und jeden Helfer, der sie benötigt? Mit anderen Worten, machen Sie den Controller darauf aufmerksam, welche Ansichten welche Konstanten benötigen. Das klingt eher nach einer Verletzung von MVC.
AlexC

Antworten:

228

Wenn Ihr Modell wirklich für die Konstanten "verantwortlich" ist, sollten Sie sie dort anbringen. Sie können Klassenmethoden erstellen, um auf sie zuzugreifen, ohne eine neue Objektinstanz zu erstellen:

class Card < ActiveRecord::Base
  def self.colours
    ['white', 'blue']
  end
end

# accessible like this
Card.colours

Alternativ können Sie Klassenvariablen und einen Accessor erstellen. Dies wird jedoch nicht empfohlen, da Klassenvariablen bei der Vererbung und in Multithread-Umgebungen möglicherweise überraschend wirken.

class Card < ActiveRecord::Base
  @@colours = ['white', 'blue']
  cattr_reader :colours
end

# accessible the same as above

Mit den beiden oben genannten Optionen können Sie das zurückgegebene Array bei jedem Aufruf der Accessor-Methode bei Bedarf ändern. Wenn Sie eine wirklich unveränderliche Konstante haben, können Sie diese auch für die Modellklasse definieren:

class Card < ActiveRecord::Base
  COLOURS = ['white', 'blue'].freeze
end

# accessible as
Card::COLOURS

Sie können auch globale Konstanten erstellen, auf die von überall in einem Initialisierer wie im folgenden Beispiel zugegriffen werden kann. Dies ist wahrscheinlich der beste Ort, wenn Ihre Farben wirklich global sind und in mehr als einem Modellkontext verwendet werden.

# put this into config/initializers/my_constants.rb
COLOURS = ['white', 'blue'].freeze

Hinweis: Wenn wir oben Konstanten definieren, möchten wir häufig freezedas Array. Dies verhindert, dass anderer Code das Array später (versehentlich) ändert, indem beispielsweise ein neues Element hinzugefügt wird. Sobald ein Objekt eingefroren ist, kann es nicht mehr geändert werden.

Holger Just
quelle
1
Vielen Dank. Sieht so aus, als hätte ich das Ruby-Klassen-Fu vermisst, um die Klassenmethoden zu definieren. In diesem Fall gefällt mir jedoch die Initialisierungsoption, da die Farben in mehreren Modellen und Ansichten verwendet werden. Danke vielmals!
AlexC
21
Wenn Sie die config/initializers/my_constants.rbRoute gehen, denken Sie daran, den Server neu zu starten:touch tmp/restart.txt
user664833
4
Das def self.coloursBeispiel ist nicht ideal. Bei jedem Aufruf def self.colourswird eine neue Instanz des Arrays zurückgegeben . #freezewird in diesem Fall nicht helfen. Es wird empfohlen, es als Ruby-Konstante zu deklarieren. In diesem Fall erhalten Sie immer dasselbe Objekt zurück.
Zabba
@Zabba Wenn die Zuweisung eines einzelnen Arrays für Ihre App einen spürbaren Unterschied macht, sollten Sie Ruby wahrscheinlich gar nicht erst verwenden ... Das heißt, die Verwendung einer Methode und die Rückgabe eines völlig neuen Arrays kann jedes Mal ein paar haben Vorteile: (1) Es ist das Nächste, was Sie an unveränderlichen Objekten an Ihrer Klassengrenze in Ruby erreichen können, und (2) Sie behalten eine einheitliche Schnittstelle für Ihre Klasse bei, mit der Möglichkeit, den Rückgabewert später basierend auf dem inhärenten Status anzupassen (z. B. durch Lesen der Farben aus der Datenbank), ohne die Schnittstelle zu ändern.
Holger Nur
@Holger Nur eines Ihrer Ziele kann mit einer Konstanten erreicht werden: class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endDie Zuweisung eines Arrays in einer beliebigen Sprache kann jedoch möglicherweise problematisch sein. Zum einen wird Speicher ohne (guten) Grund verwendet. Wenn Sie aus einer Datenbank laden und den Wert zwischenspeichern möchten, können Sie auch eine Klasseninstanzvariable verwenden, die mithilfe der def self.coloursMethode verzögert geladen werden kann. Einverstanden über den Aspekt der Unveränderlichkeit.
Zabba
70

Einige Optionen:

Verwenden einer Konstante:

class Card
  COLOURS = ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
end

Lazy geladen mit Klasseninstanzvariable:

class Card
  def self.colours
    @colours ||= ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
  end
end

Wenn es sich um eine wirklich globale Konstante handelt ( vermeiden Sie jedoch globale Konstanten dieser Art ), können Sie beispielsweise auch eine Konstante der obersten Ebene eingeben config/initializers/my_constants.rb.

Zabba
quelle
1
Heh. Fairer Kommentar - Syntaxfehler beim Tippen aus dem Speicher mein Beispiel :) Danke für den Tipp!
AlexC
2
Dann extenddas Modul in der Klasse, damit es mit verfügbar ist Card.COLOURS.
undefinierte
Bei Verwendung extendfunktioniert es bei mir nicht. Bei der Verwendung includekann ich wie Card::COLOURS
folgt
Sie sollten dies definitiv NICHT unter platzieren /models. Es ist viel besser, wenn Sie einen Initialisierer erstellen.
Linkyndy
@linkyndy Ich würde sagen, es ist in Ordnung, es unter zu stellen /models, aber nur, wenn es sich in einem Modul befindet, z. B. module Constants; COLOURS = ...; endin einer aufgerufenen Datei models/constants.rb.
Kelvin
56

Ab Rails 4.2 können Sie die config.xEigenschaft verwenden:

# config/application.rb (or config/custom.rb if you prefer)
config.x.colours.options = %w[white blue black red green]
config.x.colours.default = 'white'

Welches wird verfügbar sein als:

Rails.configuration.x.colours.options
# => ["white", "blue", "black", "red", "green"]
Rails.configuration.x.colours.default
# => "white"

Eine andere Methode zum Laden der benutzerdefinierten Konfiguration:

# config/colours.yml
default: &default
  options:
    - white
    - blue
    - black
    - red
    - green
  default: white
development:
  *default
production:
  *default
# config/application.rb
config.colours = config_for(:colours)
Rails.configuration.colours
# => {"options"=>["white", "blue", "black", "red", "green"], "default"=>"white"}
Rails.configuration.colours['default']
# => "white"

In den Rails 5 und 6 können Sie das configurationObjekt zusätzlich direkt für die benutzerdefinierte Konfiguration verwenden config.x. Es kann jedoch nur für nicht verschachtelte Konfigurationen verwendet werden:

# config/application.rb
config.colours = %w[white blue black red green]

Es wird verfügbar sein als:

Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Halil Özgür
quelle
1
Ich mag am Rails.configuration.coloursliebsten (obwohl ich wünschte, es wäre nicht so lange)
Tom Rossi
@ TomRossi Ich stimme zu, zB configist so gut wie configuration. Wir könnten hoffen, irgendwann eine Abkürzung zu bekommen :)
Halil Özgür
Ist dies in Schienen 6 immer noch die beste Möglichkeit, Konstanten zu definieren, die von mehreren Controllern gemeinsam genutzt werden sollen? Danke für die Antwort!
Crashalot
18

Wenn eine Konstante in mehr als einer Klasse benötigt wird, füge ich sie in config / initializers / contant.rb immer in Großbuchstaben ein (Liste der folgenden Zustände wird abgeschnitten).

STATES = ['AK', 'AL', ... 'WI', 'WV', 'WY']

Sie sind in der gesamten Anwendung verfügbar, außer im Modellcode als solcher:

    <%= form.label :states, %>
    <%= form.select :states, STATES, {} %>

Um die Konstante in einem Modell zu verwenden, verwenden Sie attr_accessor, um die Konstante verfügbar zu machen.

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end
Hank Snow
quelle
1
schön, config/initializers/constants.rbwäre wahrscheinlich eine bessere Wahl
Adit Saxena
Ich benutze dies auch, bin aber kürzlich auf das Problem gestoßen, dass diese Konstanten in application.rb
Zia Ul Rehman Mughal
Meine Konstanten funktionierten, wurden aber aus irgendeinem Grund gestoppt (da meine Datei irgendwie aus den Initialisierern verschoben wurde). Nachdem ich diese Antwort überprüft hatte, schaute ich genau hin und schob sie zurück und jetzt arbeiten. Vielen Dank
Muhammad Nasir Shamshad
Ich denke nicht, dass attr_accessor benötigt wird. Sprechen Sie über eine bestimmte Rails-Version?
Mayuresh Srivastava
16

Für anwendungsweite Einstellungen und für globale Konstanten empfehle ich die Verwendung von Settingslogic . Diese Einstellungen werden in einer YML-Datei gespeichert und können über Modelle, Ansichten und Controller aufgerufen werden. Darüber hinaus können Sie verschiedene Einstellungen für alle Ihre Umgebungen erstellen:

  # app/config/application.yml
  defaults: &defaults
    cool:
      sweet: nested settings
    neat_setting: 24
    awesome_setting: <%= "Did you know 5 + 5 = #{5 + 5}?" %>

    colors: "white blue black red green"

  development:
    <<: *defaults
    neat_setting: 800

  test:
    <<: *defaults

  production:
    <<: *defaults

Irgendwo in der Ansicht (ich bevorzuge Hilfsmethoden für solche Dinge) oder in einem Modell kann man zum Beispiel eine Reihe von Farben erhalten Settings.colors.split(/\s/). Es ist sehr flexibel. Und Sie müssen kein Fahrrad erfinden.

Voldy
quelle
7

Verwenden Sie eine Klassenmethode:

def self.colours
  ['white', 'red', 'black']
end

Dann Model.colourswird das Array zurückgegeben. Alternativ können Sie einen Initialisierer erstellen und die Konstanten in ein Modul einschließen, um Namespace-Konflikte zu vermeiden.

Steve Ross
quelle
4

Versuchen Sie, alle an einem Ort konstant zu halten. In meiner Anwendung habe ich einen Konstantenordner in den Initialisierern wie folgt erstellt:

Geben Sie hier die Bildbeschreibung ein

und ich halte normalerweise alle konstant in diesen Dateien.

In Ihrem Fall können Sie eine Datei im Konstantenordner als erstellen colors_constant.rb

colours_constant.rb

Geben Sie hier die Bildbeschreibung ein

Vergessen Sie nicht, den Server neu zu starten

Ghanshyam Anand
quelle
1
Dies ist die beste Antwort, die ich hier gefunden habe. Danke dir.
Versprich Preston den
3

Eine weitere Option, wenn Sie Ihre Konstanten an einer Stelle definieren möchten:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Machen Sie sie dennoch global sichtbar, ohne dass Sie vollständig qualifiziert darauf zugreifen müssen:

DSL::Constants::MY_CONSTANT # => 1
MY_CONSTANT # => NameError: uninitialized constant MY_CONSTANT
Object.instance_eval { include DSL::Constants }
MY_CONSTANT # => 1
zusammenzuckend
quelle
3

Ein üblicher Ort, um anwendungsweite globale Konstanten zu setzen, ist darin config/application.

module MyApp
  FOO ||= ENV.fetch('FOO', nil)
  BAR ||= %w(one two three)

  class Application < Rails::Application
    config.foo_bar = :baz
  end
end
Dennis
quelle
2

Normalerweise habe ich ein Nachschlagemodell / eine Nachschlagetabelle in meinem Rails-Programm und verwende es für die Konstanten. Es ist sehr nützlich, wenn die Konstanten für verschiedene Umgebungen unterschiedlich sein sollen. Wenn Sie planen, sie zu erweitern, beispielsweise zu einem späteren Zeitpunkt "Gelb" hinzufügen möchten, können Sie der Nachschlagetabelle einfach eine neue Zeile hinzufügen und damit fertig sein.

Wenn Sie dem Administrator die Berechtigung zum Ändern dieser Tabelle erteilen, werden diese nicht zur Wartung an Sie weitergeleitet. :) TROCKEN.

So sieht mein Migrationscode aus:

class CreateLookups < ActiveRecord::Migration
  def change
    create_table :lookups do |t|
      t.string :group_key
      t.string :lookup_key
      t.string :lookup_value
      t.timestamps
    end
  end
end

Ich benutze seeds.rb, um es vorab zu füllen.

Lookup.find_or_create_by_group_key_and_lookup_key_and_lookup_value!(group_key: 'development_COLORS', lookup_key: 'color1', lookup_value: 'red');
SriSri
quelle
1

Die globale Variable sollte im config/initializersVerzeichnis deklariert werden

COLOURS = %w(white blue black red green)
Vogel
quelle
Vielen Dank! Andere haben dies bereits erwähnt. Es ist die letzte Zeile von Holgers Antwort, und Zabba erwähnt diese Technik ebenfalls, obwohl Zabba davor warnt.
AlexC
0

Abhängig von Ihrem Zustand können Sie auch einige Umgebungsvariablen definieren und über ENV['some-var']Ruby-Code abrufen. Diese Lösung passt möglicherweise nicht zu Ihnen, aber ich hoffe, sie kann anderen helfen.

Beispiel: Sie verschiedene Dateien erstellen können .development_env, .production_env, .test_envund laden Sie es Ihre Anwendungsumgebungen nach, überprüfen Sie diese gen dotenv-Schienen , die diese für Ihre automatisieren.

Fangxing
quelle