Ruby on Rails: où définir les constantes globales?

213

Je commence tout juste ma première webapp Ruby on Rails. J'ai un tas de différents modèles, vues, contrôleurs, etc.

Je veux trouver un bon endroit pour coller des définitions de constantes véritablement mondiales, qui s'appliquent à l'ensemble de mon application. En particulier, ils s'appliquent à la fois dans la logique de mes modèles et dans les décisions prises selon mes vues. Je ne trouve aucun endroit SEC pour mettre ces définitions là où elles sont disponibles à la fois pour tous mes modèles et aussi dans toutes mes vues.

Pour prendre un exemple spécifique, je veux une constante COLOURS = ['white', 'blue', 'black', 'red', 'green']. Ceci est utilisé partout, dans les modèles et les vues. Où puis-je le définir en un seul endroit pour qu'il soit accessible?

Ce que j'ai essayé:

  • Variables de classe constantes dans le fichier model.rb auxquelles elles sont le plus associées, telles que @@COLOURS = [...]. Mais je ne pouvais pas trouver une manière saine de le définir afin que je puisse écrire dans mes vues Card.COLOURSplutôt que quelque chose de maladroit Card.first.COLOURS.
  • Une méthode sur le modèle, quelque chose comme def colours ['white',...] end- même problème.
  • Une méthode dans application_helper.rb - c'est ce que je fais jusqu'à présent, mais les assistants ne sont accessibles que dans les vues, pas dans les modèles
  • Je pense que j'ai peut-être essayé quelque chose dans application.rb ou environment.rb, mais ceux-ci ne semblent pas vraiment corrects (et ils ne semblent pas fonctionner non plus)

N'y a-t-il simplement aucun moyen de définir quoi que ce soit pour être accessible à la fois à partir de modèles et de vues? Je veux dire, je sais que les modèles et les vues doivent être séparés, mais dans certains domaines, il y aura sûrement des fois où ils devront se référer aux mêmes connaissances spécifiques au domaine?

AlexC
la source
J'apprécie que cela soit VRAIMENT tard, mais pour les autres lecteurs, je me demande pourquoi vous ne les avez pas simplement définis dans votre modèle et utilisez vos contrôleurs pour les transmettre à vos vues. De cette façon, vous auriez une séparation plus nette des préoccupations - plutôt que de créer des dépendances entre contrôleur / vue ET modèle / vue.
Tom Tom
2
@TomTom: passez ces constantes dans chaque vue et aide qui en a besoin? En d'autres termes, indiquez au contrôleur quelles vues nécessitent quelles constantes? Cela ressemble plus à une violation de MVC.
AlexC

Réponses:

229

Si votre modèle est vraiment «responsable» des constantes, vous devez les y coller. Vous pouvez créer des méthodes de classe pour y accéder sans créer de nouvelle instance d'objet:

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

# accessible like this
Card.colours

Alternativement, vous pouvez créer des variables de classe et un accesseur. Ceci est cependant déconseillé car les variables de classe peuvent sembler surprenantes avec l'héritage et dans les environnements multi-thread.

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

# accessible the same as above

Les deux options ci-dessus vous permettent de modifier le tableau renvoyé à chaque appel de la méthode d'accesseur si nécessaire. Si vous avez une vraie constante vraiment immuable, vous pouvez également la définir sur la classe de modèle:

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

# accessible as
Card::COLOURS

Vous pouvez également créer des constantes globales accessibles de partout dans un initialiseur comme dans l'exemple suivant. C'est probablement le meilleur endroit si vos couleurs sont vraiment globales et utilisées dans plus d'un contexte de modèle.

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

Remarque: lorsque nous définissons des constantes ci-dessus, nous voulons souvent freezele tableau. Cela empêche un autre code de modifier ultérieurement (par inadvertance) le tableau en ajoutant par exemple un nouvel élément. Une fois qu'un objet est gelé, il ne peut plus être modifié.

