Ruby on Rails: gdzie definiować stałe globalne?

213

Właśnie zaczynam pracę z moją pierwszą aplikacją Ruby on Rails. Mam kilka różnych modeli, widoków, kontrolerów i tak dalej.

Chcę znaleźć dobre miejsce do przyklejenia definicji prawdziwie globalnych stałych, które mają zastosowanie w całej mojej aplikacji. W szczególności dotyczą one zarówno logiki moich modeli, jak i decyzji podjętych w moich poglądach. Nie mogę znaleźć SUCHEGO miejsca, w którym można by umieścić te definicje, w których są one dostępne zarówno dla wszystkich moich modeli, jak i we wszystkich moich widokach.

Aby podać konkretny przykład, chcę stałą COLOURS = ['white', 'blue', 'black', 'red', 'green']. Jest to stosowane wszędzie, zarówno w modelach, jak i widokach. Gdzie mogę to zdefiniować w jednym miejscu, aby było dostępne?

Co próbowałem:

  • Zmienne klasy stałej w pliku model.rb, z którymi są najbardziej skojarzone, takie jak @@COLOURS = [...]. Ale nie mogłem znaleźć rozsądnego sposobu, aby to zdefiniować, abym mógł pisać w swoich poglądach, Card.COLOURSa nie coś w stylu kludgy Card.first.COLOURS.
  • Metoda na modelu, coś w rodzaju def colours ['white',...] end- ten sam problem.
  • Metoda w application_helper.rb - do tej pory to robię, ale pomocniki są dostępne tylko w widokach, a nie w modelach
  • Wydaje mi się, że mogłem wypróbować coś w application.rb lub environment.rb, ale te nie wydają się słuszne (i one też nie działają)

Czy po prostu nie ma sposobu, aby zdefiniować coś, co byłoby dostępne zarówno z modeli, jak i widoków? Mam na myśli, że wiem, że modele i widoki powinny być oddzielne, ale na pewno w niektórych domenach będą potrzebne momenty, aby odwoływać się do tej samej wiedzy specyficznej dla domeny?

AlexC
źródło
możliwy duplikat aplikacji Where / How do kodowania stałych w aplikacji Rails 3
Thilo
Rozumiem, że NAPRAWDĘ jest późno, ale dla innych czytelników zastanawiam się, dlaczego nie zdefiniowaliście ich tylko w swoim modelu i nie użyliście kontrolerów, by przekazać je do swoich poglądów. W ten sposób uzyskasz wyraźniejszy podział problemów - zamiast tworzyć zależności między kontrolerem / widokiem ORAZ modelem / widokiem.
Tom Tom
2
@TomTom: Czy przekazać te stałe do każdego widoku i pomocnika, który ich potrzebuje? Innymi słowy, poinformuj kontrolera, które widoki potrzebują, które stałe? To brzmi bardziej jak naruszenie MVC.
AlexC

Odpowiedzi:

229

Jeśli twój model jest naprawdę „odpowiedzialny” za stałe, powinieneś je tam przykleić. Możesz utworzyć metody klasowe, aby uzyskać do nich dostęp bez tworzenia nowej instancji obiektu:

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

# accessible like this
Card.colours

Alternatywnie możesz utworzyć zmienne klas i akcesor. Jest to jednak odradzane, ponieważ zmienne klasowe mogą zadziwić dziedziczeniem i środowiskami wielowątkowymi.

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

# accessible the same as above

Dwie powyższe opcje pozwalają zmienić zwracaną tablicę przy każdym wywołaniu metody akcesora, jeśli jest to wymagane. Jeśli masz prawdziwie niezmienną stałą, możesz również zdefiniować ją w klasie modelu:

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

# accessible as
Card::COLOURS

Można również utworzyć stałe globalne, które są dostępne z dowolnego miejsca w inicjalizatorze, jak w poniższym przykładzie. Jest to prawdopodobnie najlepsze miejsce, jeśli twoje kolory są naprawdę globalne i używane w więcej niż jednym kontekście modelu.

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

Uwaga: kiedy zdefiniujemy stałe powyżej, często chcemy freezetablicy. Zapobiega to późniejszemu (niezamierzonemu) zmodyfikowaniu tablicy przez inny kod, np. Poprzez dodanie nowego elementu. Po zamrożeniu obiektu nie można go już zmienić.

