2012年3月23日金曜日

AWS 上の Rails 環境

「クラウド上に Rails 環境を作るのであれば Heroku じゃないの?」という疑問が聞こえてきそうですが、欲しかった環境が ruby 1.8.x + Rails 2.3.5 (かつ Bundler の恩恵を受けていないアプリケーション?)だったので、heroku で動かすには苦労しそうだとプロの意見もいただいたので、AWS に白羽の矢を立てた次第です。

んで、以下は AWS の Linux AMI に rails 環境を作った記録です:

…と思ったんですが、結局、昔書いた Openskip の環境構築の手順のほとんどそのままで動かせました。違いは
  • 今回は rvm 環境を先に用意した
  • Amazon Linux AMI がデフォルトで用意しているパッケージが少ないのでいろいろ yum install した
    • patch, make, gcc,...
    • git
    • mailx (/bin/mail コマンド)
    • mysql-server
    • *-devel (ここは CentOS でも一緒か…)
  • インストールした gem は同じだけれどバージョンがちょっと違う
くらいです。
当たり前と言えば当たり前ですね、Amazon Linux AMI が CentOS 類似OSなので。

Amazon EC2 を使ってみる

手元で改修したプログラムの動作を見てもらおうと思ったときに、これまでは手元でしか動かせなかったので直接会った時にしかどんな感じで動くのか見てもらえませんでした。
これだと会えないと開発の機会がなくなります。困ります。

ということで、
「インターネット上にテスト環境が欲しい!」
と思い、Amazon EC2 の無料使用範囲を利用してテスト環境(ステージング環境?)を作ってみることにしました。

初めの一歩

多分このドキュメントなんだと思います:
Get Started with EC2 with a Linux/UNIX Instance

手順として
  1. サインアップ
  2. インスタンス(仮想マシン)の作成と起動
  3. インスタンスへのアクセス
  4. インスタンスの terminate 
となっています。

サインアップ

サインアップは普通にweb上のサービスを利用する感じで進みます。途中、ケータイへ Amazon さんから自動着信&webページに表示された4桁の番号入力を求められるのが普通でない感じ。
サインアップが終わると、AWS Management Console というWebサイトが使えるようになると思います。このWebサイトでインスタンスの管理(作成、起動、一時停止、停止などなど)を行えます。

あ。ssh の鍵生成はこのタイミングでやっておくとよいと思います。

インスタンスの作成と起動

インスタンスの作成のプロセスも、Web上のクリックぽちぽちで進められます。
今回は Quick Launch Start のウィザードを使って、AMI Linux 2011.9 x86_64 を立ち上げてみました。
Amazon Linux AMI についてのドキュメント を読んでみると、
Amazon Linux AMI is similar to CentOS in file system layout and package management. Applications built for CentOS should run without recompilation on the Amazon Linux AMI.
と書いてあって、「CentOS と同様のファイル構造とパッケージ管理」というところを重視して思わず選びました。手元の環境(CentOS 6 on VMware & Thinkpad)に近い方が楽だと思ったので…。
Quick だとほとんど選ぶことは無いのですが、インスタンスのタイプが micro (= 無料使用範囲で使えるインスタンス)であることだけはしっかりと確認しました。

インスタンスの起動はAWS management console から行います。これも「クリックぽち」です。

インスタンスへのアクセス…の前に

インスタンスが持っているFQDNの確認と、セキュリティグループの概念の理解が(多分)必要です。

インスタンスのFQDNを確認するには、AWS management conslole から確認します。これがないとsshできませんし…。

セキュリティグループは、「ネットワークの口に存在するファイアウォール(のルールセット)」に相当するものだと思っています。
定義するものはファイアウォールルールそのもので、そのルールをセキュリティグループとしてまとめ、各インスタンスにどのセキュリティグループに属させるかを設定します。
複数の同様のサーバを運用する場合にセキュリティグループが威力を発揮しそうです。今は一台なので、OS上でiptables を走らせても(セキュリティレベルと言う意味では)あまり変わりないかもしれません。

インスタンスへのアクセス 

SSH の鍵があって、FQDN が分かっていれば、後は ssh コマンドを走らせるだけです。
…が、一度引っかかりました。AWS management console で生成した ssh の秘密鍵をダウンロードするんですが、そのパーミッションを 6xx から 400 にしないといけません。秘密鍵ですもんね。
普段 ssh-keygen で作ると自動的に 400 だと思いますが、ダウンロードファイルだから普通のパーミッションになってました。

インスタンスのterminate

「停止」と言う言葉を使わずに terminate と書いたのは、ちょっと悲しい体験をきちんと伝えたかったからです…。

停止っていうと、僕の中では再開できるものだったんです。が、ご存知の通り、インスタンスを terminate するとそのインスタンス上のデータは破棄され、再利用することはできません。
"Get started" を読むと Note として、
You cannot restart a terminated instance. However, you can launch additional instances of the same AMI.
と書いてあるのでちゃんと読めという話なんですが、ドキュメントの手順どおり terminate してパニックになりました。

「このインスタンスをしばらく使わないから課金されない状態にしたいわー」と言うときはインスタンスに対する操作として stop というのがあるので、そちらを使うと start ←→ stop を行き来できます。


こんな感じで Amazon EC2 に Linux サーバを立てて、その上で rails の環境を動かしています。
立ち上がった Linux サーバはいろいろひどい状況なので、もう少しマトモな環境にしていかねば…。

2012年3月8日木曜日

vmware tools の仕業?