Holger Just
la source
1
Merci beaucoup. On dirait qu'il me manquait la classe Ruby-fu pour définir les méthodes de classe. Mais j'aime en fait l'option d'initialisation dans ce cas, car les couleurs sont utilisées dans plusieurs modèles et vues. Merci beaucoup!
AlexC
21
Si config/initializers/my_constants.rbvous touch tmp/restart.txt
suivez
4
L' def self.coloursexemple n'est pas idéal. Chaque fois que vous appelez def self.colours, une nouvelle instance du tableau sera retournée . #freezen'aidera pas dans ce cas. La meilleure pratique consiste à le déclarer en tant que constante Ruby, auquel cas vous récupérerez toujours le même objet.
Zabba
@Zabba Si l'allocation d'un seul tableau fait une différence notable pour votre application, vous ne devriez probablement pas utiliser Ruby en premier lieu ... Cela dit, utiliser une méthode et renvoyer un tableau complètement nouveau à chaque fois peut avoir un couple des avantages: (1) c'est la chose la plus proche que vous pouvez obtenir vers des objets immuables sur votre frontière de classe dans Ruby et (2) vous gardez une interface uniforme sur votre classe avec la possibilité d'adapter la valeur de retour plus tard en fonction de l'état inhérent (par exemple en lecture des couleurs de la BD) sans changer d’interface.
Holger Just
@Holger Just, au moins un de vos objectifs peut toujours être atteint en utilisant une constante: class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endCela dit, l'allocation d'un tableau dans n'importe quelle langue peut être potentiellement problématique; d'une part, il utilise la mémoire sans (bonne) raison. Si vous chargez à partir d'une base de données et que vous souhaitez mettre en cache la valeur, vous pouvez également utiliser une variable d'instance de classe, qui peut être chargée paresseusement à l'aide de la def self.coloursméthode. Je suis d'accord sur l'aspect immuabilité.
Zabba
70

Quelques options:

En utilisant une constante:

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

Lazy chargé à l'aide de la variable d'instance de classe:

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

S'il s'agit d'une constante véritablement globale ( évitez cependant les constantes globales de cette nature ), vous pouvez également envisager de mettre une constante de niveau supérieur config/initializers/my_constants.rbpar exemple.

Zabba
la source
1
Il h. Commentaire correct - erreur de syntaxe lors de la frappe à partir de la mémoire mon exemple :) Merci pour le conseil!
AlexC
2
Ensuite, extendle module dans la classe pour qu'il soit disponible avec Card.COLOURS.
undefinedvariable
Lorsque j'utilise extendson ne fonctionne pas pour moi. Lors de l'utilisation, includeje peux accéder comme:Card::COLOURS
Abhi
Vous ne devriez certainement PAS placer cela sous /models. C'est beaucoup mieux si vous créez un initialiseur.
linkyndy
@linkyndy Je dirais que c'est bien de le mettre sous /models, mais seulement si c'est dans un module, par exemple module Constants; COLOURS = ...; enddans un fichier appelé models/constants.rb.
Kelvin
57

Depuis Rails 4.2, vous pouvez utiliser la config.xpropriété:

# 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'

Qui sera disponible en tant que:

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

Une autre méthode de chargement de la configuration personnalisée:

# 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"

Dans Rails 5 et 6 , vous pouvez utiliser l' configurationobjet directement pour une configuration personnalisée, en plus de config.x. Cependant, il ne peut être utilisé que pour une configuration non imbriquée:

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

Il sera disponible sous la forme:

Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Halil Özgür
la source
2
J'aime Rails.configuration.coloursmieux (même si je souhaite que ce ne soit pas si long)
Tom Rossi
@TomRossi Je suis d'accord, par exemple, configc'est aussi bon que configuration. Nous pourrions espérer obtenir un raccourci à un moment donné :)
Halil Özgür
est-ce toujours le meilleur moyen dans rails 6 de définir des constantes à partager entre plusieurs contrôleurs? Merci d'avoir répondu!
Crashalot
@Crashalot Il est toujours répertorié dans la documentation. "Le meilleur"? Ça dépend. Cela peut être dans leur ancêtre commun. Ou entre ApplicationControllers'il n'y a rien d'autre entre les deux. Si la constante n'est pas directement liée aux contrôleurs, je considérerais quand même une configuration globale, etc.
Halil Özgür
@ HalilÖzgür merci pour la réponse. comment définissez-vous les constantes dans un ancêtre commun?
Crashalot
18

Si une constante est nécessaire dans plus d'une classe, je la mets dans config / initializers / contant.rb toujours en majuscules (la liste des états ci-dessous est tronquée).

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

