LoginSignup
153
141

More than 5 years have passed since last update.

[rails] accepts_nested_attributes_for の場合は、inverse_of を付けておくと良い気がする

Posted at

inverse_of

上記サイト様が上げているサンプルが分かりやすい

inverse_ofを使わなかった場合

 class Dungeon < ActiveRecord::Base
  has_many :traps
end

class Trap < ActiveRecord::Base
  belongs_to :dungeon
end


d = Dungeon.first
t = d.traps.first
d.level == t.dungeon.level # => true
d.level = 10
d.level == t.dungeon.level # => false

d.object_id ==  t.dungeon.object_id #=> false

inverse_ofを使った場合

class Dungeon < ActiveRecord::Base
  has_many :traps, :inverse_of => :dungeon
end

class Trap < ActiveRecord::Base
  belongs_to :dungeon, :inverse_of => :traps
end


d = Dungeon.first
t = d.traps.first
d.level == t.dungeon.level # => true
d.level = 10
d.level == t.dungeon.level # => true

d.object_id == t.dungeon.object_id #=> true

つまり inverse_of を使うと、dとt.dungeonがメモリ内で同一のインスタンスを見るようになる。

with accepts_nested_attributes_for

accepts_nested_attributes_for の場合なぜ良いか。

親recordが出来る前に、associationをたどれるようになるのがよい。

inverse_ofを付けない場合

# app/models/survey.rb
class Survey
    belongs_to :user
    has_many :questions
    accepts_nested_attributes_for :questions
end

# app/models/question.rb
class Question
    validates :survey_id, presence: true
    belongs_to :survey
end

こんなmodelをまとめてsaveすると

    def create
        @survey = current_user.surveys.build(survey_params)
        if @survey.save
            redirect_to @survey
        else
            render :new
        end
    end

子recordのvalidate時には、親recordができていないので、親をたどれない。
子recordのcreate時なら、親recordができているので、たどれるようになる。

子recordのbefore_validate内

[1] pry(#<Question>)> self
=> #<Question id: nil, survey_id: nil, name: "hoge", created_at: nil, updated_at: nil>
[2] pry(#<Question>)> survey
=> nil

親をたどれない

子recordのbefore_create内

[1] pry(#<Question>)> self
=> #<Question id: nil, survey_id: 1, name: "hoge", created_at: "2014-12-27 05:30:55", updated_at: "2014-12-27 05:30:55">
[2] pry(#<Question>)> survey
=> #<Survey id: 1, name: 'aaa', created_at: "2014-12-27 05:30:55", updated_at: "2014-12-27 05:30:55">

survey_idがあるので、before_createなら親をたどれる。

inverse_ofを付けた場合

# app/models/survey.rb
class Survey
    belongs_to :user
    has_many :questions, inverse_of: :survey
    accepts_nested_attributes_for :questions
end

# app/models/question.rb
class Question
    validates :survey, presence: true
    belongs_to :survey, inverse_of: :questions
end

子recordのbefore_validate内

[1] pry(#<Question>)> self
=> #<Question id: nil, survey_id: nil, name: "hoge", created_at: nil, updated_at: nil>

survey_idはnilだけど。。。

[2] pry(#<Question>)> survey
=> #<Survey id: nil, name: 'aaa', created_at: nil, updated_at: nil>

before_validate段階で、親がたどれる!
しかもこの際に、SQLは発行されないです。(idでたどる訳じゃないから、そりゃそうか)

※ 投稿用に自分の検証環境のclass名などをSurveyとQuestionに書き換えちゃったので、なんか誤植あったらすいません。

inverse_ofがなくて困る事

何となく思うのは、外部制約キーを付けている場合。
あと、子のbefore_validate内にて、親recordとの複合条件にてvalidateをしたい場合などに困りそう。

自分の場合は、親 > 子1 > 子2 の様な多重はrelationで、子2から親1の中身を使ってvalidateしよう、と思った

inverse_ofの制約

throughアソシエーションと一緒には動きません
polymorphicアソシエーションと一緒には動きません
for belongs_to associations has_many inverse associations are ignored.

らしいです。

参考

153
141
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
153
141