2013年6月15日土曜日

[Rails3] サイドバーの実装には Cells が便利

RailsでViewを作りこんでいくと、ifやeach、さらに変数を代入するだけの行とか出てきてしまってあまり綺麗なViewではなくなっていくとこがよくある。
ifとか文字列の連結を省略したいだけなら ActiveDecorator とかが便利

↑こうゆうやつ

でもブログのサイドバーみたいにサイト全体で使うViewがあったりすると、

  • データはControllerで用意するの? => before_filter使うとか
  • Viewから直接Modelは呼び出したくないよね。。
  • そもそもViewファイルはどこに置けばいいんだろ? => views/layouts/_sidebar.html.erbとか?
  • SidebarControllerを実装してサイドバーだけajaxでhtmlを取得するとか?

いろいろモヤモヤするとこがあって、あんまり最適解じゃない気がする。

でも Cells というgemを使うとViewの部品を小さなコントローラのような形で実装できるようになるのでスッキリ綺麗に書けて凄くイイネ!

インストール

Gemfileに以下を書いてbundle installするだけ

Gemfile

+ gem 'cells'
$ bundle install

ジェネレート

インストールするとジェネレータが追加されているので、cellの名前とアクション名を渡して作成出来る

$ bundle exec rails g cell sidebar show

ERB以外のテンプレートを使う場合は -e オプションを指定すればOK

$ bundle exec rails g cell sidebar show -e haml

生成したCellは app/cells にある

app
├── cells
│   ├── sidebar
│   │   └── show.html.haml
│   └── sidebar_cell.rb

使ってみる

Cellを埋め込みたいViewの部分に render_cell を使うとレンダリングされる。引数は Cell名, アクション名

Cellの中では普通にModelが呼べるので

こんなかんじでModelからデータを受け取ってインスタンス変数に入れておけばCellのViewで使えるようになる

render_cellには3番目の引数があって、ここに渡したものはCellのアクションメソッドの引数に入るようになる

もう少し便利に

このままだと通常のViewで使えるヘルパーメソッドは一切呼べないので、ApplicationCell を作って継承するようにしてる。

helperというメソッドに使いたいヘルパーのモジュールを渡すと使えるようになる

また helper_method というメソッドも用意されていて、個別にヘルパーを追加できるようにもなってる。
自分はdeviseの current_user をCellのViewでも使いたかったので以下のようにしてみた

これで c.current_user とすれば呼び出せて、ログインしてたら編集ボタンを表示するようにできた

以前はSidebarControllerを実装してサイドバーのhtmlだけajaxで取得とかやってたこともあるけど、そもそもjsがオフってあると使えないし、Controllerはアプリケーションのリソースのためだけに使うようにしたいからこの方法がベストだと思う。
railsに取り込まれてもいいんじゃないかなぁ

2013年6月14日金曜日

[Rails3] has_many through で polymorphic な関連で相互に参照する

Rails3で has_many through でしかも polymorphic な関連を実装していて、polymorphicなモデル側からhas_manyを探せるけど、逆のやり方に苦戦したのでメモ

User モデルは複数の Clubに属すことができて、Clubもまた複数の Userを受け入れられるという感じ。

最初にやってた方法

Membership はpolymorphicなモデルにしていて、例えばClub以外にもFamilyとか他のグループでも同じモデルを使いまわしたかった。

データを作ってみる

rails consoleでデータを作成

user = User.create({ name: 'kozo' })
=> #<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">
club = Club.create({ name: 'football' })
=> #<Club id: 1, name: "football", created_at: "2013-06-14 03:14:50", updated_at: "2013-06-14 03:14:50">

club.memberships.create({ user_id: user.id })
=> #<Membership id: 1, user_id: 1, membable_id: 1, membable_type: "Club", created_at: "2013-06-14 03:15:19", updated_at: "2013-06-14 03:15:19">

Club -> Userの参照はうまくいく

こんな感じにClub側からUserを参照するのは簡単にできちゃう

club = Club.find(1)
club.members
# =>[#<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">]

User -> Clubの参照はできなかった・・・

