Ruby on Rails: Onde definir constantes globais?

213

Estou apenas começando com meu primeiro aplicativo da web Ruby on Rails. Eu tenho vários modelos, visualizações, controladores e assim por diante.

Estou querendo encontrar um bom lugar para definir definições de constantes verdadeiramente globais que se apliquem a todo o meu aplicativo. Em particular, eles se aplicam tanto na lógica dos meus modelos quanto nas decisões tomadas em meus pontos de vista. Não consigo encontrar nenhum lugar DRY para colocar essas definições em que estejam disponíveis para todos os meus modelos e também para todas as minhas visualizações.

Para dar um exemplo específico, quero uma constante COLOURS = ['white', 'blue', 'black', 'red', 'green']. Isso é usado em todo o lugar, nos modelos e nas visualizações. Onde posso defini-lo em apenas um local para que seja acessível?

O que eu tentei:

  • Variáveis ​​de classe constante no arquivo model.rb às quais estão mais associadas, como @@COLOURS = [...]. Mas não consegui encontrar uma maneira sensata de defini-lo para que eu possa escrever em meus pontos de vistaCard.COLOURS vez de algo parecido com um desastre Card.first.COLOURS.
  • Um método no modelo, algo como def colours ['white',...] end - o mesmo problema.
  • Um método no application_helper.rb - é o que estou fazendo até agora, mas os auxiliares só estão acessíveis nas visualizações, não nos modelos
  • Acho que posso ter tentado algo no application.rb ou environment.rb, mas esses não parecem realmente corretos (e também não parecem funcionar)

Não existe apenas uma maneira de definir algo que possa ser acessado a partir de modelos e de visualizações? Quero dizer, eu sei que modelos e visualizações devem ser separados, mas certamente em alguns domínios haverá momentos em que eles precisarão se referir ao mesmo conhecimento específico de domínio?

AlexC
fonte
Eu aprecio que isso seja MUITO atrasado, mas para outros leitores, eu me pergunto por que você não apenas os definiu em seu modelo e usou seus controladores para transmiti-los às suas visualizações. Dessa forma, você teria uma separação mais clara das preocupações - em vez de criar dependências entre controlador / visão E modelo / visão.
Tom Tom
2
@ TomTom: Passe essas constantes para cada visualização e ajudante que precisar delas? Em outras palavras, informe o controlador sobre quais visualizações precisam de quais constantes? Isso parece mais uma violação do MVC.
AlexC #

Respostas:

228

Se o seu modelo é realmente "responsável" pelas constantes, você deve colocá-las lá. Você pode criar métodos de classe para acessá-los sem criar uma nova instância de objeto:

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

# accessible like this
Card.colours

Como alternativa, você pode criar variáveis ​​de classe e um acessador. No entanto, isso é desencorajado, pois as variáveis ​​de classe podem parecer surpreendentes com a herança e em ambientes com vários threads.

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

# accessible the same as above

As duas opções acima permitem alterar a matriz retornada em cada chamada do método acessador, se necessário. Se você tiver uma constante verdadeiramente imutável, também poderá defini-la na classe de modelo:

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

# accessible as
Card::COLOURS

Você também pode criar constantes globais acessíveis em qualquer lugar do inicializador, como no exemplo a seguir. Este é provavelmente o melhor lugar, se suas cores forem realmente globais e usadas em mais de um contexto de modelo.

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

Nota: quando definimos constantes acima, geralmente queremos freezea matriz. Isso evita que outros códigos modifiquem mais tarde (inadvertidamente) a matriz adicionando, por exemplo, um novo elemento. Depois que um objeto é congelado, ele não pode mais ser alterado.

Holger Just
fonte
1
Muito obrigado. Parece que estava faltando a classe Fu-Ruby para definir os métodos de classe. Mas, na verdade, eu gosto da opção inicializador neste caso, porque as cores são usadas em vários modelos e visualizações. Muito Obrigado!
AlexC #
21
Se seguir o config/initializers/my_constants.rbcaminho, lembre-se de reiniciar o servidor:touch tmp/restart.txt
user664833
4
O def self.coloursexemplo não é ideal. Cada vez que você chama def self.colours, uma nova instância da matriz será retornada . #freezenão vai ajudar neste caso. A melhor prática é declará-la como uma constante Ruby. Nesse caso, você sempre recuperará o mesmo objeto.
Zabba
@Zabba Se a alocação de uma única matriz fizer uma diferença notável para o seu aplicativo, você provavelmente não deveria estar usando Ruby em primeiro lugar ... Dito isso, usar um método e retornar uma matriz completamente nova a cada vez pode ter alguns de vantagens: (1) é a coisa mais próxima que você pode obter de objetos imutáveis ​​no limite de sua classe em Ruby e (2) mantém uma interface uniforme em sua classe com a possibilidade de adaptar o valor de retorno posteriormente com base no estado inerente (por exemplo, por lendo as cores do banco de dados) sem alterar a interface.
21416 Holger Apenas
@ Holger Apenas pelo menos um de seus objetivos ainda pode ser alcançado usando uma constante: class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endDito isso, a alocação de uma matriz em qualquer idioma pode ser potencialmente problemática; por um lado, está usando a memória sem motivo (bom). Se estiver carregando de um banco de dados e quiser armazenar em cache o valor, também é possível usar uma variável de instância de classe, que pode ser carregada com preguiça usando o def self.coloursmétodo No entanto, concordamos com o aspecto da imutabilidade.
Zabba 30/05
70

Algumas opções:

Usando uma constante:

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

Preguiçoso carregado usando a variável de instância de classe:

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

