gpt4 book ai didi

ruby-on-rails - 在 Rails 中访问未保存的 parent 和 child 的祖 parent

转载 作者:行者123 更新时间:2023-12-04 20:30:14 27 4
gpt4 key购买 nike

我有一个表格来保存 Parent和许多 Child对象在一起。在 Child 初始化期间对象,它需要访问 Grandparent .以下是模型的外观:

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

请记住,该表单用于同时保存新的 parent 和许多 child ,但我试图访问祖 parent 对象中的信息。我读到 inverse_of选项应该可以解决问题,但是 child.grandparent仍然是 nil很遗憾。

这是实际导致故障的 Controller 部分:
@parent = @grandparent.parents.build(parent_params)
# prior to saving @parent

由于某种原因, parent 不知道祖 parent 是谁。

更新

看起来我可以用这段代码克服这个错误:
@parent = Parent.new(parent_params.merge(grandparent: @grandparent))

但这对我来说似乎不是很“有条理”。

更新 2

根据要求,这是我的表单 Controller 。
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

我可以放置 byebugafter_initialize Child 的代码我可以看到未保存的 ParentChild并可以通过以下方式访问它们:
p = self.parent
=> Parent object

p.grandparent
=> nil

self.grandparent
=> nil

最佳答案

出现此问题的原因是父实例在添加到祖父实例(与祖父实例相关联)之前已初始化。让我通过以下示例向您说明:

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: :parents
has_many :children, inverse_of: :parent,
before_add: :run_before_add, 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

运行方法 Grandparent.test从 Rails 控制台输出:
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

从中可以看出,实际上直到最后才将父级添加到祖父级。换句话说,直到 child 初始化和它自己的初始化结束, parent 才知道祖 parent 。

如果我们修改每个 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: @grandparent进入 params 哈希可能是最简单的解决方案。

当您明确指定 grandparent: @grandparent在您传递给 @grandparent.parents.build 的参数哈希中,祖 parent 从一开始就被初始化。可能是因为所有属性都会在 #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

您甚至可以调用 merge(grandparent: @grandparent)直接在您的 Controller 方法中 #parent_params ,像这样:
def parent_params
params.require(:parent).permit(
:parent_attribute,
children_attributes: [
:some_attribute,
:other_attribute,
:id,
:_destroy
]
).merge(grandparent: @grandparent)
end

PS:为过长的答案道歉。

关于ruby-on-rails - 在 Rails 中访问未保存的 parent 和 child 的祖 parent ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48087668/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com