【问题标题】:Enforcing cascading deletes Across a has_many through association通过关联在 has_many 中强制级联删除
【发布时间】:2017-10-12 18:19:13
【问题描述】:

我搜索过 Stackoverflow 和其他地方,但无法解决这个问题。

问题:当尝试通过 Rails 销毁操作销毁实例变量时,我收到这篇文章标题中的错误消息。

相关代码:

class Company < ApplicationRecord
  has_many :describes, dependent: :destroy
  has_many :descriptors, through: :describes, source: :metadatum
end

class Metadatum < ApplicationRecord
  has_many :describes, dependent: :destroy
  has_many :descriptees, through: :describes, source: :company

  ...
end

class Describe < ApplicationRecord
  belongs_to :company
  belongs_to :metadatum
end

class CompaniesController < ApplicationController
  ...

  def destroy
    @company = Company.find(params[:id])
    @company.destroy
    redirect_to companies_url
  end

  ...
end

ActiveRecord::Schema.define(version: <some version #) do

  create_table "companies", force: :cascade do |t|
    t.string   "name"
    t.string   "description"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.index ["name"], name: "index_companies_on_name", unique: true
  end

  create_table "describes", id: false, force: :cascade do |t|
    t.integer  "company_id"
    t.integer  "metadatum_id"
    t.datetime "created_at",   null: false
    t.datetime "updated_at",   null: false
    t.index ["company_id"], name: "index_describes_on_company_id"
    t.index ["metadatum_id"], name: "index_describes_on_metadatum_id"
  end

  create_table "metadata", force: :cascade do |t|
    t.string   "name"
    t.string   "description"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.index ["name"], name: "index_metadata_on_name", unique: true
  end

end

class CreateCompanies < ActiveRecord::Migration[5.0]
  def change
    create_table :companies do |t|
      t.string :name
      t.string :description

      t.timestamps
    end
    add_index :companies, :name, unique: true
  end
end

class CreateMetadata < ActiveRecord::Migration[5.0]
  def change
    create_table :metadata do |t|
      t.string :name
      t.string :description

      t.timestamps
    end
    add_index :metadata, :name, unique: true
  end
end

class CreateDescribes < ActiveRecord::Migration[5.0]
  def change
    create_table :describes, id: false do |t|
      t.references :company, foreign_key: true
      t.references :metadatum, foreign_key: true

      t.timestamps
    end
  end
end

“CreateDescribes”迁移文件将“create_table”“id”选项设置为“false”,因为我永远不需要直接访问“describes”连接表。我找到了一个 Stackoverflow 帖子(链接让我无法理解),其中建议执行级联删除操作要求连接表条目具有唯一标识符。传统的 Rails 强制唯一数据库表记录的方法是拥有一个默认名为“id”的主键。我尝试通过删除“CreateDescribes”迁移文件中的“id: false”键/值对并确保 schema.rb 文件中的“describes”表在重新运行“db:migrate”后反映这一点来实施此建议。

不幸的是,这种方法产生了同样的错误。 Rails 服务器日志将 'CompaniesController' 'destroy' 操作中的以下代码行标识为此错误的来源:

@company.destroy

生成的错误信息是:

undefined method `to_sym' for nil:NilClass Did you mean? to_s

当我创建一个新的 'Company' 对象并将其对应的记录保存到数据库时,我确认了它的存在,例如,通过交叉检查 'params' 哈希中的 'id' 值与 'id' 字段在数据库表的记录中。

“CompaniesController”“创建”操作通过公司和元数据模型之间的关联,通过 has_many 将一组元数据对象关联到新的公司对象。

def create
  @company = Company.new(company_attributes)

  params[:metadata][:ids].each do |m|
    if !m.empty?
      @company.descriptors << Metadatum.find(m)
    end
  end

  if @company.save

  ...
end

我确认关联已被捕获。

有趣的是,当我没有将 Metadatum 对象与新的 Company 对象关联时,我稍后能够成功地销毁 Company 对象。只有当我将 Metadatum 对象关联到 Company 对象时,我以后才会遇到此错误。

该错误表明在 nil 类上尝试了销毁操作。当我确认被销毁的公司对象存在时,它不能为零。识别为 nil 类的 Rails Server 日志是什么?更重要的是,为什么对具有关联 Metadatum 对象的 Company 对象的破坏操作不会在适当的“描述”连接表上传播强制级联删除?

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

    我在其他地方收到的对这个问题的回复导致了解决方案。首先,我需要在 'Company' 和 'Metadatum' 模型中的 'has_many through' 关联中添加 'dependent: :destroy' 约束。

    class Company < ApplicationRecord
      has_many :describes, dependent: :destroy
      has_many :descriptors, through: :describes, source: :metadatum, dependent: :destroy
    ...
    
    class Metadatum < ApplicationRecord
      has_many :describes, dependent: :destroy
      has_many :descriptees, through: :describes, source: :company, dependent: :destroy
    ...
    

    其次,我需要坚持允许“create_table”方法自动生成“id”主键的 Rails 约定。因此,对于数据库模式中的“描述”表,我实现了以下步骤:

    步骤 01:从相应的迁移文件中删除“id: false”约束。

    class CreateDescribes < ActiveRecord::Migration[5.0]
      def change
        # create_table :describes, id: false do |t|
        create_table :describes do |t|
          t.references :company, foreign_key: true
          t.references :metadatum, foreign_key: true
    
          t.timestamps
        end
      end
    end
    

    步骤02:重建生成的数据库,然后重新运行迁移。

    rails db:reset
    rails db:migrate
    

    警告:'rails db:reset' 清除数据库。由于我在数据库中植入了测试数据,所以目前我并不关心这个。但是,如果您进一步进行开发并且单独的“seed.rb”文件不足以重新创建您的测试状态,它确实会出现问题。

    现在,'schema.rb' 中的 'describes' 表可以正确读取:

    create_table "describes", force: :cascade do |t|
      t.integer  "company_id"
      t.integer  "metadatum_id"
      t.datetime "created_at",   null: false
      t.datetime "updated_at",   null: false
      t.index ["company_id"], name: "index_describes_on_company_id"
      t.index ["metadatum_id"], name: "index_describes_on_metadatum_id"
    end
    

    在测试应用程序时,会发生级联删除,并删除相应的“描述”表记录。

    【讨论】:

      猜你喜欢
      • 2012-05-13
      • 2012-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多