rails 覆盖嵌套属性更新

rails overwrite nested attributes update

我有这个模特人物

class Person
 generate_public_uid generator: PublicUid::Generators::HexStringSecureRandom.new(32)

 has_many :addresses, as: :resource, dependent: :destroy
 accepts_nested_attributes_for :addresses, allow_destroy: true, update_only: true,
                                        reject_if: proc { |attrs| attrs[:content].blank? }
end

在我个人 table 中,我有这个 public_id 是在创建个人时自动生成的。 现在添加地址中的嵌套属性工作正常。但更新与嵌套属性默认值不同。 我的目标是使用 public_id

更新地址
class Address
    generate_public_uid generator: PublicUid::Generators::HexStringSecureRandom.new(32)
    
    belongs_to :resource, polymorphic: true
end

这是我的地址模型

 { person: { name: 'Jack', addresses_attributes: { id: 1, content: 'new@gmail.com' } } }

这是关于如何更新嵌套属性中的记录的rails

 { person: { name: 'Jack', addresses_attributes: { public_id: XXXXXXXX, content: 'new@gmail.com' } } }

我想使用 public_id 来更新地址记录,但遗憾的是这不起作用不知道如何实现它?

因为您的参数中仍然需要一个 :id 字段,除非您想直接在模型中更改 to_param。尝试这样的事情:

person  = Person.first
address = person.address
person.update({ name: 'Jack', adddresses_attributes: { id: address.id, public_id: XXX, _destroy: true } } )

这就是我拥有嵌套属性的方式

#app/models/person.rb
class Person < ApplicationRecord
  ...
  has_many :addresses, dependent: :destroy
  accepts_nested_attributes_for :addresses, reject_if: :all_blank, allow_destroy: true
  ...
end

我的控制器

#app/controllers/people_controller.rb
class PeopleController < ApplicationController
   ...
   def update
     @person = Person.find_by(id: params[:id])
     if @person.update(person_params)
       redirect_to person_path, notice: 'Person was successfully added'
     else
       render :edit, notice: 'There was an error'
     end
   end
   ...
   private
   def person_params
     params.require(:person).permit(
        ... # list of person fields
        addresses_attributes: [
          :id,
          :_destroy,
          ... # list of address fields 
        ]
     )
   end
   ...
end

希望对您有所帮助

如果您需要更多帮助,请告诉我

Rails 通常假定您有一个名为 id 的列作为主键。虽然可以解决此问题,但 Rails 中和周围的许多工具都采用此默认设置 - 因此,如果您偏离此默认假设,您将会非常头疼。

但是,您并非被迫使用整数 id。正如其他人已经指出的那样,您可以更改 ID 的类型。事实上,您可以通过执行 id: type 来提供任何支持的类型,其中 type 可以是例如在你的情况下是 :string。这应该适用于大多数 Rails 的默认功能(包括嵌套属性)以及最常用的 gem。

既然您说您正在使用 public_id 作为主键,我假设您不介意删除当前编号的 ID。不使用自动递增编号键的主要优点是您不会公开显示记录创建增长和记录顺序。由于您使用的是 PostgreSQL,因此您可以使用 UUID is id 实现与当前 PublicUid::Generators::HexStringSecureRandom.new(32) 相同的目标(但格式不同)。

accepts_nested_attributes_for 使用主键(通常是 id)。通过使用 UUID 作为 id 列的数据类型,Rails 将自动使用它们。

我自己从未使用过此功能,所以我将使用 this article 作为参考。此解决方案不使用 public_uid gem,因此您可以将其从 Gemfile 中删除。

假设您从一个全新的应用程序开始,您的第一次迁移应该是:

bundle exec rails generate migration EnableExtensionPGCrypto

其中应包含:

def change
  enable_extension 'pgcrypto'
end

要为所有未来的 table 启用 UUID,请创建以下初始化程序:

# config/initializers/generators.rb
Rails.application.config.generators do |g|
  g.orm :active_record, primary_key_type: :uuid
end

通过上述设置更改,所有创建的 table 都应使用 UUID 作为 ID。请注意,对其他 table 的引用也应使用 UUID 类型,因为这是主键的类型。

您可能只想对某些 table 使用 UUID。在这种情况下,您不需要初始化程序并在 table 创建时显式传递主键类型。

def change
  create_table :people, id: :uuid, do |t|
    # explicitly set type uuid ^ if you don't use the initializer
    t.string :name, null: false
    t.timestamps
  end
end

如果您不是从全新的应用程序开始,事情会更加复杂。 确保在尝试此迁移时有数据库备份。这是一个示例(未经测试):

def up
  # update the primary key of a table
  rename_column :people, :id, :integer_id
  add_column :people, :id, :uuid, default: "gen_random_uuid()", null: false
  execute 'ALTER TABLE people DROP CONSTRAINT people_pkey'
  execute 'ALTER TABLE people ADD PRIMARY KEY (id)'

  # update all columns referencing the old id
  rename_column :addresses, :person_id, :person_integer_id
  add_reference :addresses, :people, type: :uuid, foreign_key: true, null: true # or false depending on requirements
  execute <<~SQL.squish
    UPDATE addresses
    SET person_id = (
      SELECT people.id
      FROM people
      WHERE people.integer_id = addresses.person_integer_id
    )
  SQL

  # Now remove the old columns. You might want to do this in a separate
  # migration to validate that all data is migrating correctly.
  remove_column :addresses, :person_integer_id
  remove_column :people, :integer_id
end

以上提供了一个示例场景,但最有可能是 extended/altered 以适合您的场景。


我建议阅读完整的 article,其中解释了一些额外的信息。