Module Sequel::Plugins::IdentityMap::ClassMethods
In: lib/sequel/plugins/identity_map.rb

Methods

Public Instance methods

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.

[Source]

     # File lib/sequel/plugins/identity_map.rb, line 44
 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[:id_map]
 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:               # Replace the row proc to remove the left key alias before calling the previous row proc.
 67:               # Associate the value of the left key alias with the associated object (through its object_id).
 68:               # When loading the associated objects, lookup the left key alias value and associate the
 69:               # associated objects to the main objects if the left key alias value matches the left primary key
 70:               # value of the main object.
 71:               # 
 72:               # The deleting of the left key alias from the hash before calling the previous row proc
 73:               # is necessary when an identity map is used, otherwise if the same associated object is returned more than
 74:               # once for the association, it won't know which of current objects to associate it to.
 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[:id_map]
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:               # See above comment in many_to_many eager_loader
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

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.

[Source]

     # File lib/sequel/plugins/identity_map.rb, line 166
166:         def call(row)
167:           return super unless idm = identity_map
168:           if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
169:             o.merge_db_update(row)
170:           else
171:             o = super
172:             idm[identity_map_key(o.pk)] = o
173:           end
174:           o
175:         end

Returns the current thread-local identity map. Should be a hash if there is an active identity map, and nil otherwise.

[Source]

     # File lib/sequel/plugins/identity_map.rb, line 149
149:         def identity_map
150:           Thread.current[:sequel_identity_map]
151:         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.

[Source]

     # File lib/sequel/plugins/identity_map.rb, line 155
155:         def identity_map_key(pk)
156:           "#{self}:#{pk ? Array(pk).join(',') : "nil:#{rand}"}"
157:         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.

[Source]

     # File lib/sequel/plugins/identity_map.rb, line 179
179:         def with_identity_map
180:           return yield if identity_map
181:           begin
182:             self.identity_map = {}
183:             yield
184:           ensure
185:             self.identity_map = nil
186:           end
187:         end

[Validate]