昨日せっかくどうでもいいことでハマッタので、ブログに書いておこうと思います。

3/4 に CentOS の yum upgrade (多分このとき 6.0 → 6.2になる)を行い、3/6 に VMware Player の 4.0.2 へのアップグレードと VMware tools の再インストールをしたのですが、3/7 に
「VMware ゲストOSの画面を全画面表示したときに、ウィンドウ領域全体で描画されない(800x600になる)」
という事態になりました。

開発なんてやってられない狭さなので時間をかけてでも解決させたわけですが、結論から言うと
  • インストールされているはずの /usr/lib/xorg/modules/drivers/vmwlegacy_drv.so (from xorg-x11-drv-vmware パッケージ)が無かった
  • /etc/X11/xorg.conf の設定で、device identifier "Videocard0" に対する driver が "vesa" になっていた
というところが原因らしく、それぞれ
  • xorg-x11-drv-vmware の再インストール( yum reinstall )
  • driver を "vmware" に変更
    • man vmware すると VMware SVGA video driver についてのマニュアルが立ち上がるのは知らなかった…
ということをやって元通りの環境を奪回しました。

/etc/X11/xorg.conf 辺りが関わっているので、ゲストOS上の操作が何か関わっているはずで、vmware-tools-config.pl あたりが怪しそうな予感だけはあります。追跡しないけれど。

…あ。VMware player の再インストール(4.0.0 へのダウングレード)しちゃったけど、次のアップグレードが怖いなっと。

2012年2月9日木曜日

小さな commit ベースで仕事すること

一応、rails プロジェクトを作るときに一番最初に git init && git . && git commit して、rails プロジェクト全体を git でコントロールできるようにしてはいました。

が。

実際のところ、commit したことはこれまでありません。「ある程度の状態にしてから…」とかダメな事を考えています。

で、今どうなっているかというと、
  • 今まで何したか良く覚えていない
  • 次に何をすればいいのか良く覚えていない
という状態です。まさに、昨日の自分は他人。

コードレビューをしてもらえる機会ができそうなんですが、その時に「何を思って問題にどう対処したのか」を説明できない悪寒がひしひしと忍び寄っています。
こういう時に変な完璧主義が出てきて、一から作り直したい症候群になるんですよね。まぁ、今作っているのは習作なので何度でも一からやり直せばいいと思うんですけど…。

…みたいなことを、たまたま本を読んでいたタイミングで思うのです。


2012年1月6日金曜日

devise でメールアドレス以外を認証に使う

先ほどメールアドレスで認証するサンプルが作れたのですが、今、考えている状況に
  • メールアドレスが確定しない状況(変更可能な状況)
  • そもそも持っていない可能性もある状況
  • 将来的に外部認証と組み合わせる時にメールアドレスを強制できない状況
が含まれるので、メールアドレス以外をユーザIDとして使う方法が必要になりました。

今の時代は恵まれているので、しっかり誰かが道を作ってくれています。
http://ja.asciicasts.com/episodes/210-customizing-devise
http://d.hatena.ne.jp/kaorumori/20110824/1314168534

config/initializers/devise.rb にて
config.authentication_keys = [ :username ]
元々の設定値は :email になっています。

さらに、app/views/devise/session/new.html.erb および app/views/devise/registrations/new.html.erb にて、:username 用の text_field を追加します。

この辺り(テキストフィールド欄の追加)は見よう見まねで何とかなったのですが…。
上記の2つの記事では「既に存在しているユーザのフィールド(username)の値を認証に使う」というものでしたが、username の値を登録したいと思って regisrations/new.html.erb に単純にテキストフィールドを追加したらハマりました。

コンソールログをきちんと読め、という話なのですが、
WARNING: Can't mass-assign protected attributes: username
というログが出てきます。
app/models/user.rb で、attr_accessible に :username を追加してあげる必要があったようです。
こんな記事を見つけました。
http://trwtnb.blogspot.com/2009/11/rubyrails-warning-cant-mass-assign.html

おまけ

email フィールドに unique 制約があるので(適当なメールアドレスをでっち上げればいいだけなんですが…)、user テーブルを何度も作り直しました。
ってことで、 rake db:migrate:reset をおぼえた!

2011年12月12日月曜日

devise を使う

ちょっと前に Rails + OmniAuth + OAuth on Twitter/Facebook でユーザ認証のサンプルを作ってみたのですが、認証に関する便利な仕組みとして Devise というのを知ったのでそちらを使ってみることにしました。

なお、OmniAuth との連携については wiki ページに情報があります。
最終的には OmniAuth との連携の形に持っていきたいと思います。

まず最初に

https://github.com/plataformatec/devise の Installation の項目にある部分:
  • Devise の gem のインストール
  • Gemfile への追記: gem 'devise'
  • rails g devise:install
  • rails g devise user
  • rails g devise:views
までを行いました。
"rails g devise:install" の後にメッセージが出ます。要約すると:
  1. config.action_mailer.default_url_options を設定せよ
    • config/environments/development.rb だと、"{ :host => 'localhost:3000' }" とか
  2. config/routes.rb にて、root_url を設定(root :to を定義)せよ
    • トップページを表現する適当な controller を用意して、"...#index" とかにしておけば良いはず
  3. app/views/layouts/application.html.erb (つまり global_layout)で notice, alert が表示されるようにせよ
    • こんな感じ。"<p class="notice"><%= notice %></p>"
  4. heroku 上にデプロイする場合には、config/application.rb にて config.asset.initialize_on_precompile = false を宣言せよ
