我对Ruby和Rails都很陌生(使用2.3.8),所以请原谅我,如果我错过了一些非常明显的东西,但我一直在努力解决这个问题,而且我的搜索毫无结果。
在我的代码中,我有计划,计划有很多Plan_Steps。每个Plan_Step都有一个数字(表示'1st','2nd'等)。我有一个更新计划的表单,我需要验证每个Plan_Step都有一个唯一的编号。下面的代码可能会更好地解释设计:
模型/ plan.rb:
Class Plan < ActiveRecord::Base
has_many :plan_steps
accepts_nested_attributes_for :plan_steps, :allow_destroy => true
validate :validate_unique_step_numbers
# Require all steps to be a unique number
def validate_unique_step_numbers
step_numbers = []
plan_steps.each do |step|
#puts step.description
if !step.marked_for_destruction? && step_numbers.include?(step.number)
errors.add("Error Here")
elsif !step.marked_for_destruction?
step_numbers << step.number
end
end
end
控制器/ plans_controller.rb:
...
def update
@plan = Plan.find(params[:id])
if @plan.update_attributes(params[:plan])
#Success
else
#Fail
end
end
现在,当我的表单提交更新时,params哈希看起来像这样:
{"commit"=>"Submit",
"action"=>"update",
"_method"=>"put",
"authenticity_token"=>"NHUfDqRDFSFSFSFspaCuvi/WAAOFpg5AAANMre4x/uu8=",
"id"=>"1",
"plan"=>{
"name"=>"Plan Name",
"plan_steps_attributes"=>{
"0"=>{"number"=>"1", "id"=>"1", "_destroy"=>"0", "description"=>"one"},
"1"=>{"number"=>"2", "id"=>"3", "_destroy"=>"0", "description"=>"three"},
"2"=>{"id"=>"2", "_destroy"=>"1"}},
"controller"=>"plans"}
数据库包含Plan_Steps的条目,其中包含以下内容:
ID=1, Number=1, Description='one'
ID=2, Number=2, Description='two'
请注意,ID = 2存在且Number = 2,我要做的是删除ID = 2并创建一个Number = 2的新条目(ID = 3)。
好的,所以设置完毕,这是我的问题:
当我在验证中调用plan_steps时,它似乎是从数据库中提取值而不是从传递给update_attributes的params []数组中提取值。
例如,如果我在验证中取消注释'puts'行,我会看到数据库中存在的Plan_Steps描述,而不是传入参数中存在的描述。这意味着我无法验证传入的Plan_Steps。
我也无法在Plan_Steps模型中进行验证,因为除非我错了,否则将对数据库进行验证(而不是传入的参数)。
如果这是一个措辞不好的问题,我道歉,但这是相当具体的。如果您需要任何澄清,请询问。
请记住,我是一个菜鸟,所以我很容易犯一些非常愚蠢的错误。
答案 0 :(得分:1)
据我所知,您在模型中执行的任何验证都将查看数据库。如果要比较参数中的值,则需要在达到db验证之前执行此操作(完全不推荐)。此外,仅供将来参考,您可以使用内置的validates_uniqueness_of来实现验证:
validates_uniqueness_of :number, :scope => :plan_id
至于你最终想要完成什么(并且记住我对你的项目知之甚少,所以请你带着一点点盐),我建议计算一下步后端而不是依赖于用户输入。我会提出具体建议,但如果不知道如何收集“数字”值(拖放,手动输入,列表位置等等),很难说。
答案 1 :(得分:0)
433887,
我为你的问题写了一些测试,因为我不确定accept_nested_attributes是如何在内部工作的。如果在传递的参数中包含'id'属性,那么不存在的记录会被忽略,如果记录不存在,请参见下文。
#test/fixtures/plans.yml
only_plan:
id: 1
#test/fixtures/plan_steps.yml
one:
plan_id: 1
number: 1
description: one
two:
plan_id: 1
number: 2
description: two
#test/unit/plan_test.rb
require 'test_helper'
class PlanTest < ActiveSupport::TestCase
# These are just helpers I like to use so that Test::Unit gives good
# feedback as to which call you're testing.
def assert_to(assump, inst_sub, meth, *args )
assert_equal assump, instance_variable_get(inst_sub).send(meth, *args),
"#{inst_sub}.#{meth}(#{args.inspect}) should have been #{assump.inspect}"
end
def assert_chain(assump, inst_sub, *meths)
assert_equal( assump, meths.inject(instance_variable_get(inst_sub)) do |s,i|
s.send(*i)
end,
"#{inst_sub}.#{meths.join('.')} should have been #{assump.inspect}")
end
test "example given" do
assert_chain 2, :@only_plan, :plan_steps, :size
# attributes=, and then save() is
# an equivalent operation to update_attributes().
# I only split them here to show the marked_for_destruction? portion.
@only_plan.attributes= {
:plan_steps_attributes =>
{
"0"=>{"number"=>"1", "id"=>@one.id.to_s,
"_destroy"=>"0", "description"=>"one"},
"1"=>{"number"=>"2", "id"=>(@two.id + 1).to_s,
"_destroy"=>"0", "description"=>"three"},
"2"=>{"id"=>@two.id.to_s,
"_destroy"=>"1"},
}
}
#The validations of the _resulting_ affected records pass
assert_chain true, :@only_plan, :errors, :empty?
@two_in_plan_steps = @only_plan.plan_steps.detect{|x| x.id == @two.id}
assert_chain true, :@two_in_plan_steps, :marked_for_destruction?
#Three was ignored because of the id
assert_chain true, :@only_plan, :save
#The relevant records have been created and destroyed
@plan_step_set = @only_plan.reload.plan_steps.reload.map{|i|
[i.description, i.number]}
assert_chain true, :@two_in_plan_steps, :destroyed?
assert_to [['one', 1]], :@plan_step_set, :sort
#removing the id makes it appear correctly
assert_to( true, :@only_plan, :update_attributes, {
:plan_steps_attributes =>
{
"1"=>{"number"=>"2", "_destroy"=>"0", "description"=>"three"},
}
}
)
@plan_step_set = @only_plan.reload.plan_steps.reload.map{|i|
[i.description, i.number]}
assert_to [['one', 1], ['three', 2]], :@plan_step_set, :sort
end
end
当然,给定的测试数据实际上根本没有使用您的验证。
很难确切地说出您希望验证的内容。 “每个PlanStep都有一个数字(表示'1st','2nd'等)”似乎表明你可能正试图在db('1st','2nd'等等中存储plan_steps的序数比'1st','3rd',其他一些独特的数字。)标准很难使用,而且方便,易于生成。只要您放入数据库的'数字'将按正确顺序放置行,您可以通过遍历after_initialize回调中的plan_steps集合,或者通过向关联添加mysql hacks来为它们分配序数。
但是您的示例数据和代码似乎表明不是这样,因此我们无法给您提供任何可靠的建议。
您是否尝试让用户对某些元素进行重新排序,在这种情况下,您可能需要上面的序数解决方案而不对位置进行任何验证(只需要很好的默认值以便新的PlanSteps将自己置于列表的末尾)或者'数字的重要性和重要性稀疏?
在客户制作和使用这些PlanSteps时,客户应该看到哪些条件(如果有)?