因为RoR不提供 validate_on_destroy ,所以我基本上是使用before_destroy
回调来实现的。
使用before_destory
可以阻止已删除effort_logged?
的项目。以下实现不起作用,因为当没有记录时我想删除项目及其所有依赖项。只要实现了before_destroy
,我就无法这样做。
如果我理解:dependent => :destroy
与before_destroy
相关的工作方式,则在调用父级before_destroy
方法之前删除相关子级。如果我的假设是正确的,那么访问effort_logged?
方法中的子项会以某种方式导致它们不被删除?有没有更好的方法来检查父母是否可以根据其子女被删除?
除了对RoR如何工作的好奇心,我的目标是通过以下两个测试:
鉴于下面列出的所有内容,我希望这两项测试都能通过。
项目模型
class Project < ActiveRecord::Base
has_many :project_phases, :dependent => :destroy
def before_destroy
if effort_logged?
errors.add_to_base("A project with effort logged cannot be deleted")
false
else
true
end
end
def effort_logged?
project_phases.each do |project_phase|
project_phase.deliverables.each do |deliverable|
if (deliverable.effort_logged?)
return true
end
end
end
end
end
项目阶段模型
class ProjectPhase < ActiveRecord::Base
belongs_to :project
has_many :deliverables, :dependent => :destroy
end
可交付模式
class Deliverable < ActiveRecord::Base
has_many :effort_logs, :dependent => :destroy
def effort_logged?
total_effort_logged != 0
end
def total_effort_logged
effort_logs.to_a.sum {|log| log.duration}
end
end
努力日志模型
class EffortLog < ActiveRecord::Base
belongs_to :deliverable
end
测试无法删除已记录的项目
test "cannot delete project with effort logged" do
project = projects(:ProjectOne)
assert !project.destroy, "#{project.errors.full_messages.to_sentence}"
end
在没有努力记录项目删除时删除依赖项
进行测试test "when no effort logged project deletion deletes dependents" do
project = projects(:ProjectNoEffort)
# all phases of the project
project_phases = project.project_phases
# all deliverables of all phases of the project
project_phases_deliverables = {}
# all effort logs of all deliverables of the project
deliverables_effort_logs = {}
project_phases.each do |project_phase|
project_phases_deliverables[project_phase.name + "-" + project_phase.id.to_s] =
project_phase.deliverables
end
project_phases_deliverables.each { |project_phase, project_phase_deliverables|
project_phase_deliverables.each do |deliverable|
deliverables_effort_logs[deliverable.name + "-" + deliverable.id.to_s] =
deliverable.effort_logs
end
}
project.destroy
assert_equal(0, project_phases.count,
"Project phases still exist for the deleted project")
project_phases_deliverables.each { |project_phase, project_phases_deliverables|
assert_equal(0, project_phases_deliverables.count,
"Deliverables still exist for the project phase \"" + project_phase + "\"")
}
deliverables_effort_logs.each { |deliverable, deliverables_effort_logs|
assert_equal(0, deliverables_effort_logs.count,
"Effort logs still exist for the deliverable \"" + deliverable + "\"")
}
end
答案 0 :(得分:4)
我发现了这个问题,因为我遇到了同样的问题。事实证明,回调的顺序很重要。在Rails中定义关系时,:dependent
选项实际上会在幕后创建回调。如果在关系之后定义before_destroy
回调,则在关系被销毁之后才会调用回调。
解决方案是更改回调的顺序,以便您首先定义任何回调取决于仍然存在的关系。关系定义应该在之后出现。
您的代码应该更像这样:
class Project < ActiveRecord::Base
# this must come BEFORE the call to has_many
before_destroy :ensure_no_effort_logged
# this must come AFTER the call to before_destroy
has_many :project_phases, :dependent => :destroy
# this can be placed anywhere in the class
def ensure_no_effort_logged
if effort_logged?
errors.add_to_base("A project with effort logged cannot be deleted")
false
else
true
end
end
end
答案 1 :(得分:0)
你是对的,孩子们在你到达之前就已经被消灭了。 它不优雅,但你可以这样做吗? : (顺便说一句,对不起,我没有对此进行测试。这是一个比其他任何事情更多的想法。)
before_destroy :ready_to_die?
ready_to_die?
会检查它是否具有零努力值。
如果是,请自行销毁。如果否,则引发异常(我的示例是EffortLogError)。
注意:如果你想手动销毁某些东西,你需要先将它归零。
然后在您的Project上有一个具有更具描述性名称的方法:
def carefully_destroy
Begin
Project.transaction do
self.destroy
end
rescue EffortLogError
self.errors.add_to_base("A Project with effort can't be deleted")
#do some sort of redirect to the right spot.
end
end
答案 2 :(得分:0)
尝试在将错误添加到before_destroy过滤器后立即添加引发ActiveRecord :: Rollback。
答案 3 :(得分:0)
在调试测试并密切关注我的方法和变量的值后,我能够确定effort_logged?
方法存在问题。当有记录的努力时,它将返回true。但是,当没有记录的努力时,它将返回project_phases
的数组。我修改了effort_logged?
以使用retval
并解决了问题。以下方法可以代表重构。
def effort_logged?
retval = false
project_phases.each do |project_phase|
project_phase.deliverables.each do |deliverable|
if (deliverable.effort_logged?)
retval = true
end
end
end
return retval
end