Ruby on Rails: ¿Dónde definir constantes globales?

213

Recién estoy comenzando con mi primera aplicación web Ruby on Rails. Tengo un montón de diferentes modelos, vistas, controladores, etc.

Quiero encontrar un buen lugar para pegar definiciones de constantes verdaderamente globales, que se apliquen a toda mi aplicación. En particular, se aplican tanto en la lógica de mis modelos como en las decisiones tomadas en mis puntos de vista. No puedo encontrar ningún lugar SECO para poner estas definiciones donde estén disponibles tanto para todos mis modelos como para todas mis vistas.

Para tomar un ejemplo específico, quiero una constante COLOURS = ['white', 'blue', 'black', 'red', 'green']. Esto se usa en todo el lugar, tanto en modelos como en vistas. ¿Dónde puedo definirlo en un solo lugar para que sea accesible?

Lo que probé:

  • Variables de clase constante en el archivo model.rb con las que están más asociadas, como @@COLOURS = [...]. Pero no pude encontrar una forma sensata de definirlo para poder escribir en mis puntos de vista en Card.COLOURSlugar de algo muy grosero Card.first.COLOURS.
  • Un método en el modelo, algo así como el def colours ['white',...] endmismo problema.
  • Un método en application_helper.rb: esto es lo que estoy haciendo hasta ahora, pero los ayudantes solo son accesibles en vistas, no en modelos
  • Creo que podría haber intentado algo en application.rb o environment.rb, pero eso no parece correcto (y tampoco parece funcionar)

¿No hay forma de definir algo que sea accesible tanto desde los modelos como desde las vistas? Quiero decir, sé que los modelos y las vistas deben estar separados, pero seguramente en algunos dominios habrá ocasiones en que necesiten referirse al mismo conocimiento específico del dominio.

AlexC
fuente
Aprecio que esto sea REALMENTE tarde, pero para otros lectores me pregunto por qué no los definió en su modelo y utilizó sus controladores para pasarlos a sus puntos de vista. De esta manera, tendría una separación más clara de las preocupaciones, en lugar de crear dependencias entre el controlador / vista Y el modelo / vista.
Tom Tom
2
@TomTom: ¿Pasa estas constantes en cada vista y ayuda que las necesita? En otras palabras, ¿hace que el controlador sepa qué vistas necesitan qué constantes? Eso suena más como una violación de MVC.
AlexC

Respuestas:

228

Si su modelo es realmente "responsable" de las constantes, debe pegarlas allí. Puede crear métodos de clase para acceder a ellos sin crear una nueva instancia de objeto:

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

# accessible like this
Card.colours

Alternativamente, puede crear variables de clase y un descriptor de acceso. Sin embargo, esto se desaconseja, ya que las variables de clase pueden ser algo sorprendentes con la herencia y en entornos de subprocesos múltiples.

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

# accessible the same as above

Las dos opciones anteriores le permiten cambiar la matriz devuelta en cada invocación del método de acceso si es necesario. Si tiene una constante verdaderamente inmutable, también puede definirla en la clase de modelo:

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

# accessible as
Card::COLOURS

También podría crear constantes globales a las que se pueda acceder desde cualquier lugar en un inicializador como en el siguiente ejemplo. Este es probablemente el mejor lugar, si sus colores son realmente globales y se usan en más de un contexto de modelo.

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

Nota: cuando definimos las constantes anteriores, a menudo queremos freezela matriz. Eso evita que otro código modifique más tarde (sin darse cuenta) la matriz, por ejemplo, agregando un nuevo elemento. Una vez que un objeto está congelado, ya no se puede cambiar.

Holger Just
fuente
1
Muchas gracias. Parece que me faltaba el ruby ​​class-fu para definir los métodos de clase. Pero en realidad me gusta la opción de inicializador en este caso, porque los colores se usan en múltiples modelos y vistas. ¡Muchas gracias!
AlexC
21
Si va por la config/initializers/my_constants.rbruta, recuerde reiniciar el servidor:touch tmp/restart.txt
user664833
44
El def self.coloursejemplo no es ideal. Cada vez que llame def self.colours, se devolverá una nueva instancia de la matriz . #freezeNo ayudará en este caso. La mejor práctica es declararlo como una constante de Ruby, en cuyo caso siempre obtendrá el mismo objeto.
Zabba
@Zabba Si la asignación de una sola matriz hace una diferencia notable para su aplicación, probablemente no debería usar Ruby en primer lugar ... Dicho esto, usar un método y devolver una matriz completamente nueva cada vez puede tener un par Ventajas: (1) es lo más cercano que puede obtener hacia objetos inmutables en el límite de su clase en Ruby y (2) mantiene una interfaz uniforme en su clase con la posibilidad de adaptar el valor de retorno más tarde en función del estado inherente (por ejemplo, por leer los colores del DB) sin cambiar la interfaz.
Holger Solo
@Holger Simplemente, al menos uno de sus objetivos todavía se puede lograr usando una constante: class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endDicho esto, la asignación de una matriz en cualquier idioma puede ser potencialmente problemática; por un lado, está usando memoria sin ninguna razón (buena). Si se carga desde una base de datos y desea almacenar en caché el valor, también se puede usar una variable de instancia de clase, que se puede cargar de forma diferida mediante el def self.coloursmétodo. Sin embargo, coincidimos en el aspecto de la inmutabilidad.
Zabba
70

Algunas opciones:

Usando una constante:

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

Carga diferida usando la variable de instancia de clase:

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

Si es una constante verdaderamente global ( sin embargo, evite las constantes globales de esta naturaleza ), también podría considerar poner una constante de nivel superior, config/initializers/my_constants.rbpor ejemplo.

