Điều gì gây ra lỗi ActiveRecord :: ReadOnlyRecord này?


203

Cái này theo cái này câu hỏi trước, được trả lời. Tôi thực sự phát hiện ra rằng tôi có thể xóa tham gia khỏi truy vấn đó, vì vậy bây giờ truy vấn đang hoạt động là

start_cards = DeckCard.find :all, :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]  

Điều này xuất hiện để làm việc. Tuy nhiên, khi tôi cố gắng di chuyển các DeckCards này vào một liên kết khác, tôi gặp lỗi ActiveRecord :: ReadOnlyRecord.

Đây là mã

for player in @game.players 
  player.tableau = Tableau.new
  start_card = start_cards.pop 
  start_card.draw_pile = false
  player.tableau.deck_cards << start_card  # the error occurs on this line
end

và các Mô hình có liên quan (tableau là thẻ người chơi trên bàn)

class Player < ActiveRecord::Base
  belongs_to :game
  belongs_to :user
  has_one :hand
  has_one :tableau
end

class Tableau < ActiveRecord::Base
  belongs_to :player
  has_many :deck_cards
end  

class DeckCard < ActiveRecord::Base
  belongs_to :card
  belongs_to :deck  
end

Tôi đang thực hiện một hành động tương tự ngay sau mã này, thêm DeckCardsvào tay người chơi và mã đó đang hoạt động tốt. Tôi tự hỏi liệu tôi có cần belongs_to :tableautrong Mô hình DeckCard không, nhưng nó hoạt động tốt khi thêm vào tay người chơi. Tôi có một tableau_idhand_idcác cột trong bảng DeckCard.

Tôi đã tra cứu ReadOnlyRecord trong api rails, và nó không nói gì nhiều ngoài mô tả.

Câu trả lời:


283

Rails 2.3.3 và thấp hơn

Từ ActiveRecord CHANGELOG(v1.12.0, ngày 16 tháng 10 năm 2005) :

Giới thiệu hồ sơ chỉ đọc. Nếu bạn gọi object.readonly! sau đó nó sẽ đánh dấu đối tượng là chỉ đọc và nâng cao ReadOnlyRecord nếu bạn gọi object.save. đối tượng.readonly? báo cáo xem đối tượng là chỉ đọc. Passing: readonly => true cho bất kỳ phương thức tìm kiếm nào sẽ đánh dấu các bản ghi được trả về là chỉ đọc. Tùy chọn: tham gia bây giờ ngụ ý: chỉ đọc, vì vậy nếu bạn sử dụng tùy chọn này, việc lưu cùng một bản ghi sẽ thất bại. Sử dụng find_by_sql để làm việc xung quanh.

Sử dụng find_by_sqlkhông thực sự là một thay thế vì nó trả về dữ liệu hàng / cột thô, không ActiveRecords. Bạn có hai lựa chọn:

  1. Buộc biến @readonlyđối tượng thành false trong bản ghi (hack)
  2. Sử dụng :include => :cardthay vì:join => :card

Đường ray 2.3.4 trở lên

Hầu hết những điều trên không còn đúng nữa, sau ngày 10 tháng 9 năm 2012:

  • sử dụng Record.find_by_sql một lựa chọn khả thi
  • :readonly => trueđược tự động suy ra chỉ nếu :joinsđã được chỉ định mà không cần một rõ ràng :select và cũng không một rõ ràng (hoặc công cụ tìm-phạm vi-thừa hưởng) :readonlytùy chọn (xem hình thực hiện set_readonly_option!trong active_record/base.rbcho Rails 2.3.4, hoặc thi hành to_atrong active_record/relation.rbcustom_join_sqlactive_record/relation/query_methods.rbcho Rails 3.0.0)
  • tuy nhiên, :readonly => trueluôn được suy luận tự động has_and_belongs_to_manynếu bảng tham gia có nhiều hơn hai cột khóa ngoại và :joinsđược chỉ định mà không có giá trị rõ ràng :select(nghĩa là các :readonlygiá trị do người dùng cung cấp bị bỏ qua - xem finding_with_ambiguous_select?trong active_record/associations/has_and_belongs_to_many_association.rb.)
  • kết luận, trừ khi xử lý một bảng tham gia đặc biệt và has_and_belongs_to_manysau đó @aaronrustad, câu trả lời chỉ áp dụng tốt trong Rails 2.3.4 và 3.0.0.
  • làm không sử dụng :includesnếu bạn muốn đạt được một INNER JOIN( :includesám chỉ một LEFT OUTER JOIN, mà là ít chọn lọc và kém hiệu quả hơn INNER JOIN.)

