Module: Sequel::Plugins::IdentityMap::ClassMethods
- Defined in:
- lib/sequel/plugins/identity_map.rb
Instance Method Summary collapse
-
#associate(type, name, opts = {}, &block) ⇒ Object
Override the default :eager_loader option for many_*_many associations to work with an identity_map.
-
#call(row) ⇒ Object
If the identity map is in use, check it for a current copy of the object.
-
#identity_map ⇒ Object
Returns the current thread-local identity map.
-
#identity_map_key(pk) ⇒ Object
The identity map key for an object of the current class with the given pk.
-
#with_identity_map ⇒ Object
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.
Instance Method Details
#associate(type, name, opts = {}, &block) ⇒ Object
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/sequel/plugins/identity_map.rb', line 44 def associate(type, name, opts = {}, &block) if opts[:eager_loader] super elsif type == :many_to_many opts = super el = opts[:eager_loader] model = self left_pk = opts[:left_primary_key] uses_lcks = opts[:uses_left_composite_keys] uses_rcks = opts[:uses_right_composite_keys] right = opts[:right_key] join_table = opts[:join_table] left = opts[:left_key] lcks = opts[:left_keys] left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias opts[:eager_loader] = lambda do |eo| return el.call(eo) unless model.identity_map h = eo[:key_hash][left_pk] eo[:rows].each{|object| object.associations[name] = []} r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]] l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]] # Replace the row proc to remove the left key alias before calling the previous row proc. # Associate the value of the left key alias with the associated object (through its object_id). # When loading the associated objects, lookup the left key alias value and associate the # associated objects to the main objects if the left key alias value matches the left primary key # value of the main object. # # The deleting of the left key alias from the hash before calling the previous row proc # is necessary when an identity map is used, otherwise if the same associated object is returned more than # once for the association, it won't know which of current objects to associate it to. ds = opts.associated_class.inner_join(join_table, r + l) pr = ds.row_proc h2 = {} ds.row_proc = proc do |hash| hash_key = if uses_lcks left_key_alias.map{|k| hash.delete(k)} else hash.delete(left_key_alias) end obj = pr.call(hash) (h2[obj.object_id] ||= []) << hash_key obj end model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record| if hash_keys = h2.delete(assoc_record.object_id) hash_keys.each do |hash_key| if objects = h[hash_key] objects.each{|object| object.associations[name].push(assoc_record)} end end end end end opts elsif type == :many_through_many opts = super el = opts[:eager_loader] model = self left_pk = opts[:left_primary_key] left_key = opts[:left_key] uses_lcks = opts[:uses_left_composite_keys] left_keys = Array(left_key) left_key_alias = opts[:left_key_alias] opts[:eager_loader] = lambda do |eo| return el.call(eo) unless model.identity_map h = eo[:key_hash][left_pk] eo[:rows].each{|object| object.associations[name] = []} ds = opts.associated_class opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])} ft = opts[:final_reverse_edge] conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]] # See above comment in many_to_many eager_loader ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias]) pr = ds.row_proc h2 = {} ds.row_proc = proc do |hash| hash_key = if uses_lcks left_key_alias.map{|k| hash.delete(k)} else hash.delete(left_key_alias) end obj = pr.call(hash) (h2[obj.object_id] ||= []) << hash_key obj end model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record| if hash_keys = h2.delete(assoc_record.object_id) hash_keys.each do |hash_key| if objects = h[hash_key] objects.each{|object| object.associations[name].push(assoc_record)} end end end end end opts else super end end |
#call(row) ⇒ Object
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.
166 167 168 169 170 171 172 173 174 175 |
# File 'lib/sequel/plugins/identity_map.rb', line 166 def call(row) return super unless idm = identity_map if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})] o.merge_db_update(row) else o = super idm[identity_map_key(o.pk)] = o end o end |
#identity_map ⇒ Object
Returns the current thread-local identity map. Should be a hash if there is an active identity map, and nil otherwise.
149 150 151 |
# File 'lib/sequel/plugins/identity_map.rb', line 149 def identity_map Thread.current[:sequel_identity_map] end |
#identity_map_key(pk) ⇒ Object
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.
155 156 157 |
# File 'lib/sequel/plugins/identity_map.rb', line 155 def identity_map_key(pk) "#{self}:#{pk ? Array(pk).join(',') : "nil:#{rand}"}" end |
#with_identity_map ⇒ Object
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.
179 180 181 182 183 184 185 186 187 |
# File 'lib/sequel/plugins/identity_map.rb', line 179 def with_identity_map return yield if identity_map begin self.identity_map = {} yield ensure self.identity_map = nil end end |