Ruby On Rails 笔记6——常用回调上

回调允许你在对象状态发生变化之前或之后触发逻辑,它们是在对象生命周期某个确定时刻调用的方法。

代码感受一下

ruby 复制代码
class BirthdayCake < ApplicationRecord
  after_create -> { Rails.logger.info("Congratulations, the callback has run!") }
end
ruby 复制代码
irb> BirthdayCake.create
Congratulations, the callback has run!
1 Callback Registration

1.1 当普通方法调用

ruby 复制代码
class User < ApplicationRecord
  validates :username, :email, presence: true

  before_validation :ensure_username_has_value

  private
    def ensure_username_has_value
      if username.blank?
        self.username = email
      end
    end
end

1.2 当一个代码块调用。如果代码块内的代码很短,只需一行,可以考虑这种方式

ruby 复制代码
class User < ApplicationRecord
  validates :username, :email, presence: true

  before_validation do
    self.username = email if username.blank?
  end
end

1.3 也可以将一个 proc 传递给要触发的回调。

ruby 复制代码
class User < ApplicationRecord
  validates :username, :email, presence: true

  before_validation ->(user) { user.username = user.email if user.username.blank? }
end

1.4 也可以定义一个自定义回调对象。

ruby 复制代码
class User < ApplicationRecord
  validates :username, :email, presence: true

  before_validation AddUsername
end

class AddUsername
  def self.before_validation(record)
    if record.username.blank?
      record.username = record.email
    end
  end
end

1.5 回调也可以注册为仅在特定生命周期事件中触发,可以使用 :on 选项来完成,并允许完全控制何时以及在何种情况下触发回调。

ruby 复制代码
class User < ApplicationRecord
  validates :username, :email, presence: true

  before_validation :ensure_username_has_value, on: :create

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

  private
    def ensure_username_has_value
      if username.blank?
        self.username = email
      end
    end

    def set_location
      self.location = LocationService.query(self)
    end
end
2. Available Callbacks

2.1 Validation Callbacks.

before_validation/after_validation, 当记录直接通过valid?(或者别名validate)或invalid?方法验证时或者直接通过create, update ,save, 验证回调会被触发。

ruby 复制代码
class User < ApplicationRecord
  validates :name, presence: true
  before_validation :titleize_name
  after_validation :log_errors

  private
    def titleize_name
      self.name = name.downcase.titleize if name.present?
      Rails.logger.info("Name titleized to #{name}")
    end

    def log_errors
      if errors.any?
        Rails.logger.error("Validation failed: #{errors.full_messages.join(', ')}")
      end
    end
end
ruby 复制代码
irb> user = User.new(name: "", email: "john.doe@example.com", password: "abc123456")
=> #<User id: nil, email: "john.doe@example.com", created_at: nil, updated_at: nil, name: "">

irb> user.valid?
Name titleized to
Validation failed: Name can't be blank
=> false

2.2 Save Callbacks

ruby 复制代码
class User < ApplicationRecord
  before_save :hash_password
  around_save :log_saving
  after_save :update_cache

  private
    def hash_password
      self.password_digest = BCrypt::Password.create(password)
      Rails.logger.info("Password hashed for user with email: #{email}")
    end

    def log_saving
      Rails.logger.info("Saving user with email: #{email}")
      yield
      Rails.logger.info("User saved with email: #{email}")
    end

    def update_cache
      Rails.cache.write(["user_data", self], attributes)
      Rails.logger.info("Update Cache")
    end
end
ruby 复制代码
irb> user = User.create(name: "Jane Doe", password: "password", email: "jane.doe@example.com")

Password encrypted for user with email: jane.doe@example.com
Saving user with email: jane.doe@example.com
User saved with email: jane.doe@example.com
Update Cache
=> #<User id: 1, email: "jane.doe@example.com", created_at: "2024-03-20 16:02:43.685500000 +0000", updated_at: "2024-03-20 16:02:43.685500000 +0000", name: "Jane Doe">

2.3 Create Callbacks

ruby 复制代码
class User < ApplicationRecord
  before_create :set_default_role
  around_create :log_creation
  after_create :send_welcome_email

  private
    def set_default_role
      self.role = "user"
      Rails.logger.info("User role set to default: user")
    end

    def log_creation
      Rails.logger.info("Creating user with email: #{email}")
      yield
      Rails.logger.info("User created with email: #{email}")
    end

    def send_welcome_email
      UserMailer.welcome_email(self).deliver_later
      Rails.logger.info("User welcome email sent to: #{email}")
    end
end
ruby 复制代码
irb> user = User.create(name: "John Doe", email: "john.doe@example.com")

User role set to default: user
Creating user with email: john.doe@example.com
User created with email: john.doe@example.com
User welcome email sent to: john.doe@example.com
=> #<User id: 10, email: "john.doe@example.com", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe">

2.4 Update Callbacks