Holger Just
źródło
1
Dziękuję Ci bardzo. Wygląda na to, że brakowało mi Ruby class-fu, aby zdefiniować metody klasowe. Ale tak naprawdę podoba mi się opcja inicjalizacyjna w tym przypadku, ponieważ kolory są używane w wielu modelach i widokach. Wielkie dzięki!
AlexC,
21
Jeśli config/initializers/my_constants.rbtouch tmp/restart.txt
jedziesz
4
Ten def self.coloursprzykład nie jest idealny. Za każdym razem, gdy dzwonisz def self.colours, zwracana jest nowa instancja tablicy . #freezenie pomoże w tym przypadku. Najlepszą praktyką jest zadeklarowanie jej jako stałej Ruby, w którym to przypadku zawsze otrzymasz ten sam obiekt.
Zabba
@Zabba Jeśli przydział jednej tablicy robi zauważalną różnicę dla twojej aplikacji, prawdopodobnie nie powinieneś używać Ruby w pierwszej kolejności ... To powiedziawszy, używając metody i zwracając zupełnie nową tablicę za każdym razem, możesz mieć parę zalet: (1) jest to najbliższa rzecz, którą możesz dostać się do niezmiennych obiektów na granicy klasy w Rubim i (2) utrzymujesz jednolity interfejs w swojej klasie z możliwością dostosowania wartości zwracanej później w oparciu o stan naturalny (np. odczyt kolorów z DB) bez zmiany interfejsu.
Holger Zaledwie
@Holger Po prostu, przynajmniej jeden z twoich celów może być nadal osiągnięty przy użyciu stałej: class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endTo powiedziawszy, przydzielanie tablicy w dowolnym języku może być potencjalnie problematyczne; po pierwsze, używa pamięci bez (dobrego) powodu. Jeśli ładujesz z DB i chcesz buforować wartość, możesz również użyć zmiennej instancji klasy, którą można leniwie załadować za pomocą def self.coloursmetody. Zgodzili się jednak co do aspektu niezmienności.
Zabba
70

Niektóre opcje:

Używając stałej:

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

Lazy załadowane przy użyciu zmiennej instancji klasy:

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

Jeśli jest to prawdziwie globalna stała ( unikaj jednak globalnych stałych tego rodzaju ), możesz również rozważyć na przykład wprowadzenie stałej najwyższego poziomu config/initializers/my_constants.rb.

Zabba
źródło
1
Heh Uczciwy komentarz - błąd składniowy podczas pisania z pamięci mój przykład :) Dziękuję za podpowiedź!
AlexC,
2
Następnie extendmoduł w klasie, aby był dostępny z Card.COLOURS.
niezdefiniowana
Podczas korzystania extendnie działa dla mnie. Podczas korzystania includemogę uzyskać dostęp jak:Card::COLOURS
Abhi
Zdecydowanie NIE powinieneś umieszczać tego pod /models. O wiele lepiej jest, jeśli utworzysz inicjator.
linkyndy
@linkyndy Powiedziałbym, że można go umieścić /models, ale tylko jeśli jest w module, np. module Constants; COLOURS = ...; endw pliku o nazwie models/constants.rb.
Kelvin,
57

Od wersji Rails 4.2 możesz korzystać z config.xwłaściwości:

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

Które będą dostępne jako:

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

Inna metoda ładowania niestandardowej konfiguracji:

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

W Railsach 5 i 6 możesz dodatkowo użyć tego configurationobiektu do niestandardowej konfiguracji config.x. Można go jednak używać tylko do konfiguracji zagnieżdżonej:

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

Będzie dostępny jako:

Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Halil Özgür
źródło
2
Rails.configuration.coloursNajbardziej lubię (choć chciałbym, żeby nie było tak długo)
Tom Rossi
@TomRossi Zgadzam się, np. configJest tak dobry jak configuration. W pewnym momencie możemy spodziewać się skrótu :)
Halil Özgür
czy jest to nadal najlepszy sposób w szynach 6, aby zdefiniować stałe, które będą współużytkowane przez wiele kontrolerów? Dziękuję za odpowiedź!
Crashalot,
@Crashalot Nadal jest wymieniony w dokumentach. „Najlepszy”? To zależy. Może być u ich wspólnego przodka. Lub ApplicationControllerjeśli nie ma nic innego pomiędzy. Jeśli stała nie jest bezpośrednio związana z kontrolerami, nadal rozważałbym konfigurację globalną itp.
Halil Özgür
@ HalilÖzgür dzięki za odpowiedź. jak definiujesz stałe wspólnego przodka?
Crashalot
18

Jeśli potrzebna jest stała w więcej niż jednej klasie, umieszczam ją w config / initializers / contant.rb zawsze we wszystkich wielkich literach (lista stanów poniżej jest obcięta).

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

Są one dostępne w całej aplikacji poza kodem modelu jako takim:

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

