Среда и конфигурирование Rails часть 2

Пакеты RubyGem

Прежде всего отметим, что сценарий начальной загрузки затребует (с помощью require) библиотеку rubygems. Скорее всего, вы знаете, что такое система RubyGems, поскольку устанавливали с ее помощью Rails, выполнив команду gem install rails.

По причинам, которые выходят за рамки настоящего обсуждения, Rails иногда загружает только сценарий начальной загрузки. Поэтому далее этот сценарий считывает файл config/environment.rb как текст (пропуская закомментированные строки) и с помощью модуля regexp ищет и разбирает строку RAILS_GEM_VERSION.

Определив, какую версию Rails загружать, сценарий затребует gem- пакет Rails. Если в этот момент окажется, что в файле environment.rb указана версия Rails, отсутствующая на вашей рабочей станции, то при попытке запустить сервер или консоль Rails будет выдано примерно такое сообщение об ошибке:


Cannot find gem for Rails =1.1.5:
Install the missing gem with ‘gem install -v=1.1.5 rails’, or
change environment.rb to define RAILS_GEM_VERSION with your
desired version.

Не могу найти gem для Rails =1.1.5:
Установите отсутствующий gem командой ‘gem install -v=1.1.5 rails’ или
измените файл environment.rb, указав в параметре RAILS_GEM_VERSION нужную вам версию.

Инициализатор

Далее сценарий начальной загрузки затребует сценарий initializer.rb (инициализатор), который отвечает за конфигурирование и позже будет использован сценарием environment.

И наконец, сценарий начальной загрузки передает инициализатору подразумеваемые по умолчанию пути загрузки (иными словами, строит classpath). Путь загрузки собирается из списка компонентных сред, составляющих саму Rails, и папок вашего приложения, в которых хранится код. Напомню, что в Ruby путь загрузки определяет, где метод
require должен искать вызываемый код. До этой точки путь загрузки содержал лишь текущий рабочий каталог.

Подразумеваемые пути загрузки

Код, с помощью которого Rails задает пути загрузки, легко читается и хорошо прокомментирован, поэтому я просто процитирую его без пояснений (листинг 1.1).

Листинг 1.1. Некоторые методы в файле railties/lib/initializer.rb

def default_frameworks
[ :active_record, :action_controller, :action_view,
:action_mailer, :action_web_service ]
end
def default_load_paths
paths = ["#{root_path}/test/mocks/#{environment}"]
# Добавить каталог контроллера приложения
paths.concat(Dir["#{root_path}/app/controllers/"])
# Затем подкаталоги компонентов.
paths.concat(Dir["#{root_path}/components/[_a-z]*"])
# Затем стандартные каталоги для включаемых файлов.
paths.concat %w(
app
app/models
app/controllers
app/helpers
app/services
app/apis
components
config
lib
vendor
).map { |dir| "#{root_path}/#{dir}" }.select { |dir|
File.directory?(dir) }
paths.concat Dir["#{root_path}/vendor/plugins/*/lib/"]
paths.concat builtin_directories
end
def builtin_directories
# Каталоги builtin включаются только для среды разработки.
(environment == 'development') ?
Dir["#{RAILTIES_PATH}/builtin/*/"] : []
end

Rails, модули и код автозагрузки

Обычно в Ruby для включения в приложение кода из другого файла вы употребляете предложение require. Но в Rails это стандартное поведение расширено за счет простого соглашения, позволяющего в большинстве случаев загружать ваш код автоматически.

Если вам доводилось пользоваться консолью Rails, то вы уже знаете, как это соглашение проявляется: никогда ничего не приходится загружать явно!

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

  • если класс или модуль не является вложенным, вставить подчерк между именами констант и затребовать файл с получившимся именем, например:

– EstimationCalculator преобразуется в require ‘estimation_calculator’;
– KittTurboBoost преобразуется в require ‘kitt_turbo_boost’;

  • если класс или модуль является вложенным, Rails вставляет подчерк между именами констант в каждом объемлющем модуле и требует файл из соответствующего подкаталога, например:

– MacGyver::SwissArmyKnife преобразуется в require ‘mac_gyver/ swiss_army_knife’;

  • Some::ReallyRatherDeeply::NestedClass преобразуется в ‘some/ really_rather_deeply/nested_class’ и, если этот класс еще не загружен, Rails ожидает найти его в файле nested_class.rb, который находится в подкаталоге really_rather_deeply каталога some, расположенного где-то в пути загрузки Ruby (например, в одном из подкаталогов app или в каталоге lib подключаемого модуля).

Таким образом, вам редко придется явно загружать Ruby-код в приложение для Rails (с помощью require), если вы будете придерживаться соглашений об именовании.

Встройка Rails Info

Зачем метод default_load_paths в листинге 1.1 вызывает метод builtin_directories? Что вообще такое «каталоги встроек»? Это то место, в котором Rails ищет поведение приложения (то есть модели, помощников и контроллеры). Можете называть это механизмом подключения до полнительных модулей, который предлагается платформой.

В настоящее время существует единственная «встройка» (builtin) railties/builtin/rails_info. Известна она, главным образом, как цель, на которую ведет ссылка «About your application’s environment» (О среде вашего приложения), отображаемая на странице index.html («Welcome Aboard») в любом новом приложении Rails.

Чтобы проверить, как она работает, запустите любое приложение Rails в режиме разработки и укажите в броузере адрес http://localhost:3000/rails/info/properties. Вы увидите на экране диагностическую информацию, в том числе номера версий и параметры:

Ruby version 1.8.5 (i686-darwin8.8.1)
RubyGems version 0.9.0
Rails version 1.2.0
Active Record version 1.14.4
Action Pack version 1.12.5
Action Web Service version 1.1.6
Action Mailer version 1.2.5
Active Support version 1.3.1
Edge Rails revision 33
Application root /Users/obie/prorails/time_and_expenses
Environment development
Database adapter mysql
Database schema version 8

Конфигурирование

Вернемся к сценарию environment, где нас ожидают параметры, определяемые разработчиком. Следующие две строки выглядят так:

Rails::Initializer.run do |config|
# Параметры в файлах config/environments/* более приоритетны, чем
# заданные здесь

Комментарий напоминает, что параметры, заданные в файлах описания среды для конкретного режима, имеют больший приоритет, чем параметры в файле environment.rb. Связано это с тем, что такие файлы загружаются позже, поэтому ранее загруженные значения одноименных параметров перезаписываются.

Пропуск частей среды

Первый параметр позволяет не загружать ненужные вам части Rails (Ruby – интерпретируемый язык, поэтому если есть возможность уменьшить объем кода, анализируемого интерпретатором, ею надо воспользоваться просто из соображений производительности). Во многих приложениях для Rails не используются ни электронная почта, ни
веб-службы, поэтому мы и взяли их в качестве примеров:

# Пропустить среды, которые вы не собираетесь использовать (работает только #
совместно с vendor/rails)
config.frameworks -= [ :action_web_service, :action_mailer ]

Дополнительные пути загрузки

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

