Cách đúng để ghi đè phương thức setter trong Ruby on Rails là gì?


184

Tôi đang sử dụng Ruby on Rails 3.2.2 và tôi muốn biết liệu sau đây có phải là cách "đúng" / "đúng" / "chắc chắn" để ghi đè phương thức setter cho thuộc tính lớp của tôi không.

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self[:attribute_name] = value
end

Các mã trên dường như làm việc như mong đợi. Tuy nhiên, tôi muốn biết liệu, bằng cách sử dụng đoạn mã trên, trong tương lai tôi sẽ gặp vấn đề hay ít nhất, những vấn đề "tôi nên mong đợi" / "có thể xảy ra" với Ruby on Rails . Nếu đó không phải là cách đúng để ghi đè một phương thức setter, thì cách nào là đúng?


Lưu ý : Nếu tôi sử dụng mã

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self.attribute_name = value
end

Tôi nhận được lỗi sau đây:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70

4
Tôi thích thuật ngữ được áp dụng '"đúng" / "đúng" / "chắc chắn"'. Khi bạn cung cấp cho nó 3 cách nó thực sự đảm bảo không có giải thích sai. Làm tốt lắm!
Jay

5
@Jay - "Độ mịn Ý"; -)
Backo

2
Nói rõ hơn, "cấp độ ngăn xếp quá sâu" đang đề cập đến thực tế rằng đó là một cuộc gọi đệ quy ... chính cuộc gọi của nó.
Nippysaurus

Câu trả lời:


294

================================================== ========================= Cập nhật: ngày 19 tháng 7 năm 2017

Bây giờ tài liệu Rails cũng đề xuất sử dụng supernhư thế này:

class Model < ActiveRecord::Base

  def attribute_name=(value)
    # custom actions
    ###
    super(value)
  end

end

================================================== =========================

Câu trả lời gốc

Nếu bạn muốn ghi đè các phương thức setter cho các cột của bảng trong khi truy cập thông qua các mô hình, đây là cách để làm điều đó.

class Model < ActiveRecord::Base
  attr_accessible :attribute_name

  def attribute_name=(value)
    # custom actions
    ###
    write_attribute(:attribute_name, value)
    # this is same as self[:attribute_name] = value
  end

end

Xem Ghi đè người truy cập mặc định trong tài liệu Rails.

Vì vậy, phương pháp đầu tiên của bạn là cách chính xác để ghi đè các setters cột trong Mô hình của Ruby on Rails. Các bộ truy cập này đã được Rails cung cấp để truy cập vào các cột của bảng dưới dạng các thuộc tính của mô hình. Đây là những gì chúng ta gọi là ánh xạ ORM ActiveRecord.

Cũng nên nhớ rằng attr_accessible ở đầu mô hình không liên quan gì đến người truy cập. Nó có một chức năng hoàn toàn khác nhau (xem câu hỏi này )

Nhưng trong Ruby thuần túy, nếu bạn đã xác định các trình truy cập cho một lớp và muốn ghi đè trình thiết lập, bạn phải sử dụng biến thể hiện như thế này:

class Person
  attr_accessor :name
end

class NewPerson < Person
  def name=(value)
    # do something
    @name = value
  end
end

Điều này sẽ dễ hiểu hơn một khi bạn biết những gì attr_accessorlàm. Mật mãattr_accessor :name này tương đương với hai phương thức này (getter và setter)

def name # getter
  @name
end

def name=(value) #  setter
  @name = value
end

Ngoài ra phương thức thứ hai của bạn không thành công vì nó sẽ gây ra một vòng lặp vô hạn khi bạn đang gọi cùng một phương thức attribute_name=bên trong phương thức đó.


9
Đối với Rails 4, hãy bỏ qua attr_accessiblevì nó không còn ở đó nữa và nó sẽ hoạt động
zigomir

11
Tại sao không gọi super?
Nathan Lilienthal

1
Tôi có ấn tượng rằng vì các phụ kiện và nhà văn được tạo ra một cách linh hoạt, supercó thể không hoạt động. Nhưng, có vẻ như không phải vậy. Tôi chỉ kiểm tra nó và nó hoạt động cho tôi. Ngoài ra, câu hỏi này cũng hỏi tương tự
rubyprince

4
Có một gotcha rất lớn với write_attribute. Chuyển đổi sẽ được bỏ qua. Xin lưu ý rằng write_attributesẽ bỏ qua chuyển đổi múi giờ với ngày, điều này hầu như sẽ không được mong muốn.
Tim Scott

2
siêu sẽ hoạt động tốt, tuy nhiên có một số lý do mà bạn có thể không muốn cho chúng tôi. Ví dụ, trong mongoid gem có một lỗi mà bạn không thể đẩy vào mảng nếu bạn siêu phương thức getter. Đó là lỗi vì cách quản lý mảng trong bộ nhớ. Ngoài ra, @name cũng sẽ trả về giá trị được đặt thay vì sau đó gọi phương thức ghi đè của bạn. Tuy nhiên trong các giải pháp trên cả hai sẽ hoạt động tốt.
newdark-it

44

Sử dụng supertừ khóa:

def attribute_name=(value)
  super(value.some_custom_encode)
end

Ngược lại, để ghi đè người đọc:

def attribute_name
  super.some_custom_decode
end

1
Câu trả lời tốt hơn IMO được chấp nhận vì nó giữ cho cuộc gọi phương thức giới hạn trong cùng một tên. Điều này bảo tồn hành vi được ghi đè thừa kế thành property_name =
Andrew Schwartz

Việc ghi đè phương thức getter đã trở nên nguy hiểm trong Rails 4.2 do thay đổi này: github.com/rails/rails/commit/ trộm Người trợ giúp biểu mẫu trước đây sẽ gọi giá trị không phổ biến của trường và không gọi trình getter tùy chỉnh của bạn. Bây giờ họ gọi phương thức của bạn và do đó sẽ tạo ra kết quả khó hiểu trong biểu mẫu của bạn tùy thuộc vào cách bạn ghi đè giá trị.
Brendon Muir

16

Trong đường ray 4

giả sử bạn có thuộc tính tuổi trong bảng của bạn

def age=(dob)   
    now = Time.now.utc.to_date
    age = now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
    super(age) #must add this otherwise you need to add this thing and place the value which you want to save. 
  end

Lưu ý: Đối với những người mới đến trong đường ray 4, bạn không cần chỉ định attr_accessible trong mô hình. Thay vào đó, bạn phải liệt kê danh sách các thuộc tính của mình ở cấp điều khiển bằng phương thức cho phép .


3

Tôi đã thấy rằng (ít nhất là cho các bộ sưu tập mối quan hệ ActiveRecord) mẫu sau hoạt động:

has_many :specialties

def specialty_ids=(values)
  super values.uniq.first(3)
end

(Điều này lấy 3 mục không trùng lặp đầu tiên trong mảng được thông qua.)


0

Sử dụng attr_writerđể ghi đè setter attr_writer: property_name

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
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.