user = User.find(1)
user.membered_clubs
# => ActiveRecord::HasManyThroughSourceAssociationNotFoundError: 
 Could not find the source association(s) :membable in model Membership.
 Try 'has_many :membered_clubs, :through => :memberships, :source => <name>'. Is it one of :user or :club?

ドキュメントにはsourceとsource_typeを使えばOKって書いてあるっぽい

ruby/rails/RailsGuidesをゆっくり和訳してみたよ/Active Record Associations - 株式会社ウサギィwiki

4.3.2.18 :source

:source オプションは has_many :through アソシエーションの元になるアソシエーション名を指定します。 元のアソシエーションの名前が自動的にアソシエーション名から推測できない場合にのみ、このオプションを使用する必要があります。

4.3.2.19 :source_type

:source_type オプションは polymorphic アソシエーションを通じて進める has_many :through アソシエーションの元になる型を指定します。

でもググったらすぐ出てきた

has_many :through - The other side of polymorphic :through associations

MembershipとUserに細かく書いてあげるといいっぽい

club = Club.find(1)
=> #<Club id: 1, name: "football", created_at: "2013-06-14 03:14:50", updated_at: "2013-06-14 03:14:50">
club.members
=> [#<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">]

user = User.find(1)
=> #<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">
user.membered_clubs
=> [#<Club id: 1, name: "football", created_at: "2013-06-14 03:14:50", updated_at: "2013-06-14 03:14:50">]

できた!

2012年12月13日木曜日

ヨドバシで予約したiPad miniの状況をMechanizeでチェックする

12/1にiPad mini買いに行ったら全モデル完売したので入荷するまで予約で待てと言われた。
しょうがないので予約したら商品いつ手に入るかわからないし全額払わされるのにポイントが貯まらないし、入荷したかどうかは自分でWebページを見ろという散々な状況です。

気になるけど酷いUI使ってわざわざ見るのもかったるいのでMechanizeにやってもらうことにしました。
herokuにデプロイしてschedulerで毎時0分に実行しています。

2012年10月31日水曜日

PHPのスコープのお話についてのお話

こちらの記事をたまたま見て気になったので投稿。
PHPでforeachなどを使う時の注意点。ブロックスコープのお話。

確かにループで一時的に必要な変数にスコープが無いと都合が悪いのはその通りなんだけど、解決策にモヤモヤ…

スコープが欲しいところに無いなら作ればいいわけで

関数に入れればスコープが作れる。

もっと言えばクラスにすればいいと思う。

2012年10月23日火曜日

Backboneを学ぶ #0 環境構築

転職して新しい職場ではBackboneを使いそうなのに、今までこれで何も作ったことが無いので勉強することにしました。
ひと通り出来るようになるまで続けたいので連載にしてみます。まずは開発環境の構築など

  • サーバサイド
    • Sinatra (マイクロフレームワーク)
    • haml (テンプレートエンジン)
    • scss (cssメタ言語)
    • CoffeeScript (javascriptメタ言語)
    • Shotgun (開発用rackサーバ)
  • クライアントサイド
    • jquery.1.8.2.js
    • underscore.js
    • backbone.js
  • リポジトリ
  • サーバ
2012年8月24日金曜日

TextMate2の新しいタブを開くショートカットを変更した

TextMate2のNew Tabが 「option + command + N」 で左手が辛いので
以上
2012年8月20日月曜日

Rails3でクエリパラメータでルーティング(Advanced Constraints)

少し前にsendagaya.rbで発表したRails3のAdvanced Constraintsをここでも紹介。

routes.rbの中でURLを定義する時にsubdomainとかを条件に含めるときに使うのがconstraintsというオプションなんですが、これを使ってURLの「?」以降を条件にしたりもできます。

方法はrequestオブジェクトを受け付けるmatches?というメソッドを実装したクラスを定義してインスタンスをconstraintsに渡してあげるだけです。

ここではpostというリソースの集合であるindex、つまり /posts というURLにGETで ?q=hoge 等のパラメータが付いている時に posts_controller#search というアクションに紐付けるということを行なっています。

ただしこのままだと q に対するvalueが空の状態 /posts?q でもsearchアクションに処理が渡るので、コントローラ側で何かしらの対応が必要です。

基本的にはrequestオブジェクトを使って判別できるならどんな条件でもいいので、Accept-languageで振り分けるとかIPアドレスでブラックリストを作るとかもできてしまいます。

今回はindexアクションの中でifで分岐させておくことでも十分対応可能ですが、render以外はほとんど共通処理がないよとかになると、アクションそのものを分けてしまったほうが綺麗なコードになりそうですよね。

2012年8月14日火曜日

オープンソースになったTextMate2をbuildしてみた

すっかりVimに慣れてしまった今日この頃ですが、お世話になったTextMateの2がオープンソースになったのでbuildしてみました。ちなみに環境は OS X lion 10.7.4です。

  1. Githubへ行ってみる

    https://github.com/textmate/textmate.git

    READMEにbuild方法は書かれていますね。

  2. 必要なライブラリをインストール

    とりあえず brew install ragel boost multimarkdown hg ninja とやってみたのですが ninja なんてないよって怒られちゃいました

    というわけで先に brew update してから再度実行したらちゃんと入りました

  3. リポジトリをclone

    git clone https://github.com/textmate/textmate.git でホームディレクトリにcloneしました

    textmate というフォルダができているはずなのでそこに移動 cd textmate

  4. build実行

    の前に、READMEをみると git submodule update --init とやってねと書いてあるので実行

    そして ./configure && ninja 実行!したら clang が古いと言われてしまいました

    brew install --HEAD llvm --with-clang で入るはずなんですが、Error: can't convert nil into String とか言われる

    brew update も brew doctor も問題ないんだよなぁ

    Githubのbug-fixing-checklistを見るとXCodeのupdateして見た方が良さそうだなと思ったので Apple からCommando line tools for XCodeの割と最近のやつをDLした

    でやり直しても駄目だなぁ。とよく見てみたらXCode 4.4+と書いてある、手元のXCodeは4.3でした。App Storeから入れ直そう。

  5. XCodeをupdateしたので

    気を取り直して ./configure && ninja を再度実行してみた。そしたら今度は pgrep が無いよと言われた

    brew install pgrep を実行。(そろそろめんどくさくなってきた)

    ./configure && ninja よし。こんどこそうまく行ってそう。(なにやらwarningとかdepricatedが出てきてますが)

  6. build成功した

    やっと成功した。

    へー2からはPreferencesでbundleとかthemeを追加できるようになってるんですね。逆に言うと最初は何も入ってないからハイライトも何も無いのだけど。

    めでたし

2012年8月8日水曜日

bashでgitの現在のbranch名を表示させる

git-completionというシェルスクリプトを使えばできるということを知ったのでやってみた。

  1. gitのバージョンを調べる

    $ git --version
    $ 1.7.7.5
    
  2. github(http://github.com/gitster/git)に行きcodeのtreeを同じバージョンに切り替える(ブランチの切り替え部分と同じところです)

  3. git-completion.bashまでtreeをたどりraw表示に切り替えて保存
    自分はhttps://raw.github.com/gitster/git/v1.7.7.5/contrib/completion/git-completion.bashでした

  4. DLしたファイルに使い方は大体書いてあるのでその通りにやれば動くと思います

    $ cd ~
    $ mv git-completion.bash .git-completion.sh
    $ vim .bashrc (or .bash_profile)
    $ PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' を追記
    $ source .bashrc
    
  5. こんなかんじで表示されました

    [kozo@kozo-mp current_dir (master)]$ 
    
2012年7月26日木曜日

OS X Mountain Lionにアップデートした

早速OS X Mountain Lionにアップデートしたのでいくつか作業を記録します。

  1. App StoreからMountain Lionをダウンロード
  2. インストーラをコピーしておく
  3. インストールで暫し待つ(40分くらい)
  4. App StoreからXCodeをインストール
  5. iTerm2を立ちあげておもむろにgit --versionと打ってみた→消されてる!
  6. ググったらこんなの発見
  7. XCodeを立ち上げてPreferences経由でcommand line toolsをインストール
    • git --versionでインストールを確認
  8. Homebrewのアップデート
  9. imagemagickが再インストール必要みたいなので一旦brew remove imagemagick
  10. brew install imagemagick --use-gccとやるとXQuartzがないよと言われる
  11. 再度 brew install imagemagick --use-gcc を実行
    • gccがみつからないらしく失敗
    • めんどくさくなったので --use-gcc オプションを消してインストール
  12. で、何?感やばい
2012年7月19日木曜日

IE9でiframe内で遷移した場合window.parentのメソッドを呼べない

状況としては、ビデオ一覧ページから各ビデオをiframeを使ったレイヤーで立ち上げるページにて
ビデオはMediaElement.jsというのを使っていて、HTML5対応の判別して、ダメならFlashとかSilverlightとかに切り替えてくれるようになってます。(何も再生手段がなければmp4をDLさせます)

閉じるボタンを押してもビデオが再生されたまま

iframeが非表示になっただけでページ内に生きているから

レイヤーを表示させてるjsを調べる
なんだこれ誰かの自作じゃねえか!closeイベントとか取れねえよ!

親ウィンドウから子iframeにアクセスするか、
子iframeから親ウィンドウに渡すか

後者を選択して window.parent.videos.push(video)
親のレイヤーを非表示にするときの処理に

if (window.videos) {
  for (var i in window.videos) {
    if (!window.videos[i].paused) {
      window.videos[i].pause();
    }
  }
}

というのを挟むようにした。
IE、他ブラウザほぼ動く。

IE9だけ動かない!
iframe内で次の動画へ遷移できるようにしてあったんだけど
ビデオのhtmlは一本につき1ファイルなのでこれは単純にリンクを押してるだけ
親から直接ではなくiframe内で遷移後にエラーする。
(でもIEのステータスバーにはエラー表示が出ないんだけどね)

IE9のF12開発者ツールでブレークポイントつけてみる
「解放されたスクリプトからコードを実行できません」
ハァ(゚д゚)?

ググってみた。あぁそういえばIEではJavaScriptじゃなくてJScriptだったな……
つまり信頼性の低いスクリプトから親ウィンドウにあるオブジェクトのメソッドを実行できないってことですかね!!
というわけでpushじゃなくて代入にした。
window.parent.videos[window.parent.videos.length] = video;

動いた!
プロパティの参照と代入は問題ないみたいです。

2012年7月6日金曜日

CentOS 6 でRails + thin + nginxを動かした

thinってオワコンじゃないですよね?herokuで採用されているし別にいいですよね。
本当はunicorn使いたかったんですが難しい…… ><;
ぐぐると記事沢山出てきますが uninitialized constant errorで起動しない問題が解決できなくてやめちゃいました。いつかリベンジします。

CentOS 6

マシンはvagrant使ってcentos6を用意しました。
参考:http://www.ryuzee.com/contents/blog/4292
vagrantって便利ですね。元々Virtual Boxでwindows動かしてたのですんなりです。ホントに。するっと。

IPアドレスと

config.vm.network :hostonly, "192.168.33.10"

メモリとnameを設定しておきました

config.vm.customize do |vm|
  vm.memory_size = 1024
  vm.name = 'vagrant_centos6'
end

Ruby

rbenv + ruby-buildで1.9.3-p194をインストール

Thin

Gemfileのgroup :productionにgem 'thin'を追加して普通にbundle install

config/thin.ymlは

pid: tmp/pids/thin.pid
log: log/thin.log
servers: 1
port: 3000
daemonize: true
environment: production
user: vagrant
group: staff

以下で起動、停止、再起動

$ be thin start|stop|restart -C config/thin.yml

Nginx

yumでnginxをインストール(参考:http://blog.livedoor.jp/sasata299/archives/51810645.html

/etc/nginx/nginx.confは

worker_processes  1; 

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/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  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;

    # include /etc/nginx/conf.d/*.conf;

    upstream backend {
        server localhost:3000;
    }
    upstream backend2 {
        server localhost:3001;
    }
    server {
        listen 80;
        server_name centos6.vag;
        location / {
            proxy_pass http://backend;
        }
    } 
    server {
        listen 80;
        server_name 2.centos6.vag;
        location / {
            proxy_pass http://backend2;
        }
    } 
}

試す

あとはmacのhostsに

192.168.33.10 centos6.vag
192.168.33.10 2.centos6.vag

を設定してブラウザでアクセス!

サクセス!

でもnginx.confの書き方冗長だなぁもっといいのあるんだろうか。

※追記

上記の状態で試しに request.url を覗いてみたら http://backend2/ とかでちゃったよ!困るよ!

というわけでnginx.confを一部修正してみたら問題なく動いた

    ...省略...
    upstream 2.centos6.vag {
        server localhost:3001;
    }
    server {
        listen 80;
        server_name 2.centos6.vag;
        location / {
            proxy_pass http://2.centos6.vag;
        }
    }
    ...省略...

request.url でhttp://2.centos6.vagが返ってきた!

※追記2

画像など静的ファイルが返らないことがわかったので以下のようにconfig修正

    server {
        listen 80;
        server_name 2.centos6.vag;

        proxy_set_header    X-Real-IP       $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header    Host            $http_host;
        proxy_redirect      off;
        proxy_max_temp_file_size    0;

        location ~ .*\.(jpg|JPG|gif|GIF|png|PNG|swf|SWF|css|CSS|js|JS|inc|INC|ico|ICO) {
            root /home/vagrant/sample2/public;
            ssi  on;
            break;
        }

        location / {
            proxy_pass http://2.centos6.vag;
        }
    }

※追記3

thinの再起動などダウンタイムにnginxのデフォルトエラーページが返されるのでconfig追加

     error_page   500 502 503 504  /500.html;↲
        location = /500.html {↲
        root   /home/vagrant/sample2/public;↲
     }

※追記4

マシンを再起動したときにthinも自動起動するようにした!

/etc/init.d/sampleを作成

#!/bin/sh
# chkconfig: 2345 86 25
# description: Sample
#

PATH=/home/vagrant/.rbenv/shims:/home/vagrant/.rbenv/bin:$PATH
eval "$(rbenv init -)"

APP_DIR=/home/vagrant/sample
THIN_CONFIG=$APP_DIR/config/thin.yml

case $1 in
  start)
  cd $APP_DIR
  bundle exec thin start -C $THIN_CONFIG
  ;;
  stop)
  cd $APP_DIR
  bundle exec thin stop -C $THIN_CONFIG
  ;;
  restart)
  cd $APP_DIR
  bundle exec thin restart -C $THIN_CONFIG
  ;;
