irohiroki's blog

Ruby, Rails, and Web technologies

RubyKaigi2010のA Metaprogramming Spell BookのスピーカーであるPaolo Perrotta氏が著し、@kdmsnr氏が訳した『 メタプログラミングRuby』を読みました。

大変面白かったのでご紹介します。

目次は以下の通り。第1部だけ少し細かく書いてあります。

第1部 メタプログラミングRuby

  • 第1章 月曜日:オブジェクトモデル
    • 1.1 ビルと一緒の月曜日
    • 1.2 オープンクラス
    • 1.3 クラスの真実
    • 1.4 クイズ:引かれていない線
    • 1.5 メソッドを呼び出すときに何が起きているの?
    • 1.6 クイズ:絡み合ったモジュール
    • 1.7 オブジェクトモデルのまとめ
  • 第2章 火曜日:メソッド
    • 2.1 重複問題
    • 2.2 動的メソッド
    • 2.3 method_missing()
    • 2.4 クイズ:バグ退治
    • 2.5 もっとmethod_missing()
  • 第3章 水曜日:ブロック
    • 3.1 水曜日の過ごし方
    • 3.2 クイズ:Ruby#
    • 3.3 クロージャ
    • 3.4 instance_eval()
    • 3.5 呼び出し可能オブジェクト
    • 3.6 ドメイン特化言語を書く
    • 3.7 クイズ:より良いDSL
  • 第4章 木曜日:クラス定義
    • 4.1 クラス定義のわかりやすい説明
    • 4.2 クイズ:クラスのタブー
    • 4.3 特異メソッド
    • 4.4 特異クラス
    • 4.5 クイズ:モジュールの不具合
    • 4.6 エイリアス
    • 4.7 クイズ:壊れた計算
  • 第5章 金曜日:コードを記述するコード
    • 5.1 道案内
    • 5.2 Kernel#eval
    • 5.3 クイズ:属性のチェック(手順1)
    • 5.4 クイズ:属性のチェック(手順2)
    • 5.5 クイズ:属性のチェック(手順3)
    • 5.6 クイズ:属性のチェック(手順4)
    • 5.7 フックメソッド
    • 5.8 クイズ:属性のチェック(手順5)
  • 第6章 エピローグ

第2部 Railsにおけるメタプログラミング

  • 第7章 ActiveRecordの設計
  • 第8章 ActiveRecordの中身
  • 第9章 安全なメタプログラミング

第3部 付録

  • 付録A よく使うイディオム
  • 付録B ドメイン特化言語
  • 付録C 魔術書
  • 付録D 参考書
  • 付録E から騒ぎ

本のタイトルは『メタプログラミングRuby』ですが、メタプログラミングに限らずRubyでスムーズにプログラミングするために必要な知識がどっさり載っています。例えば、複数のクラスに同じクラスメソッドを定義するにはどういう方法があるか?クラスインスタンス変数とは何か?クラス変数との違いは?など、曖昧に覚えていることや使うたびに調べているようなことが、整理されて論理的に理解できるようになりました。

Rubyのコードを論理的に読み解くには、オブジェクトモデルとスコープの理解が欠かせません。Rubyのオブジェクトモデルには"メタクラス"あるいは"特異クラス"と呼ばれる隠れたクラスが潜んでおり、重要な役割を果たしています。

例えばあるクラスAのインスタンスaを作り、その特異クラスにメソッドを定義すると特異メソッドになります。

class A
end

a = A.new

class << a
  def foo
    p 'foo'
  end
end

a.foo # => "foo"

しかしaのクラスであるAにはfooメソッドがありませんし、Aのスーパークラスにも特異クラスは現れません。

a.class # => A
A.instance_methods.grep(/foo/) # => []

A.ancestors # => [A, Object, Kernel, BasicObject]

では特異クラスはどこにあるのでしょうか?

