Как стать автором
Обновить

Слияние Rails и Merb: Год спустя (часть 1 из 6)

Время на прочтение9 мин
Количество просмотров801
Автор оригинала: Yehuda Katz
Шесть последовательных статей о слиянии Rails и Merb были опубликованы на www.engineyard.com с декабря 2009 до апреля 2010 года. Это первая из них.

Сегодня ровно год с того дня, как мы объявили о слиянии Rails и Merb. Тогда было много скептических отзывов относительно успешности этого предприятия. Самой распространенной ассоциацией у тех, кто слышал о наших планах, был единорог. На RailsConf в прошлом году, и DHH (Девид Хайнемайер Ханссон, автор Rails. — прим. перев.), и я упоминали единорога в своих докладах, вызывая смех относительно поставленных нами огромных ожиданий и явной невозможности выполнить их для версии 3.0.

Прошел год, настало время отразить то, как хорошо мы потрудились для достижения поставленных целей. На протяжении следующих нескольких дней я детально опишу прогресс, совершенный в сторону каждого из пунктов моего оригинального поста (http://www.engineyard.com/blog/2008/rails-and-merb-merge/).

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

Модульность


Rails станет более модульным, начиная с реализации самого ядра, включая возможность включать или выключать отдельные компоненты по желанию. Мы сфокусируемся на уменьшении повторений кода внутри Rails, чтобы сделать возможной замену частей Rails без вмешательства в остальные части. Это то, чем является хваленая «модульность» Merb.

Мы потратили большое количество времени на этом этапе, принесшем нам действительно много плодов. Я приведу несколько характерных примеров.

ActiveSupport

Во-первых, мы прошлись по ActiveSupport, сделав его приспособленным к выбору нужных элементов. Это значит, что использование inflector (перевод слов из единственного числа во множественное и наоборот), time extensions, class extensions, и всего остального теперь возможно без самостоятельного изучения графа зависимостей. Вот что я имею ввиду (на примере метода to_sentence в ActiveSupport из Rails 2.3):
Copy Source | Copy HTML<br/>module ActiveSupport<br/>  module CoreExtensions<br/>    module Array<br/>      module Conversions<br/>        def to_sentence(options = {})<br/>          ...<br/>          options.assert_valid_keys :words_connector, :two_words_connector, :last_word_connector, :locale<br/>          ...<br/>        end<br/>      ...<br/>    end<br/>  end<br/>end <br/>

Как видите, в to_sentence присутствует вызов assert_valid_keys, который находится в другом модуле, что означает, что чтобы подключить только active_support/core_ext/array/conversions, вам нужно было бы пройтись по файлу, найти все неявные зависимости, и подключить соответствующие модули отдельно. И конечно, структура этих зависимостей могла бы легко измениться в будущей версии Rails, поэтому надеяться на то, что найдется в результате, было бы небезопасно. В Rails 3 тот же файл начинается с:
Copy Source | Copy HTML<br/>require 'active_support/core_ext/hash/keys'<br/>require 'active_support/core_ext/hash/reverse_merge'<br/>require 'active_support/inflector' <br/>

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

Лучше того, теперь отдельные части Rails явно декларируют свои зависимости на ActiveSupport. Так, например, код, добавляющий запись в лог для ActionController получил такие строки в начале:
Copy Source | Copy HTML<br/>require 'active_support/core_ext/logger'<br/>require 'active_support/benchmarkable' <br/>

Это означает, что все части Rails теперь знают, какие именно части ActiveSupport им нужны. Для простоты сделано так, что Rails 3 поставляется с полным набором модулей ActiveSupport, так что вы сможете использовать вещи типа 3.days или 3.kilobytes без проблем. В то же время, если вы хотите большего контроля над тем, какие именно модули подключаются в приложение, это возможно. Вы можете объявить config.active_support.bare = true в конфигурации, и включатся только те части ActiveSupport, которые явно указаны в файлах проекта. Вам все равно придется инклудить разные побрякушки, если хотите их использовать – 3.days не будет работать прямо из коробки с включенным флагом bare.

ActionController

Еще одна область нуждавшаяся в переработке — это ActionController. Раньше ActionController содержал множество в корне отличных элементов внутри себя. При ближайшем рассмотрении мы обнаружили, что на самом деле это были три различных компонента.

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

В Rails 3 каждый из этих компонент отделен от другого. Функциональность диспетчера была вынесена в ActionDispatch, ее код ужат и действительно превращен в концептуальный компонент. Части ActionController, предназначенные для использования не-HTTP контроллерами, были перемещены в новый компонент под названием AbstractController, от которого наследуются и ActionController, и ActionMailer.

Наконец, сам ActionController подвергся тщательной реконструкции. По сути, мы изолировали каждый отдельный компонент и сделали возможным начать с минимума и дополнить его компонентами на ваш выбор. Наш старый друг ActionController::Base просто начинает оттуда же и добавляет все нужное. Например, посмотрите на начало новой версии этого класса:
Copy Source | Copy HTML<br/>module ActionController<br/>  class Base < Metal<br/>    abstract!<br/>include AbstractController::Callbacks<br/>include AbstractController::Logger<br/>include ActionController::Helpers<br/>include ActionController::HideActions<br/>include ActionController::UrlFor<br/>include ActionController::Redirecting<br/>include ActionController::Rendering<br/>include ActionController::Renderers::All<br/>include ActionController::Layouts<br/>include ActionController::ConditionalGet<br/>include ActionController::RackDelegation<br/>include ActionController::Logger<br/>include ActionController::Benchmarking<br/>include ActionController::Configuration <br/>

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

Вот полностью рабочий контроллер Rails 3:
Copy Source | Copy HTML<br/>class FasterController < ActionController::Metal<br/>  abstract!<br/>  # Rendering будет включен лейаутами, но я включу <br/>  # его тут для понятности<br/>  include ActionController::Rendering <br/>  include ActionController::Layouts<br/>  append_view_path Rails.root.join("app/views") <br/>end<br/> <br/>class AwesomeController < FasterController <br/>  def index <br/>    render "очень_быстро" <br/>  end <br/>end <br/>

И дальше в файле маршрутизации с полным успехом можно сделать:
Copy Source | Copy HTML<br/>MyApp.routes.draw do<br/>  match "/must_be_fast", :to => "awesome#index"<br/>end <br/>

В сущности, ActionController::Base стал всего лишь одним из способов создания контроллеров. Это то же, что классический Rails, но с возможностью сделать свое, если оригинал не по вкусу. Действительно легко привести в соответствие вашим требованиям: если вы хотите добавить функциональность before_filter в FasterController, можно просто добавить AbstractController::Callbacks.

Заметьте, что добавление этих модулей автоматически добавило AbstractController::Rendering (функциональность рендеринга общая с ActionMailer), AbstractController::Layouts и ActiveSupport::Callbacks.

Это делает возможным элементарно добавить только нужную вам функциональность в чувствительных для производительности местах без необходимости использования полностью другого API. Если требуется дополнительная функциональность, вы легко можете добавить дополнительные модули или в конце-концов использовать полный ActionController::Base без необходимости отказываться от чего-то по пути.

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

Непосредственная выгода из всего этого — что ActionMailer намеренно получает всю функциональность ActionController простым путем. Все, начиная с лейаутов и хелперов до фильров использует идентичный код, используемый в ActionController, так что ActionMailer никогда не сможет соскользнуть с функциональности ActionController (в процессе того, как будет развиваться сам ActionController).

Мидлваре также получает руку помощи. ActionController::Middleware, мидлваре со всеми преимуществами ActionController, позволяет вам добавлять любые возможности ActionController по вашему усмотрению (как Rendering, ConditionalGet, Request и Response объекты, и т.д.). Вот пример:
Copy Source | Copy HTML<br/># Длинный способ<br/>class AddMyName < ActionController::Middleware<br/>  def call(env)<br/>    status, headers, body = @app.call(env)<br/>    headers["X-Author"] = "Yehuda Katz"<br/>    headers["Content-Type"] = "application/xml"<br/> <br/>    etag = env["If-None-Match"]<br/>    key = ActiveSupport::Cache.expand_cache_key(body + "Yehuda Katz")<br/>    headers["ETag"] = %["#{Digest::MD5.hexdigest(key)}"]<br/>    if headers["ETag"] == etag<br/>      headers["Cache-Control" = "public"]<br/>      return [304, headers, [" "]]<br/>    end<br/> <br/>    return status, headers, body<br/>  end<br/>end<br/> <br/>

Copy Source | Copy HTML<br/># Использование дополнительных Rack хелперов<br/>class AddMyName < ActionController::Middleware<br/>  include ActionController::RackDelegation<br/> <br/>  def call(env)<br/>    self.status, self.headers, self.response_body = @app.call(env)<br/> <br/>    headers["X-Author"] = "Yehuda Katz"<br/> <br/>    # вы можете делать теперь больше хороших вещей<br/>    self.content_type = Mime::XML # delegates to the response<br/>    response.etag = "#{response.body}Yehuda Katz"<br/>    response.cache_control[:public] = true<br/> <br/>    self.status, self.response_body = 304, nil if request.fresh?(response)<br/> <br/>    response.to_a<br/>  end<br/>end <br/>

Copy Source | Copy HTML<br/># Использование хелперов ConditionalGet<br/>class AddMyName < ActionController::Middleware<br/>  # подключение RackDelegation<br/>  include ActionController::ConditionalGet<br/> <br/>  def call(env)<br/>    self.status, self.headers, self.response_body = @app.call(env)<br/> <br/>    headers["X-Author"] = "Yehuda Katz"<br/> <br/>    self.content_type = Mime::XML<br/>    fresh_when :etag => "#{response.body}Yehuda Katz", :public => true<br/> <br/>    response.to_a<br/>  end<br/>end <br/>

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

В следующий раз я расскажу о улучшении производительности в Rails 3. Надеюсь, это будет не слишком быстро. :)
Теги:
Хабы:
+41
Комментарии12

Публикации

Истории

Работа

Ruby on Rails
11 вакансий

Ближайшие события