esac

exit 0

登録する

$ sudo /etc/init.d/sample start
$ sudo chkconfig sample on

2012年6月20日水曜日

jQuery.fn.loadのcallbackで受け取るjQuery.Event.targetはIEでDOM要素をとれない時がある

タイトルで大体言いきりました。

写真のスライドショーを作成していて、画像タグはJavaScript側で作成してDOMに追加するようにしていました。 何故こうしたかというと、環境によって画像の読み込みが終わらないうちにスライドショーの自動再生が始まってしまうのを防ぐためです。

具体的には以下のようなコードで画像の読み込み完了を待ってDOMに追加しています。

$('').attr(src: '/images/hoge.jpg?' + new Date().getTime()).load (e) =>
  $img = $ e.target
  $('#container').append $img

'?' + new Date().getTime() の部分ですが、コレをしておかないとIEがonloadイベントをサボるんですよね。。
必ずイベントを発火させるようにしています。

でIMG要素を $ e.target でjQuery化してappendという流れですが、実はこれだとIEでIMG要素がとれていないんです!
気づかずにそのまま処理を進めると画面真っ白になってお馴染みのわかりづらいエラーメッセージにかなり困惑しました。

IE8で以下の様に検証してみました。

A) e.targetでIMG要素が取得できない

$('').attr(src: '/images/hoge.jpg?' + new Date().getTime()).load (e) =>
  console.log e.target.nodeName # => #document

