Rails4でSTIのモデルのform_forで詰まった話

STIとは

STI(Single table inheritance)は,
DBのtable上で, オブジェクト指向における
継承関係にあるクラスをうまく扱うための考え方です.

STI on rails

railsSTIをサクッと実装する方法を提供してくれているので,
type columnに子供のクラス名を指定するだけです.

# app/model/product.rb
class Product < ApplicationRecord
end

# app/model/product/smart_phone.rb
class Product::SmartPhone < Product
end

# app/model/product/pc.rb
class Product::Pc < Product
end

# app/model/product/tablet.rb
class Product::Tablet < Product
end

p = Product.new(type: 'Product::SmartPhone')
p.class.name #=> Product::SmartPhone

問題

# config/routes.rb
resources :products

# app/views/products/_form.html.slim
# @product = Product.where(type: 'Product::SmartPhone').first
# @product.class.name #=> Product::SmartPhone
= form_for @product do |f|
....

STIの親クラス名でroutingを定義している時に,
edit画面で, 子クラスのobjectをform_forの引数に渡すと..

rails に product_smart_phone_path を探しに行かれてundefinedになって死ぬ.

最終解決策

= form_for @product.becomes(Product) do |f|

ちなみにrails 5だと

= form_for @product, as: :product do |f|

で解決するそうな..
version upは大切ですな...

結論への紆余曲折

最初は form_for 内で url_for が呼ばれてるんだろどうせと思い,
railsgithub repositoryで url_for を検索して code 読みまくった.
(ここでrailsアーキテクチャのわかってなさを痛感...)
そしてurl_forにobject渡した時は
ActiveModel::Nameのインスタンスが帰ってきて
そのインスタンスに対してmethod呼び出しをして
呼び出すpathが決まる事を知った.
そこで

class Product::SmartPhone << Product
  def self.model_name
    superclass.model_name
  end
end

とやって無理やり子クラスのurl_forの値を親クラスのものに
していた.

が正しい事をやってるかんが全くない &&
これだけのために大掛かりなことをしすぎてる感

があって結局 becomesに