วิธีที่ถูกต้องในการแทนที่เมธอด setter ใน Ruby on Rails คืออะไร?


184

ฉันใช้ Ruby on Rails 3.2.2 และฉันต้องการทราบว่าต่อไปนี้เป็นวิธี "ถูกต้อง" / "ถูกต้อง" / "แน่ใจ" เพื่อแทนที่เมธอด setter สำหรับแอตทริบิวต์ class ของฉัน

attr_accessible :attribute_name

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

  self[:attribute_name] = value
end

รหัสข้างต้นดูเหมือนว่าจะทำงานตามที่คาดไว้ แต่ผมอยากจะทราบว่าโดยการใช้รหัสดังกล่าวในอนาคตผมจะมีปัญหาหรืออย่างน้อยสิ่งที่เป็นปัญหา "ผมควรคาดหวัง" / "ที่อาจเกิดขึ้น" กับ Ruby on Rails หากนั่นไม่ใช่วิธีที่ถูกต้องในการแทนที่เมธอด setter วิธีที่ถูกต้องคืออะไร


หมายเหตุ : ถ้าฉันใช้รหัส

attr_accessible :attribute_name

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

  self.attribute_name = value
end

ฉันได้รับข้อผิดพลาดต่อไปนี้:

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

4
ฉันชอบคำศัพท์ที่ใช้ว่า "เหมาะสม" / "ถูกต้อง" / "แน่ใจ" " เมื่อคุณให้ 3 วิธีมันช่วยให้มั่นใจได้อย่างแท้จริงว่าไม่มีการตีความผิด ทำได้ดีมาก!
Jay

5
@Jay - "ความวิจิตรอิตาเลียน"; -)
Backo

2
เพื่อให้ชัดเจน "ระดับสแต็กลึกเกินไป" หมายถึงความจริงที่ว่าเป็นการเรียกซ้ำ ... การเรียกตัวเอง
Nippysaurus

คำตอบ:


295

================================================== ========================= อัปเดต: 19 กรกฎาคม 2017

ตอนนี้เอกสาร Railsยังแนะนำให้ใช้superเช่นนี้:

class Model < ActiveRecord::Base

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

end

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

คำตอบเดิม

หากคุณต้องการแทนที่เมธอด setter สำหรับคอลัมน์ของตารางขณะเข้าถึงผ่านโมเดลนี่เป็นวิธีที่ทำได้

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

ดูการแทนที่ค่าเริ่มต้นของ accessorsในเอกสารประกอบ Rails

ดังนั้นวิธีแรกของคุณคือวิธีที่ถูกต้องในการแทนที่ตัวตั้งค่าคอลัมน์ในรุ่นของ Ruby on Rails อุปกรณ์เสริมเหล่านี้มีให้โดย Rails เพื่อเข้าถึงคอลัมน์ของตารางเป็นคุณสมบัติของแบบจำลอง นี่คือสิ่งที่เราเรียกการแมป ActiveRecord ORM

โปรดทราบว่าattr_accessibleด้านบนของโมเดลไม่มีส่วนเกี่ยวข้องกับอุปกรณ์เสริม มันมีฟังก์ชั่นที่แตกต่างอย่างสิ้นเชิง (ดูคำถามนี้ )

แต่ใน Ruby บริสุทธิ์ถ้าคุณกำหนด accessors สำหรับคลาสและต้องการแทนที่ setter คุณจะต้องใช้ประโยชน์จากตัวแปร instance เช่นนี้

class Person
  attr_accessor :name
end

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

สิ่งนี้จะง่ายต่อการเข้าใจเมื่อคุณรู้ว่าattr_accessorทำอะไร รหัสattr_accessor :nameเทียบเท่ากับทั้งสองวิธี (getter และ setter)

def name # getter
  @name
end

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

ด้วยวิธีที่สองของคุณล้มเหลวเพราะจะทำให้เกิดการวนซ้ำไม่สิ้นสุดในขณะที่คุณกำลังเรียกวิธีเดียวกันattribute_name=ภายในวิธีการนั้น