B) e.targetでIMG要素がとれる

$img = $ 'img.sample'
$img.attr(src: $img.attr('src') + '?' + new Date().getTime()).load (e) =>
  console.log e.target.nodeName # => IMG

違いはJSで要素を作成するか、htmlに記述されている要素を取得するかですね。

まぁぶっちゃけ loadのcallback内でthisが対象のIMG要素なのでコレ使えばいいんですよ。
でもCoffeeScriptつかってclass化したオブジェクトの中だと @(this) は常にクラスそのものとして使いたいので、 関数のthisにバインドしてくれる => を使いたいんですよね。

というわけで jQuery.Event オブジェクトの出番なわけです。
Aの方法だとIEで期待通りでないことがわかったので調べたところ、jQuery.Event.currentTarget というのがありました。
console.log e.currentTarget.nodeName でちゃんとIMGが返ってきたよ〜!

今後調べたいこと

  • $('<img />') と書いた時にブラウザ別に何が行われているのか
  • jQuery.Eventのコンストラクタを読む

追記:

  • jQuery APIの.load()を見たらDeprecatedのタグが付いてたのでbindでも試してみたけど、結果は一緒
2012年6月4日月曜日

herokuで(mbstring有効な)phpがすぐ動いた

herokuでphpが動くのは前々から知っていたのですが、mbstringが有効なphpがいとも簡単に動くという情報を見つけたので試してみました。

