Ruby on Rails: где определять глобальные константы?

213

Я только начинаю с моим первым веб-приложением Ruby on Rails. У меня есть куча разных моделей, представлений, контроллеров и так далее.

Я хочу найти хорошее место, чтобы придерживаться определений действительно глобальных констант, которые применяются ко всему моему приложению. В частности, они применяются как в логике моих моделей, так и в решениях, принимаемых по моим взглядам. Я не могу найти СУХОЕ место, чтобы поместить эти определения, где они доступны как для всех моих моделей, так и для всех моих представлений.

Чтобы взять конкретный пример, я хочу постоянную COLOURS = ['white', 'blue', 'black', 'red', 'green']. Это используется повсеместно, и в моделях, и в представлениях. Где я могу определить его в одном месте, чтобы он был доступен?

Что я пробовал:

  • Постоянные переменные класса в файле model.rb, с которыми они больше всего связаны, например @@COLOURS = [...]. Но я не мог найти вменяемый способ определить это так, чтобы я мог писать в своих взглядах, Card.COLOURSа не что-то клёвое Card.first.COLOURS.
  • А метод на модели, что-то вроде def colours ['white',...] end- такая же проблема.
  • Метод в application_helper.rb - это то, что я делаю до сих пор, но помощники доступны только в представлениях, а не в моделях
  • Я думаю, что я мог попробовать что-то в application.rb или environment.rb, но это не совсем верно (и они тоже не работают)

Нет ли способа определить что-либо доступное как из моделей, так и из представлений? Я имею в виду, я знаю, что модели и представления должны быть отдельными, но наверняка в некоторых доменах будут времена, когда им нужно ссылаться на одни и те же предметные знания?

AlexC
источник
Я ценю, что это ДЕЙСТВИТЕЛЬНО поздно, но для других читателей мне интересно, почему вы не просто определили их в своей модели и использовали ваши контроллеры, чтобы передать их вашим представлениям. Таким образом, вы получите более четкое разделение интересов - вместо создания зависимостей между контроллером / представлением И моделью / представлением.
Том Том
2
@TomTom: передать эти константы в каждое представление и помощник, который нуждается в них? Другими словами, информируйте контроллер о том, какие представления нуждаются в каких константах? Это похоже на нарушение MVC.
AlexC

Ответы:

229

Если ваша модель действительно «отвечает» за константы, вы должны вставить их туда. Вы можете создавать методы класса для доступа к ним, не создавая новый экземпляр объекта:

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

# accessible like this
Card.colours

Кроме того, вы можете создать переменные класса и метод доступа. Это, однако, не рекомендуется, поскольку переменные класса могут вызывать удивление при наследовании и в многопоточных средах.

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

# accessible the same as above

Две вышеупомянутые опции позволяют вам изменять возвращаемый массив при каждом вызове метода доступа, если это необходимо. Если у вас действительно истинная неизменяемая константа, вы также можете определить ее в классе модели:

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

# accessible as
Card::COLOURS

Вы также можете создать глобальные константы, которые доступны из любого места в инициализаторе, как в следующем примере. Это, вероятно, лучшее место, если ваши цвета действительно глобальны и используются в нескольких контекстах модели.

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

Примечание: когда мы определяем константы выше, часто мы хотим, чтобы freezeмассив. Это препятствует тому, чтобы другой код позже (непреднамеренно) изменил массив, например, добавив новый элемент. Как только объект заморожен, он больше не может быть изменен.

