Daily Archives: 21/11/2016

Rails – Quan hệ giữa model

Nếu bạn đã từng học lý thuyết về cơ sở dữ liệu quan hệ thì bạn sẽ biết là các bảng trong một CSDL sẽ có thể có các mối quan hệ với nhau, như quan hệ 1-1, 1-n, n-n…v.v, nếu bạn chưa biết về lý thuyết quan hệ này thì tự tìm hiểu thêm, trong bài này mình không giải thích mà chỉ hướng dẫn cách mô tả các mối quan hệ này trong Rails.

Trong các bài trước chúng ta đã tạo một model là product dùng để lưu thông tin của từng sản phẩm, model cart có chức năng lưu thông tin của một giỏ hàng (mặc dù không có trường nào), bây giờ chúng ta sẽ tạo một model dùng để kết nối thông tin giữa sản phẩm và giỏ hàng.

Model đó sẽ có tên line_item, chúng ta tạo model như sau:

C:\Projects\Rails\depot>rails generate scaffold line_item product_id:integer cart_id:integer
...

Model này sẽ có 2 cột dùng để lưu id của cart và product là product_idcart_id.

Tiếp theo chúng ta chạy lệnh rake db:migrate để tạo bảng.

C:\Projects\Rails\depot>rake db:migrate
== 20161121012142 CreateLineItems: migrating ==========================================
-- create_table(:line_items)
   -> 0.0150s
== 20161121012142 CreateLineItems: migrated (0.0158s) =================================

Vậy là chúng ta đã tạo xong bảng trong CSDL và lớp model, tuy nhiên hiện tại model lại không biết gì về các quan hệ này, chúng ta phải gọi thêm một số phương thức nữa, các phương thức mà chúng ta sẽ dùng được gọi là Associations.

Chúng ta sửa file cart.rb trong thư mục app/models như sau:

class Cart < ActiveRecord::Base
    has_many :line_items, :dependent => :destroy
end

Phương thức has_many :line_items chỉ định một đối tượng Cart sẽ có nhiều liên kết tới đối tượng LineItem, tham số :dependent => :destroy cho biết khi một đối tượng Cart bị hủy thì đối tượng LineItem đó cũng sẽ bị hủy theo.

TIếp theo chúng ta sửa file model line_item.rb như sau:

class LineItem < ActiveRecord::Base
    belongs_to :product
    belongs_to :cart
end

Phương thức belongs_to cho biết một đối tượng LineItem sẽ “thuộc về” một đối tượng nào đó, ở đây là ProductCart. Phương thức này bắt buộc Rails phải “hiểu” rằng sẽ không bao giờ tồn tại một đối tượng LineItem nào mà không liên kết/thuộc về một đối tượng CartProduct nào đó.

Khi một đối tượng LineItem “thuộc về” một đối tượng ProductCart thì chúng ta có thể truy xuất các đối tượng ProductCart đó trong đối tượng LineItem.

Cuối cùng chúng ta sửa file model product.rb như sau:

class Product < ActiveRecord::Base
    validates :title, :description, :image_url, :presence => true
    validates :price, :numericality => {:greater_than_or_equal_to => 1.0}
    validates :title, :uniqueness => true
    validates :image_url, :format => {
        :with => %r{\.(gif|jpg|png)\Z}i,
        :message => 'Chi nhan file GIF, JPG, PNG'
    }
 
    has_many :line_items
    before_destroy :check_if_has_line_item
 
private
 
    def check_if_has_line_item
        if line_items.empty?
            return true
        else
            errors.add(:base, 'This product has a LineItem')
            return false
        end
    end
end

Tương tự với model Cart, một đối tượng Product cũng sẽ có nhiều liên kết tới một đối tượng LineItem.

before_destroy :check_if_has_line_item

Ngoài ra ở đây chúng ta còn sử dụng tới một hàm Callback của lớp ActiveRecord::Basebefore_destroy, hàm Callback ở đây là các hàm sẽ được gọi trước hoặc sau khi Rails thực hiện một thao tác có cập nhật lên CSDL, chẳng hạn như before_destroy tức là gọi một phương thức nào đó trước khi hủy một dòng trong bảng, ở đây là phương thức check_if_has_line_item do chúng ta tự định nghĩa, phương thức này chúng ta để phạm vi truy cập là private.

private
 
    def check_if_has_line_item
        if line_items.empty?
            return true
        else
            errors.add(:base, 'This product has a LineItem')
            return false
        end
    end

Phương thức này sẽ kiểm tra xem một đối tượng Product có liên kết nào tới một đối tượng LineItem nào không, nếu không thì trả về true, ngược lại thì chúng ta tạo một phần tử trong thuộc tính errors có khóa là :base và giá trị là 'This product has a LineItem', rồi trả về false.

Nếu phương thức ở before_destroy trả về false thì Rails sẽ không hủy đối tượng đó.

Bạn có thể tìm hiểu thêm về các hàm callback ở đây:

http://guides.rubyonrails.org/active_record_callbacks.html

Bạn có thể tìm hiểu thêm về các phương thức Associations ở đây:

http://guides.rubyonrails.org/association_basics.html