12/08/2018, 15:52

ActiveRecord::Base.store Ưu nhược điểm và cách sử dụng

Store là một cách đơn giản để truy cập và lưu trữ các cặp giá trị key/value vào trong model. Trang API documentation đưa ra một ví dụ về bảng User có các thuộc tính settings. "Settings" không cần phải có một model để lưu trữ, nhưng vẫn cần một phương tiện để truy cập chúng, và đây là lúc chúng ...

Store là một cách đơn giản để truy cập và lưu trữ các cặp giá trị key/value vào trong model. Trang API documentation đưa ra một ví dụ về bảng User có các thuộc tính settings. "Settings" không cần phải có một model để lưu trữ, nhưng vẫn cần một phương tiện để truy cập chúng, và đây là lúc chúng ta sẽ sử dụng store.

Đằng sau bức màn, store chỉ đơn giản là một Hash được serialized và deserialized khi lưu và đọc. Có một vài phép truy cập được thêm vào để cho nó bớt hashy, nhưng bản chất thì hash đó vẫn tồn tại như chúng ta sẽ thấy sau này.

Trước bản Rails 3.2, để thực hiện lưu trữ giống chức năng này, bạn có 3 lựa chọn: - Tự thực thi nó - Muck bảng của bạn với một danh sách các field phụ khác - Tạo một bảng khác để lưu trữ các field

Chúng ta sẽ có một ví dụ nhỏ, trong ví dụ này, chúng ta sẽ cho phép người dùng chuyển đổi các loại email khác nhau mà họ muốn nhận. Điều đầu tiên chúng ta cần làm là thêm một trường, trong đó sẽ lưu các thiết lập

class AddContactSettingsToUsers < ActiveRecord::Migration
  def change
    add_column :users, :contact_settings, :string, :limit => 4096
  end
end

Có thể bạn sẽ cần một khoảng trống cho cài đặt của mình để đặt giới hạn string cho một số lớn. Ngoài ra, bạn có thể sử dụng text thay vì string nếu bạn thích, nhưng tôi có xu hướng chạy bảo thủ hơn.

Tiếp theo, chúng ta sẽ cần để cho model User biết chúng ta sẽ sử dụng trường contact_settings làm store.

class User < ActiveRecord::Base
  store :contact_settings, accessors: [ :daily_email, :weekly_email, :account_email ]
  
  has_many :owner_restaurants
end

Giống như tôi đã nói, các field được sử dụng như là store thực sự chỉ là một Hash. Bạn có thể thấy rằng trong console khi tôi lấy lại bản ghi người dùng đầu tiên:

irb(main):001:0>  u = User.first
  User Load (0.3ms)  SELECT `users`.* FROM `users` LIMIT 1
 => #<User id: 1, name: "John Galt", email: "john@galtsgulch.com", created_at: "2017-08-19 11:27:15", updated_at: "2017-08-19 11:27:15", contact_settings: {}> 
irb(main):001:1>  u.contact_settings
 => {} 
irb(main):001:2>  u.contact_settings.class
 => Hash 

Thay vì truy cập các thuộc tính thông qua contact_settings giống như một Hash bình thường, bạn truy cập chúng như thể chúng là thuộc tính của chính model User.

irb(main):001:1> u.weekly_email = true
=> true 
irb(main):001:2> u.account_email = true
=> true 
irb(main):001:3> u.daily_email = false
=> false 
irb(main):001:4> u
=> #<User id: 1, name: "John Galt", email: "john@galtsgulch.com", created_at: "2017-08-19 11:27:15", updated_at: "2017-08-19 11:27:15", contact_settings: {:weekly_email=>true, :account_email=>true, :daily_email=>false}> 
irb(main):001:5> u.contact_settings
=> {:weekly_email=>true, :account_email=>true, :daily_email=>false} 

Như đã đề cập trước đó, các field lưu trữ chỉ là các Hash. Điều này có nghĩa là bạn có thể truy cập chúng và sử dụng các phương thức trên chúng giống như bất kỳ Hash khác. Bạn thậm chí có thể thêm các thuộc tính không được định nghĩa trong store.

irb(main):001:1> u.contact_settings[:foo] = "bar"
irb(main):001:2> u.contact_settings
=> {:weekly_email=>true, :account_email=>true, :daily_email=>false, :foo=>"bar"}

Nếu chúng ta lưu bản ghi và nhìn vào nó trong cơ sở dữ liệu (không có: foo => "bar"), nó sẽ giống như thế này:

mysql> select * from users;
+----+--------------+----------------------+---------------------+---------------------+-------------------------------------------------------------------+
| id | name         | email                | created_at          | updated_at          | contact_settings                                                  |
+----+--------------+----------------------+---------------------+---------------------+-------------------------------------------------------------------+
|  1 | John Galt    | john@galtsgulch.com  | 2017-08-19 11:27:15 | 2017-08-19 11:27:15 | ---
:weekly_email: true
:account_email: true
:daily_email: false
|
|  2 | Howard Roark | howard@architect.com | 2017-08-19 11:27:16 | 2017-08-19 11:27:16 | NULL                                                              |
+----+--------------+----------------------+---------------------+---------------------+-------------------------------------------------------------------+

Tôi nghĩ rằng nhiều ưu điểm cho ActiveRecord.store là khá rõ ràng:

  • nó loại bỏ sự cần thiết cho một bảng khác hoặc lĩnh vực phụ;
  • đơn giản hoá việc thêm các thuộc tính mới;
  • chúng hoạt động giống như các thuộc tính model bình thường.

Có một số lý do chống lại việc sử dụng tính năng store. Đối số chính là dữ liệu thực sự không được chuẩn hóa. Nhưng chúng tôi đang sử dụng Rails, có nghĩa là chúng tôi có khuynh hướng lạm dụng tính bình thường vì lợi ích của ứng dụng. Một điểm trừ nữa là dirty attributes không hoạt động tốt trong Store. Ví dụ: bạn không thể gọi User#weekly_email_changed?. Điều duy nhất bạn có thể làm là kiểm tra xem trường Store có thay đổi không (ví dụ: User#contact_settings_changed?). Một lần nữa, nó không thực sự là một vấn đề lớn và tôi đoán rằng điều này sẽ được giải quyết trong bản nâng cấp trong tương lai. Nhược điểm chính đối với việc sử dụng store - và điều này thực sự là một vấn đề lớn - là bạn không thể thực hiện tìm kiếm hiệu quả trên các trường Store. Cách duy nhất để thực hiện tìm kiếm là bao quanh thuật ngữ tìm kiếm với các ký tự "%".

SELECT * FROM users WHERE contact_settings LIKE '%weekly_email: true%';

Nếu dấu "%" chỉ ở cuối, đó có thể là một điều, nhưng với "%" ở đầu thì bây giờ là cách tìm kiếm cơ sở dữ liệu có thể chậm nhất.

Tôi thực sự nghĩ rằng tính năng Store mới trong Rails 3.2 là một tính năng tốt. Họ đã làm tốt và sử dụng nó khá liền mạch (nghĩa là các thuộc tính store trông giống và hoạt động giống như bất kỳ thuộc tính nào khác). Nếu cơ sở dữ liệu của ứng dụng của bạn khá lớn hoặc nếu bạn có kế hoạch chạy nhiều truy vấn đối với các thuộc tính trong Store (ví dụ: thu thập nhiều số liệu), bạn có thể muốn sử dụng một bảng riêng biệt. Tuy nhiên đối với hầu hết các ứng dụng, đây vẫn là một giải pháp rất an toàn và hữu dụng.

0