Rails中has_many等的原理

10月 4th, 2014 5,698 留下评论 阅读评论

刚接触rails的时候着实被has_many给惊到,这么厉害的功能,尤其是has_many的延迟加载,直到真正需要读取数据的时候才会进行SQL查询(person.posts.size,并不会执行select *到内存后再对Array执行size),但是用普通的调试方法怎么看has_many的结果是一个Array,比如.class方法,然后就觉得很神奇。

看了源码之后,就了解has_many实现机制其实很简单,就是由一个代理类ActiveRecord::Associations::HasManyAssociation来实现的,真正的数据被封装在内部,真正需要用到数据的时候再去加载,这就是延迟加载;至于为什么看上去这么像Array或者说简直就是个Array是因为,HasManyAssociation算是一个白板类,删掉了从Object继承过来的大部分方法,包括.class方法,当你调用.class方法时就进入到了method_missing方法,然后就交给这个类内部的真正的数据结构Array去调用这个方法,自然表现出来就是一个Array了。所以要显示出这个类原来样貌,只要随意调用一个不存在的函数,比如person.posts.abcdefg(),就会提示HasManyAssociation没有这个方法了。

用法举例:

user.posts     // select * from

user.posts.size 或 user.posts.count  //select count(*) from

user.posts.length // select * from 后,再对Array取size;这个适用于需要先判断数量然后同时取数据(少量),省去count(*)这一条sql

user.posts << Post.new 或 user.posts.create // insert into ...

user.posts.clear 或 user.posts.destroy_all  // delete from ...

要尤其注意target的用法,因为HasOneAssociation本身就有target方法(用来获取Association对象本身),所以若对象本身又有target方法,则首先会调用Association对象的target方法。

class A
    has_one :relation, :class_name => 'B'
end

class B
    belong_to :target, :class_name => 'C'
end

a = A.find_by_id(1111)

a.relation => B的Association对象
p a.relation  =>    B(先调用Association.method_missing方法,转而调用B.inspect方法)

a.relation.target => 只是B对象
p a.relation.target     =>    B(直接调用B.inspect方法)

a.relation.target.target  =>  C的Association对象
p a.relation.target.target       =>    C

这里选择打印调试则会出现比较奇怪的现象(p a.relation和p a.relation.target的结果是一样的,是因为p和puts调试,调用inpect和to_s方法,都是Association没有的,会通过method_missing转为调用对象本身的方法)

Categories: ruby on rails 标签:
  1. 还没有评论呢。