Se for uma constante verdadeiramente global ( embora evite constantes globais dessa natureza ), você também pode considerar colocar uma constante de nível superior, config/initializers/my_constants.rbpor exemplo.

Zabba
fonte
1
Heh. Comentário justo - erro de sintaxe ao digitar da memória meu exemplo :) Obrigado pela dica!
AlexC
2
Em seguida, extendo módulo da classe estará disponível com Card.COLOURS.
undefinedvariable
Ao usar o extendseu não está funcionando para mim. Ao usar include, posso acessar como:Card::COLOURS
Abhi
Você definitivamente não deve colocar isso embaixo /models. É muito melhor se você criar um inicializador.
linkyndy
@linkyndy Eu diria que não há problema em colocá-lo sob /models, mas apenas se estiver dentro de um módulo, por exemplo, module Constants; COLOURS = ...; endem um arquivo chamado models/constants.rb.
Kelvin
56

No Rails 4.2, você pode usar a config.xpropriedade:

# 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á disponível como:

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

Outro método para carregar a configuração 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"

Nos Rails 5 e 6 , você pode usar o configurationobjeto diretamente para configuração personalizada, além de config.x. No entanto, ele só pode ser usado para configuração não aninhada:

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

Ele estará disponível como:

Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Halil Özgür
fonte
1
Eu gosto Rails.configuration.coloursmelhor (embora eu gostaria que não fosse tão longo)
Tom Rossi
@ TomRossi Eu concordo, por exemplo, configé tão bom quanto configuration. Podemos esperar para obter um atalho em algum ponto :)
Halil Özgür
ainda é a melhor maneira no Rails 6 para definir constantes a serem compartilhadas entre vários controladores? obrigado pela resposta!
Crashalot 19/06
18

Se uma constante é necessária em mais de uma classe, eu a coloco em config / initializers / contant.rb sempre em todas as maiúsculas (a lista de estados abaixo é truncada).

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

Eles estão disponíveis através do aplicativo, exceto no código do modelo, como tal:

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

Para usar a constante em um modelo, use attr_accessor para disponibilizar a constante.

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end
Hank Snow
fonte
1
bom, config/initializers/constants.rbprovavelmente seria uma escolha melhor embora
Adit Saxena
Eu também usar isso, mas recentemente se deparou com o problema que essas constantes não são acessíveis em application.rb
Zia Ul Rehman Mughal
minhas constantes estavam funcionando, mas pararam por algum motivo (como, de alguma forma, meu arquivo saiu dos inicializadores). Depois de verificar esta resposta, olhei atentamente e as movi para trás e agora trabalhando. Obrigado
Muhammad Nasir Shamshad
Eu não acho que attr_accessor é necessário. Você está falando sobre alguma versão específica do Rails?
Mayuresh Srivastava 7/03
16

Para configurações em todo o aplicativo e constantes globais, recomendo usar o Settingslogic . Essas configurações são armazenadas no arquivo YML e podem ser acessadas a partir de modelos, visualizações e controladores. Além disso, você pode criar configurações diferentes para todos os seus ambientes:

  # 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

Em algum lugar da visualização (prefiro métodos auxiliares para esse tipo de coisa) ou em um modelo que você pode obter, por exemplo, uma variedade de cores Settings.colors.split(/\s/). É muito flexível. E você não precisa inventar uma bicicleta.

Voldy
fonte
7

Use um método de classe:

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

Em seguida Model.colours, retornará essa matriz. Como alternativa, crie um inicializador e agrupe as constantes em um módulo para evitar conflitos de espaço para nome.

Steve Ross
fonte
4

Tente manter todas as constantes em um só lugar. No meu aplicativo, criei a pasta constantes dentro dos inicializadores da seguinte maneira:

insira a descrição da imagem aqui

e geralmente mantenho tudo constante nesses arquivos.

No seu caso, você pode criar um arquivo na pasta constantes como colors_constant.rb

colors_constant.rb

insira a descrição da imagem aqui

Não se esqueça de reiniciar o servidor

Ghanshyam Anand
fonte
1
Esta é a melhor resposta que encontrei aqui. Obrigado.
Promise Preston
3

Outra opção, se você deseja definir suas constantes em um só lugar:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Mas ainda os torne visíveis globalmente sem precisar acessá-los de maneira totalmente qualificada:

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

Um local comum para colocar constantes globais em todo o aplicativo é o interior 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
fonte
2

Normalmente, tenho um modelo / tabela de 'pesquisa' no meu programa de trilhos e o uso para as constantes. É muito útil se as constantes forem diferentes para ambientes diferentes. Além disso, se você planeja estendê-los, digamos que deseja adicionar 'amarelo' em uma data posterior, basta adicionar uma nova linha à tabela de pesquisa e concluir o processo.

Se você der permissões de administrador para modificar esta tabela, elas não entrarão em contato com você para manutenção. :) SECO.

Aqui está como meu código de migração se parece:

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

Eu uso o seeds.rb para preenchê-lo 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
fonte
1

A variável global deve ser declarada no config/initializersdiretório

COLOURS = %w(white blue black red green)
pássaro
fonte
Obrigado! Outros já mencionaram isso. É a última linha da resposta de Holger, e Zabba também menciona essa técnica, apesar de Zabba alertar contra.
AlexC
0

De acordo com sua condição, você também pode definir algumas variáveis ​​ambientais e buscá-la ENV['some-var']no código ruby. Essa solução pode não ser adequada para você, mas espero que ajude outras pessoas.

Exemplo: você pode criar arquivos diferentes .development_env, .production_env, .test_enve carregá-lo de acordo com seus ambientes de aplicativos, verifique este gen dotenv-rails que automatizam isso para o seu.

fangxing
fonte