2010-05-05 17 views
17

Tôi có một mô hình Dự án chấp nhận các thuộc tính lồng nhau cho Tác vụ.validates_uniqueness_of trong đường ray mô hình lồng nhau bị phá hủy

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 

Xác thực tính duy nhất trong Mô hình nhiệm vụ đưa ra sự cố khi cập nhật Dự án.

Khi chỉnh sửa dự án, tôi xóa một nhiệm vụ T1 và sau đó thêm một nhiệm vụ mới có cùng tên T1, xác thực tính duy nhất hạn chế việc lưu dự án.

params băm nhìn cái gì đó như

task_attributes => { {"id" => 
"1","name" => "T1", "_destroy" => 
"1"},{"name" => "T1"}} 

Validation vào nhiệm vụ được thực hiện trước khi phá hủy công việc cũ. Do đó xác nhận không thành công. Bất kỳ ý tưởng làm thế nào để xác nhận như vậy mà nó không xem xét nhiệm vụ bị phá hủy?

+0

Chỉ tò mò Tại sao bạn không cập nhật tác vụ cũ thay vì xóa cũ và tạo nhiệm vụ mới có cùng tên. – Salil

+0

Bạn có nghĩa là tôi cần phải đi qua nhiệm vụ cũ và kiểm tra xem có bất kỳ nhiệm vụ cũ với tên giống như nhiệm vụ mới nhưng đó là đánh dấu để bị phá hủy và sau đó chỉ cần cập nhật nhiệm vụ cũ? – arun

+0

Arun ... đây chỉ là một trường hợp thử nghiệm (thêm một nhiệm vụ có cùng tên với tác vụ khác mà bạn đang xóa) hoặc bạn đang làm điều này trên mọi nghĩa sửa, xóa nhiệm vụ và tạo lại chúng. – concept47

Trả lời

13

Andrew France đã tạo bản vá trong thread, nơi xác thực được thực hiện trong bộ nhớ.

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 
+1

Cảm ơn RainerB, điều này giải quyết được vấn đề của tôi. – arun

+3

add_to_base không được dùng nữa và không khả dụng trong 3.1. Sử dụng self.errors.add (: base, message) –

+1

Heh, tôi nhận được một email từ một người nào đó nói rằng họ đã tìm được cách giải quyết của tôi thông qua StackOverflow. Rất vui được biết nó đã giúp mọi người. Có lẽ tôi nên cập nhật nó cho Rails 3 vì thực hiện đó sẽ được coi là rất xấu ngày nay! –

-3

Ref this

Tại sao bạn không sử dụng: phạm vi

class Task < ActiveRecord::Base 
    validates_uniqueness_of :name, :scope=>'project_id' 
end 

này sẽ tạo ra tác duy nhất cho mỗi dự án.

+0

scope => 'project_id' sẽ hoạt động cho các id dự án khác nhau, nhưng trong trường hợp trên project_id là giống nhau. Tôi cần phải hủy một nhiệm vụ rồi thêm một nhiệm vụ mới có cùng tên cho một dự án. – arun

+0

bạn vui lòng dán mã của bạn vào đây để mã có thể giúp bạn. – Salil

+0

Trong điều khiển def cập nhật @project = Project.find (params [: id]) @ project.update_attributes (params [: Dự án]) cuối đang mẫu là giống như tôi đã đề cập above.I đã làm chính xác tương tự như mô tả của Ryan Bates trong tập Railscasts 197 – arun

1

Theo tôi được biết, cách tiếp cận Reiner về việc chứng thực trong bộ nhớ sẽ không được thực tế trong trường hợp của tôi, như tôi đã có rất nhiều "sách", 500K và phát triển. Đó sẽ là một hit lớn nếu bạn muốn đưa tất cả vào bộ nhớ.

Các giải pháp tôi đưa ra là:

Đặt điều kiện độc đáo trong cơ sở dữ liệu (mà tôi đã tìm thấy luôn luôn là một ý tưởng tốt, như trong Rails kinh nghiệm của tôi không phải lúc nào làm tốt công việc ở đây) bằng cách thêm dòng sau vào tập tin chuyển đổi của bạn trong db/di chuyển /:

add_index :tasks [ :project_id, :name ], :unique => true 

trong bộ điều khiển, đặt lưu hoặc update_attributes bên trong một giao dịch, và giải cứu các ngoại lệ cơ sở dữ liệu. Ví dụ:

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 

cuối

+0

Cách tiếp cận tuyệt vời! Câu trả lời này cung cấp giải pháp tốt nhất, imo. Mạnh mẽ, nhanh chóng, không an toàn và đáng tin cậy. Chỉ mục cơ sở dữ liệu không thể nói dối. ;) – mycargus

1

Đối với Rails 4.0.1, vấn đề này được đánh dấu là bị cố định theo yêu cầu kéo này, https://github.com/rails/rails/pull/10417

Nếu bạn có một bảng với một chỉ số lĩnh vực độc đáo, và bạn đánh dấu kỷ lục để hủy và bạn tạo bản ghi mới có cùng giá trị với trường duy nhất , sau đó khi bạn gọi lưu, chỉ số duy nhất cấp cơ sở dữ liệu sẽ bị ném.

Cá nhân điều này vẫn không hiệu quả đối với tôi, vì vậy tôi chưa nghĩ rằng nó hoàn toàn được sửa.

1

Câu trả lời của Rainer Blessing rất tốt. Nhưng sẽ tốt hơn khi chúng ta có thể đánh dấu nhiệm vụ nào được nhân đôi.

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