Open nyamadori opened 5 years ago
STI の場合は、ベースクラスのスキーマ定義、CTI の場合は、自身のモデルのスキーマ定義を見るようにする
した。
rspec を実行すると、今度はこのエラーが出たので調べる。
Failure/Error:
FeatureRequest.create(
**issue_attributes,
**feature_request_attributes,
)
ActiveModel::MissingAttributeError:
can't write unknown attribute `user_id`
# ./spec/models/feature_request_spec.rb:35:in `block (3 levels) in <top (required)>'
can't write unknown attribute
user_id
と言われるが、 FeatureRequest.reflections
にはベースクラスである Issue から継承した user
がちゃんと存在する。
[2] pry(main)> FeatureRequest.reflections
=> {"user"=>
#<ActiveRecord::Reflection::BelongsToReflection:0x00007fb1b0202da8
@active_record=
Issue(id: integer, user_id: integer, product_id: integer, status: integer, title: string, description: text, prior
@association_scope_cache=#<Concurrent::Map:0x00007fb1b0202a88 entries=0 default_proc=nil>,
@constructable=true,
@foreign_type=nil,
activemodel-5.2.3/lib/active_model/attribute_set.rb:58:in `write_from_user' に
p self[name]
を仕掛けて name = user
の属性情報を得る。
これ。
#<ActiveModel::Attribute::Null:0x00007fd5ceaefc58 @name="user_id", @value_before_type_cast=nil, @type=#<ActiveModel::Type::Value:0x00007fd5ce50c2a0 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=nil>
すると上記が得られる。ActiveModel::Attribute::Null は、指定された属性名に対応する属性情報がない場合に返ってくる型らしい(いわゆる Null Object パターン)。
属性情報がある場合は以下が得られる。
#<ActiveModel::Attribute::FromDatabase:0x00007fd5ce507980 @name="id", @value_before_type_cast=nil, @type=#<ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer:0x00007fd5d31cec50 @precision=nil, @scale=nil, @limit=nil, @range=-9223372036854775808...9223372036854775808>, @original_attribute=nil, @value=nil>
ちなみに :user という関連名から外部キーカラム名の解決は以下で行われる。
activerecord-5.2.3/lib/active_record/associations/belongs_to_association.rb:98:in
association の情報はスーパークラスから引き継がれるが、属性の情報は self.table_name =
で指定したテーブル名から作成されるので、can't write unknown attribute
user_id` というエラーが出るようだ。
ActiveModel::AttributeSet#[]
をオーバライドして、モデルのスーパークラスの定義を見に行くようにすればいいかと思ったけど、ActiveModel::AttributeSet
のコンテキストでは、モデルのスーパークラスを見に行けないことが分かった。
だから、テーブルスキーマを読み込み属性情報を作る段階で、モデルのスーパークラスの定義を参照するようにする必要がありそうだ。
何がしたいか
https://github.com/nyamadori/rails_cti_pof/blob/master/spec/models/feature_request_spec.rb のような API を実装したい。
Rails version
5.2.3
実装方法の調査
https://github.com/nyamadori/rails_cti_pof/commit/21d6f89c02c93f554213eca501f09762689a3950 のコミット上で実現したい仕様を書いた rspec を実行
レコード作成に失敗しているので、まずはそこから調べる。
次の場所に Binding.pry を仕掛け、rspec を実行
pry で inspect
CTI を実装する上で、FeatureRequest クラスにもかかわらず、スキーマ定義が issues テーブルのものになる仕様は困るが、Rails で STI を実装するにはこれが最も自然だと思われる。STI と CTI を一アプリケーション内で同時に使えるようにしたいので、この仕様は互換性を保った状態にする必要がある。
また、このエラーを防ぐには ActiveRecord が認識するスキーマ定義を変えて、スキーマ定義に
sponser
が入るようにすれば良さそうだが、悪手だと思う。というのは、実際のテーブルの状態と異なるスキーマ定義ができてしまうからだ。STI との互換性を保つため、何らかの方法でモデルが STI のモデルか、CTI のモデルかを区別できるようにする。
STI の場合は、ベースクラスのスキーマ定義、CTI の場合は、自身のモデルのスキーマ定義を見るようにする。