Rails เป็นเฟรมเวิร์กขนาดใหญ่พร้อมเครื่องมือในตัวที่มีประโยชน์มากมายสำหรับสถานการณ์เฉพาะ ในชุดนี้ เราจะมาดูเครื่องมือที่ไม่ค่อยมีใครรู้จักซึ่งซ่อนอยู่ในฐานโค้ดขนาดใหญ่ของ Rails
ในบทความนี้ เราจะเน้นที่ store
. ของ ActiveRecord และ store_accessor
วิธีการ ทั้งสองวิธีนี้มุ่งเป้าไปที่กรณีการใช้งานของการจัดเก็บข้อมูลที่มีโครงสร้างในคอลัมน์ฐานข้อมูล เช่น JSON หรือ YAML ขณะที่ store_accessor
เป็นวิธีที่สะดวกแก่เราในการดึงค่าจากข้อมูลเหล่านี้โดยไม่ทำให้เกิดการอุดตันของโมเดลด้วยเมธอดของ getter store
ก้าวไปอีกขั้นและจัดลำดับ/ดีซีเรียลไลซ์ข้อมูลให้เป็นรูปแบบที่เราเลือกอย่างโปร่งใส เพื่อให้เข้าใจว่าสิ่งนี้มีประโยชน์อย่างไร เราจะดูที่ตัวเลือกสำหรับการจัดเก็บ JSON ในฐานข้อมูลเชิงสัมพันธ์และสาเหตุบางประการที่คุณอาจต้องการทำเช่นนั้น
JSON ในฐานข้อมูล
ฉันควรชี้แจงว่าเมื่อฉันพูดว่า 'ฐานข้อมูล' ในบทความนี้ ฉันหมายถึงฐานข้อมูลเชิงสัมพันธ์ โดยเฉพาะ PostgreSQL และ MySQL เนื่องจากเป็นฐานข้อมูลที่ใช้กันอย่างแพร่หลายที่สุดในชุมชน Rails
อาจมีคนถามว่าทำไมคุณถึงต้องการเก็บ JSON ในฐานข้อมูลเชิงสัมพันธ์ แท้จริงแล้ว วิธีใช้ประโยชน์จากฐานข้อมูลเชิงสัมพันธ์คือการแบ่งข้อมูลเพื่อให้ ความสัมพันธ์ ฐานข้อมูลสามารถบังคับใช้ระหว่างกันได้ (เช่น คีย์นอก) และสามารถจัดทำดัชนีข้อมูลเพื่อปรับปรุงประสิทธิภาพการสืบค้นได้
ข้อเสียประการหนึ่งของโมเดลฐานข้อมูลเชิงสัมพันธ์คือต้องทราบโครงสร้างข้อมูลล่วงหน้า และ เหมือนกันทุกแถวในตาราง หากแอปพลิเคชันของคุณสร้างขึ้นจากข้อมูลที่ไม่ตรงตามข้อกำหนดเหล่านี้ คุณอาจต้องการตรวจสอบฐานข้อมูล NoSQL สำหรับเว็บแอปส่วนใหญ่ เราต้องการยึดติดกับฐานข้อมูลเชิงสัมพันธ์ ~~ปีศาจ~~ ที่เรารู้จักสำหรับข้อมูลส่วนใหญ่ และเพียงแค่ "โรย" ในโครงสร้างข้อมูลแบบไดนามิกเหล่านี้อย่างรอบคอบ ในกรณีเหล่านี้ บางอย่างเช่นคอลัมน์ JSON ก็สมเหตุสมผลดี
JSON เทียบกับ JSONB
PostgreSQL มีคอลัมน์ JSON สองประเภท:json
และ jsonb
. ความแตกต่างที่สำคัญคือ jsonb
ถูกแยกวิเคราะห์ ณ เวลาเขียน ซึ่งหมายความว่าข้อมูลจะถูกจัดเก็บในรูปแบบที่ฐานข้อมูลสามารถสืบค้นได้เร็วขึ้น ข้อแม้คือเนื่องจาก JSON ถูกแยกวิเคราะห์แล้ว เมื่อส่งออกเป็นข้อความ อาจไม่ตรงกับที่ผู้ใช้ป้อนอีกต่อไป ตัวอย่างเช่น คีย์ที่ซ้ำกันอาจถูกลบ หรือลำดับคีย์อาจไม่ตรงกับต้นฉบับ
เอกสาร PostgreSQL ระบุว่าในกรณีส่วนใหญ่ jsonb
คือสิ่งที่คุณต้องการเว้นแต่คุณจะมีเหตุผลเฉพาะอย่างอื่น
json
ของ MySQL คอลัมน์ทำงานคล้ายกับ jsonb
ใน PostgreSQL เพื่อรองรับผลลัพธ์ 'ตามที่ผู้ใช้ป้อน' คุณอาจต้องใช้ varchar
คอลัมน์หรืออะไรทำนองนั้น
JSON กับ Text
นอกเหนือจากการอนุญาตให้แยกวิเคราะห์ข้อมูลล่วงหน้าแล้ว การใช้คอลัมน์ JSON แทนการจัดเก็บข้อมูลเดียวกันในช่องข้อความยังช่วยให้สามารถสืบค้นข้อมูลที่ใช้ข้อมูลนั้นเองได้ ตัวอย่างเช่น คุณสามารถสอบถามระเบียนทั้งหมดที่มีคู่คีย์-ค่าเฉพาะอยู่ในคอลัมน์ โปรดทราบว่า Rails เองไม่รองรับการสืบค้นข้อมูลเฉพาะ JSON จำนวนมาก (ถ้ามี) เนื่องจากเป็นแบบเฉพาะฐานข้อมูล ดังนั้น หากคุณต้องการใช้ประโยชน์จากคุณลักษณะเหล่านี้ คุณจะต้องใช้การสืบค้น SQL เพื่อดำเนินการ
คอลัมน์ JSON ใน Rails
Rails รองรับการสร้าง json
(และ jsonb
ในคอลัมน์ PostgreSQL) ในการย้ายข้อมูล:
class CreateItems < ActiveRecord::Migration[7.0]
def change
create_table :items do |t|
t.jsonb :user_attributes
...
end
end
end
เมื่ออ่านคอลัมน์นี้ ผลลัพธ์ที่ได้คือ Hash:
> Item.first.user_attributes
Item Load (0.6ms) SELECT "items".* FROM "items" ORDER BY "items"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> {"color"=>"text-red-400"}
> Item.first.update!(user_attributes: {color: "text-blue-400"})
> Item.first.user_attributes.dig(:color)
=> "text-blue-400"
ตอนนี้เรามีแอตทริบิวต์ Hash แล้ว คุณอาจถูกล่อลวงให้เพิ่มวิธีการช่วยเหลือให้กับโมเดลเพื่ออ่าน/เขียนค่า:
class Item < ApplicationRecord
def color=(value)
self.user_attributes["color"] = value
end
def color
user_attributes.dig("color")
end
end
วิธีการต่างๆ เช่น ฟังก์ชันนี้ใช้ได้ดีอย่างสมบูรณ์ แต่อาจกลายเป็นเทอะทะอย่างรวดเร็ว หากคุณมีคีย์ JSON จำนวนมากที่ต้องจัดการ โชคดีที่ Rails ช่วยคุณได้
ร้านค้าและ store_accessor ของ ActiveRecord
การจัดเก็บ JSON ในฐานข้อมูลมีสองด้าน:การทำให้เป็นอนุกรมและการเข้าถึง หากคุณกำลังใช้ json
-type คอลัมน์ในฐานข้อมูลของคุณ คุณไม่จำเป็นต้องกังวลเกี่ยวกับลักษณะการซีเรียลไลซ์เซชั่น Rails และตัวแปลงฐานข้อมูลจะจัดการให้คุณ (คุณสามารถข้ามไปที่ store_accessor
. ได้เลย ). หากคุณกำลังจัดเก็บข้อมูลในคอลัมน์ข้อความ store
. ของ ActiveRecord วิธีนี้เหมาะสำหรับคุณ ซึ่งช่วยให้มั่นใจได้ว่าข้อมูลที่คุณเขียนลงในคอลัมน์นั้นจะถูกจัดลำดับในรูปแบบที่คุณเลือก
ร้านของ ActiveRecord
ActiveRecord มี store
วิธีการเรียงลำดับข้อมูลที่เราอ่านหรือเขียนลงในคอลัมน์ของเราโดยอัตโนมัติ:
class Item < ApplicationRecord
store :user_attributes, accessors: [:color], coder: JSON
end
ที่นี่ :user_attributes
เป็นคอลัมน์ที่เราต้องการใช้ ในขณะที่ accessors
คือรายการคีย์ที่เราต้องการเข้าถึง (แค่ color
ในกรณีของเราที่นี่) และสุดท้าย เราระบุวิธีที่เราต้องการเข้ารหัสข้อมูล เรากำลังใช้ JSON แต่คุณสามารถใช้อะไรก็ได้ที่คุณชอบที่นี่ รวมถึงสิ่งต่างๆ เช่น YAML หรือการเข้ารหัสแบบกำหนดเอง วิธีนี้จะจัดการกับการทำให้เป็นอนุกรม (ด้วย coder ที่คุณเลือก) และเรียก store_accessor
ภายใต้ประทุน
store_accessor ของ ActiveRecord
เราสร้างเมธอด get/set ในโมเดลของเราโดยใช้ store_accessor
:
class Item < ApplicationRecord
store_accessor :user_attributes, :color
store_accessor :user_attributes, :name, prefix: true
store_accessor :user_attributes, :location, prefix: 'primary'
end
ที่นี่อีกครั้ง user_attributes
คือคอลัมน์ฐานข้อมูลที่เราต้องการใช้ ตามด้วยคีย์ที่เราต้องการใช้ในข้อมูล JSON และสุดท้าย เรามีตัวเลือกให้ใช้คำนำหน้า (หรือส่วนต่อท้าย) โปรดทราบว่า store_accessor
ไม่รองรับข้อมูลที่ซ้อนกัน เฉพาะคู่คีย์-ค่าระดับบนสุดเท่านั้น prefix
และ suffix
ตัวเลือกใช้บูลีน สตริง หรือสัญลักษณ์ หากบูลีน true
ถูกส่งผ่าน จากนั้นชื่อของคอลัมน์จะถูกใช้เป็นคำนำหน้า/ส่วนต่อท้าย
=>item = Item.create!(color: 'red', user_attributes_name: 'Jonathan', primary_location: 'New Zealand')
>#<Item:0x000055d63f4f0360
id: 4,
user_attributes: {"color"=>"red", "name"=>"Jonathan", "location"=>"New Zealand"}>
=>item.color
>"red"
=> item.user_attributes_name
>"Jonathan"
=> item.name
>NoMethodError: undefined method `name'...
=> item.primary_location
>"New Zealand"
การใช้งานจริง
ฉันต้องการเพียงบางครั้งที่จะหลงทางจากสคีมาฐานข้อมูลเชิงสัมพันธ์ที่ทราบล่วงหน้าโดยทั่วไป หลายครั้งที่ฉันมี มันทำให้โครงสร้างฐานข้อมูลทั้งสะอาดและเรียบง่ายกว่าที่ไม่มีตัวเลือกเหล่านี้
ตัวอย่างหนึ่งที่ฉันพบคือการสนับสนุน API หลายตัวที่ผู้ใช้เชื่อมต่อบัญชีของตนเอง สิ่งนี้จะกลายเป็นเรื่องยุ่งยากเมื่อ API ไม่ได้ใช้รูปแบบการตรวจสอบสิทธิ์แบบเดียวกัน บางคนอาจใช้ชื่อผู้ใช้+รหัสผ่าน ในขณะที่บางคนอาจใช้คีย์ API และยังมีคีย์ API ข้อมูลลับ และรหัสผู้ขายอีกด้วย วิธีหนึ่งคือการเพิ่มคอลัมน์ในตารางต่อไป โดยที่หลายๆ คอลัมน์จะเป็น null
สำหรับผู้ให้บริการส่วนใหญ่ การใช้ json
อย่างไรก็ตาม เราสามารถเก็บได้เฉพาะค่าที่ API เฉพาะต้องการ
โปรเจ็กต์ด้านข้างที่ฉันกำลังทำงานอยู่นั้นใช้ที่เก็บข้อมูล JSON เพื่อให้ผู้ใช้สามารถตั้งค่าแอตทริบิวต์ตามอำเภอใจของไอเท็ม ซึ่งรวมถึงแอตทริบิวต์ที่ผู้ใช้กำหนด ด้วยลักษณะที่ลื่นไหลและคาดเดาไม่ได้ของข้อมูลนี้ บางอย่างเช่นที่เก็บข้อมูล JSON (พร้อม store_accessor
สำหรับแอตทริบิวต์ที่รู้จัก) เป็นแบบธรรมชาติ
สรุป
ข้อมูล JSON (และตัวช่วยของ ActiveRecord รอบตัว) จะมีประโยชน์มากเมื่อข้อมูลและโครงสร้างข้อมูลเปลี่ยนแปลงได้หรือไม่สามารถรับรู้ได้ แน่นอนว่าการจัดเก็บข้อมูลประเภทนี้เป็นเหมือนการแลกเปลี่ยน ในขณะที่คุณมีความยืดหยุ่นมากในโครงสร้างข้อมูลสำหรับบันทึกเฉพาะ คุณละทิ้งความสมบูรณ์ของข้อมูลบางส่วนที่ข้อจำกัดของฐานข้อมูลสามารถมอบให้คุณได้ คุณยังลดความสามารถในการสืบค้นข้ามระเบียนด้วยการสืบค้น ActiveRecord ทั่วไป การรวม ฯลฯ
ต่อไปนี้คือข้อควรปฏิบัติบางประการ หากคุณ:
- รู้ว่าคีย์ JSON จะเหมือนกันทุกแถวหรือ
- กำลังจัดเก็บ ID (คีย์หลัก) ของตารางฐานข้อมูลอื่นๆ หรือ
- กำลังจัดเก็บค่าที่ใช้เพื่อค้นหาบันทึกจากตารางใน JSON
จากนั้น คุณควรสร้างตารางใหม่ที่สามารถใช้ประโยชน์จากฐานข้อมูลเพื่อบังคับใช้ความสมบูรณ์ของข้อมูลสำหรับคุณ อย่างไรก็ตาม หากคุณกำลังจัดเก็บข้อมูลเฉพาะแถวที่ไม่เกี่ยวข้องโดยตรงกับตารางอื่น JSON อาจช่วยให้คุณลดความซับซ้อนของโครงสร้างฐานข้อมูลได้