Ruby
RubyOnRails
render
cocoon

rails4×Cocoonで部分テンプレートに変数を渡す際に詰まったのでメモ

はじめに

エンジニア歴一年未満のひよっこの初投稿記事です。
現在業務ではCMSをいじってます。
拙いコードもあるかと思うのでアドバイスやお叱りはコメントでいただけると幸いです。
※実際のコードよりも少し簡略化しているものを書いてます。

環境

  • Ruby: 2.3.6
  • Rails: 4.2.8

概要

動的フォームを作るgem : cocoonを使ってフォームを作っていた際にlocalsでうまく変数が渡せなかったのですが、公式ドキュメントを使って一応解決できたので備忘録代わりに。

model

以下のように、post/tag/categoryという3つのモデルがあり、
- category - post / category - tagはそれぞれ一対多
- posttagは多対多
の関係でつながっています。今回はpostのform内でpost_tagsを生成する必要があり、cocoonを使いました。

post.rb
class Post < ActiveRecord::Base
  belongs_to :category
  has_many :post_tags, dependent: :destroy
  has_many :tags, through: :post_tags
  accepts_nested_attributes_for :post_tags , allow_destroy: true
end
tag.rb
class Tag < ActiveRecord::Base
  belongs_to :category
  has_many :post_tags, dependent: :destroy
  has_many :posts, through: :post_tags
end
category.rb
class Category < ActiveRecord::Base
  has_many :posts
  has_many :tags
end

やりたかったこと

上記のアソシエーションを持つため、
『postと同じcategoryを持つtagしか、post_tagsに登録したくない!!』
というのが今回やりたかったことです

エラーとなったform

view

<%= form_for(@post) do |f| %>
  <!--中略-->
  <div class="form-group">
    <%= f.label :post_tags, 'タグ', class: 'control-label' %>
    <%= link_to_add_association "タグ追加", f, :post_tags, partial: "parts/tag_fields", data: {association_insertion_node: '#tag-box', association_insertion_method: 'append'} %>
      <div id="tag-box">
        <%= f.fields_for :post_tags do |d| %>
          <%= render 'parts/tag_fields', { f: d, tags: @tags }  %>
        <% end %>
      </div>
    </div>
  </div>
  <%= f.submit "保存", class: 'btn btn-primary' %>
<% end %>
parts/_tag_fields.html.erb
<div class="nested-fields">
  <%= f.hidden_field :id %>
  <div class="input-group input-group-sm" style="margin-bottom: 5px;">
    <div>
      <%= f.collection_select :tag_id, tags, :id, :name, {}, class: 'form-control input-sm' %>
    </div>
  </div>
</div>

controller

posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post_and_tags

  def edit; end

  # 中略

  private

  def set_post_and_tags
    @post = Post.find(params[:post_id])
    @tags = @post.category.tags
  end

end

以上を実行すると、undefined local variable or method 'tags'となってしまいます。
「なぜだ!!renderでちゃんとオプション指定しているのに。。」

なにが問題だったのか

オプションの記述でいろいろミスってるかな、、と思い試しましたが解決せず。
基本に立ち返って公式ドキュメントを、、、と、調べたらありました。(始めからそうすべきでした)

render_options : options passed through to the form-builder function (e.g. simple_fields_for, semantic_fields_for or fields_for). If it contains a :locals option containing a hash, that is handed to the partial.

= link_to_add_association 'add something', f, :something,
    render_options: {locals: { sherlock: 'Holmes' }}

なるほど、link_to_add_associationの方にも指定せねばいけないのですね。

修正したコード

<%= form_for(@post) do |f| %>
   <!--中略-->
   <div class="form-group">
     <%= f.label :post_tags, 'タグ', class: 'control-label' %>
     <%= link_to_add_association "タグ追加", f, :post_tags, partial: "parts/tag_fields", data: {association_insertion_node: '#tag-box', association_insertion_method: 'append'}, , render_options: {locals: { tags: @tags }} %>
     <div id="tag-box">
       <%= f.fields_for :post_tags do |d| %>
         <%= render 'parts/tag_fields', { f: d, tags: @tags }  %>
       <% end %>
      </div>
    </div>
  </div>
  <%= f.submit "保存", class: 'btn btn-primary' %>
<% end %>

少し見辛いですが、
link_to_add_associationの最後にrender_options: {locals: { tags: @tags }}を付け加えたら解決しました。

参考