我正在使用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,它与我的相似,但我想要一些更具伸缩性的东西,这样我就不必把它放在任何地方。
我即将制作一个模块,将为每个嵌入式关系添加一个clear_unsaved_#{relation}
方法,但这个想法让我感到沮丧,所以我想看看是否有人更好地了解如何做到这一点,以及哪里最好打电话给它。
我最终创建了一个猴子补丁,它覆盖了Mongoid的embeds_many
和embeds_one
类方法,还定义了一个实例方法,用于清除该关系的未保存文档。这对我来说是最直接的方式,因为它的代码很少,这意味着我不必记住包含它的位置。
# 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
您可以在setup_instance_methods!
和Mongoid::Association::Embedded::EmbedsMany
中修补Mongoid::Association::Embedded::EmbedsOne
方法,以包括设置实例方法以清除未保存的。你可以通过查看Mongoid::Association::Accessors#self.define_ids_setter!
找到一个Mongoid人如何做这种事情的例子。我建议你在我使用的解决方案中使用prepend
进行修补,这样你就可以继承方法的其余部分了。
Mongoid选择使用哪个类来实例化来自a constant called MACRO_MAPPING
in Mongoid::Association
的关联,所以你可以创建继承EmbedsMany
和EmbedsOne
的类,仅重写setup_instance_methods!
以添加所需的实例方法,然后你只需要修补MACRO_MAPPING
来映射到你的新类。
如果你是反猴子补丁,你可以使用我的DirtyTrackingEmbedded
模块中的代码来制作一个同样的东西的ActiveSupport::Concern
。您需要将重写的方法放在class_methods
块中,然后确保在将Mongoid::Document
包含在您想要的任何模型类中之后包含此模块。