廃墟

本ブログは更新を終了しました。 技術的な記事のみ、有用性を鑑みて残しておきます。

Angular.js と Rails の form_for を良い感じに連携する

最近とある案件で Angular.js を使い始めたのですが。
どうにもRailsと相性がそこまで良くないというか、2つの世界をCoCで繋ぐ決定的なgemなどが出てきていないからか、不便さを感じます。

そんな中で、特に不便に感じたのが、form_for がロクに使えないことでした。

$ rails g model post title:string

みたいなモデルを想定してください。

愚直にやると、Angualr.jsで非同期にPOSTする時のformは、こんな感じになってしまいます。

# slim記法の例
div 'ng-controller'=>'PostCtrl'
  = form_for(@post, html:{ 'ng-submit'=>'submit()' }) do |f|
    = text_field :title, 'ng-model' => 'post.title'
    = button :submit
  end

うーん、何かこう、いけ好かないですよね。 同じ様な事を何回も書いてるし…

ということで、こんなのを作ってみた。

# /app/helpers/angular_form_builder.rb

# http://uzuki05.hateblo.jp/entry/2013/04/05/114356

class AngularFormBuilder < ActionView::Helpers::FormBuilder
  def ng_text_field(method, options = {})
    options[:html] ||= {}
    @template.text_field(@object_name, method, objectify_options(options[:html].reverse_merge!(ng_options(method))))
  end

  # TODO: text_field 以外の互換メソッドも用意しましょう

  private

  def ng_options(method)
    {
      'ng-model' => "#{@object_name}.#{method}",
    }
  end
end
# /app/helpers/application_helper.rb

module ApplicationHelper
  def ng_form_for(*args, &proc)
    args << {} unless args.last.is_a?(Hash)
    options = args.last
    if args.first.is_a?(Symbol)
      options.merge!(as: args.shift)
    end
    options.reverse_merge!(
                             builder: AngularFormBuilder,
                             format: :json,
                             html: {
                               'ng-submit' => 'submit()'
                             }
                           )
    form_for(*args, &proc)
  end
end

これを使うと、フォーム部分は以下の様に書けます。

# slim記法の例
div 'ng-controller'=>'PostCtrl'
  = ng_form_for(@post) do |f|
    = ng_text_field :title
    = button :submit
  end

あとは、Angular.jsのControllerをこれに準拠して書いていけば、それなりに柔軟さを保ちつつ、RailsのFormBuilderだけで概ね記述できて良いんじゃないかな、と思います。

ちなみに、CSRF対策については以下のStackoverflowでの議論に準拠すると良いと思います。

angularjs - Rails CSRF Protection + Angular.js: protect_from_forgery makes me to log out on POST - Stack Overflow


Angular.js触って2日目ぐらいなので、もっと良い方法あったらおしえてください!