Aby użyć stałej w modelu, użyj attr_accessor, aby udostępnić stałą.

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end
Hank Snow
źródło
1
fajnie, config/initializers/constants.rbprawdopodobnie byłby jednak lepszym wyborem
Adit Saxena
również tego używam, ale ostatnio natknąłem się na problem, że te stałe nie są dostępne w aplikacji.
rb
moje stałe działały, ale z jakiegoś powodu zostały zatrzymane (ponieważ jakoś mój plik wyszedł z inicjalizatorów). Po sprawdzeniu tej odpowiedzi dokładnie przyjrzałem się i przeniosłem je z powrotem, a teraz działa. Dzięki
Muhammad Nasir Shamshad
Nie sądzę, aby attr_accessor był potrzebny. Mówisz o jakiejś konkretnej wersji Railsów?
Mayuresh Srivastava
16

W przypadku ustawień dla całej aplikacji i stałych globalnych zalecam użycie Settingslogic . Ustawienia te są przechowywane w pliku YML i są dostępne w modelach, widokach i kontrolerach. Ponadto możesz tworzyć różne ustawienia dla wszystkich środowisk:

  # 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

Gdzieś w widoku (wolę metody pomocnicze dla tego rodzaju rzeczy) lub w modelu można uzyskać np. Tablicę kolorów Settings.colors.split(/\s/). Jest bardzo elastyczny. I nie musisz wymyślać roweru.

Voldy
źródło
7

Użyj metody klasowej:

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

Następnie Model.colourszwróci tę tablicę. Alternatywnie, utwórz inicjalizator i zawiń stałe w module, aby uniknąć konfliktów przestrzeni nazw.

Steve Ross
źródło
4

Staraj się utrzymywać stałą wartość w jednym miejscu. W mojej aplikacji utworzyłem folder stałych wewnątrz inicjatorów w następujący sposób:

wprowadź opis zdjęcia tutaj

i zwykle utrzymuję wszystko w tych plikach na stałym poziomie.

W twoim przypadku możesz utworzyć plik w folderze stałych jako colors_constant.rb

colors_constant.rb

wprowadź opis zdjęcia tutaj

Nie zapomnij zrestartować serwera

Ghanshyam Anand
źródło
1
To najlepsza odpowiedź, jaką tu znalazłem. Dziękuję Ci.
Obiecaj Preston
3

Inna opcja, jeśli chcesz zdefiniować swoje stałe w jednym miejscu:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Ale nadal sprawiają, że są widoczne na całym świecie, bez konieczności uzyskiwania do nich dostępu w pełni wykwalifikowany sposób:

DSL::Constants::MY_CONSTANT # => 1
MY_CONSTANT # => NameError: uninitialized constant MY_CONSTANT
Object.instance_eval { include DSL::Constants }
MY_CONSTANT # => 1
Wincenty
źródło
3

Wewnątrz znajduje się wspólne miejsce do umieszczania stałych globalnych dla całej aplikacjiconfig/application .

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

  class Application < Rails::Application
    config.foo_bar = :baz
  end
end
Dennis
źródło
2

Zwykle mam program / tabelę „lookup” w moim programie railsowym i używam jej dla stałych. Jest to bardzo przydatne, jeśli stałe będą różne dla różnych środowisk. Ponadto, jeśli masz plan ich rozszerzenia, powiedz, że chcesz dodać „żółty” w późniejszym terminie, możesz po prostu dodać nowy wiersz do tabeli odnośników i gotowe.

Jeśli dasz administratorowi uprawnienia do modyfikowania tej tabeli, nie przyjdą do ciebie w celu konserwacji. :) SUCHY.

Oto jak wygląda mój kod migracji:

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

Używam seeds.rb, aby wstępnie wypełnić.

Lookup.find_or_create_by_group_key_and_lookup_key_and_lookup_value!(group_key: 'development_COLORS', lookup_key: 'color1', lookup_value: 'red');
SriSri
źródło
1

Zmienna globalna powinna być zadeklarowana w config/initializerskatalogu

COLOURS = %w(white blue black red green)
ptak
źródło
Dzięki! Inni już o tym wspominali. To ostatni wiersz odpowiedzi Holgera i Zabba wspomina również o tej technice, choć Zabba ostrzega przed nią.
AlexC
0

W zależności od warunków możesz także zdefiniować niektóre zmienne środowiskowe i pobrać je za pomocą ENV['some-var'] kodu ruby, to rozwiązanie może nie pasować do ciebie, ale mam nadzieję, że może pomóc innym.

Przykład: można tworzyć różne pliki .development_env, .production_env, .test_envi załadować go według swoich środowisk aplikacji, zaznacz to gen dotenv barierkami , które automatyzują to dla twojego.

Fangxing
źródło