の4点です。
この devise:install 時のメッセージに従って作業すると1/3位の設定が終わります。

もう一つの準備

とりあえず「認証によって保護されるリソース」 を考えないと認証の意味が無いので、todo モデルを作っておきます。
rails g scaffold todo summary:string content:text importance:integer rake db:migrate
この todo を、認証した user だけがアクセスできるようにします。

リソースの保護

リソース todo へのアクセスを認証で制御するには、
  1. app/controllers/todos_controller.rb の修正
    • before_filter :authenticate_user!
  2. routes.rb の修正
    • devise_for :users
とします。
また、ログイン情報入力のための画面を用意した場合、
  • ログインしていないなら、ログイン情報入力画面
  • ログイン済みなら、 ログイン後のトップ画面
のような構成を作りたいと思います。
このような時には devise で提供されている current_user メソッドを使って、
class PortalController < ApplicationController
  def index
    if current_user
      redirect_to :user_root
      return
    end
  end
end
のようなことができます。

2011年12月11日日曜日

rails 環境の準備

何度やっても覚えないので覚えないことにしました。
  1. rvm use 1.9.2@rails3
    • (もちろん 1.9.2 の ruby とか rails3 の gemset は用意しておくべし)
  2. rails new APP_NAME
  3. vi APP_NAME/.rvmrc : 以下の内容で作成
    • rvm use 1.9.2@rails3
  4. cd APP_NAME
  5. vi Gemfile : 以下の内容を追記
    • gem 'therubyracer'
    • gem 'rspec-rails'
  6. bundle install (上記の gem は rails3 gemset に既にインストールされているので必要無し)
  7. rails g rspec:install
  8. rm public/index.html
  9. git init
  10. git add .
  11. git commit
これで、新しい rails アプリを立ち上げて、rspec を使うようにして、git 配下に置く事ができます。

文書にしてみて分かったこと → スクリプトにすれば良いのだ…。

今日も javascript の話

Rubyのことを書くブログだったんですけどね…。

画像の切り替え(同一のエリアに複数の画像をスライドショーのように表示する)を行う javascript を実現しようとして、「そんなの jQuery で一発だろう」と思っていたら、思いのほか基本機能には存在しませんでした。

ということで、「それでも誰かが作っているはずだ」と探したところ、InnerFade with jQuery というのを見つけました。
試してみたら思った通りの挙動をしてくれます。flash のことは嫌いじゃないんですが、やっぱりアクセシビリティということだと厄介な存在だとは思うんで、ちょっとずつ代替手段を見つけていきたいところです。

2011年11月30日水曜日

github のセットアップ on Windows

Github のページを参考に私物 Windows で github が使えるようにしてみます。

準備

使えるようにするための手順:
  1. Git for Windows をダウンロード&インストール
  2. インストールした Git bash を使って以下を実行:まずは暗号鍵の生成
    1. mkdir .ssh
    2. ssh-keygen -t rsa -C "my-email@add.re.ss"
      • 暗号鍵ファイルはデフォルトの .ssh/id_rsa{,.pub}
      • パスフレーズは適当に設定
    3. cat .ssh/id_rsa.pub 
      • 次の手順で内容が必要になるので表示させておく
  3. 公開鍵を github のアカウントに設定(コピペ)
  4. 接続テスト
    1. ssh -T git@github.com
  5. git クライアントの設定
    • git config --list で確認
    • git config --global 属性名 値
      • user.name, user.email, github.user, github.token

レポジトリの作成

次はレポジトリを作ります。
手元にあった hta アプリケーションをレポジトリに登録してみます。ちなみにこの hta アプリケーションは「学生に ping とか netstat とかをGUIで使わせたいんだよね」という先生の話を聞いて作ったものです。高校とか大学とかの情報教育に役に立てればうれしいのですが…。
  1. github のサイトで "New repository" ボタンを押す
  2. Project name を決めて "Create repository" ボタンを押す
    • 今回の Project name は netcmd-hta としました。
  3. レポジトリが作成される。そのページに表示される指示に従って git bash で作業:
    1. (適当な場所で)mkdir netcmd-hta
    2. cd netcmd-hta
    3. git init
    4. touch README
    5. git add README
    6. git commit -m 'first commit: add README'
    7. git remote add origin git@github.com:kazunori-kawauchi/netcmd-hta.git
      • origin という名前で github の netcmd-hta.git を参照する(リモートとの関連付け)
    8. git push -u origin master
      • commit した内容をリモートに対して push
こうやって作ったレポジトリにファイルやディレクトリを作っていって、add & commit & push して github 側に載せることができるようになりました。

ということで一応、リンクも載せておきます。
https://github.com/kazunori-kawauchi/netcmd-hta


OAuth について

…久々のブログだ。
というのはさておき。

ちょっと勉強のために OmniAuth を使った OAuth 認証を行えるサンプルの Rails アプリを作ってみました。
# OAuth で「認証」をやっている、と書くだけで素人臭さ全開ですね…。

OAuth の相手にはひとまず Twitter を選んでいます。
思ったよりも大変で1日掛かりでショボショボのサンプルが動きました。

認証基盤などは大雑把に理解しているつもりでしたが、概要と詳細の実現方法には大きな隔たりがある、実際にやってみることが大事だ、ということを再確認することになりました。
今回一番ハマッたのは
  • Twitter 側の Callback URL に(テスト環境なので…)http://127.0.0.1:3000/auth/twitter/callback/ を指定する
ところでしょうか…。
後は OmniAuth がよろしくやってくれました。

