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">]

できた!