TODAY I LEARNED by vàithằngnhốnhăng

Abstraction in Rails

Trong OOP có 4 khái niệm là:

  • Abstraction (trừu tượng)
  • Polymorphism (đa hình)
  • Inheritance (kế thừa)
  • Encapsulation (Bao đóng)

Cách nhớ là: 4 kí tự đầu tiên tạo thành chữ: A PIE

Bài này nói về Tính trừu tượng: Abstraction

Trong OOP nói chung

Trừu tượng hóa điểm tương đồng của các đối tượng để tạo Class là Base Ví dụ, class Ô tô, Xe máy, Xe đạp kế thừa từ Class Phương Tiện class Chó, Mèo kế thừa từ class Động Vật

Trong Rails nói riêng

class SomeAbstractModel < ApplicationRecord
  self.abstract_class = true

  # some heritable methods here
end

AbstractModel không có table tương ứng, nên không tạo object được

SomeAbstractClass.create!
# => NotImplementedError: (Vehicle is an abstract class and cannot be instantiated)

Cách dễ hình dung nhất về Abstract Class trong Rails là ActiveRecord::Base, nó không có bảng, nó được sử dụng để các Model kế thừa nó, nên các Model có những phương thức nó định nghĩa, như find, where

class AbstractModel < ActiveRecord::Base  
  self.abstract_class = true
end

class Foo < AbstractModel
end

class Bar < AbstractModel
end

# File activerecord/lib/active_record/base.rb, line 610
def find(*args)
  options = args.extract_options!
  validate_find_options(options)
  set_readonly_option!(options)

  case args.first
    when :first then find_initial(options)
    when :last  then find_last(options)
    when :all   then find_every(options)
    else             find_from_ids(args, options)
  end
end

Abstract Class và Module

Xét bài toán sau, có class Car, class Airplain v.v, có chung attribute weight, method convert_weight Nhưng cũng có attribute khác nhau, Car thì có bánh, Airplain thì không có bánh, mà có cánh quạt Thiết kế như nào?

  • Vấn đề của STI: sẽ có cột empty
class Vehicle < ApplicationRecord
  with_options presence: true, allow_blank: false do
    validates :weight
    validates :color
  end

  def convert_weight(unit)
    case unit
    when :lbs
      weight * 2.20462
    when :g
      weight * 1000.0
    end
  end
end

class Car < Vehicle
  validates :number_of_wheels, presence: true, allow_blank: false
end

class Airplane < Vehicle
  validates :number_of_wings, presence: true, allow_blank: false
end

migration cho bảng vehicles

create_table :vehicles do |t|
  # columns required by some children
  t.integer :number_of_wheels
  t.integer :number_of_wings
  # columns required by all children
  with_options null: false do
    t.string  :type # required for inheritence
    t.integer :weight
    t.string  :color
  end
end

Như vậy thì thằng Car thì number_of_wings null, thằng Airplane thì number_of_wheels null. Khi càng có nhiều attribute khác biệt thì càng có nhiều cột null

  • Abstract Class xử lý vấn đề đó như thế nào
class Vehicle < ApplicationRecord
  self.abstract_class = true
  with_options presence: true, allow_blank: false do
    validates :weight
    validates :color
  end
  def convert_weight(unit)
    case unit
    when :lbs
      weight * 2.20462
    when :g
      weight * 1000.0
    end
  end
end

Khác ví dụ trên thì dữ liệu của Car, Airplain sẽ lưu ở bảng cars, airplains tương ứng. Không có bảng vehicles

class CreateVehicles < ActiveRecord::Migration[5.2]
  def change
    create_table :cars do |t|
      with_options null: false do
        t.integer :weight
        t.string  :color
        t.integer :number_of_wheels
      end
    end
    create_table :airplanes do |t|
      with_options null: false do
        t.integer :weight
        t.string  :color
        t.integer :number_of_wings
      end
    end
  end
end

Lưu ý 1: Ở abstract model thì đôi khi chỉ khai báo prototype của method, logic cụ thể được định nghĩa ở Class con

Lưu ý 2: Các phương thức ở Abstract class có thể được đóng gói vào Module