次にスコープの例です。トップレベルでクラス変数@@aに値を入れたあと、適当なクラスAでも同様に@@aを使ったら、トップレベルの@@aは影響を受けるでしょうか?

@@a = 1

class A
  @@a = 2
end

p @@a

答えは"受ける"です。上のコードの結果は2になります。では、インスタンス変数@aや、ローカル変数aだったらどうでしょうか?またそれはなぜでしょうか?

以上のような質問に答えるために、それぞれの場合の答えを丸暗記する必要はありません。単純なスコープのルールを覚えておけば済みます。スコープを理解すると、class_evalやinstance_evalの使いどころや、クロージャの意味まで明確になります。『メタプログラミングRuby』にはそれらを理解するための例や解説が豊富に載っています。物語形式になっていて、リラックスして読み進めることができると思います。

『メタプログラミングRuby』を読んだ一番の成果は、上述のオブジェクトモデルとスコープの理解が深まったことでした。これだけでプログラミングが楽になったと実感したほどです。そのほかの良かった点は、第5章の連続クイズが段階的な理解度テストになっていて手頃な腕試しができることや、Proc.new, proc, lambdaの違いなど、オフトピックも面白かったことです。以上、購入を考えている方の参考になれば幸いです。

メタプログラミングRuby
メタプログラミングRuby
  • 発売元: アスキー・メディアワークス
  • 価格: ¥ 2,940
  • 発売日: 2010/08/28

Published on 09/09/2010 at 00h50 under . Tags ,

0 comments

Rails 3ではroutesのDSLが完全に刷新されました。特に、あの見難かったハッシュの塊が解かれて、:member:getなど予約語の役割をしていたシンボルはディレクティブになりました。例えばRails 2の以下の記述は

map.resources :users, :member => {:foo => :get}, :collection => {:bar => :post}

Rails 3では下のように書けます:

resources :users do
  member do
    get :foo
  end

  collection do
    post :bar
  end
end

このように、Rails 2では"予約語"がキーになったり(:member:collection)値になったり(:get:post)していたものが、Rails 3ではディレクティブになり、ユーザの与える値がシンボルとして残りました。

また、ActionController::Routing::RouteSet::Mapperインスタンスであるmapが必要なくなっています。

このエントリでは、8月21日のRails勉強会@東京第54回@a_matsudaさんに教えていただいたことをベースに、Rails 3のroutesについてまとめます。

基本

URLのパターンに対し、コントローラとアクションを指定する方法は、Rails 2では

map.connect 'friends/:id', :controller => 'users', :action => 'show'

でしたが、Rails 3では

match 'friends/:id', :to => 'users#show'

になります。mapが不要になったことと、コントローラとアクションを1つの値で指定できることで簡潔になっていますね。

逆に、オプションのパラメータは()で囲む必要があります。例えば、デフォルトルートは下のように定義されます。

match '/:controller(/:action(/:id))'

名前付き(Named)

Rails 2では下の.friendのようにMapperインスタンスへ送るメッセージがルート名になったのですが

map.friend 'friends/:id', :controller => 'users', :action => 'show'

Rails 3では基本のmatchディレクティブに:asを付けます。

match 'friends/:id', :to => 'users#show', :as => 'friend'

root

root、つまり「/」に対するrouteは、Rails 2ではmap.rootというnamed routeが「/」に予約されていましたが、Rails 3ではrootというディレクティブを使います。例えば下のようになります:

root :to => 'home#show'

基本の省略形

基本のmatchディレクティブでは、実は:toを省略できます。つまり、下の2つは同じことです。

match 'friends/:id', :to => 'users#show'
match 'friends/:id' => 'users#show'

さらに、パスのノード名がコントローラ名とアクションに一致するなら、それも省略可能です。よって下の2つは同じです。

match 'users/summary' => 'users#summary'
match 'users/summary'

特定のコントローラに対するルートは、controllerディレクティブでまとめることができます:

controller :users do
  match 'summary', :to => :short
  match 'pretty', :to => :pp