9
สำหรับ Rails 4 เพียงข้ามไปattr_accessibleเนื่องจากไม่ได้อยู่ที่นั่นแล้วและควรใช้งานได้
zigomir

11
ทำไมไม่โทรsuper?
Nathan Lilienthal

1
ฉันรู้สึกประทับใจที่ตั้งแต่ accessors และ writer ถูกสร้างขึ้นแบบไดนามิกsuperอาจไม่ทำงาน แต่ดูเหมือนว่าไม่ใช่กรณี ฉันเพิ่งตรวจสอบและใช้งานได้สำหรับฉัน นอกจากนี้คำถามนี้ถามเหมือนกัน
rubyprince

4
มี gotcha write_attributeขนาดใหญ่ที่มีคือ การแปลงจะถูกข้าม โปรดทราบว่าwrite_attributeจะข้ามการแปลงเขตเวลาด้วยวันที่ซึ่งจะไม่พึงปรารถนาเกือบตลอดเวลา
ทิมสกอตต์

2
super จะทำงานได้เช่นกัน แต่มีเหตุผลบางอย่างที่คุณอาจไม่ต้องการให้เราทำ ตัวอย่างเช่นใน mongoid gem มีข้อผิดพลาดที่คุณไม่สามารถผลักไปยังอาร์เรย์ได้ถ้าคุณใช้เมธอด getter มันเป็นข้อผิดพลาดเพราะมีวิธีจัดการอาร์เรย์ในหน่วยความจำ ด้วย @ @ จะส่งกลับค่าที่ตั้งค่อนข้างแล้วเรียกวิธีการเขียนทับของคุณ อย่างไรก็ตามในการแก้ปัญหาข้างต้นทั้งสองจะทำงานได้ดี
newdark-it

44

ใช้superคำสำคัญ:

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

ในทางกลับกันเพื่อแทนที่ผู้อ่าน:

def attribute_name
  super.some_custom_decode
end

1
คำตอบที่ดีกว่า IMO ที่ยอมรับได้เนื่องจากมันจะทำให้การเรียกใช้เมธอดถูก จำกัด ในชื่อเดียวกัน สิ่งนี้จะรักษาพฤติกรรมที่ถูกแทนที่โดยสืบทอดไปยัง attribute_name =
Andrew Schwartz

การเอาชนะวิธี getter กลายเป็นอันตรายใน Rails 4.2 เนื่องจากการเปลี่ยนแปลงนี้: github.com/rails/rails/commit/ ...... ผู้ช่วยแบบฟอร์มก่อนหน้านี้จะเรียกค่า untypecast ของฟิลด์และไม่เรียกใช้ getter ที่กำหนดเองของคุณ ตอนนี้พวกเขาเรียกวิธีการของคุณและดังนั้นจะสร้างผลลัพธ์ที่สับสนในรูปแบบของคุณขึ้นอยู่กับวิธีที่คุณเอาชนะค่า
Brendon Muir

16

ในราง 4

สมมติว่าคุณมีคุณสมบัติอายุในตารางของคุณ

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

หมายเหตุ: สำหรับผู้มาใหม่ใน Rails 4 คุณไม่จำเป็นต้องระบุattr_accessibleในรุ่น แต่คุณจะต้องแสดงรายการคุณลักษณะของคุณที่ระดับคอนโทรลเลอร์โดยใช้วิธีการอนุญาต


3

ฉันได้พบว่า (อย่างน้อยสำหรับคอลเลคชั่นความสัมพันธ์ของ ActiveRecord) รูปแบบต่อไปนี้ใช้ได้:

has_many :specialties

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

(นี่จะเป็นการจับคู่ 3 รายการแรกที่ไม่ซ้ำกันในอาเรย์ที่ส่งผ่าน)


0

การใช้attr_writerเพื่อเขียนทับ setter attr_writer: attribute_name

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.