2014-10-18 12 views
8

Tôi đã bật khóa lạc quan trên Mô hình người dùng của mình để xử lý các xung đột có thể xảy ra ở các phần khác nhau của codebase của tôi. Tuy nhiên, tôi đang trải qua một cuộc xung đột bất ngờ và tôi không biết làm thế nào để xử lý nó bởi vì tôi không biết những gì gây ra nó.Tiếp nhận "Đã cố gắng cập nhật đối tượng cũ: Người dùng" ngoại lệ khi cập nhật current_user trước khi đăng xuất

Tôi đang sử dụng đá quý lập mưu để xác thực và tôi đang sử dụng phương pháp before_logout để thiết lập lại một mã thông báo an ninh ...

class SessionsController < Devise::SessionsController 
    after_filter :after_login, :only => :create 
    before_filter :before_logout, :only => :destroy 

    def after_login 
    # logic to set the security token 
    end 

    def before_logout 
    current_user.update(security_token: nil) # <--"Attempted to update a stale object: User" 
    end 
end 

Tôi mới đến Rails (chỉ một vài tuần trong), vì vậy tôi không thực sự biết nơi nào khác trong mã của mình để xem hoặc tại sao mã này lại ném ngoại lệ. Bất kỳ đề xuất hoặc ý tưởng được đánh giá rất nhiều.

Cảm ơn trước sự thông thái của bạn!

Trả lời

4

Khóa lạc quan nghĩa là bạn muốn nhiều chủ đề có thể chỉnh sửa cùng một bản ghi người dùng.

Điều đang xảy ra trong ứng dụng của bạn là hai (hoặc nhiều) chỉnh sửa đang diễn ra cho cùng một hồ sơ người dùng và ứng dụng của bạn đang phát hiện chính xác và tăng lỗi phù hợp.

Những gì bạn sẽ thường làm là xử lý các lỗi:

  • Ví dụ, bạn có thể in một thông điệp như "Xin lỗi, người khác thay đổi nội dung này, vì vậy thay đổi của bạn không được cứu rỗi". Ví dụ: bạn có thể mã bất kỳ cách nào để điều chỉnh các chỉnh sửa, chẳng hạn như phát hiện một bản chỉnh sửa đã thay đổi tên của người đó, bất kể bản chỉnh sửa khác đã thay đổi số điện thoại của người đó, và sau đó bạn có thể mã một cách để hợp nhất hai vào một lần lưu.

Nói chung, bạn cũng có thể sử dụng user.reload để tìm nạp phiên bản mới của người dùng.

Tôi khuyên bạn trước hết nên thử tắt khóa lạc quan, trong khi bạn đang học Rails. Khóa lạc quan là một tối ưu hóa tuyệt vời cho tốc độ, nhưng nó không cần thiết để có được một ứng dụng Rails & Devise điển hình chạy thành công.

Bạn hỏi về những gì đặc biệt Vạch có thể làm:

  • Khi một hồ sơ người dùng được cập nhật, thiết lập Rails ActiveRecord điển hình cập nhật một lĩnh vực bảng updated_at với timestamp hiện hành.

  • Thiết lập Devise điển hình của các mô-đun như trackable cập nhật hồ sơ người dùng bằng dấu thời gian đăng nhập và/hoặc đăng xuất gần đây nhất.

Một cách dễ dàng để xem những gì đang xảy ra là nhìn vào lĩnh vực lock_version:

  • Khi Rails tải một kỷ lục (ví dụ một người sử dụng) sau đó Rails increments số phiên bản lock.

  • Khi ứng dụng của bạn lưu người dùng, Rails ActiveRecord so sánh số lock_version đối tượng người dùng với số khóa lock_version cơ sở dữ liệu.

  • Nếu các số phù hợp, thì lưu thành công; nếu số không khớp, thì Rails sẽ tăng ngoại lệ đối tượng cũ.

Ví dụ mã:

def before_logout 
    logger.debug current_user.lock_version 
    current_user.reload  
    logger.debug current_user.lock_version 
    current_user.update(security_token: nil) 
end 

lập mưu và một số module khác nhau của nó thêm các trường vào bảng user. Điều này là nhanh chóng và thuận tiện cho các ứng dụng web điển hình, nhưng các trường được thêm vào này có xu hướng làm gián đoạn bộ nhớ đệm, khóa, kiên trì, tuần tự hóa và tương tự.

(Ngoài ra, bạn có thể cân nhắc việc đánh giá Devise so với các giải pháp bảo mật khác. Một đặc biệt mà tôi thích được gọi là Sorcery, nó cung cấp một bộ công cụ của một số phương pháp mà bạn có thể lắp ráp theo cách bạn muốn. Theo kinh nghiệm cá nhân của tôi, Sorcery là một cách tuyệt vời để tìm hiểu chính xác từng bước đang làm trong mã của riêng bạn, và bạn có thể kiểm soát bộ nhớ đệm và khóa theo những cách bạn muốn.)

