Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

Globals ที่ดีขึ้นด้วยโมดูล ActiveSupport ขนาดเล็ก

โดยทั่วไป ตัวแปรทั่วโลกนั้นไม่ดี แต่บางครั้ง โกลบอลในตำแหน่งที่ถูกต้องอาจทำให้โค้ดของคุณง่ายขึ้นมาก

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

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

คุณต้องการสิ่งที่เป็นสากล แต่สำหรับ แค่คำขอของคุณ .

วิถีทับทิมธรรมดา

โดยปกติ คุณจะเห็นสิ่งนี้จัดการด้วย Thread.current[] . ดูเหมือนว่านี้:

Thread.current[:current_user] = user

นี้เป็นเรื่องง่ายพอ เป็นทางออกที่ดี แต่มีข้อเสียใหญ่สองประการ:

  1. อาจมีคนเขียนทับข้อมูลของคุณโดยไม่ได้ตั้งใจ

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

  2. ไม่มีโครงสร้างที่ดี

    Thread.current[] เป็นเพียงข้อมูลขนาดใหญ่ คุณไม่สามารถจัดทำเอกสารได้อย่างง่ายดาย ถ้าคุณต้องการรู้ว่าคุณสามารถทำอะไรได้บ้าง คุณต้องค้นหาสิ่งที่คุณใส่เข้าไป

    แน่นอน หากคุณทิ้งข้อมูลส่วนกลางมากพอที่เป็นปัญหา คุณต้องกังวลมากกว่า Thread.current[] ขาดโครงสร้าง แต่ก็ยังเป็นประเด็นที่ต้องจำไว้

ดังนั้น มีทางออกที่ดีกว่า Thread.current[] . หรือไม่ ? อย่างที่คุณอาจจินตนาการได้ Rails เองจัดการข้อมูลคำขอในเครื่องจำนวนมาก และกล่องของสารพัดที่เป็น ActiveSupport ก็จะมีรูปแบบที่แตกต่างกันออกไป

ทางราง

หากคุณค้นหาผ่าน ActiveSupport คุณอาจพบ ActiveSupport::PerThreadRegistry . คุณคงทราบได้จากชื่อนี้ว่านี่คือสิ่งที่เราต้องการ

ด้วย ActiveSupport::PerThreadRegistry ตัวอย่างก่อนหน้านี้จะมีลักษณะดังนี้:

class RequestRegistry
  extend ActiveSupport::PerThreadRegistry

  attr_accessor :current_user, :current_permissions
end

RequestRegistry.current_user = user
RequestRegistry.current_user # => user

มันเป็นงานอีกเล็กน้อย แต่ด้วย ActiveSupport::PerThreadRegistry :

  • คุณมีที่สำหรับวางเอกสาร ใครก็ตามที่กำลังดูชั้นเรียนของคุณจะรู้ว่าคุณคาดหวังข้อมูลใดจากทั่วโลก และจะใช้ข้อมูลใดได้บ้าง

  • คุณได้รับ API ที่ดูดี อันที่จริง ดูเหมือนว่าคุณกำลังตั้งค่าตัวแปรคลาส นั่นอาจเป็นสิ่งที่คุณควรทำหากคุณไม่ต้องกังวลกับเธรด

  • ข้อมูลใดๆ ที่คุณตั้งค่าในชั้นเรียนจะถูกเนมสเปซ คุณไม่ต้องกังวลกับ RequestRegistry.current_user ชนกับ PhoneNumberApiRegistry.current_user , พวกเขาได้รับการปฏิบัติแยกกันโดยสิ้นเชิง

Rails ใช้ ActiveSupport::PerThreadRegistry ภายใน สำหรับสิ่งต่างๆ เช่น การจัดการการเชื่อมต่อ ActiveRecord การจัดเก็บแบบสอบถาม EXPLAIN และ ActiveSupport::Notifications ดังนั้น หากคุณกำลังมองหาตัวอย่างที่ดียิ่งขึ้นเกี่ยวกับพลังของ PerThreadRegistry , Rails เป็นจุดเริ่มต้นที่ดี

เมื่อเธรดและคำขอไม่ตรงกัน

สำหรับเซิร์ฟเวอร์ Rails บางตัว เธรดเดียวจะจัดการกับคำขอหลายรายการ ซึ่งหมายความว่าทุกอย่างที่คุณใส่ลงใน PerThreadRegistry จะรอรับคำขอต่อไป นี่คงไม่ใช่สิ่งที่คุณต้องการ

คุณสามารถทำสิ่งที่ Rails ทำ และล้างสิ่งที่คุณใส่ลงใน PerThreadRegistry หลังจากที่คุณทำกับพวกเขาเสร็จแล้ว ขึ้นอยู่กับสิ่งที่คุณใส่ในนั้น คุณอาจจะทำเช่นนี้อยู่แล้ว

ในความคิดเห็น MattB ชี้ไปที่วิธีแก้ปัญหาที่ง่ายกว่า:request_store มันทำหน้าที่เหมือน Thread.current[] แต่จะเคลียร์ตัวเองหลังจากคำขอแต่ละครั้ง

ซึ่งจะทำให้ประตูเปิดกว้างสำหรับบางคนในการสร้าง PerRequestRegistry ซึ่งรวมความปลอดภัยของ request_store เข้ากับ API ของ PerThreadRegistry . หากใครเคยทำสิ่งนี้มาก่อน ฉันชอบที่จะได้ยินเรื่องนี้!

ลองใช้เลย

ข้อมูลทั่วโลกอาจไม่ดี globals มากเกินไปอาจนำไปสู่รหัสที่ไม่สามารถบำรุงรักษาได้อย่างรวดเร็ว

แต่หากจำเป็นต้องเก็บข้อมูลไว้ตรงกลางสำหรับคำขอทั้งหมด ให้ไปไกลกว่า Thread.current[] . ให้ ActiveSupport::PerThreadRegistry หรือ request_store ยิง หากไม่เป็นเช่นนั้น จะทำให้การจัดการกับข้อมูลทั่วโลกมีความเสี่ยงน้อยลง