2011年11月14日月曜日

vForum2011 に行ってきました

Ruby とは全く関係の無い話ですが、vForum2011 というVMware社が主催するイベントに参加してきました。

事前に興味を持っていたのは
  • 仮想デスクトップ環境
  • 仮想環境のバックアップ
辺りだったので、ほとんどその周辺技術のみの情報収集に終始しました。

仮想デスクトップ

BCP(Business Continuity Plan: 事業継続計画)の観点から仮想デスクトップが注目されているそうです。
3月11日の震災の影響もあり、日本でも一気に導入が進みうる状況が出来上がっています。

個人的には、「数百台のデスクトップマシンを集約的に管理するツール」として使えるかどうかを見に行ったのですが、
  • ○:多くのマシンを管理するのには向いていそう
    • ネットブート等に比べてもサーバ1台あたりのクライアント数が多い(最大100台くらいまでいける)
    • 仮想ゲストOSのテンプレートから各ユーザに割り当てる仮想ゲストを作成し、重複部分は共有できる
  • ×:描画のパフォーマンスはチューニング次第でそこそこ出るが、動画や音声を伴うと辛い
などの情報を受けて「教育用環境(特に言語学習)には採用しづらい部分がある」という判断を勝手に下しました。言語学習はどうしても外せない部分なので、「一括管理」を目指すのであればそこだけ分離させるのはどうも筋が悪そうに思えます。

そうなると教職員向けの環境としての仮想デスクトップの方が導入しやすい気がしてくるなぁ…。
教職員の方も、それはそれで「いつまでも Excel 2003を使う」みたいな状況があるので、アプリケーション仮想化と組み合わせて考えないといけないのですけれど…。

Rubyベストプラクティス 3章(中盤)

前・後編にしようと思ったら思いの外、内容がボリュームあって読むのに時間がかかってブログの更新も止まりがちです…。
ひとまず細かく分けることにしました。

ということで、今回は 3.3節 の特異メソッドの話のみです。

3.3節 オブジェクトごとの振る舞い

普通のオブジェクト指向プログラミング言語では振る舞いをクラスに結び付けて定義します。
Rubyではクラスだけでなく、(同一クラスの特定の)オブジェクトに振る舞いを結びつけることができます。

個人的にはまだこの機能の使いどころはつかめていません。この機能を頭の片隅に置きながら Ruby プログラムをたくさん書くことでしか体得できないのだろうなぁ、と考えています。
なお、Rubyベストプラクティスには
スタブメソッドの呼び出しが、テストの実行結果全体に影響を及ぼすことがないようにしたいためだ。
とテストケースの実行における例が書いてあります。

irb で見る基本的な特異メソッド

Rubyベストプラクティスの p.79 のサンプルを実行してみました:
irb(main):001:0> class User; end
=> nil
irb(main):002:0> user = User.new
=> #<User:0xb776a770>
irb(main):003:0> def user.logged_in?
irb(main):004:1>   true
irb(main):005:1> end
=> nil
irb(main):006:0> user.logged_in?
=> true
irb(main):007:0> another_user = User.new
=> #<User:0xb775715c>
irb(main):008:0> another_user.logged_in?
NoMethodError: undefined method `logged_in?' for #<User:0xb775715c>
    from (irb):8
user には logged_in? メソッドが定義されていて、別のオブジェクト another_user では logged_in? が使えないのが分かります。

実際に動的に特異メソッドを定義する

プログラムの内部で動的に特異メソッドを定義しようと思うと "define_method(symbolname, &block_body}" を使うのがこれまでの知識ですが、

irb(main):002:0> user = User.new
=> #<User:0xb773177c>
irb(main):003:0> user.define_method(:logged_in?) { true }
NoMethodError: undefined method `define_method' for #<User:0xb773177c>
    from (irb):3
直接だと define_method は使えません。

define_method を行うようにするには
irb(main):004:0> singleton = class << user; self; end
=> #<Class:#<User:0xb773177c>>
irb(main):006:0> singleton.send(:define_method, :logged_in?) { true }
=> #<Proc:0xb7713cb8@(irb):6>
irb(main):007:0> user.logged_in?
=> true
と記述します。
ここで使われているテクニック:
  • 004 の部分( singleton = class << user; self; end )では、「クラスメソッドの定義」と同じ手法が使われています(が、詳しくは後で…)
  • 006 の部分( singleton.send(...) )では、本来 private method である define_method を send メソッドを経由することで回避しています。
これでプログラム内で動的にメソッドを定義することが可能になりました。

Ruby におけるクラスメソッドの定義

Ruby ではクラスメソッドを以下のように定義します:
class A
  class << self
    def message
      "hello, world"
    end
  end
end

A.message
# => "hello, world"
「Ruby の世界では全てがオブジェクト」というのを思い出すと、2行目の "class << self " がクラス A のオブジェクトを拡張して(特異メソッドを定義して)いる、と読むことが…できるそうです。僕にはまだもう少し時間がかかりそう。

なので、irb:004 で行っている class << user; self; end は user オブジェクトを拡張するためのクラス(= user オブジェクトの特異メソッドを定義するための「空間(クラス)」)を生成していることになります。
後はそのオブジェクトに対して define_method してあげればいいわけです。

2011年11月1日火曜日

rvm と bundle の関係(初級)

rvm (Ruby Version Manager)は Ruby および RubyGems の切り替えを行うものですが、gem パッケージを管理する方法として bundler もあります。
いろんなブログを読んでいると「併用が最強」という風に見えるので、 bundler についても理解しておこうと思います。

Bundler は Gemfile を元に動作します。例えば、
gem 'rails', :version "> 3.1.0"
と Gemfile に書いてあれば、
$ bundle install  
とすることで、適切なバージョンの rails をインストールしてくれます。

また、システム上の gem に複数のバージョンの rails がインストールされていても
$ bundle exec rails generate ...
とすることで、Gemfile で指定されたバージョンを使ってコマンドを実行してくれます。

次の段階はきっと「バージョンアップにどうやって対応していくか」だと思うんですが、初歩の段階での環境構築についてはこのくらい分かっておけば何とかなりそうな気がしています。

あと、rvm との併用ってことで流れをまとめると
  1. Rails アプリケーションのルートディレクトリ($APP_HOMEとする)に移動する
  2. Gemfile を書いて、$APP_HOME に置く
  3. rvm --create use 1.9.2@yourappname とかで、新しい gemset の環境を作りつつ、その環境に移る
  4. bundle install --path vendor/bundle で、新しく作った gemset 環境下に gem をインストール
  5. .rvmrc を書いて、$APP_HOME に置く: "rvm use 1.9.2@yourappname" とだけ書いておけばOK(?)
こうすれば bundle exec のお世話にならなくても、その rvm 環境では単一のバージョンの gem しか存在しない「きれいな環境」なので、頭を悩まさなくても良さそうな予感です。

rails3 + rspec2 (の一歩手前)

Rails3 + Rspec2 の使い方

