0

Tips, Tricks và Warnings với Rails Association

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.

  1. 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
  1. 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

Viblo
Let's register a Viblo Account to get more interesting posts.