簡體   English   中英

在 Ruby on Rails 中,如何保存許多引用同一類的其他實例的 ActiveRecord 對象?

[英]In Ruby on Rails, how do I save many ActiveRecord objects that have references to other instances of the same class?

抽象地思考這個有點棘手,所以這里有一個例子:

模式文件

create_table "comments", force: :cascade do |t|
  t.string "text"
  t.bigint "post_id"
  t.bigint "author_id"
  t.bigint "parent_id"
  t.index ["parent_id"], name: "index_comments_on_parent_id"
end

評論.rb

class Comment < ApplicationRecord
  belongs_to :parent, class_name: 'Comment'
  belongs_to :author, class_name: 'User'
  belongs_to :post
end

評論控制器.rb

def update
  # params contains data for a chain of comments, each the child of the preceding one
  comment_data = params["_json"]
  comments = []
  comment_data.each do |cd|
    com = Comment.new(text: cd["text"], post_id: cd["post_id"], author_id: cd["user_id"])
    com.parent = comments.last
    comments.push(com)
  end

  comments.each { |com| com.save }

end

然后我嘗試通過 HTTP post 請求發送一些東西:

> POST /comments HTTP/1.1
> Host: mywebsitebackend.net
> Content-Type: application/json

| [
|   {
|      "post_id" : 1,
|      "user_id" : 99,
|      "text" : "You suck!!!!!"
|   },
|   {
|      "post_id" : 1,
|      "user_id" : 1,
|      "text" : "Be respectful, or I'll block you."
|   },
|   {
|      "post_id" : 1,
|      "user_id" : 99,
|      "text" : "Typical *******, doesn't belive in free speech."
|   }
| ]

我期望的是:將新評論保存到基礎數據庫中,並正確引用結構(即,第一條評論的父級為零,其他兩條評論則為前一條)。

我得到的是:第二條評論被保存到數據庫中,但沒有父 ID。 第三條評論已正確保存。 第一個完全丟失。

即使我手動檢查參考鏈並確保以正確的順序保存所有內容,我什至不知道如何獲得我需要的東西; 這個例子應該有效,因為首先保存了無父注釋。 我也不想深入研究:如果底層結構更復雜,每個評論有多個評論鏈和多個子級,它會變得非常復雜。 此外,Ruby和ActiveRecord不應該抽象出這樣的東西嗎?

那么我做錯了什么,當創建了同一個類的多個新 ActiveRecord 對象並且其中一些對象相互引用時,如何正確保存數據?

Rails 版本:5.1.7

操作系統:macOS 10.15.7 (Catalina)

數據庫:PostgreSQL 12.4

您所描述的稱為自引用關聯或聯接。

要設置自引用關聯,您只需創建一個指向同一個表的可為空的外鍵列:

class CreateObservations < ActiveRecord::Migration[6.0]
  def change
    add_reference :comments, :parent, 
      null: true,
      foreign_key: { to_table: :comments }
  end
end

還有一個指向同一個類的關聯:

class Comment < ApplicationRecord
  belongs_to :parent, 
             class_name: 'Comment',
             optional: true,
             inverse_of: :children
             
  has_many :children,
             class_name: 'Comment',
             foreign_key: 'parent_id',
             inverse_of: :parent
end

如果您不將列設置為可空,並且belongs_to關聯是可選的,您最終會遇到雞與蛋的情況,在這種情況下您實際上無法在表中插入第一條記錄,因為它沒有任何可引用的內容。

如果要同時創建記錄和子項,請使用accepts_nested_attributes

class Comment < ApplicationRecord
  belongs_to :parent, 
             class_name: 'Comment',
             optional: true,
             inverse_of: :children
             
  has_many :children,
             class_name: 'Comment',
             foreign_key: 'parent_id',
             inverse_of: :parent

  accepts_nested_attributes_for :children
end

這使您可以使用以下內容創建 reddit 樣式的評論線程:

Comment.create!(
  text: "You suck!!!!!", 
  user_id: 99, 
  children_attributes: [
    { 
      text: "Be respectful, or I'll block you.", 
      user_id: 1,
      children_attributes: [
        {
          text: "Typical *******, doesn't belive in free speech.",
          user_id: 99
        }
      ]
    }
  ]
])

因為它會自動處理遞歸。 請參閱有關如何 為嵌套屬性創建表單以及如何在控制器中將它們列入白名單的指南。 如果嵌套屬性包含 id,則嵌套記錄將被更新而不是創建新記錄。

處理整個 post_id 問題可以通過不同的方式完成:

  • 使用回調從父評論設置帖子。 這是我最不喜歡的。
  • 使列可以為空並通過向上遍歷樹來獲取父項。
  • 改用多態關聯,以便評論可以屬於評論或帖子。 通過遍歷樹獲取原始帖子。

問題似乎是您只是在之后保存評論。 通常 Rails 只在記錄保存后生成 id,所以當你在保存之前分配 parent_id 時它仍然是 nil。 您可以在交易中添加所有這些,只是為了確定。 像這樣的東西:

def update
  # params contains data for a chain of comments, each the child of the preceding one
  comment_data = params["_json"]
  comments = []
  Comment.transaction do
    comment_data.each do |cd|
      com = Comment.new(text: cd["text"], post_id: cd["post_id"], author_id: cd["user_id"])
      com.parent = comments.last if comments.present?
      com.save!
      comments.push(com)
    end
  end
end

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM