Override the default :eager_loader option for many_*_many associations to
work with an identity_map. If the :eager_graph association option is used,
you’ll probably have to use :uniq=>true on the current association
amd :cartesian_product_number=>2 on the association mentioned by
:eager_graph, otherwise you’ll end up with duplicates because the row
proc will be getting called multiple times for the same object. If you do
have duplicates and you use :eager_graph, they’ll probably be lost.
Making that work correctly would require changing a lot of the core
architecture, such as how graphing and eager graphing work.
44: def associate(type, name, opts = {}, &block)
45: if opts[:eager_loader]
46: super
47: elsif type == :many_to_many
48: opts = super
49: el = opts[:eager_loader]
50: model = self
51: left_pk = opts[:left_primary_key]
52: uses_lcks = opts[:uses_left_composite_keys]
53: uses_rcks = opts[:uses_right_composite_keys]
54: right = opts[:right_key]
55: join_table = opts[:join_table]
56: left = opts[:left_key]
57: lcks = opts[:left_keys]
58: left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
59: opts[:eager_loader] = lambda do |eo|
60: return el.call(eo) unless model.identity_map
61: h = eo[:key_hash][left_pk]
62: eo[:rows].each{|object| object.associations[name] = []}
63: r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
64: l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75: ds = opts.associated_class.inner_join(join_table, r + l)
76: pr = ds.row_proc
77: h2 = {}
78: ds.row_proc = proc do |hash|
79: hash_key = if uses_lcks
80: left_key_alias.map{|k| hash.delete(k)}
81: else
82: hash.delete(left_key_alias)
83: end
84: obj = pr.call(hash)
85: (h2[obj.object_id] ||= []) << hash_key
86: obj
87: end
88: model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
89: if hash_keys = h2.delete(assoc_record.object_id)
90: hash_keys.each do |hash_key|
91: if objects = h[hash_key]
92: objects.each{|object| object.associations[name].push(assoc_record)}
93: end
94: end
95: end
96: end
97: end
98: opts
99: elsif type == :many_through_many
100: opts = super
101: el = opts[:eager_loader]
102: model = self
103: left_pk = opts[:left_primary_key]
104: left_key = opts[:left_key]
105: uses_lcks = opts[:uses_left_composite_keys]
106: left_keys = Array(left_key)
107: left_key_alias = opts[:left_key_alias]
108: opts[:eager_loader] = lambda do |eo|
109: return el.call(eo) unless model.identity_map
110: h = eo[:key_hash][left_pk]
111: eo[:rows].each{|object| object.associations[name] = []}
112: ds = opts.associated_class
113: opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
114: ft = opts.final_reverse_edge
115: conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
116:
117:
118: ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
119: pr = ds.row_proc
120: h2 = {}
121: ds.row_proc = proc do |hash|
122: hash_key = if uses_lcks
123: left_key_alias.map{|k| hash.delete(k)}
124: else
125: hash.delete(left_key_alias)
126: end
127: obj = pr.call(hash)
128: (h2[obj.object_id] ||= []) << hash_key
129: obj
130: end
131: model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
132: if hash_keys = h2.delete(assoc_record.object_id)
133: hash_keys.each do |hash_key|
134: if objects = h[hash_key]
135: objects.each{|object| object.associations[name].push(assoc_record)}
136: end
137: end
138: end
139: end
140: end
141: opts
142: else
143: super
144: end
145: end