加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

ruby-on-rails – 访问Rails中未保存的父级和子级的祖父母

发布时间:2020-12-16 21:08:13 所属栏目:百科 来源:网络整理
导读:我有一个表单来保存父和许多子对象.在初始化Child对象期间,它需要访问祖父母.这是模型的样子: class Grandparent has_many :parents,inverse_of: :grandparentendclass Parent belongs_to :grandparent,inverse_of: :parents has_many :children,inverse_of
我有一个表单来保存父和许多子对象.在初始化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:为过长的答案道歉.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读