12/08/2018, 13:06

Active Record Callbacks trong Rails

1. Vòng đời của một Object Trong suốt quá trình hoạt động của một ứng dụng Rails, Object có thể được tạo ra, cập nhật hoặc bị xóa bỏ. Active Record cung cấp cho bạn những phương thức (CallBacks) gắn vào vòng đời của mỗi Object giúp bạn có thể dễ dàng quản lý ứng dụng cũng như dữ liệu của ...

1. Vòng đời của một Object

Trong suốt quá trình hoạt động của một ứng dụng Rails, Object có thể được tạo ra, cập nhật hoặc bị xóa bỏ. Active Record cung cấp cho bạn những phương thức (CallBacks) gắn vào vòng đời của mỗi Object giúp bạn có thể dễ dàng quản lý ứng dụng cũng như dữ liệu của nó. CallBacks cho phép bạn kích hoạt một logic trước hoặc sau sự thay đổi trạng thái của một Object.

2. Tổng quan về CallBacks

CallBack là những phương thức được gọi những thời điểm nhất định trong vòng đời của một đối tượng. Với Callbacks có thể viết code mà sẽ chạy bất cứ khi nào một Active Record Object được tạo ra, lưu, cập nhật, xóa, xác nhận, hoặc tải từ cơ sở dữ liệu.

2.1 Khởi tạo CallBacks

Để sử dụng CallBacks trước hết phải khai báo nó trong Model của Object. Bạn có thể viết một phương thức thông thường rồi sử dụng macro-style để khai báo nó như một CallBacks. Ví dụ :

class User < ActiveRecord::Base
  validates :login, :email, presence: true

  before_validation :ensure_login_has_a_value

  protected
    def ensure_login_has_a_value
      if login.nil?
        self.login = email unless email.blank?
      end
    end
end

hoặc cũng có thể viết dưới dạng block:

class User < ActiveRecord::Base
  validates :login, :email, presence: true

  before_create do
    self.name = login.capitalize if name.blank?
  end
end

CallBacks cũng có thể được khai báo cho một sự kiện cụ thể trong vòng đời của 1 Object:

class User < ActiveRecord::Base
  before_validation :normalize_name, on: :create

  # :on takes an array as well
  after_validation :set_location, on: [ :create, :update ]

  protected
  def normalize_name
    self.name = self.name.downcase.titleize
  end

  def set_location
    self.location = LocationService.query(self)
  end
end

Hãy khai báo CallBacks là những phương thức private hay protected. Vì nếu bạn khai báo là một phương thức public thì CallBacks có thể được gọi từ ngoài Model và vi phạm các nguyên tắc đóng gói của lập trình hướng đối tượng.

3. Danh sách CallBacks

Dưới đây là danh sách tất cả các Active Record CallBacks có sẵn được liệt kê theo các sự kiện của một Object:

3.1 Creating an Object

    before_validation
    after_validation
    before_save
    around_save
    before_create
    around_create
    after_create
    after_save
    after_commit/after_rollback

3.2 Updating an Object

    before_validation
    after_validation
    before_save
    around_save
    before_update
    around_update
    after_update
    after_save
    after_commit/after_rollback

3.3 Destroying an Object

    before_destroy
    around_destroy
    after_destroy
    after_commit/after_rollback

Lưu ý : Phương thức after_save được chạy sau cả 2 sự kiện là create và update. Nếu cụ thể hơn bạn muốn dùng riêng CallBack cho từng sự kiện khác nhau thì bạn có thể sự dụng after_create và after_up##date.

3.4 after_initialize và after_find

CallBack after_initialize gọi khi một Active Record được khởi tạo hoặc được gọi ra từ cơ sở dữ liệu. Phương thức này tỏ ra hết sức hữu dụng nhầm tránh việc ghi đè trực tiếp lên dữ liệu. CallBack after_find sẽ được gọi là bất cứ khi nào gọi một Active Record từ cơ sở dữ liệu. after_find được gọi trước after_initialize nếu cả hai cùng được sử dụng. Không có callback before_initialize và before_find.

class User < ActiveRecord::Base
  after_initialize do |user|
    puts "You have initialized an object!"
  end

  after_find do |user|
    puts "You have found an object!"
  end