Zabba
fuente
1
Je Comentario justo: error de sintaxis al escribir desde memoria mi ejemplo :) ¡Gracias por el consejo!
AlexC
2
Luego extendel módulo en la clase para que esté disponible con Card.COLOURS.
undefinedvariable
Cuando uso extendno funciona para mí. Al usar includepuedo acceder como:Card::COLOURS
Abhi
Definitivamente NO debes colocar esto debajo /models. Es mucho mejor si crea un inicializador.
linkyndy
@linkyndy Diría que está bien ponerlo debajo /models, pero solo si está dentro de un módulo, por ejemplo, module Constants; COLOURS = ...; enden un archivo llamado models/constants.rb.
Kelvin
57

A partir de Rails 4.2, puede usar la config.xpropiedad:

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

Que estará disponible como:

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

Otro método para cargar la configuración personalizada:

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

En los rieles 5 y 6 , puede usar el configurationobjeto directamente para la configuración personalizada, además de config.x. Sin embargo, solo se puede usar para configuraciones no anidadas:

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

Estará disponible como:

Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Halil Özgür
fuente
2
Me gusta Rails.configuration.coloursmás (aunque desearía que no fuera tanto)
Tom Rossi
@TomRossi Estoy de acuerdo, por ejemplo, configes tan bueno como configuration. Podríamos esperar obtener un atajo en algún momento :)
Halil Özgür
¿sigue siendo la mejor manera en los rieles 6 para definir constantes que se compartirán entre múltiples controladores? ¡gracias por la respuesta!
Crashalot
@Crashalot Todavía aparece en la lista de documentos. "El mejor"? Depende. Puede estar en su ancestro común. O en ApplicationControllersi no hay nada más en el medio. Si la constante no está directamente relacionada con los controladores, todavía consideraría una configuración global, etc.
Halil Özgür hace
18

Si se necesita una constante en más de una clase, la pongo en config / initializers / contant.rb siempre en mayúsculas (la lista de estados a continuación está truncada).

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

Están disponibles en toda la aplicación, excepto en el código del modelo como tal:

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

Para usar la constante en un modelo, use attr_accessor para hacer que la constante esté 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
fuente
1
bueno, config/initializers/constants.rbprobablemente sería una mejor opción, sin embargo
Adit Saxena
También uso esto, pero recientemente me encontré con el problema de que estas constantes no son accesibles en application.rb
Zia Ul Rehman Mughal
mis constantes funcionaban pero se detuvieron por alguna razón (como de alguna manera mi archivo se movió fuera de los inicializadores). Después de verificar esta respuesta, los miré de cerca y los moví hacia atrás y ahora estoy trabajando. Gracias
Muhammad Nasir Shamshad
No creo que se necesite attr_accessor. ¿Estás hablando de alguna versión particular de Rails?
Mayuresh Srivastava
16

Para la configuración de toda la aplicación y para las constantes globales, recomiendo usar Settingslogic . Esta configuración se almacena en un archivo YML y se puede acceder desde modelos, vistas y controladores. Además, puede crear diferentes configuraciones para todos sus entornos:

  # 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

En algún lugar de la vista (prefiero los métodos de ayuda para este tipo de cosas) o en un modelo que puede obtener, por ejemplo, una variedad de colores Settings.colors.split(/\s/). Es muy flexible. Y no necesitas inventar una bicicleta.

Voldy
fuente
7

Utiliza un método de clase:

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

Luego Model.coloursdevolverá esa matriz. Alternativamente, cree un inicializador y ajuste las constantes en un módulo para evitar conflictos de espacio de nombres.

Steve Ross
fuente
4

Intente mantener todo constante en un solo lugar. En mi aplicación, he creado una carpeta de constantes dentro de los inicializadores de la siguiente manera:

ingrese la descripción de la imagen aquí

y generalmente mantengo todo constante en estos archivos.

En su caso, puede crear un archivo en la carpeta de constantes como colors_constant.rb

colors_constant.rb

ingrese la descripción de la imagen aquí

No olvides reiniciar el servidor

Ghanshyam Anand
fuente
1
Esta es la mejor respuesta que encontré aquí. Gracias.
Promesa Preston
3

Otra opción, si desea definir sus constantes en un lugar:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Pero aún así hacerlos visibles globalmente sin tener que acceder a ellos de manera totalmente calificada:

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

Un lugar común para colocar constantes globales de toda la aplicación es dentro 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
fuente
2

Normalmente tengo un modelo / tabla de 'búsqueda' en mi programa rails y lo uso para las constantes. Es muy útil si las constantes van a ser diferentes para diferentes entornos. Además, si tiene un plan para extenderlos, digamos que desea agregar 'amarillo' en una fecha posterior, simplemente puede agregar una nueva fila a la tabla de búsqueda y listo.

Si otorga permisos de administrador para modificar esta tabla, no acudirán a usted para realizar tareas de mantenimiento. :) SECO.

Así es como se ve mi código de migración:

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

Yo uso seeds.rb para rellenarlo previamente.

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

La variable global debe declararse en el config/initializersdirectorio

COLOURS = %w(white blue black red green)
pájaro
fuente
¡Gracias! Otros ya han mencionado esto. Es la última línea de la respuesta de Holger, y Zabba también menciona esta técnica, aunque Zabba advierte en contra.
AlexC
0

De acuerdo con su condición, también puede definir algunas variables ambientales y buscarlas ENV['some-var']en código ruby, esta solución puede no ser adecuada para usted, pero espero que pueda ayudar a otros.

Ejemplo: se pueden crear diferentes archivos .development_env, .production_env, .test_envy cargarlo según sus entornos de aplicaciones, marque esta generación dotenv-carriles que automatizan esto para su.

fangxing
fuente