$ mkdir heroku-php
$ git init
$ vim index.php
<?php
phpinfo();

$ git add .
$ git commit -m "initial commit"
$ heroku create --buildpack https://github.com/winglian/heroku-buildpack-php -s cedar
$ git push heroku master

こんだけ!?全然難しくないですね。
heroku openしてみたらちゃんとphpinfoが表示されました。

でも気になったのがindex.phpの設置場所。
基本phpはindexに相当するものを設置しなければいけないと思うのですが、これってpublicディレクトリにしても動くのだろうか…?

$ mkdir public
$ git mv index.php public/
$ git commit -am "moved index.php"
$ git push heroku master

-----> Heroku receiving push
-----> Fetching custom buildpack... done
 !     Heroku push rejected, no Cedar-supported app detected

ガーン!…そもそもアプリのルートにindex.phpが無いと受け付けてくれないのか。
ということは環境変数DOCUMENT_ROOTとか使えないのかな?

$ heroku config:add DOCUMENT_ROOT=/app/www/public
Adding config vars and restarting app... done, v5
  DOCUMENT_ROOT => /app/www/public

まぁ無理でした。環境変数はセットできても何の意味も無いですね。ググっても情報みつからないので多分このオプションは存在しないのでしょう。
アプリそのものが公開ディレクトリなのはそれはそれでphpらしいですよね。

