node.js のインストール - Ubuntu 10.04 編
Ubuntu 10.04 への node.js のインストールメモ。
apt repository を新規に追加してインストール。
$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get -y update
$ sudo apt-get -y install nodejs
これだけ。。
irb でのオブジェクトの表示を更に見易く - awesome print
環境を整理していていたついでにメモを残しておく。
irb でシンタックスハイライトなどを行うには Wirble など便利な gem があるが、awesome_print はオブジェクトのデータ構造もわかりやすくインデントして表示してくれる。
Awesome Print is a Ruby library that pretty prints Ruby objects in full color exposing their internal structure with proper indentation. Rails ActiveRecord objects and usage within Rails templates are supported via included mixins.
インストールと irb でのお試し
インストール。
$ gem install awesome_print
Fetching: awesome_print-1.0.2.gem (100%)
Successfully installed awesome_print-1.0.2
1 gem installed
Installing ri documentation for awesome_print-1.0.2...
Installing RDoc documentation for awesome_print-1.0.2...
irb で試してみる。
$ irb
1.9.3-p125 :001 > require 'awesome_print'
=> true
1.9.3-p125 :002 > data = [ false, 42, %w(forty two), { :now => Time.now, :class => Time.now.class, :distance => 42e42 } ]
=> [false, 42, ["forty", "two"], {:now=>2012-03-20 14:11:20 +0900, :class=>Time, :distance=>4.2e+43}]
1.9.3-p125 :003 > ap data
[
[0] false,
[1] 42,
[2] [
[0] "forty",
[1] "two"
],
[3] {
:now => 2012-03-20 14:11:20 +0900,
:class => Time < Object,
:distance => 4.2e+43
}
]
=> [false, 42, ["forty", "two"], {:now=>2012-03-20 14:11:20 +0900, :class=>Time, :distance=>4.2e+43}]
awesome_print
を require 後に、ap メソッドに awesome_print
で表示させてたいオブジェクトを預ける。
実際のコンソールの画面も貼りつけておく。
irb での出力を常に awesome_print で
先ほどの確認では、awesome_print
を使って表示するためには ap
にオブジェクトを預ける必要があったが、ap
の入力なしに常に awesome_print
で表示が行えるようにする。
$HOME/.irbrc
に以下を追記。
($HOME はユーザアカウントの HOME 直下。)
$HOME/.irbrc
:
# load libraries
require 'rubygems'
require "awesome_print"
unless IRB.version.include?('DietRB')
IRB::Irb.class_eval do
def output_value
ap @context.last_value
end
end
else # MacRuby
IRB.formatter = Class.new(IRB::Formatter) do
def inspect_object(object)
object.ai
end
end.new
end
試してみる。
$ irb
1.9.3-p125 :001 > data = [ false, 42, %w(forty two), { :now => Time.now, :class => Time.now.class, :distance => 42e42 } ]
[
[0] false,
[1] 42,
[2] [
[0] "forty",
[1] "two"
],
[3] {
:now => 2012-03-20 14:17:37 +0900,
:class => Time < Object,
:distance => 4.2e+43
}
]
実際のコンソールの画面。
rails console でも awesome_print
Rails の console でも awesome_print
を使いたくなる。
Rails アプリケーションの Gemfile
に awesome_print
を追加してロードできるようにしておく。
開発環境とテスト環境のみで利用可能に。
$RAILS_HOME/Gemfile
:
group :development, :test do
gem 'awesome_print', '~> 1.0.2'
end
Bundler でインストールしておく。
$ bundle install
以下は Refinery CMS で使用した例。
モデルオブジェクトを見てみる。
$ rails c
Loading development environment (Rails 3.2.2)
1.9.3p125 :001 > Refinery::Page
class Refinery::Page < Refinery::Core::BaseModel {
:id => :integer,
:parent_id => :integer,
:path => :string,
:slug => :string,
:show_in_menu => :boolean,
:link_url => :string,
:menu_match => :string,
:deletable => :boolean,
:draft => :boolean,
:skip_to_first_child => :boolean,
:lft => :integer,
:rgt => :integer,
:depth => :integer,
:view_template => :string,
:layout_template => :string,
:created_at => :datetime,
:updated_at => :datetime
}
実際のコンソールの画面。
検索結果は以下のような感じ。
1.9.3p125 :002 > Refinery::Page.find(:first)
Refinery::Page Load (0.5ms) SELECT "refinery_pages".* FROM "refinery_pages" LIMIT 1
Refinery::Page::Translation Load (0.3ms) SELECT "refinery_page_translations".* FROM "refinery_page_translations" WHERE "refinery_page_translations"."refinery_page_id" = 1
#<Refinery::Page:0xa3f780c> {
:id => 1,
:parent_id => nil,
:path => "Home",
:slug => "home",
:show_in_menu => true,
:link_url => "/",
:menu_match => "^/$",
:deletable => false,
:draft => false,
:skip_to_first_child => false,
:lft => 1,
:rgt => 4,
:depth => 0,
:view_template => nil,
:layout_template => nil,
:created_at => Sun, 18 Mar 2012 04:20:54 UTC +00:00,
:updated_at => Sun, 18 Mar 2012 12:27:27 UTC +00:00
}
実際のコンソールの画面。
Refinery CMS v2 変更点
2月後半に Refinery CMS のメジャーバージョンアップがされている。
Refinery 2.0 makes a number of very important updates to the project. Chief amongst these, Refinery now supports Rails 3.2 and the asset pipeline, and is now mountable as a Rails engine.
Rails 3.2 への対応が行われており、Ruby は 1.9.3 が推奨となっている。
Refinery CMS 自体の仕組みでは、軽く触ってみたところ、名前空間の扱い、設定情報の扱いが大きく変わっていた。 Changelog - resolve/refinerycms Wiki - GitHub から変更点をまとめておく。
- ワークフローの変更
- extension のインストールには、
rake db:seed
の実行が必要 - 設定情報 (Settings) は、
config/initializers/refinery
ディレクトリでカスマイズする(admin 機能でない) - Settings extension は Refiney Core には含まれなくなった
- extension のインストールには、
- Extensions の変更
- extension の生成書式の変更
- Settings (設定情報) は
RefinerySetting
で扱われなくなった - 公式(デフォルトで組み込まれている)の extension のネームスペースの変更
ワークフローの変更
extension のインストールには、rake db:seed
の実行が必要
extension のインストールには、rake db:seed
の実行が必要となっている。
extension を作成する際の適切なワークフロー以下の通りとなる。
# Add extension to your Gemfile
rails g refinery:<your_extension>
rake db:migrate
rake db:seed
設定情報 (Settings) は、config/initializers/refinery
ディレクトリでカスマイズする(admin 機能でない)
Refinery アプリケーションの全ての設定は、config/initializers/refinery
内で行われる。
extension を作成した際には、上記ディレクトリ内にテンプレートから作成された設定がコピーされる。
Settings extension は Refiney Core には含まれなくなった
これまで Settings extension は Refinery Core に含まれてきていたが、Core から外れた。 別途 extension として導入は可能。
Extensions の変更
extension の生成書式の変更
v2.0 からは以下の書式で生成。
rails generate refinery:engine <model_name> field:type field:type field:type
より詳細なオプションは、
rails generate refinery:engine
で確認できる。
Settings (設定情報) は RefinerySetting
で扱われなくなった
Settings (設定情報)は RefinerySetting
からアクセスしていたが、使用されなくなり、代わりに Refinery::<ExtensionNamespace>.value
でアクセスする。
例えば、Prtfolio
extension では、Refinery::Portfolio.approximate_ascii_for_i18n
でアクセスを行う。
I18n
などでも、
> Refinery::I18n.default_locale
=> :ja
という感じ。
設定情報は、内部的には 2つの別のファイルの extension 内で扱われている。
1つは、lib/refinery/<extension_name>/configuration.rb
の中でデフォルトの値が宣言されており、
もう1つは、extension 生成時に generator から読み取られる lib/generators/refinery/<extension_name>/templates/config/initializers/<extension_name>.rb.erb
内にテンプレートが記載されている。
例えば、I18n
の場合は、
lib/refinery/i18n/configuration.rb
(デフォルト値の設定):
module Refinery
module I18n
include ActiveSupport::Configurable
config_accessor :current_locale, :default_locale, :default_frontend_locale,
:enabled, :frontend_locales, :locales
self.enabled = true
self.default_locale = :en
self.default_frontend_locale = self.default_locale
self.current_locale = self.default_locale
self.frontend_locales = [self.default_frontend_locale]
self.locales = self.built_in_locales
end
end
lib/generators/refinery/templates/config/initializers/refinery/i18n.rb.erb
(テンプレートの設定):
# encoding: utf-8
Refinery::I18n.configure do |config|
# config.enabled = <%= Refinery::I18n.config.enabled.inspect %>
# config.default_locale = <%= Refinery::I18n.config.default_locale.inspect %>
# config.current_locale = <%= Refinery::I18n.config.current_locale.inspect %>
# config.default_frontend_locale = <%= Refinery::I18n.config.default_frontend_locale.inspect %>
# config.frontend_locales = <%= Refinery::I18n.config.frontend_locales.inspect %>
# config.locales = <%= Refinery::I18n.config.locales.inspect %>
end
となっている。
Nginx の設定概要
Nginx に関して、とりあえず最初の一歩としてこれだけ知っておけば何とかなる、という内容をまとめておきたくメモを残すことにした。
Basic Nginx Configuration – Linode Library のページがとてもよくまとめられていたので、このページの内容をベースに書かせてもらった。
- 全体的な構成
- グローバルな設定
- サーバの設定 -
server
ディレクティブ- listen ポートの設定 -
listen
ディレクティブ - バーチャルホストの設定 -
server_name
ディレクティブ - リソース(ロケーション)の設定 -
location
ディレクティブ
- listen ポートの設定 -
- 設定ファイルの管理
全体的な構成
Nginx の設定に関しては、まずは、ディレクティブ、ブロック、コンテキストという言葉をおさえておく。
Nginx はインストール直後ですぐに使える状態になっており、設定は $NGINX_ROOT/conf/nginx.conf
に記載されている。($NGINX_ROOT
は Nginx がインストールされているルートディレクトリに読み替えること。)
その設定ファイル内のざっくりとした構成は以下のような感じになっている。
#user nobody;
worker_processes 1;
...
events {
...
}
http {
...
server {
...
location / {
...
}
...
}
...
}
worker_processes
、events
、http
、server
、ocation
というのがディレクティブであり、events
、http
、server
、location
などは{
}
(中括弧)で囲まれたブロックを引数に持っている。
このブロックを含めたディレクティブの引数に Nginx で記載可能なシンタックスを使用して条件文などを混じえてディレクティブを記載していく。
Nginx はネストされたブロックの構文を使用でき、上記のように http
ディレクティブに server
ディレクティブがネストされている構文をとれる。この時、server
ディレクティブは http
ディレクティブのコンテキストにあると言う。
ディレクティブは、コンテキストによって使えるもの、使えないものとがあるので、ドキュメント Modules で確認する。
ドキュメントの記載は以下のように、ディレクティブ名、シンタックス、デフォルト動作、コンテキストの記載があり、その後にそのディレクティブの詳細説明が続く構成となっている。
location
syntax: location [=|~|~*|^~|@] /uri/ { ... }
default: no
context: server
インストール時のオプション、パッケージマネージャー等で多少の違いはあるが、インストール直後の $NGINX_ROOT
ディレクトリ配下は以下の構成となっている。(include
ディレクティブでは起点が nginx.conf
の存在するディレクトリになる。後述。)
$ ll
total 42
drwx------ 2 nginx root 4096 2012-03-04 15:21 client_body_temp
drwxr-xr-x 4 root root 4096 2012-03-10 14:46 conf
drwx------ 2 nginx root 4096 2012-03-04 15:21 fastcgi_temp
drwxr-xr-x 2 root root 4096 2012-03-04 15:14 html
drwxr-xr-x 2 nginx root 4096 2012-03-10 17:22 logs
drwx------ 5 nginx root 4096 2012-03-05 22:30 proxy_temp
drwxr-xr-x 2 root root 4096 2012-03-04 15:14 sbin
drwx------ 2 nginx root 4096 2012-03-04 15:21 scgi_temp
drwx------ 2 nginx root 4096 2012-03-04 15:21 uwsgi_temp
設定内でのパスの指定には、絶対パスと相対パスが使えるが、相対パスを使った場合の起点は、$NGINX_ROOT
となる。
デフォルトの設定ファイルは、 $NGINX_ROOT/conf/nginx.conf.default
として残されており、このファイルを読みながら、Nginx の設定の概要を掴むことにする。
グローバルな設定
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
上記の記載で nginx.conf.default
は始まっている。
#
で始まる行はコメントなので解釈されない。- ディレクティブは変数(例:
worker_processes
,pid
)で始まり、その引数(1,logs/nginx.pid
)、または、連続した引数(logs/error.log notice
)を含む。 - 全てのディレクティブは
;
(セミコロン)で終える。 - ディレクティブにはサブディレクティブを含む(ネストする)ことができる。(上記の
events
のように。)
これらのサブディレクティブは{
}
を使って、コンテキストとスコープを定義する。 - スペースは無視されるので、インデントをうまく使って可読性を上げることができる。
- Nginx は、1つのマスタープロセスと実際にリクエストを処理する複数のワーカープロセスで動作する。ワーカープロセスは非特権ユーザで動作するが、
user
ディレクティブにはそのユーザを指定できる。worker_processes
には、動作させるワーカープロセスのプロセス数を指定する。
さらに読み進める。
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
http
ディレクティブのブロック({
}
)が書かれている。
include
ディレクティブはmime.types
ファイルをインクルードしている。相対パスでの記述になっており、この設定ファイルと同じディレクトリにmime.types
ファイルが存在する。通常、設定ファイルに記載される相対パスは、$NGINX_ROOT
が起点となるが、include
ディレクティブの場合は、nginx.conf
ファイルの存在するディレクトリが起点となる。(Since version 0.6.7 CoreModule::include)
このように設定ファイルを外出してinclude
ステートメントで読み込むことができるので、設定ファイルの可動性はあがり、管理し易くなっている。log_format
をコメントアウトし、要件に合わせたログフォーマットに変更することができる。gzip
ディレクティブはラストマイル環境がよくない場合など効果を発揮するコンテンツの圧縮をオンザフライで実行する。Apache でいうところのmod_deflate
の機能。細かな設定が行えるので、詳細は HttpGzipModule を参照のこと。
access_log
ディレクティブの設定サンプルを見てみる。
access_log logs/example.access.log;
access_log /srv/http/example.org/logs/access.log;
access_log /var/log/nginx/access/example.org;
access_log off;
- 最初の例では、相対パスになっているので、この場合は、ログは
$NGINX_ROOT/logs/example.access.log
に記録される。 - 続く2つ目、3つ目の例では、
access.log
へのフルパスでの指定を行っている。 - ログを残したく無い場合、4つ目の例のようにオフにすることも可能。(あまりやらないだろうが・・・)
サーバの設定 - server ディレクティブ
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
上記の server
ディレクティブは、http
ディレクティブに内包されている(http
ディレクティブのコンテキストにある)。
server
ディレクティブのブロックはそれぞれのサーバ自体の設定を行うところとなる。
listen ポートの設定 - listen
ディレクティブ
まず listen
ディレクティブだが、上記は 80 番ポートで待機していることを意味する。更に柔軟な記載が行えるようになっているので、詳細は、HttpCoreModule::listen を参照のこと。
バーチャルホストの設定 - server_name
ディレクティブ
server_name
ディレクティブは、名前ベースのバーチャルホストを実現する。
1つの IP アドレスで待機しているサーバは、リクエストヘッダーの HOST 名に基づいて、複数のドメインのホスティングを行える。
# サンプル1
server_name example.com;
# サンプル2
server_name example.com www.example.com;
# サンプル3
server_name *.example.com;
# サンプル4
server_name .example.com;
# サンプル5
server_name example.*;
# サンプル6
server_name example.com exmaple.net example.org;
# サンプル7
server_name localhost example1 example2;
# サンプル8
server_name "";
上記サンプルは、Basic Nginx Configuration – Linode Library の例から使わせてもらっている。
個々の名前は、スペース区切りで記述できる。Nginx はサーバに対して1つ以上の名前を持たせることができ、特定の server_name
ディレクティブは1つ以上の名前のリクエストに応答する。また、ワイルドカードの定義や、正規表現での定義も行える。
- 最初のサンプルは、
server_name
にexample.com
を定義している。これにより、http://example.com
にはこのサーバが応答する。 - 2つ目の例の
example.com www.example.com
の場合、http://example.com
とhttp://www.example.com
に応答する。 *.example.com
と.example.com
の記述の意味は等しい。また、この記述の場合、サブドメインも全てハンドリングする。例えば、www.example.com
、img.example.com
、search.example.com
など。- 5つ目の例の
example.*
の記述は、example
で始まる全てのドメインへのリクエストをハンドルする。 例えば、example.com
、example.net
、example.org
など。example.wao.com
、example.hello.com
も同様。 - 6つ目の
example.com exmaple.net example.org
は3つのドメイン全てへのリクエストをハンドルする。 - Nginx は無効なドメイン名でもバーチャルホストとして定義できる。7つ目の例の
localhost example1 example2
の場合、ホスト名となっているが、Nginx はリクエストの HTTP のヘッダのみを見てリクエストを処理するので、有効なドメイン名である必要はない。ただし、この場合は、hosts ファイルにホスト名が定義されている必要がある。 - 最後の
""
の定義では、Nginx は全てのリクエストを処理することを意味する。
リソース(ロケーション)の設定 - location
ディレクティブ
次に location
ディレクティブ。location
ディレクティブは、クライアントによってリクエストされたロケーションに対する振舞いを定義する。
location
ディレクティブは、そのブロックの前に適用するパターンを記載する。シンタックスは以下の通りとなっており、まずはこの動作概要を掴んでおくことが必要。
location [=|~|~*|^~|@] pattern { ... }
Basic Nginx Configuration – Linode Library の例を利用させていただき、その動作を理解する。
# パターングループ1
location / { }
location /images/ { }
location /blog/ { }
location /planet/ { }
location /planet/blog/ { }
# パターングループ2
location ~ IndexPage\.php$ { }
location ~ ^/BlogPlanet(/|/index\.php)$ { }
# パターングループ3
location ~* \.(pl|cgi|perl|prl)$ { }
location ~* \.(md|mdwn|txt|mkdn)$ { }
# パターングループ4
location ^~ /images/IndexPage/ { }
location ^~ /blog/BlogPlanet/ { }
# パターングループ5
location = / { }
- 最初の5つのサンプルはリテラル文字列マッチと言われるもの。ホスト名に続くリクエストの最初のパートにマッチする。
server_name
にexample.com
が設定されており、http://example.com/
へのリクエストがあったとする。この時、”location /
” ディレクティブはこのリクエストにマッチする。Nginx は “most specific match” でリクエストの条件を満たす。http://example.com/planet/blog/
とhttp://example.com/planet/blog/about
へのリクエストは、”location /planet/blog/
” で満たされる。当然、”location /planet/
” でも満たされることになる。- 2つ目のグループの
~
(チルダ)に続くサンプルでは、Nginx は正規表現での適用を行う。これらの~
だけのマッチ表現は case-sensive (大文字小文字を区別する) になる。最初の例では、IndexPage.php
はマッチするが、indexpage.php
はマッチしない。また、/BlogPlanet
、/blogplanet/
、/blogplanet/index.php
もマッチしない。Nginx は Perl Compatible Regular Expressions (PCRE) を使用している。 - 3つ目のグループの
~*
の表現は、~
の場合と同じく正規表現でのマッチで、case-insentive (大文字小文字を区別しない)になる。ここでの表現では、特定の拡張子で終わるファイル名に対してどう処理するのかを定義している。最初の例では、.pl、.PL、.cgi、.CGI、.perl、.Perl、.prl、.PrL、何れの拡張子のファイルにも適用される。 - 4つ目のグループの
^~
の表現は、最初のグループのリテラル文字列マッチのように動作する。注意点として、このマッチ文が適用されると Nginx はパターンの検索を中止する。”^~ /images/IndexPage/
“、”^~ /blog/BlogPlanet/
” パターンでのlocation
ディレクティブは、仮に他にマッチするlocation
ディレクティブが他にあったとしても、このパターンがマッチした時点で使用される。 - 最後の
=
の表現は、リクエストされたパスに完全一致した場合に適用され、マッチした時点で他のパターンの検索を中止する。例えば、最後のサンプルは、http://example.com/
にはマッチするが、http://example.com/index.html
にはマッチしない。
ここまでの記述だけでは、location
ディレクティブの全体の適用順がイマイチ掴みきれない。location
ディレクティブのパターン適用ルールは以下の通りとなる。
- 完全一致(
=
)が最初に処理される。マッチするものが見つかった場合、Nginx はそこでパターンの検索を中止し、リクエストを実行する。 - 次に残りのリテラル文字列ディレクティブが処理される。もし、
^~
が使用されていれば、Nginx はそこでパターンの検索を中止し、リクエストを実行する。それ以外の場合は、Nginx はlocation
ディレクティブを処理し続ける。 - 正規表現で定義された(
~
、~*
)全てのlocation
ディレクティブが処理される。正規表現がマッチしたら、Nginx はそこでパターンの検索を中止し、リクエストを実行する。 - 正規表現がない、または、正規表現がマッチしなかったときは、最も適切なリテラル文字列のパターンが使用される。
一旦 Nginx が特定のリクエストに対してリソースを提供する “location
” を選択したら、このリクエストに対するレスポンスは、”location
” ディレクティブのブロックによって定義される。
location / {
root html;
index index.html index.htm;
}
ドキュメントルートは、root
ディレクティブによって、html ディレクトリに定義されている。上記は相対パスでの指定なので、実際のディレクトリは、$NGINX_ROOT/html
となる。/blog/includes/style.css
へのリクエストは、他の location
ディレクティブがマッチしていないと想定した場合、$NGINX_ROOT/html/blog/includes/style.css
にあるファイルがリソースの対象となる。root
ディレクティブには絶対パスを指定することもできる。
index
ディレクティブはリクエストがファイル名を含んでいない場合に、ファイルシステムのどのファイルが使用されるべきかを指定する。http://example.com/
へのリクエストは、$NGINX_ROOT/http/index.html
のファイルがリソースの対象となる。複数のファイル名指定されている場合、Nginx は順にそのリストを処理し、存在するファイルにリクエストを適用する。上記の例の場合、index.html
が存在しなければ、index.htm
が使用される。仮にどちらも存在しない場合には、404 メッセーが送出される。
設定ファイルの管理
include
ディレクティブを活用し、ベースとなる $NGINX_ROOT/conf/nginx.conf
には必要最低限の記述のみを行い、各サーバ毎の固有の設定は別ファイルに切り出しておくとメンテナンス性があがる。
例えば、以下のような書き方。$NGINX_ROOT/conf/nginx.conf
の内容は至ってシンプルになる。
user nginx;
worker_processes 4;
pid /var/run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
最後の include
ディレクティブによって、個々のバーチャルホスト毎の設定は別ファイルに定義を行っている。
参考情報
マスターの導きを受けつつ、TDD で Ruby を学びながら悟りを開く - Koans
Koans とは
クイズ形式で Ruby を学ぶ Koans。その手法がなかなか凝っている。
- Learn Ruby with the EdgeCase Ruby Koans
ここでは上記からダウンロードできる 2010/12/23 日版を利用した。- edgecase/ruby_koans - GitHub
GitHub でソースは管理されている。
- edgecase/ruby_koans - GitHub
The goal is to learn the Ruby language, syntax, structure, and some common functions and libraries. We also teach you culture. Testing is not just something we pay lip service to, but something we live. It is essential in your quest to learn and do great things in the language.
Koans は Ruby 言語の文法、構造、そして、幾つかの共通関数やライブラリを学ぶことをゴールとしたもので、TDD の手法を用いてそれを実現している。
path_to_enlightenment.rb
というファイルを起動することで、あるテストケースがカルマにダメージを与えたとのメッセージが表示され、マスターから助言を授かることになる。
表示されるメッセージを頼りにテストケースをパスするように修正していくことで、次の新たな設問(新たなカルマへのダメージ)へと進み、30ちょっとのカテゴリの計272問(2010/12/23版)の設問をクリアすることで悟りをひらけることになる。
設問をクリアしていく過程は以下のようになる。
例えば2番目の設問は以下の通りとなっている。
マスターが「君はまだ悟りをひらいていない」と言っている。”The answers you seek…” ということで “This shoud be true – Please fix this” とある。
「please meditate on the following code:」ということで、about_asserts.rb:16:in
test_assert_with_message’` が指定されているので、該当ファイルの該当行を確認すると、
# Enlightenment may be more easily achieved with appropriate
# messages.
def test_assert_with_message
assert false, "This should be true -- Please fix this" # line: 16
end
assert
が false となっている。当然これではテストは通らないので、true に書き変えて保存する。
再度 path_to_enlightenment.rb
を起動してあげると、
$ ruby path_to_enlightenment.rb
AboutAsserts#test_assert_truth has expanded your awareness.
AboutAsserts#test_assert_with_message has expanded your awareness.
AboutAsserts#test_assert_equality has damaged your karma.
The Master says:
You have not yet reached enlightenment.
You are progressing. Excellent. 2 completed.
The answers you seek...
Failed assertion, no message given.
Please meditate on the following code:
/home/hoge/Dropbox/work/koans/about_asserts.rb:25:in `test_assert_equality'
learn the rules so you know how to break them properly
your path thus far [.X________________________________________________] 2/274
次の設問へと続く。
基本は、テストケースのアサーションの期待値を埋めていくことで設問に答えていくことになる。
メッセージ(“The ansers you seek…“)に既に答えが出てしまってはいるが、そこはじっくり読まずに、テストケースとテストメソッド名の方に集中して何を学ばせようとしているのか頭の片隅に置きながら期待値を考えていると、よく考えられた設問になっているなぁと感心してしまう。
最初の方こそ上記のようなホントに初歩的な設問だが、徐々に内容が濃くなってくるので、なかなか良い復習、勉強になる。
また、一気に 274 問実施するのは流石にキツイので、できる時間に少しづつ実施することが可能な作りになっているのもうれしい。何問目まで回答できているかは .path_progress
というファイルが覚えておいてくれているので、特に意識することなく、path_to_enlightenment.rb
を起動すれば前回の設問から続きを行うことができる。
watchr を使って回答に集中
設問に答えては path_to_enlightenment.rb
を起動するというのはメンドイ。
自動化しておく。
watchr gem を使うとファイルを監視下におき、編集された際に実行させたいことを定義することができる。
回答を記述するファイルを編集するたびに自動的に path_to_enlightenment.rb
が実行されるようにしておく。
watchr をインストールしていない場合はインストールしておく。
$ sudo gem install watchr
path_to_enlightenment.rb
と同じディレクトリに以下のファイルを用意しておく。
koans.watchr
:
def run
system("clear; ruby path_to_enlightenment.rb")
end
watch('.*\.rb$') { run }
run
watchr を起動しておく。
$ watchr koans.watchr
これで回答を行うファイルを編集する度にコンソールがリフレッシュされ path_to_enlightenment.rb
の結果を確認することができる。
triangle.rb
大半は assertion の期待値を埋めていくのだが、テストケースを通す為の関数を書く設問もある。
以下の2問。
- 設問その1:
about_triangle_project.rb
class AboutTriangleProject < EdgeCase::Koan
def test_equilateral_triangles_have_equal_sides
assert_equal :equilateral, triangle(2, 2, 2)
assert_equal :equilateral, triangle(10, 10, 10)
end
def test_isosceles_triangles_have_exactly_two_sides_equal
assert_equal :isosceles, triangle(3, 4, 4)
assert_equal :isosceles, triangle(4, 3, 4)
assert_equal :isosceles, triangle(4, 4, 3)
assert_equal :isosceles, triangle(10, 10, 2)
end
def test_scalene_triangles_have_no_equal_sides
assert_equal :scalene, triangle(3, 4, 5)
assert_equal :scalene, triangle(10, 11, 12)
assert_equal :scalene, triangle(5, 4, 2)
end
end
- 設問その2:
about_triangle_project_2.rb
class AboutTriangleProject2 < EdgeCase::Koan
# The first assignment did not talk about how to handle errors.
# Let's handle that part now.
def test_illegal_triangles_throw_exceptions
assert_raise(TriangleError) do triangle(0, 0, 0) end
assert_raise(TriangleError) do triangle(3, 4, -5) end
assert_raise(TriangleError) do triangle(1, 1, 3) end
assert_raise(TriangleError) do triangle(2, 4, 2) end
end
end
設問その1のテストの要件を満たす関数を用意し、かつ、設問その2の制約もパスする必要がある。
答えは提供されていない(見つけられなかった。。)ので、ベストアンサーかどうかはわからないが、以下の関数を書くことでテストをパスしている。
ツッコミありましたらご指摘願いたい。
triangle.rb
:
def triangle(a, b, c)
raise TriangleError.new if [a, b, c].any? { |e| e <= 0 }
raise TriangleError.new if ((a + b) <= c) || ((a + c) <= b) || ((b + c ) <= a)
case [a, b, c].uniq.size
when 1 then :equilateral
when 2 then :isosceles
else :scalene
end
end