破壊されたネストされたモデルレールのvalidates_uniqueness_of
-
03-10-2019 - |
質問
タスクのネストされた属性を受け入れるプロジェクトモデルがあります。
class Project < ActiveRecord::Base
has_many :tasks
accepts_nested_attributes_for :tasks, :allow_destroy => :true
end
class Task < ActiveRecord::Base
validates_uniqueness_of :name end
タスクモデルの一意性検証は、プロジェクトの更新中に問題をもたらします。
プロジェクトの編集では、タスクT1を削除し、同じ名前T1の新しいタスクを追加すると、一意性検証はプロジェクトの保存を制限します。
パラメーションハッシュは似ています
task_attributes => { {"id" =>
"1","name" => "T1", "_destroy" =>
"1"},{"name" => "T1"}}
タスクに関する検証は、古いタスクを破壊する前に行われます。したがって、検証は失敗しました。そのアイデアは、タスクが破壊されると見なされないように検証する方法ですか?
解決
アンドリュー・フランスはこれにパッチを作成しました スレッド, 、検証がメモリ内で行われます。
class Author
has_many :books
# Could easily be made a validation-style class method of course
validate :validate_unique_books
def validate_unique_books
validate_uniqueness_of_in_memory(
books, [:title, :isbn], 'Duplicate book.')
end
end
module ActiveRecord
class Base
# Validate that the the objects in +collection+ are unique
# when compared against all their non-blank +attrs+. If not
# add +message+ to the base errors.
def validate_uniqueness_of_in_memory(collection, attrs, message)
hashes = collection.inject({}) do |hash, record|
key = attrs.map {|a| record.send(a).to_s }.join
if key.blank? || record.marked_for_destruction?
key = record.object_id
end
hash[key] = record unless hash[key]
hash
end
if collection.length > hashes.length
self.errors.add_to_base(message)
end
end
end
end
他のヒント
私が理解しているように、私の場合は、メモリ内での検証に関するライナーのアプローチは実用的ではありません。私はたくさんの「本」、500K、成長しているからです。すべてをメモリに入れたいなら、それは大ヒットになるでしょう。
私が思いついた解決策は次のとおりです。
DB/Migrate/で移行ファイルに以下を追加することにより、レールが常に良い仕事をするとは限らないように、私の経験では、私が見つけたことは常に良い考えです)。
add_index :tasks [ :project_id, :name ], :unique => true
コントローラーでは、保存またはupdate_attributesをトランザクション内に配置し、データベースの例外を救出します。例えば、
def update
@project = Project.find(params[:id])
begin
transaction do
if @project.update_attributes(params[:project])
redirect_to(project_path(@project))
else
render(:action => :edit)
end
end
rescue
... we have an exception; make sure is a DB uniqueness violation
... go down params[:project] to see which item is the problem
... and add error to base
render( :action => :edit )
end
end
終わり
Rails 4.0.1の場合、この問題はこのプルリクエストによって修正されているとマークされています。 https://github.com/rails/rails/pull/10417
一意のフィールドインデックスを備えたテーブルがあり、破壊のレコードをマークし、一意のフィールドと同じ値の新しいレコードを作成する場合、Saveを呼び出すと、データベースレベルの一意のインデックスエラーがスローされます。
個人的には、これはまだ私にとってはうまくいきませんので、まだ完全に修正されているとは思いません。
レイナーブレッシングの答えは良いです。しかし、どのタスクが複製されているかをマークすることができればより良いです。
class Project < ActiveRecord::Base
has_many :tasks, inverse_of: :project
accepts_nested_attributes_for :tasks, :allow_destroy => :true
end
class Task < ActiveRecord::Base
belongs_to :project
validates_each :name do |record, attr, value|
record.errors.add attr, :taken if record.project.tasks.map(&:name).count(value) > 1
end
end
ref これ
使用してみませんか:スコープ
class Task < ActiveRecord::Base
validates_uniqueness_of :name, :scope=>'project_id'
end
これにより、各プロジェクトに対して一意のタスクが作成されます。