ruby-on-rails – 访问Rails中未保存的父级和子级的祖父母
我有一个表单来保存父和许多子对象.在初始化Child对象期间,它需要访问祖父母.这是模型的样子:
class Grandparent has_many :parents,inverse_of: :grandparent end class Parent belongs_to :grandparent,inverse_of: :parents has_many :children,inverse_of: :parent accepts_nested_attributes_for :children end class Child belongs_to :parent delegate :grandparent,to: :parent # Test code after_initialize do raise 'NoParentError' unless parent.present? raise 'NoGrandparentError' unless grandparent.present? # Errors here! puts 'All good!' end end 请记住,表单用于同时保存新父项和多个子项,但我正在尝试访问祖父项对象中的信息.我读到inverse_of选项应该已经完成??了这个技巧,但是不幸的是,child.grandparent仍然是nil. 这是实际导致失败的控制器部分: @parent = @grandparent.parents.build(parent_params) # prior to saving @parent 由于某种原因,父母不知道祖父母是谁. 更新 看起来我可以通过以下代码解决该错误: @parent = Parent.new(parent_params.merge(grandparent: @grandparent)) 但这对我来说似乎并不是很“困难”. 更新2 根据要求,这是我的表格控制器. class ParentsController < ApplicationController before_action :set_grandparent def new @parent = @grandparent.parents.new @parent.children.build end def create @parent = @grandparent.parents.build(parent_params) if @parent.save redirect_to @grandparent else render :new end end private def set_grandparent @grandparent = Grandparent.find(params[:grandparent_id]) end def parent_params params.require(:parent).permit(:parent_attribute,children_attributes: [:some_attribute,:other_attribute,:id,:_destroy] end end 这是我的观点: = simple_form_for [@grandparent,@parent] do |f| = f.input :parent_attribute = f.simple_fields_for :children do |child_form| = child_form.input :some_attribute = child_form.input :other_attribute = f.submit 我可以在子项的after_initialize代码中放置一个byebug,我可以看到未保存的Parent和Child,并可以通过以下方式访问它们: p = self.parent => Parent object p.grandparent => nil self.grandparent => nil 解决方法
出现此问题的原因是在将父实例添加到(与之关联)祖父母的实例之前初始化Parent的实例.让我用以下示例向您说明:
class Grandparent < ApplicationRecord # before_add and after_add are two callbacks specific to associations # See: http://guides.rubyonrails.org/association_basics.html#association-callbacks has_many :parents,inverse_of: :grandparent,before_add: :run_before_add,after_add: :run_after_add # We will use this to test in what sequence callbacks/initializers are fired def self.test @grandparent = Grandparent.first # Excuse the poor test parameters -- I set up a bare Rails project and # did not define any columns,so created_at and updated_at was all I # had to work with parent_params = { created_at: 'now',children_attributes: [{created_at: 'test'}] } # Let's trigger the chain of initializations/callbacks puts 'Running initialization callback test:' @grandparent.parents.build(parent_params) end # Runs before parent object is added to this instance's #parents def run_before_add(parent) puts "before adding parent to grandparent" end # Runs after parent object is added to this instance's #parents def run_after_add(parent) puts 'after adding parent to grandparent' end end class Parent < ApplicationRecord belongs_to :grandparent,inverse_of: :parent,after_add: :run_after_add accepts_nested_attributes_for :children def initialize(attributes) puts 'parent initializing' super(attributes) end after_initialize do puts 'after parent initialization' end # Runs before child object is added to this instance's #children def run_before_add(child) puts 'before adding child' end # Runs after child object is added to this instance's #children def run_after_add(child) puts 'after adding child' end end class Child < ApplicationRecord # whether it's the line below or # belongs_to :parent,inverse_of: :children # makes no difference in this case -- I tested :) belongs_to :parent delegate :grandparent,to: :parent def initialize(attributes) puts 'child initializing' super(attributes) end after_initialize do puts 'after child initialization' end end 从Rails控制台运行方法Grandparent.test输出: Running initialization callback test: parent initializing child initializing after child initialization before adding child after adding child after parent initialization before adding parent to grandparent after adding parent to grandparent 你可以从中看到的是,直到最后,父母实际上并没有被添加到祖父母.换句话说,在子初始化和自己的初始化结束之前,父母不知道祖父母. 如果我们修改每个puts语句以包含grandparent.present ?,我们得到以下输出: Running initialization callback test: parent initializing: n/a child initializing: n/a after child initialization: false before adding child: false after adding child: false after parent initialization: true before adding parent to grandparent: true after adding parent to grandparent: true 因此,您可以先执行以下操作,首先初始化父项,然后初始化子项(ren): class Parent < ApplicationRecord # ... def initialize(attributes) # Initialize parent but don't initialize children just yet super attributes.except(:children_attributes) # Parent initialized. At this point grandparent is accessible! # puts grandparent.present? # true! # Now initialize children. MUST use self self.children_attributes = attributes[:children_attributes] end # ... end 以下是运行Grandparent.test时输出的内容: Running initialization callback test: before parent initializing: n/a after parent initialization: true child initializing: n/a after child initialization: true before adding child: true after adding child: true before adding parent to grandparent: true after adding parent to grandparent: true 如您所见,父级初始化现在在调用子初始化之前运行并完成. 但明确地将祖父母:@grandparent传递给params哈希可能是最简单的解决方案. 当您在传递给@ grandparent.parents.build的params哈希中明确指定祖父项:@grandparent时,祖父表从一开始就被初始化.可能是因为#initialize方法运行后会立即处理所有属性.这是看起来像: Running initialization callback test: parent initializing: n/a child initializing: n/a after child initialization: true before adding child: true after adding child: true after parent initialization: true before adding parent to grandparent: true after adding parent to grandparent: true 您甚至可以直接在控制器方法#parent_params中调用merge(grandparent:@grandparent),如下所示: def parent_params params.require(:parent).permit( :parent_attribute,children_attributes: [ :some_attribute,:_destroy ] ).merge(grandparent: @grandparent) end PS:为过长的答案道歉. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |