Ruby on Rails: Де визначити глобальні константи?


213

Я тільки починаю свій перший Ruby on Rails webapp. У мене є купа різних моделей, поглядів, контролерів тощо.

Я хочу знайти гарне місце, щоб дотримуватися визначень справді глобальних констант, які застосовуються у всьому моєму додатку. Зокрема, вони застосовуються як у логіці моїх моделей, так і в рішеннях, прийнятих на мою думку. Я не можу знайти жодного місця, де можна поставити ці визначення, де вони доступні як для всіх моїх моделей, так і для всіх моїх поглядів.

Щоб взяти конкретний приклад, я хочу константу 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, але це насправді не здається правильним (і, здається, не працює)

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



Я розумію, що це ЗАСТОСНО запізнюється, але для інших читачів мені цікаво, чому ви не просто визначили їх у своїй моделі та використовували контролери для передачі їх вашим уявленням. Таким чином, у вас буде більш чіткий поділ проблем, а не створення залежностей між контролером / переглядом І моделлю / видом.
Том Том

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

Два вищезазначені параметри дозволяють змінювати повернутий масив на кожному виклику методу accessor, якщо потрібно. Якщо у вас справді незмінна константа, ви також можете визначити її в класі моделі:

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 class-fu для визначення методів класу. Але мені справді подобається варіант ініціалізатора в цьому випадку, оскільки кольори використовуються в декількох моделях і видах. Велике дякую!
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.


1
Хе. Справедливий коментар - синтаксична помилка при наборі з пам’яті мого прикладу :) Дякую за пораду!
AlexC

2
Потім extendмодуль у класі, щоб він був доступний з Card.COLOURS.
Невизначене

При використанні extendйого не працює для мене. Під час використання includeя можу отримати доступ до таких як:Card::COLOURS
Abhi

Ви точно не повинні розміщувати це під /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. Ми можемо сподіватися отримати ярлик в якийсь момент :)
Halil Özgür

це все-таки найкращий спосіб у рейках 6 визначити константи, які слід ділити між кількома контролерами? дякую за відповідь!
Crashalot

@Crashalot Це все ще вказано в документах. "Кращий"? Це залежить. Це може бути у їхнього загального предка. Або, ApplicationControllerякщо між ними немає нічого іншого. Якщо константа не пов’язана безпосередньо з контролерами, я б все-таки розглядав глобальну конфігурацію тощо
Халіл Озгюр

@ HalilÖzgür дякую за відповідь. як ви визначаєте константи в загальному предкові?
Crashalot

18

Якщо константа потрібна для більш ніж одного класу, я розміщую її у config / inicijalizers / 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напевно, буде кращим вибором
Адіт Саксена

Я також використовую це, але нещодавно натрапив на те, що ці константи недоступні у 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/). Це дуже гнучко. І не потрібно вигадувати велосипед.


7

Використовуйте метод класу:

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

Потім Model.coloursповерне цей масив. Крім того, створіть ініціалізатор і загортайте константи в модуль, щоб уникнути конфліктів у просторі імен.


4

Постарайтеся зберегти все постійне в одному місці. У своїй програмі я створив папку констант всередині ініціалізаторів:

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

і зазвичай я постійно зберігаю все в цих файлах.

У вашому випадку ви можете створити файл у папці констант як colors_constant.rb

color_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

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

У моїй програмі рейок я зазвичай маю модель "пошуку" і використовую її для констант. Це дуже корисно, якщо константи будуть різними для різних середовищ. Крім того, якщо у вас є план їх розширення, скажімо, ви хочете додати "жовтий" пізніше, ви можете просто додати новий рядок до таблиці пошуку і зробити це з ним.

Якщо ви дасте адміністратору дозволи на зміни цієї таблиці, вони не прийдуть до вас для обслуговування. :) СУХА.

Ось як виглядає міграційний код:

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');

1

Глобальна змінна повинна бути оголошена в config/initializersкаталозі

COLOURS = %w(white blue black red green)

Дякую! Інші вже згадували про це. Це останній рядок відповіді Хольгера, і Забба згадує і про цю техніку, хоча Забба застерігає від цього.
AlexC

0

Відповідно до вашої умови, ви також можете визначити деякі змінні середовища та отримати їх за допомогою ENV['some-var']рубінового коду, це рішення може не підійти вам, але я сподіваюся, що може допомогти іншим.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.