phần: bao gồm rất hữu ích trong việc giảm # các truy vấn được thực hiện, tôi không biết về điều đó; nhưng tôi đã cố gắng khắc phục bằng cách thay đổi liên kết Tableau / Deckcards thành has_many: thông qua, và bây giờ tôi nhận được thông báo 'không thể tìm thấy liên kết'; Tôi có thể phải đăng một câu hỏi khác cho điều đó
user26270

@codeman, vâng, bao gồm: sẽ giảm số lượng truy vấn sẽ đưa bảng được bao gồm vào phạm vi điều kiện của bạn (một loại tham gia ngầm mà không có Rails đánh dấu các bản ghi của bạn là chỉ đọc, nó sẽ thực hiện ngay khi nó đánh hơi bất kỳ SQL nào -ish trong tìm kiếm của bạn, bao gồm: tham gia /: chọn mệnh đề IIRC
vladr

Để 'has_many: a, through =>: b' hoạt động, hiệp hội B cũng phải được khai báo, ví dụ 'has_many: b; has_many: a ,: through =>: b ', tôi hy vọng đây là trường hợp của bạn?
vladr

6
Điều này có thể đã thay đổi trong các bản phát hành gần đây, nhưng bạn chỉ cần thêm: readonly => false như một phần của các thuộc tính phương thức find.
Aaron Rustad

1
Câu trả lời này cũng có thể áp dụng nếu bạn có một liên kết has_and_belongs_to_many với một tùy chỉnh: jo_table được chỉ định.
Lee

172

Hoặc trong Rails 3, bạn có thể sử dụng phương thức chỉ đọc (thay thế "..." bằng các điều kiện của bạn):

( Deck.joins(:card) & Card.where('...') ).readonly(false)

1
Hmmm ... Tôi đã tra cứu cả hai Railscasts trên Asciicasts, và không đề cập đến readonlychức năng.
Purplejquet

45

Điều này có thể đã thay đổi trong bản phát hành Rails gần đây, nhưng cách thích hợp để giải quyết vấn đề này là thêm : readonly => false vào các tùy chọn tìm kiếm.


3
Tôi không tin đây là trường hợp, với ít nhất 2.3.4
Olly

2
Nó vẫn hoạt động với Rails 3.0.10, đây là một ví dụ từ mã của riêng tôi tìm nạp một phạm vi có: tham gia Fundraiser.donitable.readonly (false)
Houen

16

select ('*') dường như sửa lỗi này trong Rails 3.2:

> Contact.select('*').joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> false

Chỉ cần xác minh, bỏ qua select ('*') sẽ tạo ra một bản ghi chỉ đọc:

> Contact.joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> true

Không thể nói tôi hiểu lý do nhưng ít nhất đó là cách giải quyết nhanh chóng và sạch sẽ.


4
Điều tương tự trong Rails 4. Hoặc bạn có thể làm select(quoted_table_name + '.*')
andorov

1
Đó là bronson rực rỡ. Cảm ơn bạn.
Chuyến đi

Điều này có thể hoạt động, nhưng phức tạp hơn so với việc sử dụngreadonly(false)
Kelvin

5

Thay vì find_by_sql, bạn có thể chỉ định a: select trên công cụ tìm và mọi thứ lại vui vẻ ...

start_cards = DeckCard.find :all, :select => 'deck_cards.*', :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]


3

Để tắt nó ...

module DeactivateImplicitReadonly
  def custom_join_sql(*args)
    result = super
    @implicit_readonly = false
    result
  end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly

3
Khỉ vá là dễ vỡ - rất dễ bị phá vỡ bởi các phiên bản đường ray mới. Chắc chắn không thể đưa ra có giải pháp khác.
Kelvin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.