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

Ключевые новинки в Rails 3.2:

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

Обновление до Rails 3.2

Если обновляете существующее приложение, было бы хорошо иметь перед этим покрытие тестами. Также, до попытки обновиться до Rails 3.2, необходимо сначала обновиться до Rails 3.1 и убедиться, что приложение все еще выполняется так, как нужно. Затем нужно предпринять следующие изменения:

Rails 3.2 требует как минимум Ruby 1.8.7

Rails 3.2 требует Ruby 1.8.7 или выше. Поддержка всех прежних версий Ruby была официально прекращена, и следует обновиться как можно быстрее. Rails 3.2 также совместим с Ruby 1.9.2.

Отметьте, что в Ruby 1.8.7 p248 и p249 имеются ошибки маршализации, ломающие Rails. Хотя в Ruby Enterprise Edition это было исправлено, начиная с релиза 1.8.7-2010.02. В ветке 1.9, Ruby 1.9.1 не пригоден к использованию, поскольку он иногда вылетает, поэтому, если хотите использовать 1.9.x перепрыгивайте на 1.9.2 для гладкой работы.

Что обновить в приложении

# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict

# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
config.active_record.auto_explain_threshold_in_seconds = 0.5

Также необходимо добавить конфиг mass_assignment_sanitizer в config/environments/test.rb:

# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict

Что обновить в ваших engine-ах

Замените код ниже комментариев в script/rails следующим содержимым:

ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/your_engine_name/engine', __FILE__)

require 'rails/all'
require 'rails/engine/commands'

Создание приложения Rails 3.2

# Необходим установленный рубигем 'rails'
$ rails new myapp
$ cd myapp

Сторонние гемы

Сейчас Rails использует Gemfile в корне приложения, чтобы определить гемы, требуемые для запуска вашего приложения. Этот Gemfile обрабатывается Bundler, который затем устанавливает все зависимости. Он может даже установить все зависимости локально в ваше приложение, и оно не будет зависеть от системных гемов.

Подробнее: – Bundler homepage

Живите на грани

Bundler и Gemfile замораживает ваше приложение Rails с помощью новой отдельной команды bundle. Если хотите установить напрямую из репозитория Git, передайте флажок --edge:

$ rails new myapp --edge

Если у вас есть локальная копия репозитория Rails, и вы хотите создать приложение с ее использованием, передайте флажок --dev:

$ ruby /path/to/rails/railties/bin/rails new myapp --dev

Основные особенности

Быстрый режим Development и роутинг

В Rails 3.2 режим development стал ощутимо быстрее. Вдохновившись работой Active Reload, Rails перезагружает классы только тогда, когда файлы фактически изменились. В больших приложениях наблюдается существенный прирост производительности. Распознавание маршрутов также получило прирост скорости, благодаря новому engine Journey.

Автоматические Explain запросов

Rails 3.2 поставляется с прекрасной возможностью раскрытия запросов, созданных ARel, определив метод explain в ActiveRecord::Relation. Для примера, можно запустить что-то наподобие puts Person.active.limit(5).explain и результат запроса ARel будет раскрыт. Это позволяет проверку правильности индексирования и дальнейшую оптимизацию.

Запросы, выполняющиеся более чем пол секунды, автоматически раскрываются в режиме development. Это поведение, разумеется, может быть изменено.

Тегированное логирование

При запуске многопользовательского приложения может сильно помочь фильтрация в логе, кто что делал. TaggedLogging в Active Support помогает это сделать точным, помечая строки лога поддоменами, id запросов и чем угодно, что поможет вам отладить такие приложения.

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

Начиная с Rails 3.2, руководства по Rails доступны для Kindle, и как бесплатные Kindle Reading Apps для iPad, iPhone, Mac, Android и т.д.

Railties

config.railties_order = [Blog::Engine, :main_app, :all]
rails g scaffold Post title:string:index author:uniq price:decimal{7,2}

создаст индексы для title и author, причем последний будет уникальным индексом. Некоторые типы, такие как decimal, принимают произвольные опции. В примере price будет столбцом decimal с установленными точностью и масштабом 7 и 2 соответственно.

Устарело

Action Mailer

Action Pack

Action Controller

class CarsController
  layout 'single_car', :only => :show
end

Rails будет использовать ‘layouts/single_car’ если запрос придет в экшн :show, и использовать ‘layouts/application’ (или ‘layouts/cars’, если он существует), если запрос придет в любой другой экшн.

cookies[:email] = 'user@example.com'
get :index
assert_equal 'user@example.com', cookies[:email]

Для очистки куки используйте clear.

cookies.clear
get :index
assert_nil cookies[:email]

Больше не пишется HTTP_COOKIE и куки сохраняются между запросами, поэтому если нужно манипулировать средой для вашего теста, это нужно сделать до того, как куки будут созданы.

Устарело
class ApplicationController
  layout "application"
end

class PostsController < ApplicationController
end

В вышеуказанном примере контроллер Posts больше не будет автоматически искать макет posts. Если вам нужна такая функциональность, следует либо убрать layout “application” из ApplicationController или явно установить его в nil в PostsController.

Action Dispatch

Устарело

Action View

<%= form_for @post do |f| %>
  <%= f.button %>
<% end %>
<%= form_for(@offer, :namespace => 'namespace') do |f| %>
  <%= f.label :version, 'Version' %>:
  <%= f.text_field :version %>
<% end %>
@items.each do |item|
  content_tag_for(:li, item) do
     Title: <%= item.title %>
  end
end

Можно сделать так:

content_tag_for(:li, @items) do |item|
  Title: <%= item.title %>
end
Устарело

Sprockets

Active Record

class User < ActiveRecord::Base
  store :settings, accessors: [ :color, :homepage ]
end

u = User.new(color: 'black', homepage: '37signals.com')
u.color                          # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
rake db:migrate SCOPE=blog
Client.where(:active => true).pluck(:id)
# SELECT id from clients where active = 1
Client.select('DISTINCT name')

..может быть записано так:

Client.select(:name).uniq

В relation также можно отменить уникальность:

Client.select(:name).uniq.uniq(false)
has_many :clients, :class_name => :Client # Отметьте, что символ должен начинаться с заглавной буквы
User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson")

Поэтому возможно написать следующее:

class Order < ActiveRecord::Base
  def cancel!
    transaction do
      lock!
      # ... cancelling logic
    end
  end
end

как:

class Order < ActiveRecord::Base
  def cancel!
    with_lock do
      # ... cancelling logic
    end
  end
end

Устарело

Thread.new { Post.find(1) }.join

Он должен быть изменен, чтобы закрывать соединение с базой данных в конце треда:

Thread.new {
  Post.find(1)
  Post.connection.close
}.join

Об этом должны беспокоиться только те, кто в своих приложениях создает треды.

class Project < ActiveRecord::Base
  self.table_name = "project"
end

Или определите собственный метод self.table_name:

class Post < ActiveRecord::Base
  def self.table_name
    "special_" + super
  end
end

Post.table_name # => "special_posts"

Active Model

Устарело

Active Resource

Active Support

Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))

Logger.tagged("BCX") { Logger.info "Stuff" }
# Logs "[BCX] Stuff"

Logger.tagged("BCX", "Jason") { Logger.info "Stuff" }
# Logs "[BCX] [Jason] Stuff"

Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } }
# Logs "[BCX] [Jason] Stuff"
Event.where(:created_at => Time.now.all_week)
Event.where(:created_at => Time.now.all_day)

Устарело

f = File.open('foo.log', 'w')
f.sync = true
ActiveSupport::BufferedLogger.new f