# Включить в состав путей загрузки свои каталоги
config.load_paths += %W( #{RAILS_ROOT}/extras )

Если вы не знаете, скажу, что %W преобразует список разделенных пробелами значений в массив-литерал и довольно часто используется в коде Rails для удобства (признаюсь, что поначалу меня, привыкшего к синтаксису Java, это сбивало с толку).

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

Отметим, что два последних параметра не являются параметрами в традиционном смысле этого слова, то есть результатом присваивания значения некоторому свойству. При работе с объектом конфигурации Rails в полной мере использует предоставляемую Ruby возможность добавлять и удалять элементы из массива.

Переопределение уровня протоколирования

По умолчанию принимается уровень протоколирования :debug, но это можно переопределить.

# Использовать во всех средах один и тот же уровень протоколирования
# (по умолчанию в режиме эксплуатации используется :info, в остальных :debug)
config.log_level = :debug

Хранилище сеансов ActiveRecord

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

# Хранить сеансы в базе данных, а не в файловой системе
# (создайте таблицу сеансов командой 'rake db:sessions:create')
config.action_controller.session_store = :active_record_store

Дублирование схемы

При каждом запуске тестов Rails сохраняет схему базы данных, используемой в режиме разработки, в базе данных для тестирования, вызывая автоматически сгенерированный сценарий schema.rb. Он очень похож на сценарий миграции ActiveRecord и в действительности основан на том же самом API.

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

# Использовать SQL вместо дубликатора схемы на основе Active Record при
# создании тестовой базы. Это необходимо, если дубликатор не может
# сбросить всю схему, например, потому что в ней определены ограничения
# или специфичные для конкретной СУБД типы столбцов.
config.active_record.schema_format = :sql

Наблюдатели

Наблюдатели (observer) ActiveRecord – это полноценные объекты в приложениях Rails, решающие такие задачи, как очистка кэшей и управление денормализованными данными. В стандартном файле environment.rb приведены примеры классов, которые могут выступать в вашем приложении в роли наблюдателей (в Rails на самом деле нет наблюдателей cacher и garbage_collector, но это вовсе не означает, что Ruby не выполняет уборку мусора).

# Активировать наблюдателей, которые должны работать постоянно
# config.active_record.observers = :cacher, :garbage_collector

Часовые пояса

По умолчанию в Rails используется локальное время (с помощью модуля Time.local), то есть тот же часовой пояс, что и на сервере. Чтобы указать Rails другой часовой пояс, следует модифицировать переменную окружения TZ.

Список часовых поясов обычно находится в папке /usr/share/zoneinfo. На платформе Mac перечень допустимых имен поясов хранится в файле /usr/share/zoneinfo/zone.tab. Не забывайте, что это решение (а в особенности конкретные значения) зависит от операционной системы, а сама операционная система, возможно, уже установила подходящее
значение часового пояса от вашего имени.

Задавать значение TZ можно в любом месте программы, но если вы хотите изменить его для всего веб-приложения, лучше поместить соответствующий код в файл environment.rb рядом с другими настройками часового пояса.

ENV['TZ'] = 'US/Eastern'

А что если вы хотите поддерживать разные часовые пояса для разных пользователей? Прежде всего следует сказать библиотеке ActiveRecord, чтобы она хранила время в базе данных в виде UTC (воспользовавшись модулем Time.utc).

# Потребовать, чтобы Active Record использовала время UTC, а не локальное
config.active_record.default_timezone = :utc

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

Существует gem-пакет TZInfo1, написанный на чистом Ruby, в который входит класс TimeZone, корректно работающий с летним временем. Его можно подставить вместо одноименного класса в Rails. Чтобы воспользоваться им в своем приложении, установите пакет TZInfo и подключаемый модуль tzinfo_timezone. Однако, поскольку это решение написано целиком на Ruby, оно работает медленно (хотя не исключено, что для вашей задачи его быстродействия хватит).

Но постойте – разве класс Time, входящий в стандартный дистрибутив Ruby, не умеет корректно работать с летним временем?

>> Time.now
=> Mon Nov 27 16:32:51 -0500 2006
>> Time.now.getgm
=> Mon Nov 27 21:32:56 UTC 2006

В сообщении, отправленном в список рассылки rails-mailing-list в августе 2006 года2, Гжегош Данилюк (Grzegorz Daniluk) привел пример, показывающий, как это сделать (и продемонстрировал, что он работает от 7 до 9 раз быстрее TZInfo). Добавьте следующий код в любой модуль-помощник своего приложения или оформите его в виде отдельного класса в папке lib:

# Чтобы преобразовать полученное дату и время в UTC и сохранить в БД
def user2utc(t)
ENV["TZ"] = current_user.time_zone_name
res = Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec).utc
ENV["TZ"] = "UTC"
res
end
# Чтобы отобразить дату и время
def utc2user(t)
ENV["TZ"] = current_user.time_zone_name
res = t.getlocal
ENV["TZ"] = "UTC"
res
end

Дополнительные конфигурационные параметры

Мы рассмотрели все конфигурационные параметры, для которых в стандартном файле environment.rb имеются примеры. Существуют и другие параметры, но я подозреваю, что вы о них не знаете, и вряд ли они когда-нибудь понадобятся. Если хотите ознакомиться со всем списком, загляните в исходный текст или в документацию по классу Configuration, которая начинается примерно со строки 400 файла railties/lib/initializer.rb.

КОММЕНТАРИИ