しかしphp速い感じする。ただのphpinfoというのもあるだろうけど、アプリの更新しても再起動が無いからか。

参考:HerokuのBuildpackを利用してmbstringが有効なPHPサーバを立ててみた - hnwの日記

2012年6月1日金曜日

CoffeeScriptでJavaScriptもクラス化しよう。テストを書きやすくメンテしやすく

CoffeeScriptを知ったのはRailsに組み込まれたからですが、Rails3.1で採用されてすぐに飛びついたわけではなく、しばらく敬遠してた時期もありました。しかし使い始めてからはもう手放せなくなりました。
そして次第にCoffeeScriptのclass構文を使えばよりよいJavaScriptの開発ができるのではないかと思うようになりました。
今回は最近CoffeeScriptを使ってどのように開発しているかを紹介したいと思います。

CoffeeScriptでのclass定義はこうやります

class Person
  constructor: (@my_name) ->

  greeting: ->
    alert "Hello! my name is #{@my_name}."

window.Person = window.Person || Person

コンパイルすると以下のようなJavaScriptになります

(function() {
  var Person;

  Person = (function() {

    Person.name = 'Person';

    function Person(my_name) {
      this.myname = my_name;
    }

    Person.prototype.greeting = function() {
      return alert("Hello! my name is " + this.my_name);
    };

    return Person;

  })();

  window.Person = window.Person || Person;
}).call(this);

