Mongoid 删除未保存的嵌入文档

Mongoid remove unsaved embedded documents

我正在将 mongoid 用于用户是父文档的应用程序,几乎所有其他信息都嵌入在用户中。因此,例如,我的控制器 #new 对属于用户的 Relationship 的操作看起来像:

def new
  @relationship = current_user.relationships.new(friend_id: params[:fid])
  @relationship.validate
end

因为我 运行 对将显示在视图中的关系进行验证,其中一些验证需要能够引用父项,所以我不能只调用 @relationship = Relationship.new(friend_id: params[:fid]),但是在用户的关系数组中实例化了这种关系后,它现在就在那里闲逛,即使用户决定他们毕竟不想建立新的关系并且他们去了网站的另一部分。如果他们转到关系索引页面,他们会在列表中看到它,除非我将其过滤掉。

如果这种关系是有效的并且他们在其他地方做了一些导致用户保存的事情,那么这种虚拟关系现在是真实的。如果无效,则保存将因未知原因而失败。

我有很多模型打算嵌入到用户中,所以我对每个模型都会遇到这个问题。

我知道我可以调用 current_user.reload 来清除垃圾,但每次我想这样做时都必须访问数据库,这让我觉得很荒谬。我也可以在验证后孤立关系,但这感觉很老套。

在我看来,这是一个人们应该 运行 一直关注嵌入式文档的问题,所以我认为会有某种内置的解决方案,但我似乎无法在任何地方找到它。我看到了 this question,它和我的很相似,但我想要更可扩展的东西,这样我就不必把它放在各处。

我正准备制作一个模块,为每个嵌入关系向 class 添加一个 clear_unsaved_#{relation} 方法,但这个想法让我很沮丧,所以我想看看是否有人有更好地了解如何做,以及在哪里最好调用它。

我最终制作了一个猴子补丁,它覆盖了 Mongoid 的 embeds_manyembeds_one class 方法,还定义了一个用于清除该关系的未保存文档的实例方法。这对我来说是最直接的方式,因为它的代码很少,这意味着我不必记住将它包含在某些地方。

# config/initializers/patches/dirty_tracking_embedded.rb
module DirtyTrackingEmbedded
    # override the embedding methods to also make dirty-tracking
    def embeds_many(name, options= {}, &block)
        define_method "clear_unsaved_#{name}" do
            # remove_child removes it from the array without hitting the database
            send(name).each {|o| remove_child(o) unless o.persisted?}
        end
        super
    end

    def embeds_one(name, options={}, &block)
        define_method "clear_unsaved_#{name}" do
            dirty = send(name)
            remove_child(dirty) unless dirty.persisted?
        end
        super
    end
end

module Mongoid
    module Association
        module Macros
            module ClassMethods
                prepend DirtyTrackingEmbedded
            end
        end
    end
end

然后在我的控制器中我求助于 after_action:

# app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
    after_action :clear_unsaved, only: [:new]

    def new
        @relationship = current_user.relationships.new(friend_id: params[:fid])
        @relationship.validate
    end

    private
    def clear_unsaved
        current_user.clear_unsaved_relationships
    end
end

其他可能性

不同的猴子补丁

您可以猴子修补 Mongoid::Association::Embedded::EmbedsMany and Mongoid::Association::Embedded::EmbedsOne to include setting up an instance method to clear unsaved. You can find an example of how the Mongoid folks do that sort of thing by looking at Mongoid::Association::Accessors#self.define_ids_setter! 中的 setup_instance_methods! 方法。我建议像我使用的解决方案一样使用 prepend 进行修补,这样您就可以继承该方法的其余部分。

组合猴补丁和继承

Mongoid 选择使用哪个 class 实例化来自 a constant called MACRO_MAPPING in Mongoid::Association 的关联,因此您可以创建继承自 EmbedsManyEmbedsOne 的 classes只需覆盖 setup_instance_methods! 以添加所需的实例方法,那么您只需猴子补丁 MACRO_MAPPING 即可映射到新的 classes.

关注

如果你是反猴子补丁,你可以使用我的 DirtyTrackingEmbedded 模块中的代码来制作一个 ActiveSupport::Concern 来做同样的事情。您需要将覆盖的方法放在 class_methods 块中,然后确保在将 Mongoid::Document 包含在您想要的任何模型 class 中之后包含此模块。