vimでEmmetなCoffeeScriptとjavascriptのプレビューエディタを作ってみた
結構前に自分達で使うために弟と作ったものですが、せっかくなので公開します。
CoffeeScriptとjavascriptの練習をするのによかったら使ってみてください。
js用のemeco
coffee用のemecoffee
エディタにはCodeMirrorを使用しています。
使用にあたって、注意点があります。
・Macを前提に、CocoaとEmmetをガンガン使うことを想定して作りました ・エディタはvimです ・矩形ビジュアルモードがありません ・テキストオブジェクトの操作も全然充実していません
Emmetについては、チートシートと、CodeMirrorのEmmetを参照してください。
vimの操作方法がわからない方は、
$ vimtutor
とコマンドを打てば、vimの基本操作のチュートリアルが開きます。
emecoと契約してvimmerになってよ!
ソースコードはここにあります。
cloneしたら、htmlをブラウザで開けてみてください。
画面左側がエディタで、右側が表示用ブラウザです。
画面を開いた時点でhtmlエディタにフォーカスがあたってます。
操作方法の説明はヘッダーのロゴの右側の「What is emeco?」をクリックしてください。
こんなんが出ます。
vimのemmet展開なのに、キーバインドが<C-y>,でない点に関しては目をつぶってくださいw
htmlとcssは、打鍵のたびに反映します。
jsとcoffeeは実行時に反映します。
Emecoのcss、js、coffeeのエディタに書いたソースは、htmlエディタにscriptタグやlinkタグを書かなくても右のブラウザ上で反映されるようになってます。
また、jQueryとbootstrap3が最初から動くようになっています。
エディタ上部のLoad fileは、ローカルのファイルを読み込んで開いているエディタに内容を書き出します。
Saveは<C-s>でも代用可能で、開いているエディタをローカルに保存します。
coffee、jsのときのみ表示されるRunは、<C-r>でも代用可能で、コードを実行します。
説明ばっかりじゃよくわからないと思うので、実際に使ってみましょう。
ここからはemmet初心者を対象に、emmetの入力まで書いていこうと思います。
説明が丁寧すぎてくどいので、慣れている方は戻る推奨です。
emecoffee.htmlを起動します。
起動時は、htmlエディタにフォーカスが当たっています。
iでインサートモードにして、html:5と入力してtabを押してください。
emmetが展開されて、こうなります。
head内のtitleにカーソルが当たっますが、とりあえずここはどうでもいいので、bodyの中を書き換えます。
vimmerがカーソル移動をnomalモードで行うことは世界の常識ですが、emecoを使うときは、<C-f>、<C-b>、<C-n>、<C-p>を使っての移動もオススメです。
bodyタグの中にカーソルを移動したら、ヘッダーを作ってみましょう。
左側にh2を置いてロゴ風に、右側にgoogleへのリンクでも貼って雰囲気を出します。
それでは、header>h2#logo+a[href="http://www.google.com"]と入力して、カーソルを行末に移動し、tabを押してください。
行末への移動も、<C-c>Aと<C-e>どちらでもいいです。
こうなります。
タグの中身を適当に埋めます。
emmet展開した時点でh2の中にカーソルがあたっているので、emeco練習とでも入力して、Ctrlとoptionと右を同時押ししてください。aタグの中にカーソルが移動します。Ctrl-option-右左でタグ間を移動できます。
aタグの中にはgoogleとでも書いておきます。
こうなります。
htmlが反映されてます。
ヘッダーが用意出来たので、画面左側にサイドバーを置きましょう。
サイトバーにはとりあえず、amazonへのリンクでも入れておきます。
それだけでは寂しいので、pタグにサイドバーと書いておきます。
<C-c>でnomalモードに移行して、joと入力してください。ヘッダーの下から入力ができます。見やすくするため、1行開けます。enterを押しましょう。
そこに、div#sidebar>p+a[href="http://www.amazon.co.jp"]と入力し、カーソルを末尾に移動した後、tabを押してください。
こうなります。
pタグの中にサイドバー、aタグの中にamazonと書いておきます。
最後に、main領域を作って、canvasを置きます。
一行開けて、div#main>canvas#cv[width=500 height=500]と入力してemmetを展開して下さい。
htmlとプレビュー画面はこうなりました。
次は、cssを書きます。
htmlエディタにフォーカスがあたっている状態で<C-t>を押すと、cssエディタに移動します。
こんな感じ
最初にヘッダーをそれっぽくします。
header {}をこんな感じにして中にカーソルを合わせて、スペースを2回押してください。
準備ができたら、cssを書きましょう。
背景色を設定します。bgcと入力してtabを押してください。
bacground-color: #fff;になるので、#fffをdarkblueに書き換えます。
darkblueのeを入力した瞬間に色が変わると思います。
上の隙間がキモいです。ピッタリくっつけましょう。
<C-c>oで下の行から入力を開始して、po:aと入力してtabを押してください。
こうなります。
形が大変なことになりました。
画面の端まで伸ばしましょう。
下の行から入力を開始して、w:100%と入力してtabを押してください。
こうなります。
h2とaタグが縦に並んじゃってるので、横に並べましょう。
下の行に移動して、dと入力してtabを押した後、blockをflexに変更してください。
変更した瞬間にgoogleが横に行きます。
ついでに、googleを右端に持って行きましょう。
下の行に、justify-content: space-between;と入力します。emmetが見当たらなかったですw
これでgoogleが右端に行きました。
ブラウザにカーソルを乗せて、左にスワイプすると、ブラウザが移動します。
配置はこんなもんにして、文字の色を変えましょう。
下の行に、c<Tab>と入力して、#000をsnowにします。
次はh2をいい感じに配置します。
#logo { }
の形を作り、中にml<Tab>1em、mt<Tab>0.3emと入力してください。
こうなります。
これでヘッダーは終わりにします。
次はサイドバーを配置します。
今までに入力したプロパティは入れちゃいます。
#sidebar { position: absolute; margin-top: 10em; margin-left: 1em; }
次に、ボーダーを引きます。
bd<Tab>でborder: ;を入力して、5px solid lightgrayと入力してください。
こうなります。
このままじゃごついしバーっぽくないので、ボーダーの角を丸めて、長くします。
border-radius: 10px;と入力してください。
本物のvimではbdraで行けるんですが、emecoでは行けませんでしたw
そうしたら、h:30%<Tab>で長くしましょう。
サイドバーはこんな感じ。
cssの最後はキャンバスの配置です。
#mainをいじって配置したいところですが、先にキャンバスに色をつけてどこにあるかわかるようにします。
#cv { background-color: black; }
こんなことになりました。
かぶらないように配置しましょう。
#cvの上に以下のコードを配置します。
#main { position: absolute; margin-top: 10em; margin-left: 15em; }
最終的にこうなりました。
最後にCoffeeScriptでキャンバスをいじります。
キャンバスをクリックしたら左から右に丸が移動するコードを書いてみます。
CoffeeScriptはEmmetがないので、まとまった単位でコードを書きます。
まず、全てのDOMの読み込みが終了したら動くように、$.readyを書きます。
$ ->
次に、クリックするたびにDOMを取得するのもめんどくさいので、広いスコープでキャンバスを取っておきます。
# キャンバスを取得 cv = $('#cv')[0] ctx = cv.getContext '2d'
次に、丸を左から右に移動する関数を書きます。
関数を書く前に、話を簡単にするため、広いスコープに円のx座標とタイマーIDを取っておきます。
# 円のx座標とタイマーID circleX = 0 timerID = 0
それでは、関数本体を書きます。
moveCircle = -> # キャンバスを初期化 ctx.clearRect 0, 0, 500, 500 # 円が画面外に移動したら終了 if circleX > 500 circleX = 0 clearInterval timerID return null # 白い円を描画 ctx.fillStyle = 'white' ctx.beginPath() ctx.arc circleX, 250, 10, 0, Math.PI * 2, false ctx.fill() # 円のx座標を足す circleX += 10
clearIntervalなどの関数は、入力の途中で<C-y>を押せば入力補助ができます。
そうしたら、クリックイベントにイベントハンドラをセットします。
繰り返し関数を呼ぶため、setIntervalを使います。
# キャンバスにイベントハンドラをセット $(cv).on 'click', -> timerID = setInteval moveCircle, 10
入力が終わったら、<C-r>かRunボタンをクリックしてください。
CoffeeScriptが実行されます。
実行したら、キャンバスをクリックしてください。
左から右に円がスーッと移動します。
ひと通り完成したので、保存しましょう。
<C-s>を押してください。
こんな感じになります。
適当なファイル名を入力して、enterかOKを押してください。
拡張子は入れても入れなくてもいいです。
入れない場合、開いているエディタの演算子が補完されます。
こんな感じにクライアントサイドのプログラミングをするときにちゃちゃっと使っていただけると幸いです。
初めてのRSpec
今回は基本的なRSpecの使い方を見ていこうと思います。
まず、RSpecが何なのかを見てみましょう。
この記事がわかりやすいです。
一言で言うと、テストを標準よりいい感じにかけるDSLです。
この記事は使い方を見ていくので、標準のTest::Unitよりどういい感じなのかはここでは割愛します。
興味が湧いたらぜひ標準のTest::Unitと比較してみてください。
さっそくRSpecのコードを書いてみましょう。
変数helloの中身が"Hello, World!であることをテストするコードを書いてみます。
テスト駆動開発(TDD)を意識して、テストを先に書きます。
コードを書く前に、RSpecが動くようにしましょう。
$ rspec --init
これで、カレントディレクトリにspecというディレクトリが出来ます。
そして、specディレクトリの中にはspec_helper.rbがあります。
とりあえず、このspec_helperは無視して簡単なテストを書きましょう。
specディレクトリの中にRSpecのコードを置きます。ファイル名は*_spec.rbのように、拡張子の前に_specをつけましょう。
それでは、スペックファイルを編集します。ファイル名はhello_spec.rbでいきます。
$ vim spec/hello_spec.rb
今回のテスト内容は、変数helloの内容が"Hello, World!"であるかテストすることです。
とりあえず、無邪気にRubyのコードで書いてみます。
describe "超簡単なRSpecコード" do hello == "Hello, World!" end
テストが書けました!実行してみます。
$ rspec spec
こんなんが出てきました。
undefined local variable or method `hello' for main:Object (NameError)
helloなんて変数を書いてないんだからNameErrorが出るのは当然ですね。
では、このテストに通るようにコードを書きましょう。
$ vim spec/hello_spec.rb
変数helloに"Hello, World!"を入れます。
下のコードをhello_spec.rb先頭に追加してください。
hello = "Hello, World!"
これでテストに通るはずです。また実行してみましょう。
$ rspec spec
無事通りました!
ですが、このコードはRSpecのメソッドを使っていないため、残念な感じです。
テストの部分をRSpecのメソッドを使ったものに書き換えます。
describe "超簡単なRSpecコード" do it "変数helloの内容はHello, World!" do expect(@hello).to eq "Hello, World!" end end
itメソッドは引数にブロックを取ります。
テストのコードをブロック内に記述します。
なんかそれっぽくなりました。RSpecはすごく英語っぽくて読みやすいですよね。
次に、変数helloに値を入れるところもいけてません。
別ファイルにするか、beforeブロックに入れたいです。
今回は一行しかないので、beforeブロックに入れましょう。
以下のコードをdescribeブロックの先頭に入れます。
before { @hello = "Hello, World!" }
beforeブロックの中身はテスト実行前に実行されます。
helloの前の@はhelloがローカル変数のままだと、helloがbeforeブロック内でしか生きられないからつけました。
書き換えた後のhello_spec.rbはこんな感じ。
describe "超簡単なRSpecコード" do # テストをする前に変数helloに値をセットする before { @hello = "Hello, World!" } # テストコード it "変数helloの内容はHello, World!" do expect(@hello).to eq "Hello, World!" end end
かなりそれっぽくなりました。
このコードを実行すれば、テストには通ります。
こんな感じに。
ただ、この実行結果って情報量が少なくありませんか?
せっかくdescribeとitの後に文字列を書いたのに、表示されないなんてもったいないです。
というわけで、書いた文字列が表示されるようにしましょう。
.rspecを書き換えます。
$ vim .rspec
--formatの後ろのprogressをdocに書き換えます。
これで実行してみましょう。
コード内に入力した文字列が表示されました!
これで見やすくなりました。
ちょっとやりたいことがあるので、変数fooの中が'bar'か判定するコードを追加します。
hello_rspec.rbを書き換えます。
describe '超簡単なRSpecのコード' do context 'helloのテスト' do before { @hello = "Hello, World!"} it '変数helloの内容はHello, World!' do expect(@hello).to eq 'Hello, World!' end end context 'fooのテスト' do it '変数fooの内容はbar' do expect(@foo).to eq "bar" end end end
複数のテストを書くときに、contextでブロックを分けるのをやりたかったのでした。
さっそく、テストに落ちるために実行してみましょう。
無事、テストに落ちましたか?
今回は、RSpecのメソッドで書いたので、NameErrorではなくまっとうにfailureが出たはずです。
こんな感じ。
テストに通って、今回は終わりにしましょう。
以下のコードをfooのコンテキストの先頭に書きます。
before { @foo = "bar" }
書けたら実行してみましょう。
無事に通りました。
CoffeeScriptで碁盤を作ってみた
Railsが難しいので、CoffeeScriptで息抜きします。
さらっと作ったので、低クオリティですが碁盤を作ります。
イメージはこんな感じ。
ソースは、ここにあります。
囲碁のルールを全然知らないので、canvasに碁盤を表示して、石を置くだけにします。
碁盤は19路盤、13路盤、9路盤の3種類があるみたいなので、対応しましょう。
まずは、HTMLを書きます。
設定を変更するselectと、canvasを置くだけの簡素なものです。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>碁盤</title> <!-- jQuery --> <script src="lib/jquery.js"></script> <!-- 俺のコード --> <script src="igo.js"></script> <link rel="stylesheet" href="igo.css"> </head> <body> <!-- 設定 --> <div id="config-div"> <p> 碁盤の種類: <select class="config" id="board-kind"> <option value="19">19路盤</option> <option value="13">13路盤</option> <option value="9">9路盤</option> </select> </p> <p> 石の切り替えモード: <select class="config" id="stone-mode"> <option value="mutual">交互</option> <option value="manual">手動</option> </select> </p> <p> 次に置く石: <select class="config" id="next-stone"> <option value="black">黒石</option> <option value="white">白石</option> </select> </p> </div> <!-- キャンバス --> <canvas id="board" width="600" height="600"></canvas> <canvas id="stone" width="600" height="600"></canvas> <canvas id="cursor" width="600" height="600"></canvas> </body> </html>
次に、scssを書きます。今回は息抜きなので、配置するだけにします。
// 設定 #config-div { display: flex; margin-left: 4em; margin-top: 1em; * { margin-right: 0.6em; } } // キャンバス canvas { position: absolute; top: 4em; left: 5em; border: 1px solid black; }
ほとんど生のCSSですねwこりゃひでぇw
最後に本丸のCoffeeScriptです。
canvasは使い勝手が良すぎますね。
まず、ブラウザからも変更可能な設定項目を変数にしておきます。
ページのロードが終わってから動くように、document.readyも忘れないようにします。
ついでに碁盤の交点を入れる配列も書いておきます。
$ -> # 変数 # 19, 13, 9路盤の3択 numberOfLines = 19 # 石の切り替えモード mutualMode = true # 次に置く石 nextStone = "black" # 交点のオブジェクトを入れる配列 points = []
numberOfLinesは碁盤の種類、mutualModeはtrueであれば黒石と白石を交互に打つように、nextStoneは次に打つ石の色です。
次に、canvasのDOMを取得して、変数に入れておきます。
ついでにcanvas関連のもろもろの変数も書いておきます。
# キャンバスとコンテキストを取得 boardCv = $("#board")[0] boardCtx = boardCv.getContext '2d' stoneCv = $("#stone")[0] stoneCtx = stoneCv.getContext '2d' cursorCv = $("#cursor")[0] cursorCtx = cursorCv.getContext '2d' # キャンバスの大きさと位置を取得 canvasSize = boardCv.width canvasPos = boardCv.getBoundingClientRect()
getBoundingClientRect()は、ページ内のDOMの座標をくれます。
あとで使う、canvasの大きさと座標もついでに変数に入れておきます。
ついでに、キャンバスをクリアするメソッドを作成しておきます。
引数にコンテキストを取って、どのキャンバスも初期化できるようにしておきます。
# キャンバスをクリア clearCanvas = (ctx) -> ctx.clearRect 0, 0, canvasSize, canvasSize
次に、碁盤のクラスを書きます。碁盤が3種類あるので、クラスにしました。
まず、コンストラクタを書きます。
設定をブラウザから変更できるようにするので、碁盤の設定を一括で変更するchangeLinesメソッドを実装します。
コンストラクタでも使ってしまいましょう。
# 碁盤のクラス class Board constructor: (@lines) -> @.changeLines @lines @image = new Image() @image.src = "image/wood.jpg" @lineColor = "black" @dotLines = [3, 9, 15]
linesは線の数、lineColorは碁盤の線の色、dotLinesは19路盤で点を打つ場所です。
リアルな見た目にするため、木の画像を使用します。好みの木の碁盤を作ってください。
次に、メソッドを書きます。
まず、設定を一括で変更して、碁盤を切り替えるchangeLinesメソッドを書きます。
# 碁盤の種類を変更する changeLines: (num) -> @lines = num * 1 @points = @lines - 1 @margin = canvasSize / @lines * 0.5 @linePadding = (canvasSize - @margin * 2) / @points @stoneRadius = @linePadding / 2 * 0.7 @lineEnd = canvasSize - @margin # 交点オブジェクトを修正 points = [] for col in [0..@points] for row in [0..@points] points.push new Point @, col, row
各プロパティはこんな感じです。
lines: 線の数、9, 13, 19の3種類 points: 一行の交点の数 margin: 格子と端の余白 linePadding: 線の間隔 stoneRadius: 碁石の半径 lineEnd: 線の端の座標
碁盤を切り替えると、交点の数が変わるので、後で定義する交点オブジェクトを修正してます。
次に、クリックしたときや、ホバーした時に、座標から交点を取得するためのメソッドを書きます。
# 座標から交点を取得 getPoint: (ary, x, y) -> for point in ary pointX = canvasPos.left + point.x pointY = canvasPos.top + point.y nearX = pointX - @stoneRadius <= x <= pointX + @stoneRadius nearY = pointY - @stoneRadius <= y <= pointY + @stoneRadius return point if nearX and nearY
引数のaryは交点オブジェクトを入れた配列を渡します。
nearXとnearYは、座標と交点の距離が碁石の半径以内であればtrueになります。
次は碁盤をcanvasに描画するメソッドです。
# 碁盤を描画 drawBoard: -> boardCtx.strokeStyle = @lineColor boardCtx.fillStyle = @lineColor # 木の板を用意 boardCtx.drawImage @image, 0, 0, canvasSize, canvasSize # 線を引く for line in [0..@points] thisLine = @margin + line * @linePadding # 横線 boardCtx.beginPath() boardCtx.moveTo(@margin, thisLine) boardCtx.lineTo(@lineEnd, thisLine) boardCtx.stroke() # 縦線 boardCtx.beginPath() boardCtx.moveTo(thisLine, @margin) boardCtx.lineTo(thisLine, @lineEnd) boardCtx.stroke() # 点を打つ if @lines is 19 and line in @dotLines boardCtx.beginPath() boardCtx.arc(thisLine, @margin + dot * @linePadding, 3, 0, Math.PI * 2, false) for dot in @dotLines boardCtx.fill()
canvasいっぱいに木の画像を置いて、その上に格子を描きます。
また、19路盤のときだけ謎の点があったので、描画します。
これでboardオブジェクトに必要なメソッドが揃いました。
次は交点オブジェクトを書きます。
例によって、コンストラクタから行きます。
# 交点のクラス class Point constructor: (board, @col, @row) -> @stone = "empty" @x = board.margin + @col * board.linePadding @y = board.margin + @row * board.linePadding
x、yはcanvas内の座標です。
stoneは黒石ならblack、白石ならwhite、何もなければemptyです。
次に、メソッドを書きます。
まずは石を置くメソッドです。
# 石を置く setStone: (stone) -> @stone = stone if mutualMode nextStone = ["black", "white"][(stone is "black") * 1] $("#next-stone")[0].value = nextStone
石を交互に打つモードのときには切り替わるようにしています。
三項演算子で書いたら動かなかったので、こんな書き方にしてみました。trueが1、falseが0の言語は遊べますね。
囲碁は、石を消す必要があるらしいので、石を消すメソッドを書きます。
stoneプロパティをemptyにするだけです。
# 石を消す emptyStone: -> @stone = "empty"
石を描画するメソッドを書きます。
交点オブジェクトをまとめた配列にループをかけて使う予定なので、stoneプロパティがemptyのときは弾くようにします。
# 石を描画 drawStone: (board) -> return null if @stone is "empty" stoneCtx.fillStyle = @stone stoneCtx.beginPath() stoneCtx.arc @x, @y, board.stoneRadius, 0, Math.PI * 2, false stoneCtx.fill()
本当にcanvasは簡単で使いやすいです。
この碁盤では、交点をクリックした時に石を置くつもりです。
マウスオーバした交点に半透明の石が表示されると、思ったところに石が打てそうですよね。実装しましょう。
# マウスを載せた時の挙動 onMouse: (stone) -> cursorCtx.strokeStyle = stone cursorCtx.globalAlpha = 0.3 cursorCtx.beginPath() cursorCtx.arc @x, @y, board.stoneRadius, 0, Math.PI * 2, false cursorCtx.stroke()
これで交点のオブジェクトも出来ました。
必要なクラスも揃ったので、インスタンスを作って、描画します。
# 画像のロードが終わったらボードを描画 board = new Board(numberOfLines) $(board.image).on 'load', -> board.drawBoard()
画像を使うときは、画像のロードが終わるまで待たないと描画できません。これで結構詰まりましたw
最後にイベントハンドラを書きます。
まずは、マウスオーバー時に半透明の碁石を表示しましょう。
# イベントハンドラをセット # カーソル位置に対応する交点を表示 $(cursorCv).on "mousemove", (e) -> clearCanvas cursorCtx x = e.pageX y = e.pageY pointOnCursor = board.getPoint points, x, y pointOnCursor.onMouse(nextStone) if pointOnCursor
次に、交点クリック時に石を置いたり消したりしましょう。
# クリックしたら、石をトグルして、描画する $(cursorCv).on "click", (e) -> x = e.pageX y = e.pageY clickedPoint = board.getPoint points, x, y # 石をトグル if clickedPoint switch clickedPoint.stone when "empty" then clickedPoint.setStone nextStone else clickedPoint.emptyStone() # 石を描画 clearCanvas stoneCtx point.drawStone(board) for point in points
最後に、設定を変更したら、反映できるようにします。
# 設定を変更したら、反映する $(".config").on "change", -> switch @.id # 碁盤の種類変更 when "board-kind" clearCanvas ctx for ctx in [boardCtx, stoneCtx] board.changeLines(@.value) board.drawBoard() # 石を置いたあとに、次の石を切り替えるか when "stone-mode" mutualMode = @.value is "mutual" # 次に置く石の色 when "next-stone" nextStone = @.value
以上で、碁盤の実装は完了です。
やっぱりCoffeeScriptはいいです。もうjsには戻れそうにありません。
rails-style-guideを読む①
railsのアプリを本格的に作る前に、今回からスタイルガイドを見ていこうと思います。
まだ難しいですが、汚いコードを量産するのは嫌なので、この段階で読むことにしました。
rails-style-guideは長いです。なので、記事を分割します。分割しても長いです。
この記事は10000文字以上あるので、気軽には読めませんが、rails-style-guideは難しいからまだ読んでいないという方はぜひお付き合いください。
英語で読むのはめんどくさいので、githubに置いてある日本語訳を読みます。
本文をそのまま載せることはしていません。オレオレ噛み砕き文章に改変しています。本物を読みたい方は各自参照をお願いします。
コードは基本そのまま持ってきますが、ちょいちょいコメントを入れたりしてます。
今回は見るのは、
・序曲 ・設定 ・ルーティング ・コントローラ ・モデル
です。
序曲
RSpecとSassとSlimを使いましょうってことですね。わかります。
このブログでアプリを作るときは、cucumberも使いたいなぁ。
設定
ここはコードがないので駆け足気味で。
初期化をまとめると、
・アプリの初期化は、config/initializersに書く ・gemごとの初期化は、gemと同じ名前のファイルを作って書く
環境の設定は、
・すべての環境に適用する設定をconfig/application.rbに書く ・各環境の個別の設定をconfig/environmentsの下にファイルを作って書く ・staging環境を追加で作る
こんな感じか。ちゃんと設定を書ける男になりたい。
ルーティング
ここはコードがたくさんあるので、丁寧に見ていきます。
・RESTfulリソースにアクションを追加するときは、"member"か"collection"ルートを使う
# 悪い <- memberもcollectionも使ってない get 'subscriptions/:id/unsubscribe' resources :subscriptions # 良い <- member使用 resources :subscriptions do get 'unsubscribe', on: :member end # 悪い <- memberもcollectionも使ってない get 'photos/search' resources :photos # 良い <- collection使用 resources :photos do get 'search', on: :collection end
resourcesは、scaffoldしたときに生成されるメソッドたち(index, show, newとか)のルートと、コードの中で使えるパスとオプションを生んでくれるようです。
詳しくは、ここをどうぞ
memberとcollectionの違いは、
memberが一つのインスタンス、collectionはすべてのインスタンスのアクションを書くようです。
上の例でも、memberの悪い方の例のURLには:idが入ってるけど、collectionには入ってないですね。
・複数のmember/collectionルートを定義するときは、ブロックで書く
resources :subscriptions do member do get 'unsubscribe' # more routes end end resources :photos do collection do get 'search' # more routes end end
そりゃそうですね。毎回行末にon :memberとか書いてらんないし、読みにくいですものね。
・ActiveRecordモデルの関係を表現するため、ルートはネストで書く
class Post < ActiveRecord::Base has_many :comments end class Comments < ActiveRecord::Base belongs_to :post end # routes.rb resources :posts do resources :comments end
モデルに関係を定義したら、それに応じたルートの書き方をしましょうってことですね。
・関連するアクションをグループ化するためにnamespaceを使用する。
namespace :admin do # Directs /admin/products/* to Admin::ProductsController # (app/controllers/admin/products_controller.rb) resources :products end
namespaceは、任意のディレクトリで機能を分けたいときに使うらしい。
上のコードでは、admin以下に、productsのresourcesで生まれるルートが生成される。要するに、関連するアクションはディレクトリを切りましょうってことか。
・古い記法のワイルド・コントローラ・ルートを使わない。
# とても悪い <- 古い記法 match ':controller(/:action(/:id(.:format)))'
基本的に「この書き方は廃止」っていう部分は載せない方針で行こうと思っていたのですが、これは有名みたいなので載せました。
この書き方をすると、すべてのアクションをGETメソッドで実行できちゃうみたいです。なので、めんどくさがらず、resourcesを使ってしっかり全部書きましょうってことかな。
・matchは廃止された
これも上のつながりで載せました。ググった時にmatchが出てきても使っちゃダメよと注意喚起です。
ここによると、ワイルドカードを封じて、get, postを使ってちゃんと書いて欲しいみたいですね。
ってことは、どっちにしろ上のワイルド・コントローラ・ルートは使えないってことですね。
コントローラ
ここにはコードが無いため、駆け足気味に行きます。
コントローラで大事な考え方は、ビュー層のためのデータを取り出すだけのものであるってことです。
・コントローラにはロジックは含めない。ロジックはモデルに書きましょう ・アクションは、初期のfindとnew以外には、1つのメソッドだけを起動する ・コントローラとビューの間の変数の共有は2つまで
ロジックはモデルに入れるんですね。イメージではコントローラだと思ってました。危ない危ない。
次のfindとnew以外には1つのメソッドを起動すべきっていうのは、ルーティングされたURLのアクション以外は起動しないように書けってことでいいのかな?アクションの中で他のアクションを呼ぶような書き方はダメってことですかね。
コントローラとビューの変数の共有を2つに絞るのは、複雑にしないためですかね。
モデル
ここはあまりに巨大なので、小分けに見ていきます。
まずはコードのない部分の要約を。
・非ActiveRecordモデルのクラスは自由に導入していい ・モデルには、略語のない、意味のある、短い名前をつける
ActiveRecordでないクラスは自由に書いてよくて、モデル名は頑張って考えましょうと。
・ActiveRecordのバリデーションのようなことをやるときは、ActiveAttr gemを使う
コードの完全な例はRailsCast on the subject を参照。
# ActiveRecord::Baseを継承していないクラス class Message include ActiveAttr::Model # attributeを設定 attribute :name attribute :email attribute :content attribute :priority # name, email, contentをハッシュで渡せるように # Message.new(name: 'hoge', email: 'hoge', content: 'hoge')で行ける attr_accessible :name, :email, :content # name, email, contentにvalidationを入れる validates :name, presence: true validates :email, format: { with: /\A[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i } validates :content, length: { maximum: 500 } end
ActiveRecordを継承してないモデルでActiveRecordと同じようなvalidationを書きたいときは、ActiveAttrっていうgemを使いましょうってことか。
非ActiveRecordのモデルをバンバン書く日が果たしていつ来るのか…頑張りましょう。
ここからは、ActiveRecordについてです。
・十分な理由がない限り、ActiveRecordのデフォルトを変更しない(テーブル名、主キーなど)
# 悪い - スキーマを修正できるのなら、このようにしないでください。 class Transaction < ActiveRecord::Base # テーブル名を変更してる self.table_name = 'order' ... end
テーブル名はRailsのルールを守んないと、後でわけわかんなくなりますよね。
・マクロスタイルのメソッドはクラス定義の初めにまとめる。マクロスタイルも一定の順番で書く
class User < ActiveRecord::Base # デフォルトスコープは最初に(あれば) default_scope { where(active: true) } # 続いて定数 GENDERS = %w(male female) # その後attr関係のマクロを置きます attr_accessor :formatted_date_of_birth attr_accessible :login, :first_name, :last_name, :email, :password # 関連マクロが続きます belongs_to :country has_many :authentications, dependent: :destroy # そしてバリデーションマクロ validates :email, presence: true validates :username, presence: true validates :username, uniqueness: { case_sensitive: false } validates :username, format: { with: /\A[A-Za-z][A-Za-z0-9._-]{2,19}\z/ } validates :password, format: { with: /\A\S{8,128}\z/, allow_nil: true} # 次にコールバックです before_save :cook before_save :update_username_lower # その他のマクロ(deviseなど)はコールバックの後に置かれるべきです ... end
マクロスタイルのメソッドの順番は、
・デフォルトスコープ ・定数 ・attr ・関連 ・バリデーション ・コールバック ・その他
こういうルールはしっかり守らないとダメですよね。早く「普通に書いたらこの順番になる」ようになりたいところです。
・has_and_belongs_to_manyより、結合モデルに対して、追加の属性とバリデーションを追加する、has_many :throughを使う。
# has_and_belongs_to_many を使用 class User < ActiveRecord::Base has_and_belongs_to_many :groups end class Group < ActiveRecord::Base has_and_belongs_to_many :users end # 好ましい方法 - has_many :through を使用 class User < ActiveRecord::Base has_many :memberships has_many :groups, through: :memberships end class Membership < ActiveRecord::Base belongs_to :user belongs_to :group end class Group < ActiveRecord::Base has_many :memberships has_many :users, through: :memberships end
has_and_belongs_to_manyは、多対多の関係を表すようです。
全然Railsの考え方を身につけてない身からすると、has_and_belongs_to_manyの方が直感的に見えます。
ただ、下の例だと、Membershipに属性とバリデーションが入るんですね。
試しに両方のRailsアプリを作ってコンソールでメソッドを見てみたところ、throughの方がメソッドが多かったです。気になる方は試してみてください。
簡単に言ってしまえば、多対多で相互に所有しあう関係のときは、
has_many 相手, through: 中間クラス
と書く癖をつけましょうってことですかね。
・read_attribute(:attribute)ではなく、self[:attribute]を使う
# 悪い def amount read_attribute(:amount) * 100 end # 良い def amount self[:amount] * 100 end
わざわざメソッドを使って呼ばずに、ハッシュのキーみたく呼ぼうってことですかね?
・バリデーションの書き方は、最新のsexy validationsに合わせる
# 悪い validates_presence_of :email # 良い validates :email, presence: true
sexy validationsには、こんな例がありました。
class Film < ActiveRecord::Base validates :title, :presence => true, :uniqueness => true, :length => { :maximum => 100 } validates :budget, :presence => true, :length => { :within => 1..10000000} end
これからRailsを始めるなら、sexy validationsにある書き方から覚えていけば良さそうですね。
・カスタムバリデーションが以下に該当する場合、カスタムバリデータファイルとして外に書く。カスタムバリデータは、app/validatorsに置く。
・カスタムバリデーションを2回以上使う ・バリデーションで正規表現を使用している
それではコードを。
# 悪い <- 正規表現を使ってる class Person validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i } end # 良い <- 正規表現を外出し class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << (options[:message] || 'is not a valid email') unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i end end class Person validates :email, email: true end
これは、関数にするか否かの判断基準と同じようなものですね。DRYと複雑性に対応する基準を明文化してくれてるので、オレオレ基準はやめましょうってことか。
もっと抽象化する指針として、こんなもんもありました。
・複数の関連するアプリケーションをメンテしてるか、バリデータが十分に一般的なときは、共有するgemにカスタムバリデータを抽出することも視野に入れる。
まだ、gemに抽出するのに「十分な」一般性がどんなもんかよくわからないので、慣れてきたらまたこれについて考える時が来ると思います。
・named scopeは自由に使う。
scopeは検索メソッドを追加するようです。コードを見たほうがわかりやすいので、コードを見てみましょう。
class User < ActiveRecord::Base scope :active, -> { where(active: true) } scope :inactive, -> { where(active: false) } scope :with_orders, -> { joins(:orders).select('distinct(users.id)') } end
上のコードは、以下のメソッドを作ります。
・User.active -> Userテーブルの中の、activeがtrueのレコードの配列を返す ・User.inactive -> Userテーブルの中の、activeがfalseのレコードの配列を返す ・User.with_orders -> UserテーブルとOrderテーブルのを合わせて、user_idで分ける
・named scopeはlambdaで包む
# 悪い class User < ActiveRecord::Base scope :active, where(active: true) scope :inactive, where(active: false) scope :with_orders, joins(:orders).select('distinct(users.id)') end # 良い class User < ActiveRecord::Base scope :active, -> { where(active: true) } scope :inactive, -> { where(active: false) } scope :with_orders, -> { joins(:orders).select('distinct(users.id)') } end
これは、とりあえずlambdaで囲っておけばいいということですかね。
・引数付きのlambdaを使ったnamed scopeが複雑になる場合、ActiveRecord::Relationsオブジェクトを返すクラスメソッドを作ることで、単純なscopeを定義できる。
class User < ActiveRecord::Base def self.with_orders # joinsはActiveRecord::Relationsオブジェクトを返す。 joins(:orders).select('distinct(users.id)') end end
マクロで書くときのscopeは、とにかく単純な書き方でいったほうがいいみたいですね。
複雑になりそうなときはクラスメソッドとして書きましょうと。
ここからは、わかりやすいURLを書く方法についてです。
・人が見てわかりやすいURLを使うよう心がける。URLの中では、モデルのidではなく、記述的な属性を使う。
・to_paramメソッドをオーバーライドする。デフォルト実装ではレコードのidを返すから、人が見てわかる属性を入れる。
class Person def to_param # idが先頭にあるのは、ActiveRecordのfindメソッドで見つけることができるようにするため。 # parameterizeはURLフレンドリーな値に変換する "#{id} #{name}".parameterize end end
parameterizeがどんなものか試してみました。
> "1 kenta".parameterize => "1-kenta"
スペースをハイフンにしてくれました。スペースはURLの敵ですものね。
・idの代わりに記述的な属性を使ってURLを生成する、friendly_id gemを使う。
friendly_idの詳しい書き方はここを参照。
class Person extend FriendlyId friendly_id :name, use: :slugged # かぶらないようなslug_candidatesを書く def slug_candidates [ :name, [:name, :birthday], [:name, :sex, :birthday], [:name, :age, :sex, :birthday] ] end end
以上でrails-style-guideを読む記事の第1回を終わります。
まだ全体の3分の1くらいですので、rails-style-guideは全3回の予定です。
難しいし、長いしでかなりしんどいかと思いますが、スタイルガイドは大事ですし、最後まで頑張りましょう。
初めてのSass
今回は、Sassを見ていこうと思います。
CSSはプログラミング言語じゃないので、色々不便なところがありますよね。
DRYを守るのはほぼ不可能なレベル。
そこでSassですよ!ってな感じらしい。
中身を早く見たいのは山々ですが、先にCSSにコンパイルするやり方を確認しましょう。
Sassのままじゃ動かないので、動作確認もできませんし。
こんな感じでやるみたい。
$ sass Sassファイル 吐き出すCSSのファイル名
これでSassファイルから指定した名前のCSSを吐くようです。試しに、
$ touch test.scss
と入力して空っぽのScssファイルを作成し、
$ sass test.scss test.css
とやると、空っぽのtest.cssができました。
これで、安心してSassを書けますね。
それでは、ここにある、Sassの基本を見て行きましょう。
このページにある内容で、書くのに関係ありそうなのは、
・変数 ・ネスト ・パーシャル ・インポート ・ミックスイン ・継承 ・演算子
ってとこでしょうか。一つずつ見て行きましょう。
変数
CSSで変数を使えるとか半端ないっす。超ありがてぇっす。
変数は頭に"$"をつけて定義するようです。
$base-color: #192bc1; header { background-color: $base-color; } footer { background-color: $base-color; }
カンマで書けっていうツッコミはごもっともです。
ネスト
ネストは地味にありがたいです。可読性が上がりますね。
header { background-color: darkblue; p { color: snow; } a { color: red; } }
CSSでネストできなくて絶望していた頃が懐かしい。
パーシャルとインポート
_で始まるファイル名のSassファイルをインポートして、コンパイル時に合体してくれるようです。管理が簡単になりますな。
書き方は、@import '_を抜いたファイル名'でいくようです。
たとえば、modalのCSSだけを分けて書くと仮定すると、
_modal.scssとmain.scssを用意して、
modalの分だけ_modal.scssに書きます。
.modal { .modal-content { color: red; } .modal-body { color: black; } }
で、他のスタイルをmain.scssに書きつつ、インポートします。
@import 'modal'; header { color: red; }
ミックスイン
ミックスインって何のことかと思ったら、関数じゃないですか!
何これ超すごい。
@mixinで定義して、@includeで呼び出すみたいです。
@mixin emphasize-text() { font-weight: bold; font-style: italic; } p { color: blue; &:hover { @include emphasize-text(); } }
&はネストされている親セレクタ、ここではpです。
見た目でわかると思いますが、引数行けます。
@mixin set-blue-border($width) { border: $width solid blue; } p { @include set-blue-border(2px); }
これは激アツですね。
継承
@extendで同じスタイルが当たるみたいです。これは…どうだろう。
h1 { color: white; background-color: maroon; } h2 { @extend h1 }
クラスとidをうまく振れば使わなくていい気がしています。
使いこなせれば便利になるのかなぁ。
演算子
SCSSは計算までできるとは…激萌えですな…
条件分岐ができれば完璧でしたな…
$h1-size: 10px h1 { font-size: $h1-size; } p { font-size: $h1-size / 2px; }
数値を計算できるのは、かなり可読性が上がると思います。
何に対するどんな数値なのかがわかるのは大きいです。
とりあえず、すぐに使えそうなのはこんな感じでしょうか。
それでは、CSSとSCSSでスタイルを当てて、比較してみましょう。
こんなのを作ります。ヘッダーとタイトルだけです。これ以上書くと、読んでられない文量に…
ロゴはArt Text 2 Liteでサクッと作りました。ファイル名はsass.pngです。
まずはhtmlを用意します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Scssのテスト</title> <link rel="stylesheet" href="test.css"> </head> <body> <header> <p>ヘッダー</p> </header> <h1>初めてのSass</h1> </body> </html>
それでは、スタイルを当てます。まずはCSSから。
この例では、簡単すぎてDRYは守れてますが、ちょっと色を変えてみたり、後からいじるのはめんどくさくなっています。
ファイル名はtest.cssです。
/* ページ全体の背景色をsnowにする */ body { background-color: snow; } /* ヘッダーを細いバーにして、スクロール時についてくるようにする */ header { background-color: darkblue; position: fixed; top: 0; left: 0; height: auto; width: 100%; color: snow; } header p { margin: 0; } /* h1の前にロゴを置いて、位置を変更 */ h1 { color: maroon; font-family: fantasy; margin: 1.5em; } h1:before { content: url("sass.png"); }
次に、Scssを書きます。
CSSと比べて、後からいじるのがかなり楽です。
色や大きさを変えるだけなら、変数の値を変えるだけでできます。
ScssのメンテナブルっぷりはCSSとは比較になっていませんね。
// 変数 // ページ全体 $page-background-color: snow; // ヘッダー $header-background-color: darkblue; $header-color: snow; // h1 $h1-color: maroon; $h1-font: fantasy; $h1-margin: 1.5em; $h1-font-size: 2.25em; $logo-img: "sass.png"; // ミックスイン // ヘッダーをバーにする。 @mixin make-header-bar($background-color, $color) { background-color: $background-color; position: fixed; top: 0; left: 0; width: 100%; height: auto; color: $color; * { margin-top: 0; margin-bottom: 0; } } // ロゴを置く。 @mixin put-logo($img) { &:before { content: url($img); } } // スタイルを当てる // ページ全体の背景色をセット body { background-color: $page-background-color; } // ヘッダーをバーにする header { @include make-header-bar($header-background-color, $header-color); } // h1にロゴを置いて、位置を変更 h1 { @include put-logo($logo-img); margin: $h1-margin; font-family: $h1-font; font-size: $h1-font-size; color: $h1-color; }
書けたら、コンパイルします。
$ sass test.scss test.css
いかがだったでしょうか。
個人的には、今後CSSを書く気が失せるほど、Scssの方が優れていると感じました。
Railsが生成するScssファイルにCSSをそのまま書くのはもったいないですね。
CoffeeScriptでDOMを扱ってみる
前回の記事でCoffeeScriptの文法を見たので、今回はDOMをいじってみましょう。
まずは、htmlを用意します。
めんどくさいので、CSSもjsから書いてしまいます。ご了承ください。
ガチのjsを書くのはめんどくさいので、jQuery先生のお世話になります。
HTML
<!doctype html> <html> <head> <title>CoffeeScriptで遊ぶ</title> <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> <script src="coffee.js"></script> <script src="js.js"></script> </head> <body> <div id="first"> <h1>first div</h1> <p>first div</p> </div> <div id="second"> <h1>second div</h1> <p>second div</p> </div> <div id="third"> <h1>third div</h1> <p>third div</p> </div> </body> </html>
今回は、以下の実装を行います。
・divの中のpタグをホバーで表示切り替えする
・divをクリックしたら、idをアラートする
まずは、js.jsにjsで書いてみます。
javascript
$( function() { // divを変数に入れとく var divs = $( "div" ); // pタグを非表示にして、divの外に出す $( "p" ).css({ display: "none", position: "absolute", }); // divを横に並べる $( "body" ).css({ display: "flex", }); // divを見やすい形にする divs.css({ border: "1px solid blue", backgroundColor: "gold", borderRadius: "3px", margin: "1em", }); // divにイベントハンドラを書く $.each( divs, function() { // ホバーでpを出し入れする $( this ).hover( function() { // マウスオーバーでpを表示 $( this ).find( "p" ).css( "display", "block" ); }, function() { // マウスリーブでpを非表示 $( this ).find( "p" ).css( "display", "none" ); }); // クリックしたらidをアラートする $( this ).on( "click", function() { alert( $( this ).attr( "id" ) + "がクリックされた" ); }); }); });
動作確認の時は、一応、coffee.jsのscriptタグをコメントアウトしましょう。
こんな感じになりました。
divにカーソルを乗せるとこんな感じ。
クリックすると、
思った通りになりました。
では、CoffeeScriptで同じものを書きます。
ファイル名をcoffee.coffeeにします。
CoffeeScript
$ -> # divを変数に入れとく divs = $("div") # pタグを非表示にして、divの外に出す $("p").css { display: "none", position: "absolute", } # divを横に並べる $("body").css "display", "flex" # divを見やすい形にする divs.css { border: "1px solid blue", backgroundColor: "gold", borderRadius: "3px", margin: "1em", } # divにイベントハンドラを書く $.each divs, -> $(this).hover -> # マウスオーバーでpタグを表示 $(this).find("p").css "display", "block" , -> # マウスリーブでpタグを非表示 $(this).find("p").css "display", "none" # クリックしたらidをアラートする $(this).on "click", -> alert "#{$(this).attr 'id'}がクリックされた"
書けたら、コンパイルします。
$ coffee -c coffee.coffee
これで、coffee.jsができました。
動作確認の時は、js.jsのscriptタグをコメントアウトしましょう。
同じものができればOKです。
coffeeで書くと、jQueryを使ったときの()地獄から開放されますね。
かなりシンプルなコードになりました。
CoffeeScriptはとても書きやすく、覚えやすい言語だと思います。もうクライアントサイドはCoffeeScriptだけでいいんじゃないかな、と思うレベル。
jsを覚えるのが大変だと感じる方、jsを書くのがめんどくさい方はぜひ、CoffeeScriptを書いてみてください。
次からは、Sassを見ていこうと思います。
はじめてのCoffeeScript
今日は、CoffeeScriptを見ていこうと思います。
公式のドキュメントは、ここhttp://coffeescript.org/にあります。
簡単にまとめると、
・functionは-> ・クラスをclass文で書ける ・ifやforは後置できる ・引数の()は省略できる ・関数で最後に評価した値を自動的に返す ・インデントで構造を表す ・リスト内包表記が書ける ・比較で1 < x < 5の書き方ができる ・"#{a} is variable"のように、文字列に変数を埋め込める ・文字列、正規表現を複数行で書ける ・switchはRubyのcaseにそっくり ・例外はほぼjsと一緒
で、演算子が一部変更されてます
js -> coffee === -> is !== -> isnt ! -> not && -> and || -> or true -> true, yes, on false -> false, no, off this -> @, this in -> of なし -> in
こんな感じ?
RubyとPythonに慣れてれば、すごくしっくり来そうですね。
$ coffee -c ファイル名
で出来ます。同じ名前のjsファイル吐きます。-cをつけずに、
$ coffee ファイル名
だと、jsファイルを吐かずに実行してくれます。
CoffeeScriptをコンパイルして得られるjsは、中身を見ればわかると思いますが、グローバル空間を汚さないようになっています。
なので、下のコードは、jsの方はブラウザのコンソールにコピペすれば関数を呼べるように書いていますが、CoffeeScriptの方はコンパイル後のjsをそのままコピペしても関数は呼べません。動かす際はコンパイル後のjsの無名関数の中で関数を呼び出してください。
それでは本編に入ります。
普通に文法を見てもおもしろくないので、jsと比較しつつ、遊んでみましょう。
まず、FizzBuzzを比べてみます。ifとforを使いつつ、関数にしてみます。
javascript
function fizzbuzz( n ) { for ( var i = 1; i <= n; i++ ) { // FizzかBuzzがついたら表示する文字列 var message = ''; // 3の倍数ならFizzをつける if ( i % 3 === 0 ) { message += 'Fizz'; } // 5の倍数ならBuzzをつける if ( i % 5 === 0 ) { message += 'Buzz'; } // messageが空なら数字を表示する console.log( message || i ); } }
CoffeeScript
fizzbuzz = (n) -> for i in [1..n] # FizzかBuzzがついたら表示する文字列 message = '' # 3の倍数ならFizz, 5の倍数ならBuzzをつける message += 'Fizz' if i % 3 is 0 message += 'Buzz' if i % 5 is 0 # messageが空なら数字を表示する console.log message or i
coffeeの方がかなり簡潔で見やすいです。
ifが後置で書けるので、かなりすっきりしてます。
1からnまでの配列をRubyのRangeみたいに[1..n]と書けるのがいいですね。
jsでは0がfalseなので、coffeeでunlessを使ってみましょう。
CoffeeScript
fizzbuzz = (n) -> for i in [1..n] # FizzかBuzzがついたら表示する文字列 message = '' # 3の倍数ならFizz, 5の倍数ならBuzzをつける message += 'Fizz' unless i % 3 message += 'Buzz' unless i % 5 # messageが空なら数字を表示する console.log message or i
やっぱり、unlessはすっきりします。
さらに、forを後置して、こんな風にも書けます。
関数にしなければ、ワンライナーですね。
CoffeeScript
fizzbuzz = (n) -> console.log [['Fizz'][i%3], ['Buzz'][i%5]].join("") or i for i in [1..n]
FizzBuzzはこんなもんにしておきましょう。
CoffeeScriptは書いてて楽しいです。
次に、クラス定義の違いを見てみましょう。
Carクラスを書いてみます。
車の名前を属性にして、走るメソッドを入れてみます。
javascript
// Carオブジェクトのコンストラクタ function Car( name ) { this.name = name; } // 走るメソッド Car.prototype.run = function( speed ) { console.log( this.name + 'が' + speed + 'キロで走ってます。' ); }
Coffeescript
// Carクラス class Car constructor: (@name) -> # 走るメソッド run: (speed) -> console.log "#{@name}が#{speed}キロで走ってます。"
CoffeeScriptはこれでprototypeに入れてくれるので、かなり楽ですね。
クラスベースのオブジェクト指向言語に慣れた人は、coffeeの方がかなり見やすいと思います。
最後に、じゃんけんをするプログラムを書いて、比べてみましょう。
jsとcoffeeでswitchの扱いやすさの差が歴然です。
もはやswitchじゃなくてcaseと書きたいレベル。
javascript
function janken() { // 1がグー、2がチョキ、3がパー var hands = ['ぐー', 'ちょき', 'ぱー']; // プレイヤーの手 var playerHand = window.prompt("じゃんけんをしましょう!\n何を出しますか?\n1. ぐー 2. ちょき 3. ぱー"); // 日本語入力に対応 // 不正な入力があったらはじく if ( ['1', '2', '3'].indexOf( playerHand ) === -1 ) { switch( playerHand ) { case 'ぐー': case 'グー': playerHand = 1; break; case 'ちょき': case 'チョキ': playerHand = 2; break; case 'ぱー': case 'パー': playerHand = 3; break; default: alert('何言ってるかわかんないです。'); return; } } // 内部処理用に、playerHandの値をhandsのインデックスに合わせる playerHand -= 1; var playerHandStr = hands[ playerHand ]; // COMの手。ランダムで決める var comHand = Math.floor( Math.random() * 3 ); var comHandStr = hands[ comHand ]; // 勝敗表示用の文字列 var bothHandsStr = 'あなた: ' + playerHandStr + "\n" + 'COM : ' + comHandStr + "\n"; var win = "あなたの勝ち!"; var lose = "あなたの負け…"; var draw = "あいこ!"; // 勝敗判定 // アルゴリズム使用([http://staku.designbits.jp/check-janken/]) // resultが0であいこ、1でCOMの勝ち、2でplayerの勝ち var result = ( playerHand - comHand + 3 ) % 3; var messages = [ draw, lose, win ]; // 結果を表示 alert( bothHandsStr + messages[ result ] ); }
CoffeeScript
janken = -> # 1がグー、2がチョキ、3がパー hands = ['ぐー', 'ちょき', 'ぱー'] # プレイヤーの手 playerHand = window.prompt "じゃんけんをしましょう!\n何を出しますか?\n1. ぐー 2. ちょき 3. ぱー" # 日本語入力に対応 # 不正な入力があったらはじく unless playerHand in ['1', '2', '3'] switch playerHand when 'ぐー', 'グー' then playerHand = 1 when 'ちょき', 'チョキ' then playerHand = 2 when 'ぱー', 'パー' then playerHand = 3 else alert '何言ってるかわかんないです。' return # 内部処理用にplayerHandの値をhandsのインデックスに合わせる playerHand -= 1 playerHandStr = hands[playerHand] # COMの手 comHand = Math.floor(Math.random() * 3) comHandStr = hands[comHand] # 勝敗表示用の文字列 bothHandsStr = "あなた: #{playerHandStr}\nCOM : #{comHandStr}\n" win = "あなたの勝ち!" lose = "あなたの負け…" draw = "あいこ!" # 勝敗判定 # アルゴリズム使用([http://staku.designbits.jp/check-janken/]) # resultが0であいこ、1でCOMの勝ち、2でplayerの勝ち result = (playerHand - comHand + 3) % 3 messages = [draw, lose, win] # 結果を表示 alert bothHandsStr + messages[result]
いかがですか?個人的にはcoffeeの方がかなり書きやすかったです。
CoffeeScriptの文法をさらっとなめてみました。DOMにも触ろうと思ったのですが、すでに相当な文量になってしまっているので、やめます。
CoffeeScriptを使えば、クライアントサイドのコーディングがかなり楽になりそうですね。