end

> User.new
You have initialized an object!
=> #<User id: nil>

> User.first
You have found an object!
You have initialized an object!
=> #<User id: 1>

3.5 after_touch

CallBack after_touch gọi khi một Active Record tác động vào. Ví dụ:

class User < ActiveRecord::Base
  after_touch do |user|
    puts "You have touched an object"
  end
end

> u = User.create name: "Kuldeep"
=> #<User id: 1, name: "Kuldeep", created_at: "2015-12-25 12:17:49", updated_at: "2015-12-25 12:17:49">

> u.touch
You have touched an object
=> true

Bạn cũng có thể sử dụng nó với quan hệ belongs_to

class Employee < ActiveRecord::Base
  belongs_to :company, touch: true
  after_touch do
    puts "An Employee was touched"
  end
end

class Company < ActiveRecord::Base
  has_many :employees
  after_touch :log_when_employees_or_company_touched

  private
  def log_when_employees_or_company_touched
    puts "Employee/Company was touched"
  end
end

> @employee = Employee.last
=> #<Employee id: 1, company_id: 1, created_at: "2015-12-25 17:04:22", updated_at: "2015-12-25 17:05:05">

# triggers @employee.company.touch
> @employee.touch
Employee/Company was touched
An Employee was touched
=> true

4. Chạy CallBacks

Các phương thức khi chạy sẽ kích hoạt chạy callbacks:

create
create!
decrement!
destroy
destroy!
destroy_all
increment!
save
save!
save(validate: false)
toggle!
update_attribute
update
update!
valid?

Thêm vào đó callback after_find sẽ được chạy sau khi cách phương thức find chạy:

all
first
find
find_by
find_by_*
find_by_*!
find_by_sql
last

5. Bỏ qua CallBacks

Giống như validations, CallBacks cũng có thể được bỏ qua bằng các phương thức :

decrement
decrement_counter
delete
delete_all
increment
increment_counter
toggle
touch
update_column
update_columns
update_all
update_counters

Chú ý: Bạn nên thận trọng khi sử dụng các phương thức này. Việc bỏ qua callback có thể dẫn đễn dữ liệu không hợp lệ nếu bạn để những xử lý logic quan trọng trong callback

6. CallBack cùng với Relational

CallBackscó thể làm việc thông qua các mối quan hệ. Giả sử một user có nhiều bài viết. Bài viết của user sẽ được xóa nếu người dùng bị xóa. Bạn có thể sử dụng callback để thực hiên điện điều đó :

class User < ActiveRecord::Base
  has_many :articles, dependent: :destroy
end

class Article < ActiveRecord::Base
  after_destroy :log_destroy_action

  def log_destroy_action
    puts "Article destroyed"
  end
end

> user = User.first
=> #<User id: 1>
> user.articles.create!
=> #<Article id: 1, user_id: 1>
> user.destroy
Article destroyed
=> #<User id: 1>

7. CallBacks có điều kiện

Giống như validation, bạn cũng có thế gọi callbacks với 1 điều kiện. Bạn có thể sử dụng :if hoặc :unless cùng với symbol, string, proc hoặc array.

7.1 Với symbol

Bạn có thể kết hợp :if và :unless với một symbol tương ứng với tên của một phương thức được gọi trước callback đó để quyết định việc callback đó có được thực hiện hay không. Đây là các thường xuyên được sử dụng nhất

class Order < ActiveRecord::Base
  before_save :normalize_card_number, if: :paid_with_card?
end

7.2 Với String

class Order < ActiveRecord::Base
  before_save :normalize_card_number, if: "paid_with_card?"
end

7.3 Với Proc

Cách này được sử dụng khi điều kiện của bạn sử dụng ngắn, thường là 1 dòng

class Order < ActiveRecord::Base
  before_save :normalize_card_number,
    if: Proc.new {|order| order.paid_with_card? }
end

7.4 Kiết hợp nhiều điều kiện

Rails cũng cho phép bạn kết hợp nhiều điều kiện lại với nhau. Ví dụ

class Comment < ActiveRecord::Base
  after_create :send_email_to_author, if: :author_wants_emails?,
    unless: Proc.new {|comment| comment.article.ignore_comments?}
end
0