Хольгер Джаст
источник
1
Большое спасибо. Похоже, мне не хватало класса Ruby-фу для определения методов класса. Но мне действительно нравится опция инициализатора в этом случае, потому что цвета используются в нескольких моделях и представлениях. Большое спасибо!
AlexC
21
Если вы идете по config/initializers/my_constants.rbмаршруту, не забудьте перезапустить сервер:touch tmp/restart.txt
user664833
4
def self.coloursПример не является идеальным. Каждый раз, когда вы вызываете def self.colours, будет возвращен новый экземпляр массива . #freezeне поможет в этом случае. Лучше всего объявлять ее как константу Ruby, и в этом случае вы всегда получите один и тот же объект.
Забба
@Zabba Если выделение одного массива имеет заметное значение для вашего приложения, вам, вероятно, не стоит использовать Ruby, во-первых ... При этом, используя метод и возвращая совершенно новый массив каждый раз, можно получить пару из преимуществ: (1) это самая близкая вещь, которую вы можете получить к неизменным объектам на границе вашего класса в Ruby, и (2) вы поддерживаете унифицированный интерфейс для вашего класса с возможностью адаптировать возвращаемое значение позже на основе собственного состояния (например, чтение цветов из БД) без изменения интерфейса.
Хольгер Просто
@Holger Просто, по крайней мере, одна из ваших целей все еще может быть достигнута с помощью константы: при class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endэтом размещение массива на любом языке может быть потенциально проблематичным; с одной стороны, он использует память без (веской) причины. Если вы загружаете из БД и хотите кэшировать значение, можно также использовать переменную экземпляра класса, которая может быть лениво загружена с помощью def self.coloursметода. Договорились об аспекте неизменности, хотя.
Забба
70

Некоторые варианты:

Используя константу:

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

Ленивый загружается с помощью переменной экземпляра класса:

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

Если это действительно глобальная константа ( хотя избегайте глобальных констант такого рода ), вы также можете рассмотреть возможность добавления константы верхнего уровня, config/initializers/my_constants.rbнапример.

Zabba
источник
1
Хех. Честный комментарий - синтаксическая ошибка при наборе текста из памяти моего примера :) Спасибо за совет!
AlexC
2
Тогда extendмодуль в классе, так что он будет доступен с Card.COLOURS.
неопределенная
При использовании extendего не работает для меня. При использовании includeя могу получить доступ как:Card::COLOURS
Абхи
Вы определенно НЕ должны размещать это под /models. Намного лучше, если вы создадите инициализатор.
linkyndy
@linkyndy Я бы сказал, что все в порядке /models, но только если он внутри модуля, например, module Constants; COLOURS = ...; endв файле с именем models/constants.rb.
Кельвин
57

Начиная с Rails 4.2, вы можете использовать config.xсвойство:

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

Который будет доступен как:

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

Еще один способ загрузки пользовательских настроек:

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

В Rails 5 и 6 вы можете использовать configurationобъект напрямую для пользовательской конфигурации, в дополнение к config.x. Однако его можно использовать только для не вложенной конфигурации:

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

Это будет доступно как:

Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Халил Озгюр
источник
2
Мне нравится больше Rails.configuration.coloursвсего (хотя я хотел бы, чтобы это было не так долго)
Том Росси
@ TomRossi Я согласен, например config, так же хорошо, как configuration. Мы могли бы надеяться получить ярлык в какой-то момент :)
Халил Озгюр
это все еще лучший способ в rails 6 для определения констант, которые будут разделены между несколькими контроллерами? Спасибо за ответ!
Crashalot
@Crashalot Это все еще перечислено в документах. "Лучший"? Это зависит. Это может быть у их общего предка. Или ApplicationControllerесли между ними нет ничего другого. Если константа не имеет прямого отношения к контроллерам, я все равно рассмотрю глобальную конфигурацию и т. Д.
Халил Озгюр
@ HalilÖzgür спасибо за ответ. как вы определяете константы в общем предке?
Crashalot
18

Если константа нужна более чем в одном классе, я помещаю ее в config / initializers / contant.rb всегда во всех заглавных буквах (приведенный ниже список состояний усекается).

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

Они доступны через наше приложение, за исключением кода модели как такового:

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

