1

私は Ruby と Rails (2.3.8 を使用) の両方にかなり慣れていないので、ここで明らかな何かが欠けている場合はご容赦ください。

私のコードにはプランがあり、プランには多くの Plan_Steps があります。各 Plan_Step には番号があります (「1st」、「2nd」などを示すため)。Plan を更新するためのフォームがあり、各 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) を作成することです。

OK、その設定で、ここに私の問題があります:

検証で plan_steps を呼び出すと、update_attributes に渡された params[] 配列からではなく、データベースから値を取得しているように見えます。

たとえば、検証で「puts」行のコメントを外すと、Plan_Steps の説明は、渡されたパラメーターから存在するものとしてではなく、データベース内に存在するものとして表示されます。これは、受信した Plan_Steps を検証できないことを意味します。

Plan_Steps モデルで検証を行うこともできません。間違っていない限り、検証はデータベースに対して行われるためです (渡されたパラメーターではありません)。

これが言葉遣いの悪い質問である場合は申し訳ありませんが、かなり具体的です。説明が必要な場合は、お尋ねください。

そして、覚えておいてください、私は初心者なので、本当にばかげた間違いを簡単に犯す可能性があります.

4

2 に答える 2

1

私の知る限り、モデルで実行する検証はすべてデータベースを調べます。パラメータの値を比較したい場合は、データベースの検証に到達する前に比較する必要があります (まったくお勧めしません)。また、将来の参考のために、次のように組み込みの validates_uniqueness_of を使用して検証を行うことができます。

validates_uniqueness_of :number, :scope => :plan_id

最終的に何を達成しようとしているのかについては(私はあなたのプロジェクトについてあまり知らないので、これは大まかに考えてください)、背面のステップ位置を計算することをお勧めします-ユーザー入力に頼るのではなく終了します。具体的な提案をしたいと思いますが、「数値」の値を収集する方法 (ドラッグ/ドロップ、手動入力、リストの場所など) を知らずに言うのは難しいです。

于 2010-09-02T07:32:17.513 に答える
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」などを示すため)」は、データベースにplan_stepsの序数を保存しようとしている可能性があることを示しているようです(「1st」、「2nd」などではなく'1st'、'3rd'、その他の一意の番号よりも。) 序数は扱うのが難しく、便利なことに、簡単に生成できます。データベースに入れている「数値」によって行が正しい順序で配置される限り、after_initialize コールバックで plan_steps のセットをウォークスルーするか、関連付けにmysql ハックを追加することで、序数を割り当てることができます。

しかし、あなたの例のデータとコードはそうではないことを示しているようです.

ユーザーがいくつかの要素を並べ替えられるようにしようとしていますか? その場合、おそらく位置の検証なしで上記の序数のソリューションが必要です (新しい PlanSteps がリストの最後に配置されるように、適切なデフォルトが必要です)。重要で重要なことにまばらですか?

これらの PlanSteps を作成および使用しているときに、顧客がエラーを表示する必要がある場合は、どのような状況でしょうか?

于 2010-12-01T08:52:46.903 に答える