Заметки о релизе Ruby on Rails 2.2

Ruby on Rails 2.2 принёс несколько новых и улучшенных возможностей: интернационализация, поддержка тредов, ETag, пул соединений и многое другое.

Rails 2.2 принёс несколько новых и улучшенных возможностей. Этот список покрывает только основные улучшения, но не включает все мелкие исправления программных ошибок и изменения. Чтобы увидеть всё, обратитесь к списку коммитов в главном репозитории Rails на GitHub.

Вместе с Rails 2.2 знаменует собой запуск Ruby on Rails Guides — первый результат продолжающегося хакфеста Rails Guides. Этот сайт предоставит высококачественную документацию по основным возможностям Rails.


Инфраструктура

Rails 2.2 — это значимый релиз для инфраструктуры, которая позволяет Rails работать гладко и быть связанным с остальным миром.

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

Rails 2.2 предоставляет простую систему для интернационализации (или i18n, для тех, кому надоело набирать длинное слово).

Совместимость с Ruby 1.9 и JRuby

Наряду с потокобезопасностью, было проделано много работы, чтобы Rails хорошо работал с JRuby и грядущим Ruby 1.9. Поскольку Ruby 1.9 — это движущаяся цель, запуск edge Rails на edge Ruby всё ещё остаётся делом удачи, но Rails готов к переходу на Ruby 1.9, когда тот будет выпущен.

Документация

Внутренняя документация Rails в форме комментариев в коде была улучшена во многих местах. Кроме того, проект Ruby on Rails Guides является авторитетным источником информации по основным компонентам Rails. В его первом официальном релизе страница Guides включает:

В целом, Guides предоставляют десятки тысяч слов руководства для начинающих и средних Rails-разработчиков.

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

$ rake doc:guides

Это поместит руководства в Rails.root/doc/guides, и вы сможете сразу же начать читать, открыв Rails.root/doc/guides/index.html в любимом браузере.

Лучшая интеграция с HTTP: поддержка ETag из коробки

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

class ArticlesController < ApplicationController
  def show_with_respond_to_block
    @article = Article.find(params[:id])

    # Если запрос отправляет заголовки, отличающиеся от опций, переданных в stale?, то
    # запрос действительно устарел, и блок respond_to запускается (а опции
    # из вызова stale? устанавливаются в response).
    #
    # Если заголовки запроса совпадают, то запрос свежий, и блок respond_to не
    # запускается. Вместо этого происходит рендеринг по умолчанию, который проверит заголовки
    # last-modified и etag и заключит, что нужно только отправить "304 Not Modified" вместо
    # рендеринга шаблона.
    if stale?(:last_modified => @article.published_at.utc, :etag => @article)
      respond_to do |wants|
        # обычная обработка ответа
      end
    end
  end

  def show_with_implied_render
    @article = Article.find(params[:id])

    # Устанавливает заголовки ответа и проверяет их относительно запроса. Если запрос устарел
    # (т.е. нет совпадения по etag или last-modified), то происходит рендеринг шаблона по умолчанию.
    # Если запрос свежий, то рендеринг по умолчанию вернёт "304 Not Modified"
    # вместо рендеринга шаблона.
    fresh_when(:last_modified => @article.published_at.utc, :etag => @article)
  end
end

Потокобезопасность

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

Чтобы включить многопоточную диспетчеризацию в режиме production вашего приложения, добавьте следующую строку в config/environments/production.rb:

config.threadsafe!

Active Record

Здесь стоит упомянуть два больших дополнения: транзакционные миграции и пулы соединений с базой данных. Также появился новый (и более чистый) синтаксис для условий по join-таблицам, а также ряд более мелких улучшений.

Транзакционные миграции

Исторически многоступенчатые миграции Rails были источником проблем. Если что-то шло не так во время миграции, всё до ошибки изменяло базу данных, а всё после ошибки не применялось. Кроме того, версия миграции сохранялась как выполненная, что означало, что её нельзя было просто перезапустить через rake db:migrate:redo после исправления проблемы. Транзакционные миграции изменяют это, оборачивая шаги миграции в DDL-транзакцию, так что если какой-либо из них завершается неудачно, вся миграция отменяется. В Rails 2.2 транзакционные миграции поддерживаются для PostgreSQL из коробки. Код расширяем для других типов баз данных в будущем — и IBM уже расширила его для поддержки адаптера DB2.

