【问题标题】:Rails – saving existing record id instead of duplicating recordRails – 保存现有记录 ID 而不是复制记录
【发布时间】:2021-10-18 04:51:28
【问题描述】:

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

所以有一个 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" = $1 LIMIT $2  [["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 $1  [["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,这是Scaffolding 的一个基本的:

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 模型,并直接将 Category 模型与 Pet 关联?

【问题讨论】:

  • 您是否已经尝试使用first_or_initialize 方法?听起来很合适。
  • 感谢您的回复@SebastianPlasschaert 我还没有也不确定该方法,我该如何实现它?
  • 意见:accepts_nested_attributes_for 并不是所有用例的最佳解决方案,如果您正在执行多个级别或需要大量逻辑,则更好的选择最有可能为品种创建单独的控制器和类别,并使用 AJAX 动态创建嵌套记录。这使您可以应用单独的访问控制,因为您可能不信任所有用户来创建品种或类别,并且它可以让您获得更好的用户体验。例如,您可以在用户输入时使用自动完成功能来查找品种,这样会更有帮助,并且可以避免重复数据。
  • 然而这是一个相当高级的话题,你可能想回避这个功能,直到你有更多的 Rails 经验。
  • 非常感谢 Max 的建议,这正是我想要的,但是对于我正在做的这个项目,我还没有做到这一点。我实际上是在考虑放弃breed model,只使用category 作为一般动物类型,暂时不添加太多细节......

标签: ruby-on-rails ruby postgresql simple-form ruby-on-rails-6


【解决方案1】:

如果你想使用嵌套形式,你可以将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

【讨论】:

  • 感谢您的回复!我尝试将&lt;%= f.simple_fields_for :breed, Breed.new do |breed| %&gt; 更改为&lt;%= f.simple_fields_for @pet.breed do |breed| %&gt; 但出现此错误:在服务器终端上:“UNPERMITTED PARAMETER: :breed”和视图 被突出显示,NoMethodError in Pets#create, undefined method model_name' for nil:NilClass` 这是否意味着我需要在我的 Pet#create 控制器上添加一些东西?那会是什么?
  • @a-sh。试试&lt;%= f.simple_fields_for :breed, @pet.breed do |breed| %&gt;
  • 是的,我确实尝试过,但不幸的是,它仍然重复记录,但没有更多错误:-(
  • 好的,你试过&lt;%= f.simple_fields_for :breed do |breed| %&gt;@a-sh。
  • 好的,我知道这里发生了什么,您正在尝试使用现有的Breed 创建一个新的Pet。在这种情况下,当您初始化 @pet 时,请在控制器中执行 @pet.breed = existing_breed。 (我以为你在更新现有的宠物)
【解决方案2】:

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

首先,感谢@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 天后它终于起作用了。感谢所有为帮助做出贡献的人!真的很感激!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-12
    • 1970-01-01
    相关资源
    最近更新 更多