※nameプロパティを使うと上書きしてしまうのでmynameとしました

例えばこのようなコードを person というファイル名で保存し、インスタンス生成は main という別ファイルで実行します。

main.coffee
$ ->
  kozo = new window.Person 'kozo'
  kozo.greeting()
main.js
(function() {
  $(function() {
    var kozo;
    kozo = new window.Person('kozo');
    return kozo.greeting();
  });
}).call(this);

こうすることでまず管理がしやすくなりますし、テストも気持ちよく書けると思います。

テストフレームワークはよく jasmine をつかっています。その前は QUnit だったんですが jasmine の方がドキュメントが探しやすいのとツールが整っている印象ですし、なによりコードが書きやすく後からでも見やすいですね。人によるかも知れませんが。
僕の場合CoffeeScript を使うときはほぼ Rails か sinatra 上ですので jasmine-headless-webkit というgemをつかってます。これを使うとCUIで実行結果が得られます。(jasmine-railsはこれに依存しています)

jasmineの諸々の設定は省略しますがテストコードはこんな感じにしています。

person_spec.coffee
describe "Person", ->

  beforeEach ->
    @person = new window.Person 'sample'

  afterEach ->
    delete @person

  describe "propaties", ->
    it "have 'my_name' string", ->
      expect(typeof @person.my_name).toBe "string"

  describe "greeting method", ->
    beforeEach ->
      spyOn window, "alert"
      @person.greeting()

    it "alert greeting text", ->
      expect(window.alert).toHaveBeenCalledWith "Hello! my name is sample."

DOMが絡むテストは jasmine-fixture というのをhelperとして読み込んで使っています。jqueryに依存しているのですが、むしろ使わない事のほうが少ないので気にしないです。affix という便利メソッドが使えるようになって、jqueryオブジェクトのprototypeにも追加されるので気に入ってます。

以上のような形で開発を進めてるのですが、クラスファイルと実行ファイルを分けてなるべくDOMの情報とかは実行ファイルに書き込むようにするといいと思います。
サイトのトップページにスライドショー的なものを設置するなら例えば以下の様な感じです。

main.coffee
$ ->
  slideshow = new window.Slideshow {
    container_id: '#hoge-slideshow'
    controller_id: '#hoge-controller'
    duration: 800
    wait: 3000
  }
  slideshow.start()

要はリテラル値をまとめて書いておくということです。

ただこのように実行ファイルに分けていてもファイル名が script とかにする人がいたりして…それはそれでわからなくはないのですが、mainの方が無難じゃないですかねぇ。

あぁマシン持ち寄ってこうゆう話ししながら酒を呑む仲間がほしいなぁ…

2012年5月30日水曜日

jQueryのプラグイン limp と airy を書いた

ひょんなことからjQueryの視覚系プラグインを2つ書きました。

jquery.limp.js

ソースというかデモサイトまるごとgithubにあります。
jquery.limp.js

要素をfixed配置に変えて縦スクロールに対して少し遅れてついてくるというものです。
いわゆるパララックス効果的に使えるんじゃないかと思います。

DEMO

limpという名前はlimpbizkitからとりました。僕の青春です。

jquery.airy.js

こちらもデモサイトまるごとgithubにあります。
jquery.airy.js

要素をabsoluteで配置しておくとフワフワとその場を浮遊するというものです。
こちらは使い道が難しいですね。

DEMO

両者ともcoffeescriptで書いてsinatraでサーブしてるのですが、素のjavascript書くのしんどいですね最近……

2012年5月25日金曜日

OS X LionにbrewでAPCをインストールした

まずはFormulaを検索

$ brew search apc
josegonzalez/php/php53-apc  josegonzalez/php/php54-apc

2つありました。 phpはv5.3.10なのでphp53-apcをインストールします。

リポジトリを追加