Пул соединений

Пул соединений позволяет Rails распределять запросы к базе данных по пулу соединений, который растёт до максимального размера (по умолчанию 5, но вы можете добавить ключ pool в ваш database.yml, чтобы настроить это). Это помогает устранить узкие места в приложениях, которые поддерживают много одновременных пользователей. Также есть wait_timeout, который по умолчанию равен 5 секундам, прежде чем сдаться. ActiveRecord::Base.connection_pool даёт вам прямой доступ к пулу, если он вам нужен.

development:
  adapter: mysql
  username: root
  database: sample_development
  pool: 10
  wait_timeout: 10

Хэши для условий по join-таблицам

Теперь вы можете задавать условия по join-таблицам, используя хэш. Это большая помощь, если нужно делать запрос через сложные join'ы.

class Photo < ActiveRecord::Base
  belongs_to :product
end

class Product < ActiveRecord::Base
  has_many :photos
end

# Получить все продукты с фотографиями без авторских прав:
Product.all(:joins => :photos, :conditions => { :photos => { :copyright => false }})

Новые динамические поисковики

Два новых набора методов были добавлены в семейство динамических поисковиков Active Record.

find_last_by_attribute

Метод find_last_by_attribute эквивалентен Model.last(:conditions => {:attribute => value})

# Получить последнего пользователя, зарегистрировавшегося из Лондона
User.find_last_by_city('London')

find_by_attribute!

Новая bang!-версия find_by_attribute! эквивалентна Model.first(:conditions => {:attribute => value}) || raise ActiveRecord::RecordNotFound. Вместо возврата nil, если не удаётся найти подходящую запись, этот метод вызовет исключение, если не найдёт совпадения.

# Вызвать исключение ActiveRecord::RecordNotFound, если 'Moby' ещё не зарегистрировался!
User.find_by_name!('Moby')

Связи уважают приватную/защищённую область видимости

Прокси связей Active Record теперь уважают область видимости методов на прокси-объекте. Ранее (при User has_one :account) вызов @user.account.private_method вызывал бы приватный метод на связанном объекте Account. В Rails 2.2 это не работает; если вам нужна такая функциональность, используйте @user.account.send(:private_method) (или сделайте метод публичным вместо приватного или защищённого). Обратите внимание, что если вы переопределяете method_missing, вам также следует переопределить respond_to, чтобы поведение совпадало и связи функционировали нормально.

Прочие изменения Active Record

  • rake db:migrate:redo теперь принимает опциональный VERSION, чтобы нацелиться на конкретную миграцию для повторного выполнения
  • Установите config.active_record.timestamped_migrations = false, чтобы миграции имели числовой префикс вместо UTC-метки времени.
  • Колонки счётчика кэша (для связей, объявленных с :counter_cache => true) больше не нужно инициализировать нулём.
  • ActiveRecord::Base.human_name — для человекочитаемого перевода имён моделей с учётом интернационализации.

Action Controller

На стороне контроллера есть несколько изменений, которые помогут привести в порядок ваши маршруты. Также есть внутренние изменения в движке роутинга для снижения использования памяти в сложных приложениях.

Поверхностное вложение маршрутов

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

map.resources :publishers, :shallow => true do |publisher|
  publisher.resources :magazines do |magazine|
    magazine.resources :photos
  end
end

Это включит распознавание (среди прочих) следующих маршрутов:

/publishers/1           ==> publisher_path(1)
/publishers/1/magazines ==> publisher_magazines_path(1)
/magazines/2            ==> magazine_path(2)
/magazines/2/photos     ==> magazines_photos_path(2)
/photos/3               ==> photo_path(3)

Массивы методов для маршрутов member или collection

Теперь вы можете предоставить массив методов для новых маршрутов member или collection. Это устраняет неудобство необходимости определять маршрут как принимающий любой глагол, как только он должен обрабатывать более одного. С Rails 2.2 это законное объявление маршрута:

map.resources :photos, :collection => { :search => [:get, :post] }

Ресурсы с конкретными экшнами

