【问题标题】:Destroy record instead of update in form nested model在表单嵌套模型中销毁记录而不是更新
【发布时间】:2019-02-18 23:50:36
【问题描述】:

我有一个模型opening_times,它记录了商店的营业时间。

create_table "opening_times", force: :cascade do |t|
    t.string "day"
    t.time "morning"
    t.time "evening"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.bigint "shop_id"
    t.index ["shop_id"]
  end

此模型仅通过shops 控制器的edit 操作中的嵌套表单进行更新。我没有opening_times 控制器。

那么基本上我能做的非常有限:update 操作。

虽然我有一个问题:当过去有特定日期的开放时间时,比如说星期二,并且用户想让那一天不工作,用户将两个字段 morningevening 都设为空白。

我可以在数据库中保存空值,但最好实际删除星期二的记录。

然后在模型文件中我设置了这个:

before_save :delete_records_with_missing_hours

private 

def delete_records_with_missing_hours
    if self.morning.blank? or self.evening.blank?
        self.destroy
    end
end

但它不起作用。

有没有办法删除打算在模型级别更新的记录?

【问题讨论】:

    标签: ruby-on-rails activerecord ruby-on-rails-5 nested-forms


    【解决方案1】:

    有一种 Rails 方式可以处理这个问题。它可以很容易地完成,但您需要从您的 Shop 模型中完成。在该模型中插入:

    class Shop < ApplicationRecord
      accepts_nested_attributes_for :opening_times, allow_destroy: true, reject_if: :reject_opening_time?
    
      def reject_opening_time?(attributes)
        persisted = attributes[:id].present?
        time_values = attributes.slice(:morning, :evening).values
        without_time = time_values.any?(&:blank?)
        attributes.merge!(_destroy: true) if persisted and without_time
        without_time && !persisted # Return false so as to reject new opening_time if any time attributes are empty
      end
    end
    

    现在对于每个opening_time 嵌套记录,Rails 将评估时间属性。如果任何时间值为空白,它将适当地处理记录。如果记录是持久化的,它会添加一个_destroy属性,当你保存父级时会破坏嵌套的记录。如果记录没有被持久化,当你保存父级时它会被拒绝(忽略)。

    【讨论】:

    • 非常感谢。这正是我所需要的。我不明白合并的第四行,因为它对我来说是非常复杂的语法..但我会挖掘一下。
    • 这真的是rails方式吗?我认为不应该使用reject_if 来操纵记录属性。此外,如果您将多个模型添加到 accept_nested_attributes_for 方法,它将失败。它应该与模型无关。另外,我觉得这不是 Shop 模型的任务,因为它涉及不同的模型。可能这应该已经在视图中修复了,方法是在删除开放时间的记录上设置_destroy:true
    • @bo-oz 此方法有效,它使用包含的 Rails 工具,并且不需要 cron 作业。在视图中设置_destroy: true 似乎适得其反,因为任何提交嵌套属性的客户端都需要复制逻辑。添加另一个嵌套模型可以(并且应该)使用单独的 accepts_nested_attributes_for 行来完成。我愿意在父模型或子模型的回调中设置_destroy: true,但尚未测试过什么会起作用。
    【解决方案2】:

    我认为您最好为此操作创建某种维护脚本。像 rake 任务什么的。因此,您会定期运行一个简单的查找和销毁:

    OpeningTime.where(morning:nil, evening:nil).destroy_all
    

    只需每天或每周运行一次即可清理您的数据库,并且在保存时不要打扰记录。

    我认为您当前代码的问题是您正在删除 before_save 中的记录,因此这会产生两个问题:

    1/ 如果这不是一个持久化记录,而是一个新记录,rails 应该怎么做? Destroy 会失败,并且整个事务将回滚(看看你的控制台)

    2/ 即使记录被持久化,删除它也会起作用,但接下来执行的保存操作会失败,也会导致回滚。

    【讨论】:

    • 感谢您解释为什么这会失败,它实际上每次都会回滚。我的解决方案是遍历商店模型的(最多)7 个孩子,并检查带有空的 morningevening 字段的记录,然后从控制器中将其杀死。但这似乎非常多余和丑陋。 @moveson 实际上已经在另一个答案中给了我解决方案(需要在父级中完成所有这些操作)
    【解决方案3】:

    如果您不想让morningevening 具有空值的字段,为什么不验证这些字段的存在呢?使用外部方法来验证这种情况会更清晰。

    首先,你应该把它放在Shop 模型中:

    validates_associated :opening_times
    

    此代码将确保您的关联在插入/更新之前得到验证

    现在您可以在OpeningTime 模型中进行验证,如下所示:

    validates :morning, :evening, presence: true
    

    如果你只是想在更新操作中这样做,你甚至可以这样做:

    validates :morning, :evening, presence: true, on: :update
    

    如果您希望至少存在一个值,您还可以使用:

    validate :morning_and_evening_validation
    
    def morning_and_evening_validation
      morning.present? || evening.present?
    end
    

    我认为大多数情况下它更清洁和可读。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-16
      • 1970-01-01
      • 2020-07-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多