使用委托类型处理委托人的验证错误

Handling validation errors on delegatee with delegated types

我在理解如何使用 Rails' 委托类型时遇到问题,当涉及到委托人的验证失败时。

有如下代码

inbox.rb

class Inbox < ApplicationRecord
    delegate :name, to: :inboxable
    delegated_type :inboxable, types: %w[ Mailbox Messagebox ], dependent: :destroy
end

class Mailbox < ApplicationRecord
  include Inboxable

  belongs_to :inbox_domain

  validates :alias, presence: true, uniqueness: true

  def name
    "#{self.alias}@#{self.domain.name}"
  end
end

messagees_controller.rb

def create
  @mailbox =  Inbox.create inboxable: Mailbox.new(mailbox_params)

  if @mailbox.save
    redirect_to @mailbox.inboxable, notice: "<b>#{@mailbox.name}</b> was created."
  else
    render :new
  end
end

private

def mailbox_params
  params.require(:mailbox).permit(:alias, :inbox_domain_id)
end

当我想创建一个别名已被使用的邮箱时,抛出以下错误,因为Mailbox.new 验证失败

ActiveRecord::NotNullViolation (PG::NotNullViolation: ERROR:  null value in column "inboxable_id" violates not-null constraint
DETAIL:  Failing row contains (13, 2021-09-26 20:48:53.970799, 2021-09-26 20:48:53.970799, Mailbox, null, f).
):

尝试过的解决方案

处理这种情况的正确方法是什么?我试过先明确检查 Mailbox.new,像这样:

 mailbox = Mailbox.new(mailbox_params)
   if mailbox.valid?
    @inbox =  Inbox.create inboxable: mailbox
......

虽然它在技术上可行,但一旦您还必须验证收件箱本身的属性,它就会变得一团糟

使用validates_associated触发对关联记录的验证:

class Inbox < ApplicationRecord
  delegate :name, to: :inboxable
  delegated_type :inboxable, types: %w[ Mailbox Messagebox ], dependent: :destroy
  validates_associated :inboxable
end

这将向此模型上的错误对象添加一个错误(“可收件箱无效”),并在相关邮箱无效时阻止保存。

你想要的控制器是:

def create
  # .create both instanciates and saves the record - not what you want here
  @mailbox = Inbox.new(inboxable: Mailbox.new(mailbox_params))
  if @mailbox.save
    redirect_to @mailbox.inboxable, notice: "<b>#{@mailbox.name}</b> was created."
  else
    render :new
  end
end

如果您想显示关联项的错误,您需要访问并循环遍历其上的错误对象:

# app/views/shared/_errors.html.erb
<ul>
  <% object.errors.each do |attribute, message| %>
    <li><%= message %>
  <% end %>
</ul>
<%= form_with(model: @inbox) do |form| %>
  <% if form.object.invalid? %>
    <%= render(partial: 'shared/errors', object: form.object) %>
    <% if form.object.inboxable.invalid? %> 
      <%= render(partial: 'shared/errors', object: form.object.inboxable) %>
    <% end %>
  <% end %>

  # ...
<% end %>