在表单嵌套模型中销毁记录而不是更新

Destroy record instead of update in form nested model

我有一个模型 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

但是没用。

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

我认为您最好为此操作创建某种维护脚本。比如抽佣任务之类的。因此,您定期 运行 一个简单的查找和销毁:

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

只需每天或每周运行清理您的数据库,不要在保存时为记录操心。

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

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

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

有一种 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 属性,这将在您保存父级时销毁嵌套记录。如果记录没有持久化,当你保存父项时,它会被拒绝(忽略)。

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

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

validates_associated :opening_times

此代码将确保您的关联在 inserted/updated

之前得到验证

现在您可以在 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

我认为主要是它更加清晰易读。