Rails3.2へのupgrade方法と機能について


#318 Upgrading to Rails 3.2 - RailsCastsを見て
手持ちのRails3.1アプリをRails3.2.2へupgradeしてみました。
あと、後半は新機能のお試しです。


すでにRails 3.2.2がリリースされています。
3.1系から一気に3.2.2へupgradeも可能です。


upgrade作業自体は、3.1の時のような面倒さは無く
いわいる普通のupgrade作業でした。

準備

0.Rails3.1へ

Rails2系の人はひとまず
Rails3.1.0へのupgrade方法 - うんたらかんたら日記こちらへ

1.Rails3.1(3.1.4)の最新へ

1)Gemfile内のrailsのversionをあげる
2)bundle update rails
3)bundle exec rake rails:update
4)差分を確認しながら必要な箇所は更新 bundle exec rake db:migrate
5)テストが通ることを確認 bundle exec rake spec


2)実行時に下記エラーとなりました。
rack versionがあがっているようなので、エラーメッセージのようにして、再度実施。
(※あとで動画見直したら、Gemfile内にrackの指定がなかったので、多分rackの行は削除でOK)

Bundler could not find compatible versions for gem "rack":
  In Gemfile:
    rails (= 3.1.4) ruby depends on
      rack (~> 1.3.6) ruby

    rack (1.3.3)

upgrade

2.ブランチをきる

まずは作業用ブランチを切ります。

git checkout -b rails32
3.Gemfile修正

1)Gemfile内のrails他のversionをあげる
私は3.2.0にupgrade後3.2.2へ再びupgradeしましたが、
設定ファイルの変更点は無かったので、直3.2.2へupgradeでも問題ないでしょう。

gem 'rails', '3.2.0'

group :assets do
  gem 'sass-rails', "  ~> 3.2.3"
  gem 'coffee-rails', "~> 3.2.1"
  gem 'uglifier', '>= 1.0.3'
end

2)bundle update

2)実行時に下記エラーとなりました。
rack versionがあがっているようなので、エラーメッセージのようにして、再度実施。
(※あとで動画見直したら、Gemfile内にrackの指定がなかったので、多分rackの行は削除でOK)

Bundler could not find compatible versions for gem "rack":
  In Gemfile:
    rails (= 3.2.0) ruby depends on
      rack (~> 1.4.0) ruby

    rack (1.3.6)

各種設定ファイルを修正

railscastsではコピペしてますが、
rake rails:update での差分と同じなので
各自で確認しながら必要な箇所を更新していくと
追加機能の設定個所が分かっておすすめです。

4.development.rb

追加。

# 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
5.production.rb

ここは変更は無いのですが、下記のようにコメントが追加されています。
log_tagsと、loggerのclass変更、auto_explain_threshold_in_secondsが
利用できるようになっています(後述)。

+   # Prepend all log lines with the following tags
+   # config.log_tags = [ :subdomain, :uuid ]
+ 
    # Use a different logger for distributed setups
-   # config.logger = SyslogLogger.new
+   # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)


+   # 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
6.test.rb

追加。

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

削除。

# Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
config.assets.allow_debugging = true
その他

こんなのも追加されてます。
config/initializers/inflections.rb

+ #
+ # These inflection rules are supported but not enabled by default:
+ # ActiveSupport::Inflector.inflections do |inflect|
+ #   inflect.acronym 'RESTful'
+ # end
7.テストが通ることを確認 bundle exec rake spec

あとは、masterにmerge。

以上。

新機能について

ほぼascii castと同じです。
自分なりに試した内容を記載しています。

mass_assignment_sanitizer

development.rbに追加された機能設定です。
mass assingment対策(attr_protected)している際、
更新対象カラムに含まれていた場合、例外が発生します。


試してみると下記のような例外が発生しました。
いい機能ですね。

ActiveModel::MassAssignmentSecurity::Error in UsersController#update
Can't mass-assign protected attributes: admin
auto_explain_threshold_in_seconds

