Ruby on Rails: dove definire le costanti globali?


213

Ho appena iniziato con la mia prima webapp Ruby on Rails. Ho un sacco di diversi modelli, viste, controller e così via.

Voglio trovare un buon posto per attaccare le definizioni di costanti veramente globali, che si applicano a tutta la mia app. In particolare, si applicano sia nella logica dei miei modelli, sia nelle decisioni prese nelle mie opinioni. Non riesco a trovare un posto ASCIUTTO per mettere queste definizioni dove sono disponibili sia per tutti i miei modelli sia per tutte le mie viste.

Per fare un esempio specifico, voglio una costante COLOURS = ['white', 'blue', 'black', 'red', 'green']. Questo è usato ovunque, sia nei modelli che nelle viste. Dove posso definirlo in un solo posto in modo che sia accessibile?

Cosa ho provato:

  • Variabili di classe costanti nel file model.rb a cui sono maggiormente associate, ad esempio @@COLOURS = [...]. Ma non sono riuscito a trovare un modo sano per definirlo in modo da poter scrivere nei miei punti di vista Card.COLOURSpiuttosto che qualcosa di simile Card.first.COLOURS.
  • Un metodo sul modello, qualcosa del genere def colours ['white',...] end- stesso problema.
  • Un metodo in application_helper.rb: questo è quello che sto facendo finora, ma gli helper sono accessibili solo nelle viste, non nei modelli
  • Penso che avrei potuto provare qualcosa in application.rb o environment.rb, ma quelli non sembrano proprio corretti (e nemmeno sembrano funzionare)

Non c'è modo di definire qualcosa che sia accessibile sia dai modelli che dalle viste? Voglio dire, so che i modelli e le viste dovrebbero essere separati, ma sicuramente in alcuni domini ci saranno momenti in cui dovranno fare riferimento alla stessa conoscenza specifica del dominio?



Apprezzo il fatto che sia VERAMENTE in ritardo, ma per gli altri lettori mi chiedo perché non li hai definiti nel tuo modello e non hai usato i controller per trasmetterli alle tue opinioni. In questo modo, avresti una separazione più chiara delle preoccupazioni, piuttosto che creare dipendenze tra controller / vista E modello / vista.
Tom Tom,

2
@ TomTom: passare queste costanti in ogni vista e aiuto che ne ha bisogno? In altre parole, rendere il controller consapevole di quali viste necessitano quali costanti? Sembra più una violazione di MVC.
AlexC

Risposte:


229

Se il tuo modello è veramente "responsabile" delle costanti, dovresti incollarle lì. È possibile creare metodi di classe per accedervi senza creare una nuova istanza di oggetto:

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

# accessible like this
Card.colours

In alternativa, è possibile creare variabili di classe e un accessorio. Ciò è tuttavia scoraggiato poiché le variabili di classe potrebbero comportarsi in modo sorprendente con l'ereditarietà e in ambienti multi-thread.

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

# accessible the same as above

Le due opzioni precedenti consentono di modificare l'array restituito su ogni invocazione del metodo di accesso, se necessario. Se hai una costante veramente immutabile, puoi anche definirla nella classe del modello:

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

# accessible as
Card::COLOURS

È anche possibile creare costanti globali accessibili da qualsiasi parte in un inizializzatore come nell'esempio seguente. Questo è probabilmente il posto migliore, se i tuoi colori sono davvero globali e utilizzati in più di un contesto di modello.

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

Nota: quando definiamo le costanti sopra, spesso vogliamo freezel'array. Ciò impedisce ad altro codice di modificare inavvertitamente (inavvertitamente) l'array aggiungendo ad esempio un nuovo elemento. Una volta che un oggetto è congelato, non può più essere modificato.


1
Grazie mille. Sembra che mi mancasse il ruby ​​class-fu per definire i metodi di classe. Ma in realtà mi piace l'opzione di inizializzazione in questo caso, perché i colori sono utilizzati in più modelli e viste. Grazie molto!
AlexC,

