可在“模型”类内直接指定回调处理器,你可以创建分离的处理器类,它封装所有回调
方法。这些处理器可以在多个“模型”间共享。一个处理器类是个简单的类,它定义回调方
法(before_save(),after_create(),等等)。在app/models 目录内为这些处理器类创建源
文件。
在“模型”对象内使用处理器,你创建这个处理器类的实例,并传递那个实例给各种回
调定义。几个例子会让这些更清楚些。
如果我们应用程序在多个地方使用信用卡,我们可能想在多个方法内共享我们的
normalize_credit_card_number()方法。要做到这点,我们要抽取方法到它自己的类中并在
我们希望它处理的事件名后命名它。这个方法将接受单个参数,回调被生成的“模型”对象。
class CreditCardCallbacks
# Normalize the credit card number
def before_validation(model)
model.cc_number.gsub!(/-w/, '')
end
end
现在,在我们的“模型”类中,我们可以重排这个被调用的共享的回调。
class Order < ActiveRecord::Base
before_validation CreditCardCallbacks.new
# ...
end
class Subscription < ActiveRecord::Base
before_validation CreditCardCallbacks.new
# ...
end
这个例子中,处理器类假设信用卡号码被保存在一个“模型”属性cc_number 内,Order
和Subscription 两者将有一个与之同名的属性。但是我们可以推广这个思想,让处理器类减
少对使用它的类的实现细节的依赖。
例如,我们可以创建一个普通的加密和解密处理器。这可以在它们被存储到数据库之前
加密名字字段,并在行被读回时解密它们。你可以做为一个回调处理器包括它在任何需要此
功能的“模型”内。
处理需要在“模型”数据被写回数据库之前,加密一个“模型”内给出的属性集。因为
我们的应用程序需要处理这些属性的文本文件,完成存储之间它重新排列及解密它们。它也
需要在行被从数据库读入到一个“模型”对象时解密这些数据。这些要求意味着我们需要在
保存数据和找到一个新行时解密数据库行,我们可以通过别名after_find()方法为
after_save()—同一个方法有两个名字,来保存代码。
class Encrypter
# We're passed a list of attributes that should
# be stored encrypted in the database
def initialize(attrs_to_manage)
@attrs_to_manage = attrs_to_manage
end
# Before saving or updating, encrypt the fields using the NSA and
# DHS approved Shift Cipher
def before_save(model)
@attrs_to_manage.each do |field|
model[field].tr!("a-z", "b-za")
end
end
# After saving, decrypt them back
def after_save(model)
@attrs_to_manage.each do |field|
model[field].tr!("b-za", "a-z")
end
end
# Do the same after finding an existing record
alias_method :after_find, :after_save
end
我们现在可以重排从我们的orders“模型”内调用的Encrypter 类。
require "encrypter"
class Order < ActiveRecord::Base
encrypter = Encrypter.new(:name, :email)
before_save encrypter
after_save encrypter
after_find encrypter
protected
def after_find
end
end
我们创建一个新的Encrypter 对象并用它钩住before_save,after_save,和after_find
事件。这种方式,在一个定单被保存之前,encrypter 内的方法before_save()将被调用,等
等。
那么,我们为什么要定义一个空的after_find()方法呢?记住我们说过,出于性能的原
因after_find 和after_initialize 被视为是特别的。这种特殊对待的结果之一是“活动记
录”不想知道调用一个after_finde 处理器,除非它在“模型”类中看到一个真实的
after_find()方法。我们必须定义一个空的占位符给after_find 处理。
This is all very well, but every model class that wants to make use of our
encryption handler would need to include some eight lines of code, 就像我们在
Order 类内做的那样。我们会做的更好,我们将定义一个帮助方法,它完成所有工作,并且
让那个帮助方法对所有“活动记录模型”都是有效的。要做到这些,我们将添加它到
ActiveRecord::Base 类中。
class ActiveRecord::Base
def self.encrypt(*attr_names)
encrypter = Encrypter.new(attr_names)
before_save encrypter
after_save encrypter
after_find encrypter
define_method(:after_find) { }
end
end
像这样,我们现在可以使用单个调用来添加encryption 给任何“模型”类的属性。
class Order < ActiveRecord::Base
encrypt(:name, :email)
end
一个简单的驱动程序让我们来检验这个。
o = Order.new
o.name = "Dave Thomas"
o.address = "123 The Street"
o.email = "dave@pragprog.com"
o.save
puts o.name
o = Order.find(o.id)
puts o.name
在控制台,我们在“模型”对象内看到我们的消费者的名字。
ar> ruby encrypt.rb
Dave Thomas
Dave Thomas
但是,在数据库中,很明显名字和email 地址被加密了。
方法。这些处理器可以在多个“模型”间共享。一个处理器类是个简单的类,它定义回调方
法(before_save(),after_create(),等等)。在app/models 目录内为这些处理器类创建源
文件。
在“模型”对象内使用处理器,你创建这个处理器类的实例,并传递那个实例给各种回
调定义。几个例子会让这些更清楚些。
如果我们应用程序在多个地方使用信用卡,我们可能想在多个方法内共享我们的
normalize_credit_card_number()方法。要做到这点,我们要抽取方法到它自己的类中并在
我们希望它处理的事件名后命名它。这个方法将接受单个参数,回调被生成的“模型”对象。
class CreditCardCallbacks
# Normalize the credit card number
def before_validation(model)
model.cc_number.gsub!(/-w/, '')
end
end
现在,在我们的“模型”类中,我们可以重排这个被调用的共享的回调。
class Order < ActiveRecord::Base
before_validation CreditCardCallbacks.new
# ...
end
class Subscription < ActiveRecord::Base
before_validation CreditCardCallbacks.new
# ...
end
这个例子中,处理器类假设信用卡号码被保存在一个“模型”属性cc_number 内,Order
和Subscription 两者将有一个与之同名的属性。但是我们可以推广这个思想,让处理器类减
少对使用它的类的实现细节的依赖。
例如,我们可以创建一个普通的加密和解密处理器。这可以在它们被存储到数据库之前
加密名字字段,并在行被读回时解密它们。你可以做为一个回调处理器包括它在任何需要此
功能的“模型”内。
处理需要在“模型”数据被写回数据库之前,加密一个“模型”内给出的属性集。因为
我们的应用程序需要处理这些属性的文本文件,完成存储之间它重新排列及解密它们。它也
需要在行被从数据库读入到一个“模型”对象时解密这些数据。这些要求意味着我们需要在
保存数据和找到一个新行时解密数据库行,我们可以通过别名after_find()方法为
after_save()—同一个方法有两个名字,来保存代码。
class Encrypter
# We're passed a list of attributes that should
# be stored encrypted in the database
def initialize(attrs_to_manage)
@attrs_to_manage = attrs_to_manage
end
# Before saving or updating, encrypt the fields using the NSA and
# DHS approved Shift Cipher
def before_save(model)
@attrs_to_manage.each do |field|
model[field].tr!("a-z", "b-za")
end
end
# After saving, decrypt them back
def after_save(model)
@attrs_to_manage.each do |field|
model[field].tr!("b-za", "a-z")
end
end
# Do the same after finding an existing record
alias_method :after_find, :after_save
end
我们现在可以重排从我们的orders“模型”内调用的Encrypter 类。
require "encrypter"
class Order < ActiveRecord::Base
encrypter = Encrypter.new(:name, :email)
before_save encrypter
after_save encrypter
after_find encrypter
protected
def after_find
end
end
我们创建一个新的Encrypter 对象并用它钩住before_save,after_save,和after_find
事件。这种方式,在一个定单被保存之前,encrypter 内的方法before_save()将被调用,等
等。
那么,我们为什么要定义一个空的after_find()方法呢?记住我们说过,出于性能的原
因after_find 和after_initialize 被视为是特别的。这种特殊对待的结果之一是“活动记
录”不想知道调用一个after_finde 处理器,除非它在“模型”类中看到一个真实的
after_find()方法。我们必须定义一个空的占位符给after_find 处理。
This is all very well, but every model class that wants to make use of our
encryption handler would need to include some eight lines of code, 就像我们在
Order 类内做的那样。我们会做的更好,我们将定义一个帮助方法,它完成所有工作,并且
让那个帮助方法对所有“活动记录模型”都是有效的。要做到这些,我们将添加它到
ActiveRecord::Base 类中。
class ActiveRecord::Base
def self.encrypt(*attr_names)
encrypter = Encrypter.new(attr_names)
before_save encrypter
after_save encrypter
after_find encrypter
define_method(:after_find) { }
end
end
像这样,我们现在可以使用单个调用来添加encryption 给任何“模型”类的属性。
class Order < ActiveRecord::Base
encrypt(:name, :email)
end
一个简单的驱动程序让我们来检验这个。
o = Order.new
o.name = "Dave Thomas"
o.address = "123 The Street"
o.email = "dave@pragprog.com"
o.save
puts o.name
o = Order.find(o.id)
puts o.name
在控制台,我们在“模型”对象内看到我们的消费者的名字。
ar> ruby encrypt.rb
Dave Thomas
Dave Thomas
但是,在数据库中,很明显名字和email 地址被加密了。