Rails là một framework lớn với rất nhiều công cụ tích hợp tiện dụng cho các tình huống cụ thể. Trong loạt bài này, chúng ta sẽ xem xét một số công cụ ít được biết đến ẩn trong cơ sở mã lớn của Rails.
Trong bài viết này, chúng tôi sẽ tập trung vào store
của ActiveRecord và store_accessor
các phương pháp. Cả hai phương pháp này đều nhằm vào trường hợp sử dụng để lưu trữ dữ liệu có cấu trúc trong một cột cơ sở dữ liệu, chẳng hạn như JSON hoặc YAML. Trong khi store_accessor
cung cấp cho chúng tôi một cách tiện dụng để lấy các giá trị từ những dữ liệu này mà không làm tắc nghẽn mô hình bằng các phương thức getter, store
tiến thêm một bước nữa và tuần tự hóa / giải mã hóa dữ liệu theo định dạng đã chọn của chúng tôi một cách minh bạch. Để hiểu điều này có thể hữu ích ở đâu, chúng ta cũng sẽ xem xét các tùy chọn để lưu trữ JSON trong cơ sở dữ liệu quan hệ và một số lý do bạn có thể muốn làm như vậy.
JSON trong Cơ sở dữ liệu
Tôi nên làm rõ rằng khi tôi nói 'cơ sở dữ liệu' trong bài viết này, tôi đang đề cập đến cơ sở dữ liệu quan hệ, đặc biệt là PostgreSQL và MySQL, vì chúng được sử dụng rộng rãi nhất trong cộng đồng Rails.
Người ta có thể hỏi tại sao bạn muốn lưu trữ JSON trong cơ sở dữ liệu quan hệ. Thật vậy, cách để tận dụng lợi ích của cơ sở dữ liệu quan hệ là chia nhỏ dữ liệu để mối quan hệ giữa chúng có thể được thực thi bởi cơ sở dữ liệu (ví dụ:khóa ngoại) và dữ liệu có thể được lập chỉ mục để cải thiện hiệu suất truy vấn.
Một nhược điểm của mô hình cơ sở dữ liệu quan hệ là cấu trúc dữ liệu phải được biết trước cả và giống hệt nhau cho mỗi hàng trong bảng. Nếu ứng dụng của bạn được xây dựng dựa trên dữ liệu không đáp ứng các yêu cầu này, bạn có thể muốn điều tra cơ sở dữ liệu NoSQL. Tuy nhiên, đối với hầu hết các ứng dụng web, chúng tôi muốn gắn bó với cơ sở dữ liệu quan hệ ~~ devil ~~ mà chúng tôi biết hầu hết dữ liệu và chỉ cần "rắc" vào các cấu trúc dữ liệu động này một cách thận trọng. Trong những trường hợp này, một cái gì đó như cột JSON có thể có ý nghĩa rất nhiều.
JSON so với JSONB
PostgreSQL có hai loại cột JSON:json
và jsonb
. Sự khác biệt chính là jsonb
được phân tích cú pháp tại thời điểm ghi, có nghĩa là dữ liệu được lưu trữ ở định dạng mà cơ sở dữ liệu có thể truy vấn nhanh hơn. Lưu ý là vì JSON đã được phân tích cú pháp nên khi xuất ra dưới dạng văn bản, nó có thể không còn khớp chính xác với những gì người dùng đã nhập. Ví dụ:các khóa trùng lặp có thể bị xóa hoặc thứ tự khóa có thể không khớp với khóa gốc.
Tài liệu PostgreSQL nói rằng trong hầu hết các trường hợp, jsonb
là những gì bạn muốn trừ khi bạn có một lý do cụ thể khác.
json
của MySQL cột hoạt động tương tự như jsonb
trong PostgreSQL. Để hỗ trợ đầu ra 'chỉ những gì người dùng đã nhập', bạn có thể phải sử dụng varchar
hoặc một cái gì đó tương tự.
JSON so với Văn bản
Ngoài việc cho phép phân tích trước dữ liệu, việc sử dụng cột JSON thay vì lưu trữ cùng một dữ liệu trong trường văn bản cho phép các truy vấn sử dụng chính dữ liệu đó. Ví dụ:bạn có thể truy vấn tất cả các bản ghi có một cặp khóa-giá trị cụ thể tồn tại trong cột. Lưu ý rằng bản thân Rails không hỗ trợ nhiều (nếu có) các truy vấn dành riêng cho JSON, vì chúng dành riêng cho cơ sở dữ liệu. Do đó, nếu bạn muốn tận dụng các tính năng này, bạn sẽ phải sử dụng các truy vấn SQL để làm như vậy.
Các cột JSON trong Rails
Rails có hỗ trợ tạo json
(và jsonb
trên PostgreSQL) cột trong di chuyển:
class CreateItems < ActiveRecord::Migration[7.0]
def change
create_table :items do |t|
t.jsonb :user_attributes
...
end
end
end
Khi đọc cột này, kết quả trả về là một 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"
Bây giờ chúng ta có thuộc tính Hash, bạn có thể muốn thêm một số phương thức trợ giúp vào mô hình để đọc / ghi các giá trị:
class Item < ApplicationRecord
def color=(value)
self.user_attributes["color"] = value
end
def color
user_attributes.dig("color")
end
end
Các phương thức như thế này hoạt động hoàn toàn tốt nhưng có thể nhanh chóng trở nên khó sử dụng nếu bạn có nhiều khóa JSON để xử lý; may mắn thay, Rails đã hỗ trợ chúng tôi.
Cửa hàng và store_accessor của ActiveRecord
Có hai khía cạnh của việc lưu trữ JSON trong cơ sở dữ liệu:tuần tự hóa và truy cập. Nếu bạn đang sử dụng json
-type cột trong cơ sở dữ liệu của bạn, thì bạn không cần phải lo lắng về khía cạnh tuần tự hóa. Rails và bộ điều hợp cơ sở dữ liệu sẽ xử lý nó cho bạn (bạn có thể chuyển thẳng đến store_accessor
). Nếu bạn đang lưu trữ dữ liệu trong một cột văn bản, thì store
của ActiveRecord phương pháp này dành cho bạn, đảm bảo rằng dữ liệu bạn ghi vào cột được tuần tự hóa thành định dạng bạn đã chọn.
Cửa hàng của ActiveRecord
ActiveRecord có store
phương pháp để tự động tuần tự hóa dữ liệu chúng tôi đọc hoặc ghi vào cột của mình:
class Item < ApplicationRecord
store :user_attributes, accessors: [:color], coder: JSON
end
Đây, :user_attributes
là cột chúng tôi muốn sử dụng, trong khi accessors
là danh sách các khóa mà chúng tôi muốn truy cập (chỉ là color
trong trường hợp của chúng tôi ở đây), và cuối cùng, chúng tôi chỉ định cách chúng tôi muốn dữ liệu được mã hóa. Chúng tôi đang sử dụng JSON, nhưng bạn có thể sử dụng bất kỳ thứ gì bạn thích ở đây, bao gồm những thứ như YAML hoặc mã hóa tùy chỉnh. Phương pháp này chỉ xử lý tuần tự hóa (với người viết mã đã chọn của bạn) và gọi store_accessor
dưới mui xe.
Store_accessor của ActiveRecord
Chúng tôi tạo các phương thức get / set trong mô hình của mình bằng cách sử dụng 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
Xin nhắc lại đây, user_attributes
là cột cơ sở dữ liệu mà chúng tôi muốn sử dụng, tiếp theo là khóa chúng tôi muốn sử dụng trong dữ liệu JSON và cuối cùng, chúng tôi có tùy chọn sử dụng tiền tố (hoặc hậu tố). Lưu ý rằng store_accessor
không hỗ trợ dữ liệu lồng nhau, chỉ các cặp khóa-giá trị cấp cao nhất. Tiền tố prefix
và hậu tố prefix
các tùy chọn nhận boolean, chuỗi hoặc ký hiệu. Nếu một boolean true
được chuyển, sau đó tên của cột được sử dụng làm tiền tố / hậu tố.
=>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"
Cách sử dụng trong Thế giới thực
Tôi chỉ thỉnh thoảng cần đi lạc khỏi lược đồ cơ sở dữ liệu quan hệ đã biết trước điển hình. Một vài lần tôi có, nó làm cho cấu trúc cơ sở dữ liệu sạch hơn và đơn giản hơn có thể nếu không có các tùy chọn này.
Một ví dụ mà tôi đã xem qua là hỗ trợ nhiều API nơi người dùng kết nối tài khoản của chính họ. Điều này trở nên phức tạp khi các API không sử dụng cùng một sơ đồ xác thực. Một số có thể sử dụng tên người dùng + mật khẩu, trong khi những người khác sử dụng khóa API và những người khác sử dụng khóa API, bí mật và ID người bán. Một cách tiếp cận là tiếp tục thêm các cột vào bảng, trong đó nhiều cột trong số đó sẽ là null
cho hầu hết các nhà cung cấp. Sử dụng json
tuy nhiên, chúng tôi chỉ có thể lưu trữ các giá trị mà một API cụ thể cần.
Một dự án phụ mà tôi đang thực hiện cũng sử dụng bộ nhớ JSON để cho phép người dùng đặt các thuộc tính tùy ý trên các mục, bao gồm cả các thuộc tính do người dùng xác định. Do tính chất linh hoạt và không thể đoán trước của dữ liệu này, giống như bộ nhớ JSON (với store_accessor
s cho các thuộc tính đã biết) là một sự phù hợp tự nhiên.
Tóm tắt
Dữ liệu JSON (và các trình trợ giúp của ActiveRecord xung quanh nó) có thể rất hữu ích khi dữ liệu và cấu trúc dữ liệu có thể thay đổi hoặc không thể biết được. Tất nhiên, loại lưu trữ dữ liệu này, giống như hầu hết mọi thứ, là một sự đánh đổi. Trong khi bạn nhận được rất nhiều sự linh hoạt trong cấu trúc dữ liệu cho các bản ghi cụ thể, bạn sẽ từ bỏ một số tính toàn vẹn của dữ liệu mà các ràng buộc cơ sở dữ liệu có thể cung cấp cho bạn. Bạn cũng giảm khả năng truy vấn trên các bản ghi bằng các truy vấn ActiveRecord điển hình, phép nối, v.v.
Dưới đây là một số quy tắc cơ bản, nếu bạn:
- Biết rằng các khóa JSON sẽ giống nhau cho tất cả các hàng hoặc
- Đang lưu trữ ID (khóa chính) của bất kỳ bảng cơ sở dữ liệu nào khác hoặc
- Đang lưu trữ một giá trị dùng để tra cứu bản ghi từ bảng trong JSON
Sau đó, bạn có thể tốt hơn nên tạo một bảng mới có thể tận dụng cơ sở dữ liệu để thực thi tính toàn vẹn của dữ liệu cho bạn. Tuy nhiên, nếu bạn đang lưu trữ dữ liệu theo hàng cụ thể không liên quan trực tiếp đến các bảng khác, thì JSON có thể giúp bạn đơn giản hóa cấu trúc cơ sở dữ liệu của mình.