Tips, Tricks và Warnings với Rails Association
Bài đăng này đã không được cập nhật trong 6 năm
Dưới đây là một vài điều bạn nên biết để sử dụng hiệu quả các Active Record associations trong ứng dụng của bạn:
- Kiểm soát bộ nhớ đệm
- Xung đột tên
- Cập nhật Schema
- Kiểm soát phạm vi của association
- Bi-directional associations
Kiểm soát bộ nhớ đệm
Tất cả các phương thức liên kết được xây dựng xung quanh bộ nhớ đệm, nó giữ kết quả của truy vấn gần nhất để có sẵn cho các hoạt động khác. Thậm chí cache còn được chia sẻ qua các phương thức. Ví dụ
author.books # retrieves books from the database
author.books.size # uses the cached copy of books
author.books.empty? # uses the cached copy of books
Nhưng nếu bạn muốn tải lại bộ nhớ cache thì sao, vì dữ liệu có thể đã được thay đổi bởi một số phần khác của ứng dụng? Bạn chỉ cần reload lại association.
author.books # retrieves books from the database
author.books.size # uses the cached copy of books
author.books.reload.empty? # discards the cached copy of books
# and goes back to the database
Xung đột tên
Bạn không thể sử dụng bất kì cái tên nào cho các association của bạn. Bởi vì khi bạn tạo một association sẽ thêm một số phương thức với tên đó vào model của bạn. Và thật là một ý tưởng tồi khi tạo một association với tên đã được sử dụng cho một instance method của ActiveRecord::Base. Association method sẽ ghi đè lên base method và phá vỡ mọi thứ. Ví dụ, attributes và connection là những tên tồi cho association.
Cập nhật Schema
Association rất hữu ích nhưng nó không phải là phép thuật. Bạn có trách nhiệm duy trì database schema của mình phù hợp với các association. Trong thực tế, điều này bao gồm 2 điều và tùy thuộc vào loại association mà bạn đang sử dụng. Đối với belongs_to associations bạn cần tạo foreign key và với has_and_belong_to_many association bạn cần tạo một join table.
- Tạo foreign key cho belongs_to association
Khi bạn định nghĩa một belongs_to assocition bạn cần tạo foreign key. Ví dụ, ta có model như sau:
class Book < ApplicationRecord
belongs_to :author
end
Khi báo này cần phải được hỗ trợ bằng việc khai báo foreign key trong bảng books
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.datetime :published_at
t.string :book_number
t.integer :author_id
end
end
end
Nếu bạn tạo association sau khi tạo model, ban phải nhớ tạo add_column migration để thêm foreign key.
Bạn cũng nên đánh index cho foreign key đó để cải thiện hiệu suất truy vấn và một ràng buộc để đảm bảo sự toàn vẹn dữ liệu:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.datetime :published_at
t.string :book_number
t.integer :author_id
end
add_index :books, :author_id
add_foreign_key :books, :authors
end
end
- Tạo join table cho has_and_belongs_to_many association
Nếu bạn tạo một has_and_belongs_to_many association bạn cần tạo một join table một các rõ ràng. Ngoại trừ trường hợp bạn chỉ định một cái tên rõ ràng cho bảng đó bằng cách dùng tùy chọn :join_table thì Active Record sẽ tạo bảng dựa trên class name. Ví dụ join table của books và authors sẽ có tên mặc định là authors_books bởi vì "a" đứng trước "b" trong bảng chữ cái.
Và dù tên là gì thì bạn cũng cần tự tạo migration để tạo ra join table. Ví dụ, ta có association như sau:
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
Bạn cần tạo bảng assemblies_parts và bảng này sẽ không có id.
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
def change
create_table :assemblies_parts, id: false do |t|
t.integer :assembly_id
t.integer :part_id
end
add_index :assemblies_parts, :assembly_id
add_index :assemblies_parts, :part_id
end
end
Chúng ta dùng tùy chọn id: false để tạo bảng vì bảng này sẽ không được thể hiện với 1 model. Bảng này tồn tại để đảm bảo cho association hoạt động một cách bình thường.
Bạn cũng có thể sử dụng phương thức create_join_table
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
def change
create_join_table :assemblies, :parts do |t|
t.index :assembly_id
t.index :part_id
end
end
end
Kiểm soát phạm vi của association
Mặc định, association chỉ tìm các đối tượng trong phạm vi của module hiện tại. Điều nay có thể rất quan trọng khi bạn khai báo model của Active Record trong module. Ví dụ:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account
end
class Account < ApplicationRecord
belongs_to :supplier
end
end
end
Ví dụ trên sẽ hoạt động bình thường vì Supplier và Account nằm trong cùng 1 phạm vi. Nhưng nó sẽ không hoạt động nếu Supplier và Account nằm ở các phạm vi khác nhau.
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account
end
end
module Billing
class Account < ApplicationRecord
belongs_to :supplier
end
end
end
Để liên kết đến một model nằm ở một namespace khác, bạn phải chỉ định tên đầy đủ trong khai báo:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account,
class_name: "MyApplication::Billing::Account"
end
end
module Billing
class Account < ApplicationRecord
belongs_to :supplier,
class_name: "MyApplication::Business::Supplier"
end
end
end
Bi-directional Associations (Mối quan hệ 2 chiều)
Thông thưởng các association sẽ hoạt động theo cả 2 chiều, chỉ cần bạn định nghĩa nó ở cả 2 model:
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :author
end
Active Record sẽ cố gắng tự động xác định rằng 2 model này có mối quan hệ 2 chiều dựa trên trên của association. Bằng cách này, Active Record sẽ chỉ tải 1 bản sao của object Author, làm cho ứng dụng của bạn hiệu quả hơn và ngăn ngừa dữ liệu không nhất quán.
a = Author.first
b = a.books.first
a.first_name == b.author.first_name # => true
a.first_name = 'David'
a.first_name == b.author.first_name # => true
Active Record hỗ trợ xác định tự động cho hầu hết các association với tên tiêu chuẩn. Tủy nhiên, Active Record sẽ không tự động xác định các assocition nào có chứa các option sau đây.
- :conditions
- :through
- :polymorphic
- :class_name
- :foreign_key
Ví dụ, ta có 2 model được định nghĩa như sau:
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
Khi đó Active Record không còn tự động xác định mối quan hệ 2 chiều nữa
a = Author.first
b = a.books.first
a.first_name == b.writer.first_name # => true
a.first_name = 'David'
a.first_name == b.writer.first_name # => false
Active Record cung cấp tùy chọn :inverse_of để bạn có thể khai báo một quan hệ 2 chiều 1 cách rõ ràng hơn:
class Author < ApplicationRecord
has_many :books, inverse_of: 'writer'
end
class Book < ApplicationRecord
belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
Khi đó mọi chuyện lại trở lại bình thường. Nhưng inverse_of cũng có một số hạn chế: Nó không hoạt động với :through associations, :polymorphic associations và :as associations.
Tiệu liệu tham khảo
Rails Guide: http://guides.rubyonrails.org/association_basics.html#tips-tricks-and-warnings
All rights reserved