end

resources

RESTfulなルートを定義するのに便利だったmap.resourcesですが、Rails 3でもresourcesとして使用可能です。例を挙げます:

resources :users

さらに、複数のリソースを渡せるようになりました:

resources :users, :products, :shops

resourcesはブロックを受け取り、その中で冒頭で紹介したmembergetといったディレクティブを使用できます。

resourcesをネストした場合は、Rail 2と同様、has_manyの関係を定義できます。

resources :users do
  resources :items
end

# user.rb
class User < ActiveRecord::Base
  has_many :items
end

:only:exceptといったオプションを与えて生成するルートを絞ることや、:constraintsオプションで受け付けるパラメータのパターンを制限するのもRails 2と同じです。

namespace

namespaceディレクティブを使うことで、パス(URL)とコントローラを任意の名前空間に入れることができます。例えば下のように書くことで、パスは/admin/users、コントローラはapp/controllers/admin/users_controller.rbに置かれたAdmin::UsersControllerになります。

namespace "admin" do
  resources :users
end

もしパスだけに名前空間をつけてコントローラはapp/controllersに入れたければ、scopeを使います:

scope "/admin" do
  resources :users
end

反対に、コントローラにだけ名前空間を付けたければ、scope :module => を使います:

scope :module => "admin" do
  resources :users
end

Rackアプリケーションへルート

Rails 3ではルーティングの機構自体がRackミドルウェアなので、別のRackアプリケーションを呼び出すようなルートも書けるようになりました。例えば:toの先にSinatra::Baseの子クラスを書くことができます。また、redirectというメソッドを使ってリダイレクトを指定することもできます:

match "/users/:id", :to => redirect("/accounts/%{id}")

参考

講師をしてくださった松田さんがRails 3の特集を書かれました。

WEB+DB PRESS Vol.58
WEB+DB PRESS Vol.58
  • 発売元: 技術評論社
  • 価格: ¥ 1,554
  • 発売日: 2010/08/24

Published on 29/08/2010 at 02h15 under . Tags ,

2 comments

HerokuRack専用のホスティングサービスで、Gitでpushすることでデプロイできるという特長があります。

ところが、git pushはブランチのヒストリを全て転送するため、実際のデプロイメントに必要ない過去のファイルも転送されてしまい、無駄です(バックアップとして使えるという考え方もあると思いますが、その目的であれば他にもっと効率的な方法があります)。

ここではGit 1.7.2の新しい機能を使って、実際のデプロイメントに必要なファイルだけをpushする方法を説明します。

Git 1.7.2では、checkoutコマンドに--orphanというオプションが追加されました。

* "git checkout --orphan newbranch" is similar to "-b newbranch" but prepares to create a root commit that is not connected to any existing commit.

つまり、--orphanを付けて新しいブランチを作ることで、ヒストリから切り離されたコミットを作ることができます。例えば以下のようにdeployというブランチを作ると、checkoutしたファイル(ここではREADMEのみ)が全てnew fileとしてステージされた状態になります。

$ git checkout --orphan deploy
Switched to a new branch 'deploy'
$ git status
# On branch deploy
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached ..." to unstage)
#
#       new file:   README
#

ここでcommitすればdeployブランチ唯一のコミットができます。

$ git commit -m 'for deployment.'
[deploy (root-commit) 38bbc6b] for deployment.
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 README

show-branchすることで、既存のブランチから完全に切り離されていることが分かります。

$ git show-branch
* [deploy] for deployment.
 ! [master] initial commit.
--
*  [deploy] for deployment.
 + [master] initial commit.

このdeployブランチをherokuへpushするには、以下のようにします:

$ git push heroku deploy:master

転送するオブジェクトは最小で済むはずです。

Published on 02/08/2010 at 01h40 under . Tags , ,

1 comment

Powered by Typo – Thème Frédéric de Villamil | Photo L. Lemos