#318 Upgrading to Rails 3.2
Rails 3.2がリリースされました。公式ブログに変更点のいくつかを説明した優れた記事が掲載されています。比較的重要な変更点として、開発モードで前回のリクエストから変更されたファイルのみをリロードするように変更されたことにより速度が向上しました。またJourneyというまったく新しいルーティングエンジンが採用され、より高速になりました。今回のエピソードでは、Rails 3.1のアプリケーションをRails 3.2にアップグレードする方法といくつかの新機能を紹介します。
アプリケーションのアップグレード
既存のRails 3.1のアプリケーションをRails 3.2にアップグレードするには、まずgemfileを修正します。
source 'http://rubygems.org' gem 'rails', '3.2.0' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', " ~> 3.2.3" gem 'coffee-rails', "~> 3.2.1" gem 'uglifier', '>=1.0.3' end gem 'jquery-rails'
rails gemをversion 3.2.0
にアップグレードし、assets groupのgemのバージョン番号を修正して新規のRails 3.2アプリケーションで使用されているものと一致させます。すべてを最新バージョンにアップデートするために、bundle update
を実行します。
gemfileを修正するのに加えて、既存のアプリケーションを新規のRails 3.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
を:strict
に設定し、一括設定処理時に対象がそれを禁止している場合は例外を発生させます。2行目はauto-explainのしきい値を0.5
に設定しています。これは、データベースへの問合せ処理に0.5秒以上かかる場合に、EXPLAINクエリが実行されてその結果がログに記録されます。mass_assignment_sanitizer
オプションをテスト環境にもコピーして新規のRails 3.2アプリケーションに合わせ、またデフォルトで作成されなくなった以下の行を削除しておくのがいいでしょう。
# Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets config.assets.allow_debugging = true
EXPLAINクエリ
アプリケーションがRails 3.2にアップグレードされたので、Railsコンソールで新機能のいくつかを紹介します。実行に時間がかかるデータベースクエリに対して自動的にEXPLAINクエリが実行されることはすでに説明しましたが、Active Recordクエリに対してexplain
を実行してコンソールから起動することもできます。
1.9.2-p290 :001 > Product.order(:name).explain Product Load (0.6ms) SELECT "products".* FROM "products" ORDER BY name EXPLAIN (0.1ms) EXPLAIN QUERY PLAN SELECT "products".* FROM "products" ORDER BY name => "EXPLAIN for: SELECT \"products\".* FROM \"products\" ORDER BY name\n0|0|0|SCAN TABLE products (~1000000 rows)\n0|0|0|USE TEMP B-TREE FOR ORDER BY\n"
puts _
を使って、結果をより見やすい形で出力することができます。
1.9.2-p290 :002 > puts _ EXPLAIN for: SELECT "products".* FROM "products" ORDER BY name 0|0|0|SCAN TABLE products (~1000000 rows) 0|0|0|USE TEMP B-TREE FOR ORDER BY
このEXPLAINクエリは簡単なクエリのためのものなのであまり面白くないですが、この機能は複雑なJOINクエリと共に利用されるときに非常に役に立ちます。アプリケーションの中で遅いデータベースクエリが実行される箇所がわかっていて、実行される度にEXPLAINクエリを生成させたくない場合は、そのクエリを以下のようにsilence_auto_explain
ブロックでラップします。
1.9.2-p290 :003 > ActiveRecord::Base.silence_auto_explain { Product.order(:name) }
ブロック内のクエリはどれだけ実行に時間がかかってもEXPLAINクエリを生成しません。
新しいActiveRecordメソッド
Rails 3.2のもう一つの有用な新機能はpluckメソッドです。これを利用することで、列名を渡すとその列のすべての値が返されます。
1.9.2-p290 :005 > Product.pluck(:name) (0.2ms) SELECT name FROM "products" => ["Settlers of Catan", "Flux Capacitor", "Technodrome", "Agricola", "Millennium Falcon", "Ryan's Cheesecake", "Answer to Everything"]
これはどの列に対しても有効で、すべての値を適切なデータ型の配列として返します。
1.9.2-p290 :007 > Product.pluck(:id) (0.2ms) SELECT id FROM "products" => [1, 2, 3, 4, 5, 6, 7]
関連して、select節を用いて返される列を制限する場合は新たにuniqメソッドを利用できます。例えばこれを用いて、商品をユニークな名前ごとに一つずつ返すことができます。
1.9.2-p290 :009 > Product.select(:name).uniq Product Load (0.3ms) SELECT DISTINCT name FROM "products" => [#<Product name: "Agricola">, #<Product name: "Answer to Everything">, #<Product name: "Flux Capacitor">, #<Product name: "Millennium Falcon">, #<Product name: "Ryan's Cheesecake">, #<Product name: "Settlers of Catan">, #<Product name: "Technodrome">]
もう一つ関連した新機能であるfirst_or_createはwhere
節と一緒に使われることを意図しています。
1.9.2-p290 :010 > Product.where(name: "Foo").first_or_create!
ここでは末尾に!をつけて、検証エラーがある場合には例外を発生させるようにしました。上のコードはfind_or_create_by_name
という記法で同じように動作します。初めて実行したときは(既存のレコードが存在しないと仮定して)その名前(name
)で新規にProduct
を作成し、再度実行するとその条件に一致する商品を返します。この方法が優れているのは、次のようにcreate
の呼び出しに追加の属性を渡すことができるという点です。
1.9.2-p290 :011 > Product.where(name: "Foo").first_or_create!(price: 5)
新規に作成されたレコードはこれらの属性を継承しますが、Product
を検索する場合にはその属性は考慮されません。
最後に紹介する新しいメソッドはsafe_constantize
という名前で、文字列に対して呼び出せます。
1.9.2-p290 :007 > "Product".safe_constantize => Product(id: integer, name: string, price: decimal, description: text, discontinued: boolean, created_at: datetime, updated_at: datetime)
これは一致する定数が存在する場合にそれを返します。これと通常のconstantize
メソッドとの違いは、一致する定数が見つからなかった場合に例外を発生させるのではなくnil
を返すという点です。
マイグレーションの新機能
次にマイグレーションを生成するときに使うことができる新しいオプションを紹介します。例えば新規にProductVariation
モデルを作成するとしましょう。マイグレーションを次のように指定します。
$ rails g model product_variation product_id:integer:index name 'price:decimal{7,2}'
このマイグレーションは3つの新機能を使用しています。ProductVariation
をProduct
に属する(belong to)よう定義したいので、列の一つをproduct_id
と指定しました。Rails 3.2では3つ目のindex
オプションを指定すると、自動的に列に索引を付加します。
ProductVariation
モデルに文字列のname
という列を追加します。通常はこの場合マイグレーションでname:string
と指定しますが、文字列(string
)がデフォルトになったため列が文字列の場合はデータ型を指定する必要がなくなりました。最後にdecimal
(小数)フィールドを定義する場合、オプションを渡してそのデータ型のprecision
(数字全体の有効桁数)とscale
(小数点以下の桁数)を指定できるようになりました。マイグレーションのこの部分は、シェルが修正しようと試みないように引用符で囲む必要があることに注意してください。
生成されたマイグレーションは次のようになります。
class CreateProductVariations < ActiveRecord::Migration def change create_table :product_variations do |t| t.integer :product_id t.string :name t.decimal :price, :precision => 7, :scale => 2 t.timestamps end add_index :product_variations, :product_id end end
このマイグレーションには指定したproduct_variationsがあり、その中でproduct_id
フィールドは索引が定義され、name
フィールドはデフォルトでstring
(文字列)になり、price
フィールドはprecision
とscale
のオプションが指定されています。
ジェネレーターに関連して、新規にRailsアプリケーションを作成するときに常に指定するオプションがあるかも知れません。例えば、特定のデータベースを指定したり、ユニットテストを除外したりなどです。これらのオプションを、ホームディレクトリの.railsrc
ファイルに保存することで、デフォルトに設定することができます。
$ echo -d postgresql -T > ~/.railsrc
これによって、新規にRailsアプリケーションを作成するときにこれらのオプションが自動的に付加されます。
$ rails new blog Using -d postgresql -T from /Users/eifion/.railsrc
Key-Valueストア
ActiveRecordはモデルにおいて新しい方法でkey-valueストアを定義するようになりました。例えばProductVariation
モデルにおいてcolour
とsize
のような属性があるときに、それぞれに別々の列を作りたくないとします。その場合に新しいstore
メソッドを使ってこれらの値を一つの列に保存できます。
class ProductVariation < ActiveRecord::Base store :properties, accessors: [:colour, :size] end
ここで一つ目の引数は値が保存される列の名前で、二つ目では保存したい属性を定義しています。このデータベースの列を作成しなくてはいけないことには変わりがないので、マイグレーションが必要です。
$ rails g migration add_properties_to_product_variations properties:text
忘れずにrake db:migrate
を実行してデータベースに列を追加します。
これをコンソールで実際におこないます。新規にProductVariation
を作成し、通常の属性の場合と同じようにcolour
とsize
の属性を設定します。
Loading development environment (Rails 3.2.0) 1.9.2-p290 :001 > p = ProductVariation.new(colour: 'blue', size: 3) => #<ProductVariation id: nil, product_id: nil, name: nil, price: nil, created_at: nil, updated_at: nil, properties: {:colour=>"blue", :size=>3}>
他の属性に対してと同じように、これらの属性にアクセスすることができます。
1.9.2-p290 :003 > p.colour => "blue"
properties
ハッシュを介しても同じようにアクセスできます。
1.9.2-p290 :004 > p.properties[:colour] => "blue"
これらの属性は実際のデータベース列ではないので、それらに対して検索をおこなうことはできませんが、属性のハッシュにアクセスする便利な方法です。
タグ付きログ
もう一つ、タグ付きログという機能を紹介します。これは通常は本番稼働環境で利用されるものですが、デモのために開発環境に追加してみます。そのためにlog_tags
の設定オプションを指定し、 それを属性の配列に設定します。ここで属性はリクエストに対して呼び出されるメソッド名です。
config.log_tags = [:uuid, :remote_ip]
Rails 3.2の新しいメソッドであるuuidはユニークなリクエストidを返すので、ログがどのリクエストからのものであるかを特定することができます。もう一つはリモートIPアドレスです。サーバを起動してリクエストを発行すると、ログの各行にはそれらの値がタグ付けされます。
[ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Started GET "/" for 127.0.0.1 at 2012-01-27 21:52:58 +0000 [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Processing by ProductsController#index as HTML [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Product Load (0.3ms) SELECT "products".* FROM "products" [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Rendered products/index.html.erb within layouts/application (22.0ms) [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Completed 200 OK in 81ms (Views: 73.1ms | ActiveRecord: 0.3ms) [98eec5f8976586c1165b981797086b6a] [127.0.0.1]
これは、本番環境で複数のRailsインスタンスがログファイルに書き込んでいる場合にとても便利です。これによって特定のリクエストに関連づけられたログのエントリーを識別できるようになります。