ポリモーフィックの子から親を生成する

こんにちは。初めてお目にかかる方、初めまして。
@return_right34と申します。一応社員です。

最近、これまでとは打って変わった可愛らしいデザインのまとめサイトを制作している私ですが、
とある問題にぶつかりましたので、記事を書いてみました。

問題の親子

# app/models/article.rb
class Article
  has_many :items
end

# app/models/item.rb
class Item
  belongs_to :article
  belongs_to :itemable, polymorphic: true
end

# app/models/text.rb
class Text
  has_one :item, as: :itemable
end

こんな感じにポリモーフィックになっている親子構造を、
以下のような流れで一回のフォーム動作でデータ生成をしたいと考えました。
article -> items -> itemable(Text, ...etc)

……あれ? 子から親の作成って・・・? しかもポリモーフィックだし・・・。

結論から申しますと、以下のような感じでコードを追加すれば子から親&ポリモーフィックを生成できます。

Articleモデルはごく普通に

# app/models/article.rb
class Article
  has_many :items
  accepts_nested_attributes_for :items
end

特記することはありません。

Itemモデルをこんな感じに

# app/models/item.rb
class Item
  accepts_nested_attributes_for :itemable

  def attributes=(attributes = {})
    self.itamable_type = attributes[:itemable_type]
    super
  end

  def itemable_attributes=(attributes)
    self.itemable = itemable_type.constantize.create_with(attributes).find_or_initialize_by(id: attributes['id'])
    self.itemable.attributes = attributes.slice(*%w(content)) if itemable.id
  end
end

驚いたのは、accepts_nested_attributes_forが、子から親の間柄でも働くこと。・・・そもそも、働かなかったら一回のform動作で終わらなくなってしまうのはそうなのですが。
下のメソッド二つは、ごく普通の親子関係なら必要なさそうなのですが、ポリモーフィックの場合は定義がされてないようなので、自分で追加します。
以下のURLが大変参考になりました。感謝してもしきれないです。
http://stackoverflow.com/questions/3969025/accepts-nested-attributes-for-with-belongs-to-polymorphic

コントローラはこんな感じに

# app/controllers/articles_controller.rb
class ArticlesController
  def create
    @article = Articles.new(article_params)
    @article.save
  end

  private
    def article_params
      params[:article].permit(:title, 
        items_attributes: [:itemable_type, 
          itemable_attributes: [:content]
        ],
      )
    end
end

入れ子構造を重ねております。
モデルに"belongs_to :itemable, polymorphic: true"と記述した通りに、
子から親を生成する場合であっても{関連モデル名}_attributesと書けば通るみたいです。

フォームはこんな感じに

# app/views/articles/new.html.erb
<%= simple_form_for @article do |f| %>
  <%= f.input :title %>
  <%= f.simple_fields_for :items do |item| %>
    <%= f.input :itemable_type, as: :hidden, input_html: { value: 'Text' } %> #ここ大事
    <%= f.simple_fields_for :itemable, Text.new do |itemable| %>
      <%= itemable.input :content %>
    <% end %>
  <% end %>
<% end %>

さりげなくsimple_form使っております。
コントローラではさらっと流しましたが、ポリモーフィックのタイプを明示しておくことが重要です。でなければ、親に何のクラスのインスタンスを生成するのかわからなくなってしまいます。

これで無事、articleからitems, itemsからitemable(Text, ...etc)を生成できるようになりました!


参考になる方がいらっしゃればと思います。
それでは、乱文失礼しました。