$ brew tap josegonzalez/php
Cloning into '/usr/local/Library/Taps/josegonzalez-php'...
remote: Counting objects: 1011, done.
remote: Compressing objects: 100% (491/491), done.
remote: Total 1011 (delta 480), reused 929 (delta 416)
Receiving objects: 100% (1011/1011), 140.43 KiB | 109 KiB/s, done.
Resolving deltas: 100% (480/480), done.
Tapped 48 formula

インストール実行

$ brew install php53-apc
......
To finish installing php53-apc:
  * Add the following lines to /usr/local/etc/php.ini:
    [apc]
    extension="/usr/local/Cellar/php53-apc/3.1.10/apc.so"
    apc.enabled=1
    apc.shm_segments=1
    apc.shm_size=64M
    apc.ttl=7200
    apc.user_ttl=7200
    apc.num_files_hint=1024
    apc.mmap_file_mask=/tmp/apc.XXXXXX
    apc.enable_cli=1
  * Restart your webserver.
  * Write a PHP page that calls "phpinfo();"
  * Load it in a browser and look for the info on the apc module.
  * If you see it, you have been successful!
  * You can copy "/usr/local/Cellar/php53-apc/3.1.10/apc.php" to any site to see APC's usage.
......

指示通りphp.iniに設定を追加して、php-fpmを再起動 (これがいまいちまだよくわかってない…)

$ ps ax | grep php
  493   ??  Ss     0:00.17 /usr/local/Cellar/php/5.3.10/sbin/php-fpm
  494   ??  S      0:01.15 /usr/local/Cellar/php/5.3.10/sbin/php-fpm
  495   ??  S      0:01.37 /usr/local/Cellar/php/5.3.10/sbin/php-fpm
$ kill -TERM 493
$ php-fpm

↑とりあえずkillしてから実行してみてる phpinfo()で確認OK!

2012年5月18日金曜日

WebGLに感動したのでThree.jsを触ってみた

このギャラリーの作品はどれもレベル高くてすごい

Chrome Experiments - WebGL Experiments

見てたらWebGLをかじってみたくなったのでThree.jsというのを試してみた。

mrdoob/three.js

リポジトリを落としてきてsinatraに乗っけてherokuにアップしときました。
Three.js Learning
トップページはexamplesの一覧です。

とりあえず本家のGet Startting試してみたけど、動かないとつまらないのでマウスの動きに反応するようにした。

うーん道のり長そうだなぁ

iPhoneだと動かないのかな?

2012年5月2日水曜日

railsのform_forに渡すモデルオブジェクトでurl生成する場合の話

railsのform_forヘルパーは以下のように使いますよね

= form_for(@post) do |f|
  = f.label :title
  = f.text_field :title

こう書いておけば出力されるhtmlは

大体こんな感じになるかと。
でrailsの便利なところはform_forに渡してるモデルオブジェクトが、空なのかDBのレコードから生成されたのかを判別して、actionの属性値をよしなに変更してくれるところですよね。

つまりroutesに

resouces :posts do
end

と書いてあれば 新規作成時は action="/posts" だし編集画面では action="/posts/1" になると。 でもこれが namespace 付きのroutesだとうまく行かなくて困りました。
管理画面を作る場合とかだと /admin/posts にしたくて

namespace :admin do
  resources :posts do
  end
end

と定義しておいて、form_for で生成したフォームを上記のままにしておくと、undefined method `posts_path' と怒られてしまう。 一瞬下記のやり方?とか思ったんですがrailsに限ってそんなことないよね…

= form_for params[:action] == 'new' ? admin_posts_path : edit_admin_post_path(@post) do |f|
  = f.label :title
  = f.text_field :title

ぐぐったら出てきた。

Rails Routes Namespaces and form_for - Stack Overflow
= form_for [:admin, @post] do |f|...

だってさ!
配列で指定するんですか。なるほどこれで admin_posts_path と edit_admin_post_path を切り替えてくれるんですね。

ということは…やってみた。

namespace :hoge do
  namespace :fuga do
    namespace :foo do
      namespace :bar do
        resources :hellos do
        end
      end
    end
  end
end
= form_for [:hoge, :fuga, :foo, :bar, @hello], do |f|
  = f.label :name
  = f.text_field :name

成功\(^o^)/