По умолчанию, когда вы используете map.resources для создания маршрута, Rails генерирует маршруты для семи экшнов по умолчанию (index, show, create, new, edit, update и destroy). Но каждый из этих маршрутов занимает память в вашем приложении и заставляет Rails генерировать дополнительную логику роутинга. Теперь вы можете использовать опции :only и :except для тонкой настройки маршрутов, которые Rails сгенерирует для ресурсов. Можно передать один экшн, массив экшнов или специальные опции :all или :none. Эти опции наследуются вложенными ресурсами.

map.resources :photos, :only => [:index, :show]
map.resources :products, :except => :destroy
  • Ведущий контрибьютор: Tom Stuart

Прочие изменения Action Controller

  • Теперь вы можете легко показать кастомную страницу ошибки для исключений, возникающих при роутинге запроса.
  • Заголовок HTTP Accept теперь отключён по умолчанию. Предпочитайте использовать форматированные URL (например, /customers/1.xml) для указания нужного формата. Если вам нужны заголовки Accept, вы можете снова их включить с config.action_controller.use_accept_header = true.
  • Числа бенчмарков теперь отображаются в миллисекундах, а не в крошечных долях секунд.
  • Rails теперь поддерживает HTTP-only куки (и использует их для сессий), что помогает снизить некоторые риски межсайтового скриптинга в новых браузерах.
  • redirect_to теперь полностью поддерживает URI-схемы (так, например, вы можете перенаправить на svn`ssh: URI).
  • render теперь поддерживает опцию :js для рендеринга обычного JavaScript с правильным MIME-типом.
  • Защита от подделки запросов была усилена для применения только к запросам контента в формате HTML.
  • Полиморфные URL ведут себя более разумно, если переданный параметр равен nil. Например, вызов polymorphic_path([@project, @date, @area]) с nil date даст вам project_area_path.

Action View

  • javascript_include_tag и stylesheet_link_tag поддерживают новую опцию :recursive для использования вместе с :all, чтобы вы могли загрузить целое дерево файлов одной строкой кода.
  • Включённая JavaScript-библиотека Prototype была обновлена до версии 1.6.0.3.
  • RJS#page.reload — для перезагрузки текущего адреса браузера через JavaScript.
  • Хелпер atom_feed теперь принимает опцию :instruct, позволяющую вставлять XML-инструкции обработки.

Action Mailer

Action Mailer теперь поддерживает макеты для мейлеров. Вы можете сделать ваши HTML-письма такими же красивыми, как ваши вью в браузере, предоставив подходящим образом названный макет — например, класс CustomerMailer ожидает использование layouts/customer_mailer.html.erb.

Action Mailer теперь предлагает встроенную поддержку SMTP-серверов GMail, автоматически включая STARTTLS. Для этого требуется установленный Ruby 1.8.7.

Active Support

Active Support теперь предлагает встроенную мемоизацию для Rails-приложений, метод each_with_object, поддержку префиксов в делегатах и различные другие новые утилитарные методы.

Мемоизация

Мемоизация — это паттерн инициализации метода один раз и последующего сохранения его значения для повторного использования. Вы, вероятно, использовали этот паттерн в своих приложениях:

def full_name
  @full_name ||= "#{first_name} #{last_name}"
end

Мемоизация позволяет вам решать эту задачу декларативным образом:

extend ActiveSupport::Memoizable

def full_name
  "#{first_name} #{last_name}"
end
memoize :full_name

Среди других возможностей мемоизации — unmemoize, unmemoize_all и memoize_all для включения или отключения мемоизации.

each_with_object

Метод each_with_object предоставляет альтернативу inject, используя метод, бэкпортированный из Ruby 1.9. Он итерируется по коллекции, передавая текущий элемент и memo в блок.

%w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } # => {'foo' => 'FOO', 'bar' => 'BAR'}

Ведущий контрибьютор: Adam Keys

Делегаты с префиксами

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

class Vendor < ActiveRecord::Base
  has_one :account
  delegate :email, :password, :to => :account, :prefix => true
end

Это создаст делегированные методы vendor#account_email и vendor#account_password. Также можно задать кастомный префикс:

class Vendor < ActiveRecord::Base
  has_one :account
  delegate :email, :password, :to => :account, :prefix => :owner
end

Это создаст делегированные методы vendor#owner_email и vendor#owner_password.

Ведущий контрибьютор: Daniel Schierbeck

Прочие изменения Active Support

  • Обширные обновления ActiveSupport::Multibyte, включая исправления совместимости с Ruby 1.9.
  • Добавление ActiveSupport::Rescuable позволяет любому классу подмешивать синтаксис rescue_from.
  • past?, today? и future? для классов Date и Time, чтобы облегчить сравнения дата/время.
  • Array#secondArray#fifth как алиасы для Array#[1]Array#[4].
  • Enumerable#many? для инкапсуляции collection.size > 1.
  • Inflector#parameterize создаёт версию своего ввода, готовую к использованию в URL, для использования в to_param.
  • Time#advance распознаёт дробные дни и недели, так что можно писать 1.7.weeks.ago, 1.5.hours.since и так далее.
  • Включённая библиотека TzInfo была обновлена до версии 0.3.12.
  • ActiveSupport::StringInquirer даёт вам красивый способ проверять равенство строк: ActiveSupport::StringInquirer.new("abc").abc? => true.

Railties

В Railties (основной код самого Rails) самые большие изменения — в механизме config.gems.

config.gems

Чтобы избежать проблем с развёртыванием и сделать Rails-приложения более самодостаточными, можно поместить копии всех гемов, требуемых вашим Rails-приложением, в /vendor/gems. Эта возможность впервые появилась в Rails 2.1, но в Rails 2.2 она гораздо более гибкая и устойчивая, обрабатывая сложные зависимости между гемами. Управление гемами в Rails включает следующие команды:

  • config.gem _gem_name_ в файле config/environment.rb
  • rake gems — перечислить все настроенные гемы, а также указать, установлены ли они (и их зависимости), заморожены или являются гемами фреймворка (гемы фреймворка — это те, что загружаются Rails до выполнения кода зависимостей гемов; такие гемы не могут быть заморожены)
  • rake gems:install — установить отсутствующие гемы на компьютер
  • rake gems:unpack — поместить копии требуемых гемов в /vendor/gems
  • rake gems:unpack:dependencies — получить копии требуемых гемов и их зависимостей в /vendor/gems
  • rake gems:build — собрать любые отсутствующие нативные расширения
  • rake gems:refresh_specs — привести вендорные гемы, созданные с Rails 2.1, в соответствие с тем, как Rails 2.2 их хранит

Вы можете распаковать или установить один гем, указав GEM=_gem_name_ в командной строке.

Прочие изменения Railties

  • Если вы поклонник веб-сервера Thin, вы будете рады узнать, что script/server теперь поддерживает Thin напрямую.
  • script/plugin install <plugin> -r <revision> теперь работает с git-плагинами так же, как и с svn-плагинами.
  • script/console теперь поддерживает опцию --debugger.
  • Инструкции по настройке сервера непрерывной интеграции для сборки самого Rails включены в исходный код Rails.
  • rake notes:custom ANNOTATION=MYFLAG позволяет вам выводить кастомные аннотации.
  • Rails.env обёрнут в StringInquirer, так что можно делать Rails.env.development?.
  • Чтобы устранить предупреждения об устаревании и правильно обрабатывать зависимости гемов, Rails теперь требует rubygems 1.3.1 или выше.

Устарело

Несколько фрагментов старого кода устарели в этом релизе:

  • Rails::SecretKeyGenerator был заменён на ActiveSupport::SecureRandom.

  • render_component устарел. Доступен плагин render_components, если вам нужна эта функциональность.

  • Неявные локальные присваивания при рендеринге партиалов устарели.

    def partial_with_implicit_local_assignment
      @customer = Customer.new("Marcel")
      render :partial => "customer"
    end

    Ранее код выше делал доступной локальную переменную customer внутри партиала 'customer'. Теперь вы должны явно передавать все переменные через хэш :locals.

  • country_select был удалён. Смотрите страницу устаревания для подробностей и плагина-замены.

  • ActiveRecord::Base.allow_concurrency больше не оказывает никакого эффекта.

  • ActiveRecord::Errors.default_error_messages устарел в пользу I18n.translate('activerecord.errors.messages').

  • Синтаксис интерполяции %s и %d для интернационализации устарел.

  • String#chars устарел в пользу String#mb_chars.

  • Длительности с дробными месяцами или дробными годами устарели. Вместо этого используйте арифметику базовых классов Ruby Date и Time.

  • Request#relative_url_root устарел. Вместо этого используйте ActionController::Base.relative_url_root.

Благодарности

Заметки о релизе скомпилированы Mike Gunderloy

On this page