Rails中has_many等的原理
刚接触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转为调用对象本身的方法)