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以外はほとんど共通処理がないよとかになると、アクションそのものを分けてしまったほうが綺麗なコードになりそうですよね。