+0

Cảm ơn, Joel. Tôi khá rõ ràng về những gì khóa lạc quan là, nếu không tôi sẽ không được sử dụng nó.Tôi không đồng ý rằng nó được sử dụng chủ yếu cho tối ưu hóa tốc độ và không cần thiết. Như bạn đã nói, nó được sử dụng để bảo vệ thread, mà tôi đang sử dụng trong các phần khác nhau của mã của tôi để xử lý các trường hợp khi hai chủ đề đang sửa đổi cùng một đối tượng, vì vậy tôi ghét phải tắt nó đi. Câu hỏi của tôi là những gì có thể gây ra ngoại lệ đối tượng cũ được ném khi Devise đăng xuất, hoặc một số cách tôi có thể theo dõi nó là gì. Nó không liên tục, nó xảy ra trên mỗi lần đăng xuất. Cảm ơn một lần nữa. – BeachRunnerFred

+0

(Đã thêm thông tin cho bạn) – joelparkerhenderson

1

Tôi sẽ sử dụng update_attribute trong trường hợp đó , vì bạn không quan tâm đến việc xác thực, v.v.

class SessionsController < Devise::SessionsController 
    after_filter :after_login, :only => :create 
    before_filter :before_logout, :only => :destroy 

    def after_login 
    # logic to set the security token 
    end 

    def before_logout 
    current_user.update_attribute :security_token, nil 
    end 
end 

http://apidock.com/rails/ActiveRecord/Persistence/update_attribute

0

Dường như bộ điều khiển phiên của phiên bản đã hủy phiên, vì vậy current_user của bạn bị hủy hoặc đánh dấu để hủy. Tôi đã không được sử dụng lập ra trong một thời gian nhưng bạn nên kiểm tra xem chuỗi các sự kiện đang xảy ra theo thứ tự mà bạn nghĩ rằng họ đang xảy ra.

Thử làm current_user.destroyed? trong phương thức before_logout của bạn. Có vẻ như before_logout được thiết kế để xử lý khác ngoài thao tác current_user

1

Hành vi tốt trên ngoại lệ đối tượng cũ thực sự phụ thuộc vào ứng dụng của bạn.

Một giải pháp tốt thường là để thử lại công việc:

def before_logout 
    begin 
    current_user.update(security_token: nil) 
    rescue ActiveRecord::StaleObjectError 
    current_user.reload 
    retry 
    end 
end 

Tất nhiên điều này có thể ẩn một "sai" cập nhật ở một nơi khác trong mã của bạn nhưng nó có thể giải quyết vấn đề của bạn cho đến khi bạn tìm thấy những "sai" cập nhật

3

Tôi đã gặp phải sự cố tương tự. Lỗi là do sự khác biệt trong lock_version của đối tượng bạn đang cố cập nhật. Bạn có thể thử gọi lại tải lại trên đối tượng trước khi thực hiện cập nhật. Điều này sẽ đảm bảo đối tượng là lock_version gần đây nhất.

+0

Xin chào, chào mừng bạn đến với SO. Điều này thực sự phải là một bình luận, không phải là một câu trả lời. Bạn có thể nhận xét khi bạn kiếm đủ danh tiếng. Cảm ơn. – Cthulhu

+0

Ok. Hy vọng sẽ sớm nhận được điểm yêu cầu. – anosikeosifo

0

Tôi đã tạo một mô-đun rất đơn giản để xử lý việc này khi có thể tải lại để khắc phục sự cố. Xin lưu ý rằng đây là một điều kiện chủng tộc và bạn chịu trách nhiệm về những gì bạn đang tiết kiệm cho db.

module StaleObjHandler 
    def retry 
    @staled_retries ||= 5 
    yield 
    rescue ActiveRecord::StaleObjectError => e 
    if @staled_retries.zero? 
     Rails.logger.error ' Staled object retried 5 times, raising error.' 
     raise e 
    end 

    Rails.logger.error " Staled Object Error for #{e.record} retrying..." 
    e.record.reload 
    @staled_retries -= 1 
    retry 
    end 
    module_function :retry 
end 

Sau đó, bạn chỉ có thể sử dụng nó như

StaleObjHandler.retry { account.update_attribute :balance, account.balance - total } 

Nó sẽ cố gắng để làm điều đó 5 lần theo mặc định, làm một tải lại nếu nó không thành công.

0

Bạn cần đặt giá trị mặc định (0) cho cột lock_version trong tệp di chuyển

Các vấn đề liên quan