Rails – 保存现有记录 ID 而不是复制记录

Rails – saving existing record id instead of duplicating record

我现在正在学习Rails,我完全是个新手。希望能得到这里Rails专家的帮助。我试图通过 Whosebug 进行搜索,但未能找到解决方案。

所以有一个 Pet 模型有 Breed 模型作为参考,它参考了 Category 模型,所以 3 个模型:

class Pet < ApplicationRecord
  belongs_to :breed
  accepts_nested_attributes_for :breed
  has_one :category, through: :breed

class Breed < ApplicationRecord
  belongs_to :category
  has_many :pets, dependent: :destroy
  accepts_nested_attributes_for :category

class Category < ApplicationRecord
  has_many :breeds, dependent: :destroy
  has_many :pets, through: :breed

我已经使用 simple_form 设置了表格。创建新宠物时,用户可以 selectBreed 关联的现有 Category 选项。然后在表单中,将 breed.name 属性设置为用户可以输入自己的 text_field。请在下面的嵌套属性部分:

...
<!-- Nested Breed & Category Attributes -->
<%= f.simple_fields_for :breed, Breed.new do |breed| %>
  <%= breed.error_notification %>
  <%= breed.error_notification message: breed.object.errors[:base].to_sentence if breed.object.errors[:base].present? %>
  <div class="form-row">

    <div class="form-group col-md-4">
      <%= breed.association :category, collection: Category.order(:id),
                                       label: "Animal Category", required: true %>
    </div>

    <div class="form-group col-md-8">
      <%= breed.input :name, required: true,
                             label: "Breed", placeholder: "Domestic short hair",
                             hint: "Type 'unknown', if unsure", class: "form-control" %>
    </div>
  </div>
<% end %>

所以我的问题是,如何使用现有 breed_id 记录保存新宠物而不用不同的 breed_id 复制它?

所以目前,我已经有一个 breed_id : 1 的现有记录,它由 category_id: 1 (for Cat), and name: 'domestic short hair' 组成 – 如果我在表格中创建一个具有这些相同值的新宠物,它将复制记录,它应该保存 breed_id: 1,但相反,新宠物保存在 breed_id: 15 ...

当我查看 console 时:

irb(main):001:0> Breed.find_by_name("domestic short hair")
  Breed Load (6.8ms)  SELECT "breeds".* FROM "breeds" WHERE "breeds"."name" =  LIMIT   [["name", "domestic short hair"], ["LIMIT", 1]]
=> #<Breed id: 1, name: "domestic short hair", category_id: 1, created_at: "2021-08-14 13:12:41.266154000 +0000", updated_at: "2021-08-14 13:12:41.266154000 +0000">
irb(main):002:0> Breed.last
  Breed Load (8.2ms)  SELECT "breeds".* FROM "breeds" ORDER BY "breeds"."id" DESC LIMIT   [["LIMIT", 1]]
=> #<Breed id: 15, name: "domestic short hair", category_id: 1, created_at: "2021-08-16 04:08:31.602249000 +0000", updated_at: "2021-08-16 04:08:31.602249000 +0000">

更新我的 controller / model / form helper 的最佳方法是什么(我不确定是哪一种),这样可以防止重复?我目前没有 breeds_controller,只使用 pets_controller,这是脚手架中的一个基本的:

pets_controller.rb

  def show
  end

  # GET /pets/new
  def new
    @pet = Pet.new
    @pet.build_breed
  end
  
  # GET /pets/1/edit
  def edit
  end
  
  # POST /pets or /pets.json
  def create
    @pet = current_user.pets.new(pet_params)

    respond_to do |format|
      if @pet.save
        format.html { redirect_to @pet, notice: "Pet was successfully added." }
        format.json { render :show, status: :created, location: @pet }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @pet.errors, status: :unprocessable_entity }
      end
    end
  end

...

  private

  def pet_params
   params.require(:pet).permit(:owner_id, :name, :dob, :gender, :bio, :instagram,
                                breed_attributes: [:name, :category_id])
  end

我试过在模型breed.db中写一个before_save :existing_breed_record,我认为这是错误的...

class Breed < ApplicationRecord
  belongs_to :category
  has_many :pets, dependent: :destroy 
  accepts_nested_attributes_for :category
  before_save :existing_breed_record

  private

    def existing_breed_record
     if self.find_by(category_id, name).exists?
      return self.id
    end
end

这是我的 schema.db

  create_table "breeds", force: :cascade do |t|
    t.string "name"
    t.bigint "category_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["category_id"], name: "index_breeds_on_category_id"
  end

  create_table "categories", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "pets", force: :cascade do |t|
    t.bigint "owner_id", null: false
    t.bigint "breed_id", null: false
    t.string "name"
    t.date "dob"
    t.integer "gender"
    t.string "bio"
    t.string "instagram"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["breed_id"], name: "index_pets_on_breed_id"
    t.index ["owner_id"], name: "index_pets_on_owner_id"
  end

我从指南中了解到,我认为有 find_or_create_by 方法是我需要的,但我不确定在哪里以及如何实施它...

对于数据库和Rails专家来说,为了学习,你建议我下次如何做更好?我是否应该将 breed 作为 Pet 模型的字符串 attribute/column 并直接将 Category 模型关联到 Pet?

如果您想使用嵌套形式,可以将 before_save 方法添加到 Pet

\pet.rb

before_save :check_existing_breed


def check_existing_breed
  if self.breed&.name and breed = Breed.find_by(name: self.breed&.name)
    self.breed = breed
  end
end

在老师的帮助下,我们终于找到了解决办法。它有点乱,但它不再重复任何现有记录!

首先,感谢@Yunwei.W,按照建议,我在我的嵌套表单中添加了 @pet.breed(之前是 :breed, Pet.new do ...:

...
<!-- Nested Breed & Category Attributes -->
<%= f.simple_fields_for :breed, @pet.breed do |breed| %>
...

然后在 pets_controller.rb:

def create
 # Preventing duplicates when breed name mathes exsiting record
 breed = Breed.find_by_name(params[:pet][:breed_attributes][:name]) || Breed.create(name: params[:pet][:breed_attributes][:name], category_id: params[:pet][:breed_attributes][:category_id])

 @pet = current_user.pets.new(pet_params.except(params[:pet][:breed_attributes][:name]))

 @pet.breed = breed

...

end

肯定有比这更好的解决方案,但我很高兴在使用此拦截器 3 天后它终于起作用了。感谢所有为帮助做出贡献的人!真的很感谢!