## mongoid4.0.0去掉了IdentityMap
原来在mongoid3上做的belongs_to eager_load不能再用了
那我们看看mongoid4.0.0改如何修改
## 模型,假设一张脸有多个左眼和多个右眼吧,懒得改了^^
```ruby
class Face
include Mongoid::Document
field :name, type: String
has_many :left_eyes, class_name: "Eye", as: :eyeable
has_many :right_eyes, class_name: "Eye", as: :eyeable
end
class Eye
include Mongoid::Document
field :name, type: String
belongs_to :eyeable, polymorphic: true
end
```
## no eager_load
```ruby
faces = Face.all.to_a
faces.first.left_eyes
```
MOPED: 127.0.0.1:27017 QUERY runtime: 0.6560ms
[
#<Eye _id: 545985f06368650e08020000, name: "left eye", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>,
#<Eye _id: 545985f66368650e08030000, name: "left eye2", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>
]
## has_many: eager_load , 使用 .includes方法
```ruby
faces = Face.includes(:left_eyes).to_a
faces.first.left_eyes
```
[
#<Eye _id: 545985f06368650e08020000, name: "left eye", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>,
#<Eye _id: 545985f66368650e08030000, name: "left eye2", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>
]
## mongoid 4.0.0 支持has_many的eager_load,但是不支持 belongs_to
eg:
```ruby
eyes = Eye.includes(:eyeable).to_a
```
抛出异常
Mongoid::Errors::EagerLoad:
Problem:
Eager loading :eyeable is not supported since it is a
polymorphic belongs_to relation.
查看原代码,发现当belongs_to 后面接polymorphic参数的时候,会抛出异常
```ruby
module Mongoid
module Relations
module Eager
class BelongsTo < Base
def preload
raise Errors::EagerLoad.new(@metadata.name) if @metadata.polymorphic?
@docs.each do |d|
set_relation(d, nil)
end
each_loaded_document do |doc|
id = doc.send(key)
set_on_parent(id, doc)
end
...
end
end
end
end
end
```
那我们注意掉这句,看一下会发生什么?
NameError: uninitialized constant Eyeable
问题出在这里each_loaded_document, @metadata.klass => Eyeable
这个Eyeable是从哪里来的呢?就是从我们的模型Eye中:belongs_to :eyeable
each_loaded_document这个方法就是load依赖的模型数据了
eyes = Eye.includes(:eyeable).to_a
```ruby
module Mongoid
module Relations
module Eager
class Base
def each_loaded_document
@metadata.klass.any_in(key => keys_from_docs).each do |doc|
yield doc
end
end
end
end
end
end
```
找到需要预加载的Face,并通过set_on_parent与eye建立对应关系
@metadata.klass.any_in(key => keys_from_docs) =>
Face.any_in(_id => ['id1','id2'])
```ruby
each_loaded_document do |doc|
id = doc.send(key)
set_on_parent(id, doc)
end
```
建立eye对象和face对象的对应关系
#<Eye:0x007f837b55b670>, #<Face:0x007f837b4f2800>
#<Eye:0x007f837b55b5f8>, #<Face:0x007f837b4f2800>
```ruby
def set_on_parent(id, element)
grouped_docs[id].each do |d|
set_relation(d, element)
end
end
```
生成实例变量 @_eyealbe,当调用 eyes.first.eyealbe的时候,则直接返回face对象
而不需要再查询数据库
@_eyeable, #<Face:0x007f837b4f2800>
```ruby
def set_relation(name, relation)
instance_variable_set("@_#{name}", relation)
end
```
## 综上,处理belongs_to eager_load的做法:
1. 重写preload方法,去掉raise
lib/mongoid/eager_load.rb
```ruby
module Mongoid
module Relations
module Eager
class BelongsTo < Base
def preload
@docs.each do |d|
set_relation(d, nil)
end
each_loaded_document do |doc|
id = doc.send(key)
set_on_parent(id, doc)
end
...
end
end
end
end
end
```
2. 加载initializers/mongoid.rb
```ruby
require "mongoid/eager_load"
```
3. 在belongs_to一端,声明class_name
```ruby
class Eye
include Mongoid::Document
field :name, type: String
belongs_to :eyeable, class_name: "Face", polymorphic: true
end
```
## 测试:belongs_to
2.1.1 :004 > eyes = Eye.all.to_a
2.1.1 :005 > eyes.first.eyeable
MOPED: 127.0.0.1:27017 QUERY runtime: 0.6910ms
=> #<Face _id: 545983af6368650e08000000, name: "zhangsan">
2.1.1 :002 > eyes = Eye.includes(:eyeable).to_a
2.1.1 :003 > eyes.first.eyeable
=> #<Face _id: 545983af6368650e08000000, name: "zhangsan">
## 测试:has_many
2.1.1 :006 > faces = Face.includes(:left_eyes).to_a
2.1.1 :009 > faces.first.left_eyes
MOPED: 127.0.0.1:27017 QUERY runtime: 0.7480ms
=>
[
#<Eye _id: 545985f06368650e08020000, name: "left eye", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>,
#<Eye _id: 545985f66368650e08030000, name: "left eye2", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>
]
2.1.1 :006 > faces = Face.includes(:left_eyes).to_a
2.1.1 :007 > faces.first.left_eyes
=>
[
#<Eye _id: 545985f06368650e08020000, name: "left eye", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>,
#<Eye _id: 545985f66368650e08030000, name: "left eye2", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>
]
完善一下, 如果新增一个类Dog,那么belongs_to :eyeable, class_name: "Face", polymorphic: true
这里就不能制定class_name为Face了,因为它有可能是Dog
```ruby
class Dog
include Mongoid::Document
field :name, type: String
has_many :left_eyes, class_name: "Eye", as: :eyeable
has_many :right_eyes, class_name: "Eye", as: :eyeable
end
```
所以这个类名需要动态的添加,完整代码如下:
```ruby
module Mongoid
module Relations
module Eager
class Base
def each_loaded_document_with_polymorphic(&block)
if @metadata[:polymorphic]
#查找所有关联的eyeable_type: Dog,Face
@metadata.inverse_klass.any_in(group_by_key => keys_from_docs).distinct(group_by_key.gsub(/_id$/, '_type')).compact.each do |type|
#预加载Dog,Face对象
type.constantize.any_in("_id" => keys_from_docs).each do |doc|
yield doc
end
end
else
each_loaded_document_without_polymorphic(&block)
end
end
alias_method_chain :each_loaded_document, :polymorphic
end
end
end
end
```
测试:
2.1.1 :008 > eyes = Eye.includes(:eyeable).to_a
MOPED: 127.0.0.1:27017 QUERY runtime: 0.7560ms
MOPED: 127.0.0.1:27017 QUERY runtime: 0.7770ms
MOPED: 127.0.0.1:27017 QUERY runtime: 0.4450ms
=> [#<Eye _id: 545985f06368650e08020000, name: "left eye", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>, #<Eye _id: 545985f66368650e08030000, name: "left eye2", eyeable_type: "Face", eyeable_id: BSON::ObjectId('545983af6368650e08000000')>, #<Eye _id: 5459f17e636865148c010000, name: "lisi's eye", eyeable_type: "Face", eyeable_id: BSON::ObjectId('5459f121636865148c000000')>, #<Eye _id: 545b1deb63686517f2010000, name: "dog's left eye", eyeable_type: "Dog", eyeable_id: BSON::ObjectId('545b1dd463686517f2000000')>]
2.1.1 :009 > eyes.first.eyeable
=> #<Face _id: 545983af6368650e08000000, name: "zhangsan">
2.1.1 :010 > eyes.last.eyeable
=> #<Dog _id: 545b1dd463686517f2000000, name: "dog">
2.1.1 :011 >
------------------------------------------------------------------------------------------------------------------------------------
另外欢迎关注个人微信订阅号:ruby程序员, 方便大家坐车上下班无聊的时候,共同学习ruby & rails
-------------------------------------------------------------------------------------------------------------------------------------