21
Se stai seguendo il config/initializers/my_constants.rbpercorso, ricordati di riavviare il server:touch tmp/restart.txt
user664833

4
L' def self.coloursesempio non è l'ideale. Ogni volta che si chiama def self.colours, verrà restituita una nuova istanza dell'array . #freezenon aiuterà in questo caso. La migliore pratica è dichiararla come costante Ruby, nel qual caso otterrai sempre lo stesso oggetto.
Zabba,

@Zabba Se l'allocazione di un singolo array fa una notevole differenza per la tua app, probabilmente non dovresti usare Ruby in primo luogo ... Detto questo, usare un metodo e restituire un array completamente nuovo ogni volta può avere un paio di vantaggi: (1) è la cosa più vicina che puoi ottenere verso oggetti immutabili sul tuo limite di classe in Ruby e (2) mantieni un'interfaccia uniforme sulla tua classe con la possibilità di adattare il valore di ritorno in seguito in base allo stato intrinseco (ad es. leggere i colori dal DB) senza cambiare l'interfaccia.
Holger

@Holger Solo, almeno uno dei tuoi obiettivi può ancora essere raggiunto usando una costante: class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endDetto questo, l'allocazione di un array in qualsiasi lingua può essere potenzialmente problematica; per uno, sta usando la memoria per nessuna (buona) ragione. Se si carica da un DB e si desidera memorizzare nella cache il valore, si può anche usare una variabile di istanza di classe, che può essere caricata in modo pigro usando il def self.coloursmetodo D'accordo però sull'aspetto dell'immutabilità.
Zabba,

70

Alcune opzioni:

Utilizzando una costante:

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

Lazy caricato utilizzando la variabile di istanza di classe:

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

Se è una costante veramente globale ( evita costanti globali di questa natura ), potresti anche considerare di inserire una costante di livello superiore, config/initializers/my_constants.rbad esempio.


1
Eh. Commento corretto: errore di sintassi durante la digitazione da memoria del mio esempio :) Grazie per il suggerimento!
AlexC,

2
Quindi extendil modulo nella classe quindi sarà disponibile con Card.COLOURS.
indefinibile

Quando lo uso extendnon funziona per me. Usando includeposso accedere come:Card::COLOURS
Abhi

Non dovresti assolutamente metterlo sotto /models. È molto meglio se si crea un inizializzatore.
linkyndy,

@linkyndy Direi che va bene metterlo sotto /models, ma solo se si trova all'interno di un modulo, ad esempio module Constants; COLOURS = ...; endin un file chiamato models/constants.rb.
Kelvin,

57

A partire da Rails 4.2, è possibile utilizzare config.x proprietà:

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

Quale sarà disponibile come:

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

Un altro metodo di caricamento della configurazione personalizzata:

# 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 Rails 5 e 6 , puoi usareconfiguration oggetto direttamente per la configurazione personalizzata, oltre a config.x. Tuttavia, può essere utilizzato solo per la configurazione non nidificata:

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

Sarà disponibile come:

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

2
Mi piace di Rails.configuration.colourspiù (anche se vorrei che non fosse così lungo)
Tom Rossi,

@TomRossi Sono d'accordo, ad esempio configè buono come configuration. Potremmo sperare di ottenere una scorciatoia a un certo punto :)
Halil Özgür

è ancora il modo migliore in Rails 6 di definire costanti da condividere su più controller? Grazie per la risposta!
Crashalot,

@Crashalot È ancora elencato nei documenti. "Il migliore"? Dipende. Può essere nel loro antenato comune. O ApplicationControllerse non c'è nient'altro in mezzo. Se la costante non è direttamente correlata ai controller, prenderei comunque in considerazione una configurazione globale, ecc.
Halil Özgür

@ HalilÖzgür grazie per la risposta. come si definiscono le costanti in un antenato comune?
Crashalot,

18

