1

我有以下模型及其相应的架构定义:

class Cube < ApplicationRecord
  has_many :faces, inverse_of: :cube, dependent: :destroy
  accepts_nested_attributes_for :faces
end

create_table "cubes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end


##
# NOTE: Even though this model's related database table has an +id+ column, it
#       is not a regular auto-generated, auto-incremented ID, but rather an +id+
#       column that refers to an uuid that must be manually entered for each
#       Face that is created.
##
class Face < ApplicationRecord
  belongs_to :cube, inverse_of: :faces, optional: true
  validates :id, presence: true, uniqueness: true
end

# Note that id defaults to nil.
create_table "faces", id: :uuid, default: nil, force: :cascade do |t|
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.uuid "cube_id"
  t.index ["cube_id"], name: "index_faces_on_cube_id"
end

正如您从上面的评论中看到的,id不是为Face模型自动生成的,而是期望在创建它们时输入(因为我们正在建模现实世界中已经具有唯一标识符的东西,我们想要使用而不是添加另一个 ID)。

当我尝试执行以下操作时,问题就来了

cube = Cube.new

cube.faces_attributes = [
  {
    id: "2bfc830b-fd42-43b9-a68e-b1d98e4c99e8"
  }
]

# Throws the following error
# ActiveRecord::RecordNotFound: Couldn't find Face with ID=2bfc830b-fd42-43b9-a68e-b1d98e4c99e8 for Cube with ID=

这对我来说意味着,由于我们正在传递一个id,Rails 期望这是对关联的更新Face,而不是传递一个Face要创建并与cube.

有没有办法让我禁用此默认行为accepts_nested_attributes_for,或者我是否必须为我的特定用例编写自定义代码?

4

1 回答 1

0

您需要将模型配置为使用非标准外键。

class Face < ApplicationRecord
  self.primary_key = 'uuid'
  belongs_to :cube, inverse_of: :faces, optional: true
  # your validation is for the wrong column.
  validates :uuid, presence: true, uniqueness: true
end

然而,在任何体面的数据库设计中,主键都是唯一的。这就是唯一标识符背后的全部意义!由于 NULL 不是唯一的,它也必须是 NOT NULL。这应该在数据库级别强制执行。不仅仅是应用级别的验证。

然而,一个更好的解决方案可能是坚持使用 Rails 命名外键id并将其类型更改为 UUID 而不是自动递增 id 的约定。这将大大减少每个模型所需的配置量。

于 2017-11-03T23:23:12.363 回答