| Module | Sequel::Plugins::IdentityMap::ClassMethods |
| In: |
lib/sequel/plugins/identity_map.rb
|
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 and :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.
# File lib/sequel/plugins/identity_map.rb, line 48
48: def associate(type, name, opts = {}, &block)
49: if opts[:eager_loader]
50: super
51: elsif type == :many_to_many
52: opts = super
53: el = opts[:eager_loader]
54: model = self
55: left_pk = opts[:left_primary_key]
56: uses_lcks = opts[:uses_left_composite_keys]
57: uses_rcks = opts[:uses_right_composite_keys]
58: right = opts[:right_key]
59: rcks = opts[:right_keys]
60: join_table = opts[:join_table]
61: left = opts[:left_key]
62: lcks = opts[:left_keys]
63: left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
64: opts[:eager_loader] = lambda do |eo|
65: return el.call(eo) unless model.identity_map
66: h = eo[:id_map]
67: eo[:rows].each{|object| object.associations[name] = []}
68: r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
69: l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
70:
71: # Replace the row proc to remove the left key alias before calling the previous row proc.
72: # Associate the value of the left key alias with the associated object (through its object_id).
73: # When loading the associated objects, lookup the left key alias value and associate the
74: # associated objects to the main objects if the left key alias value matches the left primary key
75: # value of the main object.
76: #
77: # The deleting of the left key alias from the hash before calling the previous row proc
78: # is necessary when an identity map is used, otherwise if the same associated object is returned more than
79: # once for the association, it won't know which of current objects to associate it to.
80: ds = opts.associated_class.inner_join(join_table, r + l)
81: pr = ds.row_proc
82: h2 = {}
83: ds.row_proc = proc do |hash|
84: hash_key = if uses_lcks
85: left_key_alias.map{|k| hash.delete(k)}
86: else
87: hash.delete(left_key_alias)
88: end
89: obj = pr.call(hash)
90: (h2[obj.object_id] ||= []) << hash_key
91: obj
92: end
93: model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
94: if hash_keys = h2.delete(assoc_record.object_id)
95: hash_keys.each do |hash_key|
96: if objects = h[hash_key]
97: objects.each{|object| object.associations[name].push(assoc_record)}
98: end
99: end
100: end
101: end
102: end
103: opts
104: elsif type == :many_through_many
105: opts = super
106: el = opts[:eager_loader]
107: model = self
108: left_pk = opts[:left_primary_key]
109: left_key = opts[:left_key]
110: uses_lcks = opts[:uses_left_composite_keys]
111: left_keys = Array(left_key)
112: left_key_alias = opts[:left_key_alias]
113: opts[:eager_loader] = lambda do |eo|
114: return el.call(eo) unless model.identity_map
115: h = eo[:id_map]
116: eo[:rows].each{|object| object.associations[name] = []}
117: ds = opts.associated_class
118: opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
119: ft = opts.final_reverse_edge
120: conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
121:
122: # See above comment in many_to_many eager_loader
123: ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
124: pr = ds.row_proc
125: h2 = {}
126: ds.row_proc = proc do |hash|
127: hash_key = if uses_lcks
128: left_key_alias.map{|k| hash.delete(k)}
129: else
130: hash.delete(left_key_alias)
131: end
132: obj = pr.call(hash)
133: (h2[obj.object_id] ||= []) << hash_key
134: obj
135: end
136: model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
137: if hash_keys = h2.delete(assoc_record.object_id)
138: hash_keys.each do |hash_key|
139: if objects = h[hash_key]
140: objects.each{|object| object.associations[name].push(assoc_record)}
141: end
142: end
143: end
144: end
145: end
146: opts
147: else
148: super
149: end
150: end
If the identity map is in use, check it for a current copy of the object. If a copy does not exist, create a new object and add it to the identity map. If a copy exists, add any values in the given row that aren‘t currently in the object to the object‘s values. This allows you to only request certain fields in an initial query, make modifications to some of those fields and request other, potentially overlapping fields in a new query, and not have the second query override fields you modified.
# File lib/sequel/plugins/identity_map.rb, line 172
172: def call(row)
173: return super unless (idm = identity_map) && (pk = primary_key)
174: if (k = identity_map_key(Array(pk).map{|x| row[x]})) && (o = idm[k])
175: o.merge_db_update(row)
176: else
177: o = super
178: if (k = identity_map_key(o.pk))
179: idm[k] = o
180: end
181: end
182: o
183: end
Returns the current thread-local identity map. Should be a hash if there is an active identity map, and nil otherwise.
# File lib/sequel/plugins/identity_map.rb, line 154
154: def identity_map
155: Thread.current[:sequel_identity_map]
156: end
The identity map key for an object of the current class with the given pk. May not always be correct for a class which uses STI.
# File lib/sequel/plugins/identity_map.rb, line 160
160: def identity_map_key(pk)
161: pk = Array(pk)
162: "#{self}:#{pk.join(',')}" unless pk.compact.empty?
163: end
Take a block and inside that block use an identity map to ensure a 1-1 correspondence of objects to the database row they represent.
# File lib/sequel/plugins/identity_map.rb, line 187
187: def with_identity_map
188: return yield if identity_map
189: begin
190: self.identity_map = {}
191: yield
192: ensure
193: self.identity_map = nil
194: end
195: end