Se è necessaria una costante in più di una classe, la metto in config / initializer / contant.rb sempre in tutte le maiuscole (l'elenco degli stati di seguito viene troncato).

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

Sono disponibili attraverso l'applicazione ad eccezione del codice modello in quanto tale:

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

Per utilizzare la costante in un modello, utilizzare attr_accessor per rendere disponibile la costante.

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end

1
bello, config/initializers/constants.rbprobabilmente sarebbe una scelta migliore però
Adit Saxena

lo uso anche io, ma recentemente ho riscontrato che queste costanti non sono accessibili in application.rb
Zia Ul Rehman Mughal,

le mie costanti funzionavano ma si fermarono per qualche motivo (come in qualche modo il mio file si è spostato fuori dagli inizializzatori). Dopo aver verificato questa risposta, ho guardato da vicino e li ho spostati indietro e ora funziona. Grazie
Muhammad Nasir Shamshad il

Non penso che attr_accessor sia necessario. Stai parlando di una particolare versione di Rails?
Mayuresh Srivastava,

16

Per le impostazioni a livello di applicazione e per le costanti globali, consiglio di utilizzare Settingslogic . Queste impostazioni sono memorizzate nel file YML e sono accessibili da modelli, viste e controller. Inoltre, puoi creare impostazioni diverse per tutti i tuoi ambienti:

  # 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

Da qualche parte nella vista (preferisco i metodi di supporto per questo tipo di cose) o in un modello puoi ottenere, per esempio, una gamma di colori Settings.colors.split(/\s/). È molto flessibile. E non è necessario inventare una bicicletta.


7

Utilizzare un metodo di classe:

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

Quindi Model.coloursrestituirà quell'array. In alternativa, creare un inizializzatore e avvolgere le costanti in un modulo per evitare conflitti nello spazio dei nomi.


4

Cerca di mantenere tutto costante in un unico posto, Nella mia applicazione ho creato la cartella constants all'interno degli inizializzatori come segue:

inserisci qui la descrizione dell'immagine

e di solito mantengo tutto costante in questi file.

Nel tuo caso puoi creare il file nella cartella delle costanti come colors_constant.rb

colors_constant.rb

inserisci qui la descrizione dell'immagine

Non dimenticare di riavviare il server


1
Questa è la migliore risposta che ho trovato qui. Grazie.
Promessa Preston

3

Un'altra opzione, se si desidera definire le costanti in un unico posto:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Ma rendili comunque visibili a livello globale senza dover accedervi in ​​modo pienamente qualificato:

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

3

All'interno è presente un luogo comune dove inserire costanti globali a livello di applicazioneconfig/application .

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

  class Application < Rails::Application
    config.foo_bar = :baz
  end
end

2

In genere ho un modello / tabella di "ricerca" nel mio programma di rotaie e lo uso per le costanti. È molto utile se le costanti saranno diverse per ambienti diversi. Inoltre, se hai un piano per estenderli, supponi di voler aggiungere "giallo" in un secondo momento, potresti semplicemente aggiungere una nuova riga alla tabella di ricerca e farla finita.

Se concedi le autorizzazioni di amministratore per modificare questa tabella, non verranno da te per la manutenzione. :) SECCO.

Ecco come appare il mio codice di migrazione:

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

Uso seeds.rb per pre-popolarlo.

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

1

La variabile globale deve essere dichiarata nella config/initializersdirectory

COLOURS = %w(white blue black red green)

Grazie! Altri lo hanno già menzionato. È l'ultima riga della risposta di Holger e Zabba menziona anche questa tecnica, anche se Zabba mette in guardia contro di essa.
AlexC,

0

In base alle tue condizioni, puoi anche definire alcune variabili ambientali e recuperarle tramite il ENV['some-var']codice ruby, questa soluzione potrebbe non essere adatta a te, ma spero che possa aiutare gli altri.

Esempio: è possibile creare file diversi .development_env, .production_env, .test_enve caricarlo secondo i vostri ambienti applicativi, selezionare questa generazione dotenv-rail che automatizzano questo per il vostro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.