诗歌rails之Hacking ActiveRecord

关键字: Hacking 看下面一个场景:
ruby代码
  1. # product.rb  
  2. class Product < ActiveRecord::Base  
  3.   validates_presence_of :price  
  4.   
  5.   def self.find_ordered  
  6.     find(:all, : order => 'name')  
  7.   end  
  8. end  
  9.   
  10. # product_test.rb  
  11. require File.dirname(__FILE__) + '/../test_helper'  
  12.   
  13. class ProductTest < Test::Unit::TestCase  
  14.   def test_find_ordered_should_order_products_by_name  
  15.     Product.delete_all  
  16.     basket = Product.create!(:name => 'Basket')  
  17.     apple = Product.create!(:name => 'Apple')  
  18.     assert_equal [apple, basket], Product.find_ordered  
  19.   end  
  20. end  
运行ProductTest,结果出错,因为Product的price有validates_presence_of声明

我们能否暂时将Product的validation关闭呢,比如:
ruby代码
  1. # product_test.rb  
  2. require File.dirname(__FILE__) + '/../test_helper'  
  3.   
  4. class ProductTest < Test::Unit::TestCase  
  5.   def test_find_ordered_should_order_products_by_name  
  6.     disable_validation do  
  7.       Product.delete_all  
  8.       basket = Product.create!(:name => 'Basket')  
  9.       apple = Product.create!(:name => 'Apple')  
  10.       assert_equal [apple, basket], Product.find_ordered  
  11.     end  
  12.   end  
  13. end  
好,就让我们一起来打开ActiveRecord的盒子,Hack一把:
ruby代码
  1. # test_helper.rb  
  2. class Test::Unit::TestCase  
  3.   self.use_transactional_fixtures = true  
  4.   self.use_instantiated_fixtures  = false  
  5.   
  6.   def disable_validation  
  7.     ActiveRecord::Base.disable_validation!  
  8.     yield  
  9.     ActiveRecord::Base.enable_validation!  
  10.   end  
  11. end  
  12.   
  13. module ValidationDisabler  
  14.   def self.included(base)  
  15.     base.class_eval do  
  16.       extend ClassMethods  
  17.       alias_method_chain :valid?, :disable_check  
  18.     end  
  19.   end  
  20.   
  21.   def valid_with_disable_check?  
  22.     if self.class.validation_disabled?  
  23.       true  
  24.     else  
  25.       valid_without_disable_check?  
  26.     end  
  27.   end  
  28.   
  29.   module ClassMethods  
  30.     def disable_validation!  
  31.       @@disable_validation = true  
  32.     end  
  33.   
  34.     def enable_validation!  
  35.       @@disable_validation = false  
  36.     end  
  37.   
  38.     def validation_disabled?  
  39.       @@disable_validation ||= false  
  40.     end  
  41.   end  
  42. end  
  43.   
  44. class ActiveRecord::Base  
  45.   include ValidationDisabler  
  46. end  
我们定义了disable_validation方法,先关闭validation,然后将控制权交给block执行,然后恢复validation
而ValidationDisabler模块的代码简直是优美无比!
我们首先定义self.included(base)方法
这个方法就像一个钩子,当下面我们打开ActiveRecord::Base类让它将ValidationDisabler include进来时触发
然后反过来让ActiveRecord::Base执行class_eval,让Base继承ValidationDisabler里的ClassMethods模块
并且给Base的valid?方法加上了AOP -- alias_method_chain

valid_with_disable_check?先判断validation_disabled?,如果返回true(即validation被disable掉了),则valid?方法返回true
如果validation_disabled?返回false(即validation没有disable),则调用valid_without_disable_check?(正常执行valid?方法)

ClassMethods模块里定义一个类变量@@disable_validation来决定是否关闭当前validation

如果大家留意Rails源码和许多Rails插件的源码的话,就会发现很多都是基于上述模式打开ActiveRecord、ActionController
等模块来修改Rails默认的行为的
posted @ 2009-07-10 11:20  麦飞  阅读(244)  评论(0编辑  收藏  举报