これも素晴らしい。遅いクエリに対してexplainを実行してログに出力。

explain

putsがおすすめ。

>> puts User.order(:name).explain
  User Load (0.3ms)  SELECT "users".* FROM "users" ORDER BY name
  EXPLAIN (0.1ms)  EXPLAIN QUERY PLAN SELECT "users".* FROM "users" ORDER BY name
EXPLAIN for: SELECT "users".* FROM "users"  ORDER BY name
0|0|0|SCAN TABLE users (~1000000 r
pluck

User.select(:name).map(&:name)と結果は同等です。

>> User.pluck(:name)
   (0.3ms)  SELECT name FROM "users" 
=> ["foo", "bar"]
uniq

重複排除。

first_or_create / first_or_create!

これも名前の通り。

safe_constantize

これはリリースノートから見落としていました。
constanize(こいつも知りませんでしたが)の例外発生させないverのようです。

>> "User".safe_constantize
=> User(id: integer, name: string, admin: integer, created_at: datetime, updated_at: datetime)
>> "User".constantize
=> User(id: integer, name: string, admin: integer, created_at: datetime, updated_at: datetime)
>> "Usera".safe_constantize
=> nil

"Usera".constantize
NameError: uninitialized constant Usera
migration

リリースノート見て理解できてませんでしたが
:index 指定が追加されています。あと小数点の設定も可、型のデフォルトはstring。

$ rails g model product_variation product_id:integer:index name 'price:decimal{7,2}'
.railsrc

今のところ使う予定は無いですが、
下記のようにしておくとrailsコマンド時にoption設定が自動で読まれるようです。

$ echo -d postgresql -T > ~/.railsrc
ActiveRecord KVS

いまいち理解していませんでしたが、面白い機能追加です。
ActiveRecord上に専用のカラムを追加して、下記のように定義すると
kvsチックに利用できます。

たまに、「運用時にデータ構成が変化したりするデータをhash形式で放り込んで自前でパースして」
みたいなことをやったりしますが、パースをActiveRecordで勝手にやってくれるので便利そうです。
当然ながら検索条件としては利用できません。

#model
class User < ActiveRecord::Base
  attr_protected :admin
  store :properties, accessors: [:colour, :size]
end
# migration
$ rails g migration add_properties_to_user properties:text
$ rake db:migrate
# rails console
>> u = User.new(colour: 'blue', size: 3)
=> #<User id: nil, name: nil, admin: 0, created_at: nil, updated_at: nil, properties: {:colour=>"blue", :size=>3}>
>> u.colour
=> "blue"
>> u.properties
=> {:colour=>"blue", :size=>3}
>> u.properties[:colour]
=> "blue"
# db
sqlite> select '/' || properties || '/'  from users where id = 4;
/---
:colour: blue
:size: 3
/

hash形式で入ってますね。

log_tags

config.log_tags = [ :uuid, :remote_ip, :port ]
これもいいですね。
ActiveSupport::TaggedLoggingの機能で[]を付けて表示しているっぽい。

Started GET "/users" for 127.0.0.1 at 2012-03-04 20:08:49 +0900
[9fa15030ac1b5e96e035fd8ebc2305bc] [127.0.0.1] [3000] Processing by UsersController#index as HTML
[9fa15030ac1b5e96e035fd8ebc2305bc] [127.0.0.1] [3000]   User Load (0.3ms)  SELECT "users".* FROM "users" 
[9fa15030ac1b5e96e035fd8ebc2305bc] [127.0.0.1] [3000]   Rendered users/index.html.erb within layouts/application (16.3ms)
[9fa15030ac1b5e96e035fd8ebc2305bc] [127.0.0.1] [3000] Completed 200 OK in 107ms (Views: 97.8ms | ActiveRecord: 0.3ms)
[6816ed840b5a98fcb1c8042567c1df51] [127.0.0.1] [3000]