Ils sont disponibles dans toute l'application, sauf dans le code modèle en tant que tel:

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

Pour utiliser la constante dans un modèle, utilisez attr_accessor pour rendre la constante disponible.

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end
Hank Snow
la source
1
bien, ce config/initializers/constants.rbserait probablement un meilleur choix
Adit Saxena
J'utilise également cela, mais j'ai récemment rencontré le problème que ces constantes ne sont pas accessibles dans application.rb
Zia Ul Rehman Mughal
mes constantes fonctionnaient mais se sont arrêtées pour une raison quelconque (comme en quelque sorte mon fichier a quitté les initialiseurs). Après avoir vérifié cette réponse, je les ai regardées attentivement et les ai reculées et maintenant je travaille. Merci
Muhammad Nasir Shamshad
Je ne pense pas que attr_accessor soit nécessaire. Parlez-vous d'une version particulière de Rails?
Mayuresh Srivastava
16

Pour les paramètres à l'échelle de l'application et pour les constantes globales, je recommande d'utiliser Settingslogic . Ces paramètres sont stockés dans un fichier YML et sont accessibles à partir de modèles, de vues et de contrôleurs. De plus, vous pouvez créer différents paramètres pour tous vos environnements:

  # 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

Quelque part dans la vue (je préfère les méthodes d'assistance pour ce genre de choses) ou dans un modèle, vous pouvez obtenir, par exemple, un tableau de couleurs Settings.colors.split(/\s/). C'est très flexible. Et vous n'avez pas besoin d'inventer un vélo.

Voldy
la source
7

Utilisez une méthode de classe:

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

Ensuite Model.coloursretournera ce tableau. Vous pouvez également créer un initialiseur et encapsuler les constantes dans un module pour éviter les conflits d'espace de noms.

Steve Ross
la source
4

Essayez de garder tous les constants à un seul endroit, dans mon application, j'ai créé un dossier de constantes à l'intérieur des initialiseurs comme suit:

entrez la description de l'image ici

et je garde généralement tout constant dans ces fichiers.

Dans votre cas, vous pouvez créer un fichier sous le dossier des constantes comme colors_constant.rb

colours_constant.rb

entrez la description de l'image ici

N'oubliez pas de redémarrer le serveur

Ghanshyam Anand
la source
1
C'est la meilleure réponse que j'ai trouvée ici. Je vous remercie.
Promise Preston
3

Une autre option, si vous souhaitez définir vos constantes en un seul endroit:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Mais toujours les rendre visibles à l'échelle mondiale sans avoir à y accéder de manière entièrement qualifiée:

DSL::Constants::MY_CONSTANT # => 1
MY_CONSTANT # => NameError: uninitialized constant MY_CONSTANT
Object.instance_eval { include DSL::Constants }
MY_CONSTANT # => 1
wincent
la source
3

Un endroit commun pour mettre des constantes globales à l'échelle de l'application est à l'intérieur 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
la source
2

J'ai généralement un modèle / table de «recherche» dans mon programme de rails et je l'utilise pour les constantes. C'est très utile si les constantes vont être différentes pour différents environnements. En outre, si vous avez l'intention de les étendre, par exemple si vous souhaitez ajouter «jaune» à une date ultérieure, vous pouvez simplement ajouter une nouvelle ligne à la table de recherche et en finir avec elle.

Si vous accordez aux administrateurs les autorisations pour modifier ce tableau, ils ne vous seront pas envoyés pour la maintenance. :) SEC.

Voici à quoi ressemble mon code de migration:

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

J'utilise seeds.rb pour le préremplir.

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

La variable globale doit être déclarée dans le config/initializersrépertoire

COLOURS = %w(white blue black red green)
oiseau
la source
Merci! D'autres l'ont déjà mentionné. C'est la dernière ligne de la réponse de Holger, et Zabba mentionne également cette technique, bien que Zabba le prévienne.
AlexC
0

Selon votre état, vous pouvez également définir certaines variables environnementales et les récupérer via ENV['some-var']du code rubis, cette solution peut ne pas vous convenir, mais j'espère que cela peut aider les autres.

Exemple: vous pouvez créer des fichiers différents .development_env, .production_env, .test_envet le charger selon vos environnements d'application, vérifiez ce gen dotenv rails qui automatisent ce pour votre.

fangxing
la source