ruby 复制代码
class User < ApplicationRecord
  before_update :check_role_change
  around_update :log_updating
  after_update :send_update_email

  private
    def check_role_change
      if role_changed?
        Rails.logger.info("User role changed to #{role}")
      end
    end

    def log_updating
      Rails.logger.info("Updating user with email: #{email}")
      yield
      Rails.logger.info("User updated with email: #{email}")
    end

    def send_update_email
      UserMailer.update_email(self).deliver_later
      Rails.logger.info("Update email sent to: #{email}")
    end
end
ruby 复制代码
irb> user = User.find(1)
=> #<User id: 1, email: "john.doe@example.com", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe", role: "user" >

irb> user.update(role: "admin")
User role changed to admin
Updating user with email: john.doe@example.com
User updated with email: john.doe@example.com
Update email sent to: john.doe@example.com

2.5 Destroy Callbacks

ruby 复制代码
class User < ApplicationRecord
  before_destroy :check_admin_count
  around_destroy :log_destroy_operation
  after_destroy :notify_users

  private
    def check_admin_count
      if admin? && User.where(role: "admin").count == 1
        throw :abort
      end
      Rails.logger.info("Checked the admin count")
    end

    def log_destroy_operation
      Rails.logger.info("About to destroy user with ID #{id}")
      yield
      Rails.logger.info("User with ID #{id} destroyed successfully")
    end

    def notify_users
      UserMailer.deletion_email(self).deliver_later
      Rails.logger.info("Notification sent to other users about user deletion")
    end
end
ruby 复制代码
irb> user = User.find(1)
=> #<User id: 1, email: "john.doe@example.com", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe", role: "admin">

irb> user.destroy
Checked the admin count
About to destroy user with ID 1
User with ID 1 destroyed successfully
Notification sent to other users about user deletion

2.6 after_initialize and after_find

每当实例化Active Record对象时,无论是直接使用new还是从数据库加载记录,都会调用after_initialize回调,这对于避免直接覆盖Active Record 初始化方法非常有效.

如果after_initialize和after_find都定义了,after_find会先调用。

ruby 复制代码
class User < ApplicationRecord
  after_initialize do |user|
    Rails.logger.info("You have initialized an object!")
  end

  after_find do |user|
    Rails.logger.info("You have found an object!")
  end
end
ruby 复制代码
irb> User.new
You have initialized an object!
=> #<User id: nil>

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

2.7 after_touch

当Active Record对象被touched就会调用after_touch回调。

ruby 复制代码
class User < ApplicationRecord
  after_touch do |user|
    Rails.logger.info("You have touched an object")
  end
end
ruby 复制代码
irb> user = User.create(name: "Kuldeep")
=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49">

irb> user.touch
You have touched an object
=> true

可以与belongs_to一起使用:

ruby 复制代码
class Book < ApplicationRecord
  belongs_to :library, touch: true
  after_touch do
    Rails.logger.info("A Book was touched")
  end
end

class Library < ApplicationRecord
  has_many :books
  after_touch :log_when_books_or_library_touched

  private
    def log_when_books_or_library_touched
      Rails.logger.info("Book/Library was touched")
    end
end
ruby 复制代码
irb> book = Book.last
=> #<Book id: 1, library_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05">

irb> book.touch # triggers book.library.touch
A Book was touched
Book/Library was touched
=> true
3.Running Callbacks

下面这些方法触发回调:

  • create
  • create!
  • destroy
  • destroy!
  • destroy_all
  • destroy_by
  • save
  • save!
  • save(validate: false)
  • save!(validate: false)
  • toggle!
  • touch
  • update_attribute
  • update_attribute!
  • update
  • update!
  • valid?
  • validate

此外,以下查找方法也会触发 after_find 回调:

  • all
  • first
  • find
  • find_by
  • find_by!
  • find_by_*
  • find_by_*!
  • find_by_sql
  • last
  • sole
  • take

每次初始化类的新对象时,都会触发 after_initialize 回调。

相关推荐
by__csdn2 小时前
第一章 (ASP.NET Core入门)第三节( 认识.NET Standard)
后端·c#·asp.net·.net·.netcore·f#·vb.net
数据小馒头2 小时前
生成测试数据(一):分钟级构建百万级数据,测试数据库性能
后端
helloworld工程师2 小时前
Dubbo应用开发之基于Dubbo协议的springboot规范性开发
spring boot·后端·dubbo
码途进化论3 小时前
前端Docker多平台构建自动化实践
前端·javascript·后端
悟空码字3 小时前
SpringBoot 整合 RabbitMQ:和这只“兔子”交朋友
java·后端·rabbitmq
BingoGo3 小时前
万物皆字符串 PHP 中的原始类型偏执
后端·php
Carve_the_Code3 小时前
订单ID容量升级:从40位到64位的架构演进
后端
一粒麦仔3 小时前
物联网的低功耗守望者:全面解析Sigfox技术
后端·网络协议
Frank_zhou3 小时前
192_如何基于复杂的指针移动完成单向链表的入队?
后端