Чтобы использовать константу в модели, используйте attr_accessor, чтобы сделать константу доступной.

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end
Хэнк Сноу
источник
1
хорошо, config/initializers/constants.rbвероятно, будет лучшим выбором, хотя
Adit Saxena
я тоже этим пользуюсь, но недавно натолкнулся на проблему, что эти константы недоступны в application.rb
Зия Уль Рехман Могал
мои константы работали, но по какой-то причине остановились (как-то мой файл вышел из инициализаторов). После проверки этого ответа я внимательно посмотрел, переместил их назад и теперь работает. Спасибо
Мухаммед Насир Шамшад
Я не думаю, что attr_accessor необходим. Вы говорите о какой-то конкретной версии Rails?
Мауреш Шривастава
16

Для настроек всего приложения и глобальных констант я рекомендую использовать Settingslogic . Эти настройки хранятся в файле YML и доступны из моделей, представлений и контроллеров. Кроме того, вы можете создавать различные настройки для всех ваших сред:

  # 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

Где-то в представлении (я предпочитаю вспомогательные методы для такого рода вещей) или в модели, вы можете получить, например, массив цветов Settings.colors.split(/\s/). Это очень гибкий. И вам не нужно изобретать велосипед.

Voldy
источник
7

Используйте метод класса:

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

Затем Model.coloursвернет этот массив. В качестве альтернативы, создайте инициализатор и оберните константы в модуле, чтобы избежать конфликтов пространства имен.

Стив Росс
источник
4

Попробуйте сохранить все константы в одном месте. В моем приложении я создал папку констант внутри инициализаторов следующим образом:

введите описание изображения здесь

и я обычно сохраняю все константы в этих файлах.

В вашем случае вы можете создать файл в папке констант как colors_constant.rb

colors_constant.rb

введите описание изображения здесь

Не забыл перезапустить сервер

Ганшьям Ананд
источник
1
Это лучший ответ, который я нашел здесь. Спасибо.
Обещание Престон
3

Другой вариант, если вы хотите определить свои константы в одном месте:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Но все же сделайте их глобально видимыми, не имея к ним полного доступа:

DSL::Constants::MY_CONSTANT # => 1
MY_CONSTANT # => NameError: uninitialized constant MY_CONSTANT
Object.instance_eval { include DSL::Constants }
MY_CONSTANT # => 1
Wincent
источник
3

Распространенное место для размещения глобальных констант всего приложения - внутри config/application.

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

  class Application < Rails::Application
    config.foo_bar = :baz
  end
end
Деннис
источник
2

Обычно я использую модель / таблицу поиска в моей программе rails и использую ее для констант. Это очень полезно, если константы будут разными для разных сред. Кроме того, если у вас есть план по их расширению, скажем, вы хотите добавить «желтый» позднее, вы можете просто добавить новую строку в таблицу поиска и покончить с этим.

Если вы дадите администратору разрешения на изменение этой таблицы, они не придут к вам для обслуживания. :) СУХОЙ.

Вот как выглядит мой код миграции:

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

Я использую seed.rb, чтобы предварительно заполнить его.

Lookup.find_or_create_by_group_key_and_lookup_key_and_lookup_value!(group_key: 'development_COLORS', lookup_key: 'color1', lookup_value: 'red');
SriSri
источник
1

Глобальная переменная должна быть объявлена ​​в config/initializersкаталоге

COLOURS = %w(white blue black red green)
птица
источник
Спасибо! Другие уже упоминали об этом. Это последняя строка ответа Хольгера, и Забба упоминает и эту технику, хотя Забба предостерегает против нее.
AlexC
0

В зависимости от вашего состояния, вы также можете определить некоторые переменные окружения и получить их через ENV['some-var']код ruby, это решение может не подойти вам, но я надеюсь, что оно может помочь другим.

Пример: вы можете создавать различные файлы .development_env, .production_env, .test_envи загрузить его в соответствии ваши среды приложений, проверить это генераторную dotenv-рельсы , которые автоматизируют это для вашего.

Fangxing
источник