rails アプリケーションのルート(?)ディレクトリで、
$ rspec spec/*
とすると、spec以下の全てのテストが対象にできます。

逆に、
$ rspec spec/model
とか
$ rspec spec/model/somemodel_spec.rb 
とすれば、「model だけ」とか「特定の model だけ」とかをテストできます。


…まだ、きちんと書けて無いけれど。

2011年10月31日月曜日

Ruby の module

scaffold の中身をちょっと掘り下げる第2回目の記事です。
といっても、Ruby における module という言語要素の理解をするためだけの記事なので、本質的には rails / scaffold とは関係ないかもしれません。

modelname_helpers.rb は module として定義されていました。
大学院時代のトラウマ経験のせいで「モジュール」という言葉には過敏に反応してしまいます…。

モジュールという言葉も曖昧な言葉なのですが、個人的によく使っていた定義は「再利用可能なプログラム単位・部品」です。
例えば、java における package や class は上記の定義においてモジュールです。既存の package を import したり、既存の class の extend することでプログラムの再利用をしています。

Ruby の module は include という形でプログラムの再利用を行います。任意の class で
include ModuleName
とすることで、その module で定義されたインスタンスメソッドや定数などを使えるようです。
公式ドキュメントではない(?)ですが、Programming RubyのWeb版でちょっと触れられているのを見つけました。

class と module

この辺りについて書きたいことはあるけれど、何か難しく考えてるだけのような気がします。
ざっくり言うと、
  • class は(class だけに)分類を行ってその共通部分を抽象化する(スーパークラスにする)
  • 分類から漏れた・分類にそぐわない共通部分を module として抽象化する(抽象サブクラスにする)
くらいの表現になりそうですが、やっぱり詳しく書くのは止めておきます。

ActiveRecordとはなんぞや

昨日の「scaffoldの中身を見ていく」を読み返してみると、いくつか不思議なことを見つけました。なので、数回に分けてちょっと掘り下げたいと思っています。
やっぱり時間をおいて改めて考えるって大事ですね。

なぜ Post クラスの定義は空でよいのか?

Post クラスはmodel部分を担うです。Rails における model は(誤解を恐れずに言うと)その Rails アプリケーションで使うデータの表現です。ということは、「Postとはどんなデータなのか?」を表現しているはずです。Javaの人の感覚なら、そのデータを構成する属性がクラスのインスタンス変数として宣言されているのが自然に思えます。

でも、空です。ActiveRecord::Base を extend している部分を除いて。

その部分が重要なんだろうと思って、ActiveRecord::Base の API ドキュメントを見てみると Posts クラスが空の定義で済む理由が何となく分かりました。一行目で。
APIドキュメントから引用:
Active Record objects don’t specify their attributes directly, but rather infer them from the table definition with which they’re linked. Adding, removing, and changing attributes and their type is done directly in the database. 
要するに、「そのクラスに対応しているデータベースのテーブル定義を見て、そのクラスを構成する属性を推定している」ということだったようで、クラスにインスタンス変数の宣言などが必要ない理由が何となく分かりました。他の "Adding, removing, and changing ..." の部分がいかに実現されているのかが理解できたら、「しっかり分かった」っていうことにします。
# 分かっている人っぽく表現すると「それって Object-Relation Mapping だよね?」

あと、「もっと詳しい(例の載った)ドキュメント」としてREADMEも挙げてあったので、そのうちきちんとフォローしたいと思います。こちらは例が豊富なので、「なぜ動作するのか」よりも「いかに使うのか」に重点を置いた感じになっているので、自分でプログラムを作る際の参考にするべきドキュメントかなあ、と。

scaffold の中身を見ていく

とあるtweetで
Scaffold をそらで写経できない Rails プログラマーは中2からやり直せ。
と、心温まるお言葉が表明されていました。

Ruby の if 文すら怪しい自分は小学生以下決定なわけですが、少しでも学年を上げるべく scaffold の写経をしたいと思います。この前、"rails g scaffold post ..."で作った足場を元に中身を見ていきます。

一通り書いてみて思った感想としては、 RailsGuide の getting started の中身を模写しただけみたいなブログになっちゃいました…。

app/models/post.rb

ソースコード:
class Post < ActiveRecord::Base
end
"rails g scaffold modelname" で作成されるモデルの中身は、ActiveRecord::Base クラスを extend しているだけの空のクラスであることがわかります。

app/helpers/posts_helpers.rb

ソースコード:
module PostsHelper
end
modelname_helpers.rb についてもほぼ同様に空の定義が与えられます(こちらは module ですが)。

config/routes.rb

このファイルは元々存在するファイルですが、generate scaffold で行が追加されます:
resource :posts
# post なんていう紛らわしいモデル名で作ってしまったのは失敗だったかなあ…。

routes.rb では URL → controller のマッピングを行っているようです。
http://guides.rubyonrails.org/routing.html の 2.2節 "CRUD, Verbs, and Actions" を見てみると、"resource :posts" ではデフォルトの7つの挙動が追加されるそうです。
  1. get /posts => index : post の一覧を表示
  2. get /posts/new => new : 新規作成のためのフォームを表示
  3. post /posts => create : 新規作成
  4. get /posts/:id => show : #{:id} 番目の post を表示
  5. get /posts/:id/edit => edit : #{:id} 番目の post を編集するフォームを表示
  6. put /posts/:id => update :  #{:id} 番目の post を更新
  7. delete /posts/:id => destroy :  #{:id} 番目の post を削除
…ちらっと目に入った2.5節の感じだと "resource :posts" と "resource :post"で意味が変わってくるような気がするので、その点は写経的には気をつけるポイントっぽい。
ただ、routes.rb の詳しい内容は上記のガイドだけでもブログ一回分以上の内容がありそうなので別記事にします。

おまけ:rake routes と rails-sh

routes.rb をググってた時、@ITの記事を見つけ、その中で rake routes コマンドと rails-sh を紹介していました。

rake routes コマンドは routes.rb で指定されたマッピングについての情報を表示するためのコマンドです。試しに実行してみたら、先ほど列挙した7つの動作が post について表示されました。resource :posts の効果が確認できた形です。

また、「rails コマンドとか rake コマンドは何かやたらと時間がかかるなあ。仮想マシンなのがいけないのかなあ」と思っていたんですが、@ITの記事を読んでて
以上のように、railsコマンドやrakeはRailsアプリケーションを実装する上で欠かせない便利なツール群です。
しかし、コマンドの実行のたびにRailsアプリケーションをロードするので、毎回少し時間がかかってしまいます。
それを解消するために本記事の筆者の1人である@jugyoが作ったツールが「rails-sh」です。
rails-shは、あらかじめRailsアプリケーションをロードした状態で起動するコマンドラインシェルのようなものです。
という記述を見つけ、時間のかかる理由が納得できました。
rails-sh を使うかどうかは後々考えます…。

app/controllers/posts_controller.rb

おそらく scaffold で生成される Ruby プログラムはここが全てなんだろうと思います。
…こういう書き方をすると、「TDDだから TestUnit の部分を重要視しないなんて、やっぱり初心者だな」と思われるんでしょうか。「メインの関心事(= システムの挙動)に関する部分で」っていう前置きがあれば正しい表現になるのかなあ…。

さて。
class PostsController < ApplicationController
...
end
まず分かることは、ApplicationController のサブクラスであることです。
ApplicationController は ActionController (ActionPack gemsの一部?)を extends しているクラスです。ApplicationController クラスの中身はほぼ空なので、その親クラスである ActionController クラスの中身に意味があるはずです…が、ActionController はちょと巨大なのでブラックボックスにしておきます。
# ほぼ空 = protect_from_forgery のみ = CSRF対策のためのコードが定義されている

PostsContoller クラスでは "resource :posts" で定義される7つの挙動に対応するメソッドが定義されています。
一番シンプルな形のメソッドは
def index
  @posts = Post.all

  respond_to do |format|
    format.html
    format.json { rendor json: @posts }
  end
end
というものです。

最初の @posts 変数への代入では Post クラス(モデルクラス)が使われています。この場合は .all なので、「全ての Post」となります。他のメソッドでの @post の代入では、
  • find メソッドを使って、モデルクラスのデータを検索している
  • new メソッドを使って、モデルクラスのデータを新規作成している
のが見て取れます。

あと、Rubyベストプラクティスを読んでいるおかげか、respond_to が「特別なメソッドを使ってDSLっぽく仕上げる」仕組みなのが想像できます。どうやら actoinpack/lib/action_controller/metal/mime_responds.rb で定義されているようですが、追いきれませんでした…。
exampleを見る限りだと、respond_to に渡されているブロックの引数 format (Webクライアントからのリクエストによって決まるらしい)によってどんな仕事をするのかを記述するようです。
また、(この部分はまったくプログラムを追っていませんが挙動から予想するに)html フォーマットの場合のデフォルトの挙動は methodname.html.erb に処理が移るようです。

その他の scaffold の生成物

きょうのところは勘弁してやる…してください。
      create    db/migrate/20111030125941_create_posts.rb
      invoke    test_unit
      create      test/unit/post_test.rb
      create      test/fixtures/posts.yml
      invoke    erb 
      create      app/views/posts
      create      app/views/posts/index.html.erb
      create      app/views/posts/edit.html.erb
      create      app/views/posts/show.html.erb
      create      app/views/posts/new.html.erb
      create      app/views/posts/_form.html.erb
      invoke    test_unit
      create      test/functional/posts_controller_test.rb
      invoke    helper
      invoke      test_unit
      create        test/unit/helpers/posts_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/posts.js.coffee
      invoke    scss
      create      app/assets/stylesheets/posts.css.scss
      invoke  scss
   identical    app/assets/stylesheets/scaffolds.css.scss
大まかに把握しておくと、db の部分、view の部分(erb および assets/coffee,scss)、テストの部分が手つかず。


2011年10月28日金曜日

Rubyベストプラクティス 3章(前半)

3章は Ruby の動的な性質を使って、柔軟なプログラムを構築する方法について。

「動的な性質」ってよく分からずに読み進めたら、結局リフレクションのことなんですね。
…ある意味、専門領域です。
ということで、3章前半はオープンクラスの話だと思ってしまおう。

3.1節 メソッドの定義を動的に変更

例として、BlankSlateというクラスが挙げられています。
ちょっと背景説明の方へ話を移すと、Ruby 1.9系では、言語組み込みとして BasicObject という「何の機能(メソッド)も持たないクラス」が提供されています。
「何の機能(メソッド)も持たないクラス」は method_missing などで動的なメソッド生成をするさいに衝突の危険を避けることが簡単なので重宝されます。
そういった便利なクラスが Ruby 1.8系では言語要素ではないので、Jim Weirich が BlankSlate を提供してくれています。

BlankSlate がやっていることを自然言語で説明すると、「そのクラスに定義されたメソッドを(一部を除いて)一旦 BlankSlate#hidden_method というハッシュに退避した上で undef_method している」です。
こうすることでメソッドの定義されていないクラスを作っています。
また、一旦退避(undef_method)させたメソッドを define_method で再定義(書き戻す)もできます。

これらの undef_method や define_method は動的にメソッド定義を行うための Ruby の仕組みで、メソッド(の定義?body?)自体がオブジェクトであることを利用しています。

3.2節 柔軟なインターフェース 

…同じタイトルが2章でもでてきます。Rubyにとって非常に重要なテーマなのでしょう…。

3.2.1 instance_eval()

2節でも似たようなテーマでコードブロックの話が出ていますが、ここでもコードブロックの話で、コードブロックに引数を渡す方法とその効用についての話…だと思います。
ちょっと何の話を目的にしているのか自信がありません…。

まずは、本に載っているサンプルプログラム(に近いもの)を実際に試してみます。
[kazu@cent6 bestpractice]$ ruby 03-2-1-instanceeval-err.rb
03-2-1-instanceeval-err.rb:29:in `generate_pdf': undefined local variable or method `full_name' for #<Document:0xb77d0e30> (NameError)
        from 03-2-1-instanceeval-err.rb:12:in `instance_eval'
        from 03-2-1-instanceeval-err.rb:12:in `generate'
        from 03-2-1-instanceeval-err.rb:28:in `generate_pdf'
        from 03-2-1-instanceeval-err.rb:35
instance_eval() で実行されるコードブロックは、そのオブジェクトをコンテキスト(= enclosing scope)とするので、そのローカル変数しか参照することができません。一方、コードブロックを call メソッドで呼び出す場合、そのコードブロックが定義された時点でのコンテキストを引き継いだ Proc オブジェクトの呼び出しになります。

つまり、
class Prawn::Document
  def self.generate(file, *args, &block)
    pdf = Prawn::Document.new(*args)

    block.arity < 1 ? pdf.instance_eval(&block) : block.call(pdf)
    ...
  end
end
というプログラムでは、
  • instance_eval(&block) は pdf オブジェクト(Documentクラス)のコンテキストで
  • block.call(pdf) は、blockの本体が作られた時点(MyBestFriendクラスのメソッド呼び出し時)のコンテキストで
実行される…はずです。

3.2.2 method_missing() と send()

Ruby では定義されていないメソッドを実行すると(さっき試したみたいに)「undefined ...」と当然エラーになりますが、method_missing() というメソッドを定義しておくと undefined methods を実行した際に、エラー処理の代わりに method_missing() が実行されます。

これを使って動的にメソッドを解釈させる仕組みの説明です。
Convention Over Configuration の例にもなっていると思います。

例として図形描画プログラムが挙げてあります。
複数の図形の描き方(線画、塗りつぶし、線画と塗りつぶしの両方、…)と複数の図形要素(直線、円、多角形…)があるときに、
stroke_line [0,0], [50,50]
fill_circle_at [100,100], :radius => 50
stroke_and_fill_polygon [100, 250], [...], ...
という書き方が出きるとスマートです。ただ、いちいち
def 描き方_図形要素(*args)
  図形要素(*args)
  描き方
end
というメソッド定義を行うのは手間がかかります。組み合わせ爆発の状態です。

これを method_missing() を使って、「"描き方_図形要素" というメソッドが来たら、"図形要素"メソッドと"描き方"メソッドを実行する」という仕組みにしています。
また、この時の「…を実行する」ための方法として send(methodname, *args, &block) を使っています。

3.2.3 DSLっぽいアクセサ

DSL = Domain Specific Language = 領域指定言語、つまり、特定分野の記述を行うための言語のことです。Ruby は、その言語の中で DSL 風に書けることがよく知られていて、一部の人には大人気です。

さて。

普通のアクセサは
font_size = 10
のようになるのですが、定義をちょっと工夫すると
font_size 10
という風に書けて、非常にDSLっぽくなります(CSSなんかでこんな記述ができそうな雰囲気)。
しかも、このメソッドは
font_size
と引数なしで呼び出すと値を返す reader にもなります。

でも、難しい定義ではなく
def font_size(size = nil)
  return @font_size unless size
  @font_size = size
end
とするだけです。

また、通常のアクセサの代わりもさせておくと、この書き方に慣れない人にとって有意義なので、
alias_method :font_size= :font_size
とエイリアスを作っておくTipsも紹介されていました。

3.3節 特異メソッド

…と思ったのですが、長くなったので3章は分割してお送りすることにします。

2011年10月27日木曜日

ブログのデザインをちょっと替えてみました。

全体の色を青系統から赤系統に替えて、ヘッダに画像を入れてみました。

ヘッダの画像はとある櫻花の画像生成を使って生成させていただきました。
こういうみんなに気軽に使ってもらえるWebサイトを自分で作れるようになりたいし、そうなるための Ruby の勉強だ!とモチベーションを上げてみます。

あと、テンプレートも替えてみました。Blogger は hN タグが使えなくてなんでだろうと思っていたらテンプレート側で使っているせいなんですね…。
自分でHTMLを編集して、カスタムCSSを使うことにしました。

…一旦、追加したカスタムCSSはテンプレートで直接「HTMLを編集」しないと修正できないのか。
修正できないのかと思って冷や汗かいた…。

ちょっと話はそれて Javascript へ。

たまにはお仕事での話もしてみます。

お仕事の方でプログラムを書く機会はさほど多く無いのですが、HTA という仕組みを使って Windows Server (ActiveDirectory) を操作するツールを VBscript で作っていたりします。
ツールをコンパイルが必要な言語で作成すると、「ソースコードが紛失して手直しが出来ない」「開発環境が必要になる・用意するのが面倒」などの困難がつきまとうので、(主に自分以外がメンテナンスするにあたって)ツール自体がソースコードファイルになり、メモ帳で修正可能なスクリプト言語を採用しています。

HTA は基本的にタダのHTML(バックエンドとしてInternetExplorerが動いているだけ?)なので、「View の上で全ての処理をするお粗末なWebアプリケーション」を書く感覚に近く、自分のスキルでも何とかなってしまいます。
…そろそろ、綺麗な形で書き直したくなってきたなぁ。

で、愚痴はともかく、ActiveDirectory から複数件のユーザアカウントデータを検索して表示するツールを作ったんですが
「ソートしたい…」
と素朴な気持ちが出てきました。

HTMLで表データをソート、となったら「Javascript で何とかなるんじゃない?jQueryとかすごい便利だし。」と思い、 …ハマりながらもうまくいきました。
# ちなみに HTA では Jscript (≒ javascript)もサポートしているので、
# jQuery が使えます。ラッキー。

Tablesorter

ということで、検索結果をソートするために Tablesorter を使いました。
Tablesorter は jQuery のプラグインです。一番基本的な使い方は "Getting Started" で示されているように
  • jQuery と Tablesorter の .js ファイルを読み込む
    • Tablesorter のサイトから両方ダウンロードできます。jQuery のバージョンにこだわりが無ければ揃えて入手できていいかも?
  • Table 要素に対して tablesorter() を実行するような Javascript を記述する
    • 特定の Table 要素をソートしたい場合は Table タグに id 属性をつけておくと良さそう
  • Table タグに class="tablesorter" を指定する(必要?)
  • Table タグの子要素として、ヘッダ行を thead タグで囲む
  • Table タグの子要素として、データ行部分を tboday タグで囲む
とします。

ただ、今回、ソートをしたいデータは VBscript で作成する(= 元のHTMLにはないデータ)なので、AJAXの際のプログラミングを参考にします。
http://tablesorter.com/docs/example-ajax.html
# Advanced example らしい…。
しかし、この例でも「空のテーブルが存在する状態」から始まっているので、table 要素全体を VBscript で生成しようとしている自分のケースにはそのままは使えません。
そこで、上記の例で出ている「あるIDの要素がクリックされたときに~」という記述を参考にして、「あるIDの Button 要素(「検索する」ボタン)がクリックされたときにtablesorter を実行する」というプログラムにしました。

VBscript で生成される前のHTML要素を javascript で扱えない(当たり前なんですが…)ことに途中で気づき、「どのタイミングでどのデータが存在しているのか?」を考えていたのが山場でした…。
jQuery で動的に要素を追加してると結構ハマるポイントのような気がします。

また、tablesorter は1行目の値を見て、「その列にどんな値が入っているのか?(文字列?数値?日時?)」を推測してくれるのですが、間違って推測することがあるので、自分で指定することにしました。
参考URL: http://mottie.github.com/tablesorter/docs/example-option-date-format.html

以下の javascript の "sorter" の部分が各列の型を指定している部分です(その前の 0: や 1: が「何列目」を示す 0-origin インデックス)。
$(function() {
  // call the tablesorter plugin 
  $("table").tablesorter({ 
 
    dateFormat : "mmddyyyy", // set the default date format 
 
    // or to change the format for specific columns, add the dateFormat to the headers option: 
    headers: { 
      0: { sorter: "shortDate" }, // dateFormat will parsed as the default above 
      1: { sorter: "shortDate", dateFormat: "ddmmyyyy" }, // set day first format 
      2: { sorter: "shortDate", dateFormat: "yyyymmdd" }  // set year first format 
    } 
 
  }); 
});
…ということで、VBscript で生成した表をソートするのに javascript が使えました、という報告です。