sequel 3.48.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +114 -0
  3. data/Rakefile +10 -7
  4. data/doc/association_basics.rdoc +25 -23
  5. data/doc/code_order.rdoc +7 -0
  6. data/doc/core_extensions.rdoc +0 -10
  7. data/doc/object_model.rdoc +4 -1
  8. data/doc/querying.rdoc +3 -3
  9. data/doc/release_notes/4.0.0.txt +262 -0
  10. data/doc/security.rdoc +0 -28
  11. data/doc/testing.rdoc +8 -14
  12. data/lib/sequel/adapters/ado.rb +7 -11
  13. data/lib/sequel/adapters/ado/access.rb +8 -8
  14. data/lib/sequel/adapters/ado/mssql.rb +4 -4
  15. data/lib/sequel/adapters/amalgalite.rb +6 -6
  16. data/lib/sequel/adapters/cubrid.rb +7 -7
  17. data/lib/sequel/adapters/db2.rb +5 -9
  18. data/lib/sequel/adapters/dbi.rb +2 -6
  19. data/lib/sequel/adapters/do.rb +4 -4
  20. data/lib/sequel/adapters/firebird.rb +4 -4
  21. data/lib/sequel/adapters/ibmdb.rb +8 -8
  22. data/lib/sequel/adapters/informix.rb +2 -10
  23. data/lib/sequel/adapters/jdbc.rb +17 -17
  24. data/lib/sequel/adapters/jdbc/as400.rb +2 -2
  25. data/lib/sequel/adapters/jdbc/cubrid.rb +1 -1
  26. data/lib/sequel/adapters/jdbc/db2.rb +1 -1
  27. data/lib/sequel/adapters/jdbc/derby.rb +1 -1
  28. data/lib/sequel/adapters/jdbc/h2.rb +2 -2
  29. data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
  30. data/lib/sequel/adapters/jdbc/informix.rb +1 -1
  31. data/lib/sequel/adapters/jdbc/mssql.rb +2 -2
  32. data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
  33. data/lib/sequel/adapters/jdbc/oracle.rb +5 -1
  34. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -3
  35. data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
  36. data/lib/sequel/adapters/jdbc/transactions.rb +3 -3
  37. data/lib/sequel/adapters/mock.rb +7 -7
  38. data/lib/sequel/adapters/mysql.rb +3 -3
  39. data/lib/sequel/adapters/mysql2.rb +4 -4
  40. data/lib/sequel/adapters/odbc.rb +2 -6
  41. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  42. data/lib/sequel/adapters/openbase.rb +1 -5
  43. data/lib/sequel/adapters/oracle.rb +13 -17
  44. data/lib/sequel/adapters/postgres.rb +20 -25
  45. data/lib/sequel/adapters/shared/cubrid.rb +3 -3
  46. data/lib/sequel/adapters/shared/db2.rb +2 -2
  47. data/lib/sequel/adapters/shared/firebird.rb +7 -7
  48. data/lib/sequel/adapters/shared/mssql.rb +9 -9
  49. data/lib/sequel/adapters/shared/mysql.rb +29 -13
  50. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +7 -7
  51. data/lib/sequel/adapters/shared/oracle.rb +22 -13
  52. data/lib/sequel/adapters/shared/postgres.rb +61 -46
  53. data/lib/sequel/adapters/shared/sqlite.rb +9 -9
  54. data/lib/sequel/adapters/sqlite.rb +17 -11
  55. data/lib/sequel/adapters/swift.rb +3 -3
  56. data/lib/sequel/adapters/swift/mysql.rb +1 -1
  57. data/lib/sequel/adapters/swift/sqlite.rb +1 -1
  58. data/lib/sequel/adapters/tinytds.rb +8 -8
  59. data/lib/sequel/ast_transformer.rb +3 -1
  60. data/lib/sequel/connection_pool.rb +4 -2
  61. data/lib/sequel/connection_pool/sharded_single.rb +2 -2
  62. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -5
  63. data/lib/sequel/connection_pool/threaded.rb +7 -7
  64. data/lib/sequel/core.rb +4 -67
  65. data/lib/sequel/database.rb +1 -0
  66. data/lib/sequel/database/connecting.rb +2 -8
  67. data/lib/sequel/database/dataset.rb +2 -7
  68. data/lib/sequel/database/dataset_defaults.rb +0 -18
  69. data/lib/sequel/database/features.rb +4 -4
  70. data/lib/sequel/database/misc.rb +6 -8
  71. data/lib/sequel/database/query.rb +5 -61
  72. data/lib/sequel/database/schema_generator.rb +22 -20
  73. data/lib/sequel/database/schema_methods.rb +48 -20
  74. data/lib/sequel/database/transactions.rb +7 -17
  75. data/lib/sequel/dataset.rb +2 -0
  76. data/lib/sequel/dataset/actions.rb +23 -91
  77. data/lib/sequel/dataset/features.rb +1 -4
  78. data/lib/sequel/dataset/graph.rb +3 -47
  79. data/lib/sequel/dataset/misc.rb +4 -33
  80. data/lib/sequel/dataset/prepared_statements.rb +3 -1
  81. data/lib/sequel/dataset/query.rb +116 -240
  82. data/lib/sequel/dataset/sql.rb +19 -97
  83. data/lib/sequel/deprecated.rb +0 -16
  84. data/lib/sequel/exceptions.rb +0 -3
  85. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  86. data/lib/sequel/extensions/columns_introspection.rb +1 -12
  87. data/lib/sequel/extensions/constraint_validations.rb +3 -3
  88. data/lib/sequel/extensions/core_extensions.rb +0 -9
  89. data/lib/sequel/extensions/date_arithmetic.rb +1 -2
  90. data/lib/sequel/extensions/graph_each.rb +11 -0
  91. data/lib/sequel/extensions/migration.rb +5 -5
  92. data/lib/sequel/extensions/null_dataset.rb +11 -13
  93. data/lib/sequel/extensions/pagination.rb +3 -6
  94. data/lib/sequel/extensions/pg_array.rb +6 -4
  95. data/lib/sequel/extensions/pg_array_ops.rb +35 -1
  96. data/lib/sequel/extensions/pg_json.rb +12 -2
  97. data/lib/sequel/extensions/pg_json_ops.rb +266 -0
  98. data/lib/sequel/extensions/pg_range.rb +2 -2
  99. data/lib/sequel/extensions/pg_range_ops.rb +0 -8
  100. data/lib/sequel/extensions/pg_row.rb +2 -2
  101. data/lib/sequel/extensions/pretty_table.rb +0 -4
  102. data/lib/sequel/extensions/query.rb +3 -8
  103. data/lib/sequel/extensions/schema_caching.rb +0 -7
  104. data/lib/sequel/extensions/schema_dumper.rb +10 -17
  105. data/lib/sequel/extensions/select_remove.rb +0 -4
  106. data/lib/sequel/extensions/set_overrides.rb +28 -0
  107. data/lib/sequel/extensions/to_dot.rb +6 -10
  108. data/lib/sequel/model.rb +6 -7
  109. data/lib/sequel/model/associations.rb +127 -182
  110. data/lib/sequel/model/base.rb +88 -211
  111. data/lib/sequel/model/errors.rb +0 -13
  112. data/lib/sequel/model/plugins.rb +2 -2
  113. data/lib/sequel/no_core_ext.rb +0 -1
  114. data/lib/sequel/plugins/after_initialize.rb +11 -17
  115. data/lib/sequel/plugins/association_autoreloading.rb +1 -47
  116. data/lib/sequel/plugins/association_dependencies.rb +2 -2
  117. data/lib/sequel/plugins/auto_validations.rb +2 -8
  118. data/lib/sequel/plugins/blacklist_security.rb +32 -2
  119. data/lib/sequel/plugins/caching.rb +1 -1
  120. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  121. data/lib/sequel/plugins/composition.rb +10 -8
  122. data/lib/sequel/plugins/constraint_validations.rb +2 -2
  123. data/lib/sequel/plugins/dataset_associations.rb +4 -0
  124. data/lib/sequel/plugins/defaults_setter.rb +8 -6
  125. data/lib/sequel/plugins/dirty.rb +6 -6
  126. data/lib/sequel/plugins/force_encoding.rb +13 -8
  127. data/lib/sequel/plugins/hook_class_methods.rb +1 -7
  128. data/lib/sequel/plugins/json_serializer.rb +13 -74
  129. data/lib/sequel/plugins/lazy_attributes.rb +2 -4
  130. data/lib/sequel/plugins/list.rb +1 -1
  131. data/lib/sequel/plugins/many_through_many.rb +4 -11
  132. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +1 -49
  133. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  134. data/lib/sequel/plugins/optimistic_locking.rb +3 -5
  135. data/lib/sequel/plugins/pg_array_associations.rb +453 -0
  136. data/lib/sequel/plugins/pg_typecast_on_load.rb +23 -7
  137. data/lib/sequel/plugins/prepared_statements.rb +1 -1
  138. data/lib/sequel/plugins/prepared_statements_associations.rb +20 -14
  139. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -2
  140. data/lib/sequel/plugins/rcte_tree.rb +1 -1
  141. data/lib/sequel/plugins/serialization.rb +5 -4
  142. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
  143. data/lib/sequel/plugins/sharding.rb +7 -1
  144. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  145. data/lib/sequel/plugins/timestamps.rb +1 -1
  146. data/lib/sequel/plugins/touch.rb +2 -2
  147. data/lib/sequel/plugins/tree.rb +1 -1
  148. data/lib/sequel/plugins/typecast_on_load.rb +19 -4
  149. data/lib/sequel/plugins/validation_class_methods.rb +0 -30
  150. data/lib/sequel/plugins/validation_helpers.rb +13 -31
  151. data/lib/sequel/plugins/xml_serializer.rb +18 -57
  152. data/lib/sequel/sql.rb +20 -22
  153. data/lib/sequel/version.rb +2 -2
  154. data/spec/adapters/db2_spec.rb +14 -23
  155. data/spec/adapters/firebird_spec.rb +25 -29
  156. data/spec/adapters/informix_spec.rb +11 -14
  157. data/spec/adapters/mssql_spec.rb +71 -77
  158. data/spec/adapters/mysql_spec.rb +165 -172
  159. data/spec/adapters/oracle_spec.rb +36 -39
  160. data/spec/adapters/postgres_spec.rb +175 -100
  161. data/spec/adapters/spec_helper.rb +13 -11
  162. data/spec/adapters/sqlite_spec.rb +36 -44
  163. data/spec/core/connection_pool_spec.rb +2 -1
  164. data/spec/core/database_spec.rb +55 -55
  165. data/spec/core/dataset_spec.rb +45 -249
  166. data/spec/core/deprecated_spec.rb +0 -8
  167. data/spec/core/expression_filters_spec.rb +23 -5
  168. data/spec/core/object_graph_spec.rb +4 -66
  169. data/spec/core/schema_spec.rb +35 -12
  170. data/spec/core/spec_helper.rb +3 -2
  171. data/spec/core_extensions_spec.rb +17 -19
  172. data/spec/extensions/arbitrary_servers_spec.rb +2 -3
  173. data/spec/extensions/association_dependencies_spec.rb +14 -14
  174. data/spec/extensions/auto_validations_spec.rb +7 -0
  175. data/spec/extensions/blacklist_security_spec.rb +5 -5
  176. data/spec/extensions/blank_spec.rb +2 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  178. data/spec/extensions/columns_introspection_spec.rb +2 -29
  179. data/spec/extensions/composition_spec.rb +10 -17
  180. data/spec/extensions/core_refinements_spec.rb +5 -1
  181. data/spec/extensions/dataset_associations_spec.rb +18 -0
  182. data/spec/extensions/date_arithmetic_spec.rb +2 -2
  183. data/spec/extensions/defaults_setter_spec.rb +9 -9
  184. data/spec/extensions/dirty_spec.rb +0 -5
  185. data/spec/extensions/eval_inspect_spec.rb +2 -0
  186. data/spec/extensions/force_encoding_spec.rb +2 -18
  187. data/spec/extensions/hash_aliases_spec.rb +8 -0
  188. data/spec/extensions/hook_class_methods_spec.rb +39 -58
  189. data/spec/extensions/inflector_spec.rb +2 -0
  190. data/spec/extensions/instance_filters_spec.rb +8 -8
  191. data/spec/extensions/json_serializer_spec.rb +1 -41
  192. data/spec/extensions/list_spec.rb +1 -1
  193. data/spec/extensions/many_through_many_spec.rb +106 -109
  194. data/spec/extensions/migration_spec.rb +2 -0
  195. data/spec/extensions/named_timezones_spec.rb +1 -0
  196. data/spec/extensions/pg_array_associations_spec.rb +603 -0
  197. data/spec/extensions/pg_array_ops_spec.rb +25 -0
  198. data/spec/extensions/pg_array_spec.rb +9 -1
  199. data/spec/extensions/pg_hstore_ops_spec.rb +13 -0
  200. data/spec/extensions/pg_hstore_spec.rb +1 -0
  201. data/spec/extensions/pg_json_ops_spec.rb +131 -0
  202. data/spec/extensions/pg_json_spec.rb +10 -4
  203. data/spec/extensions/pg_range_ops_spec.rb +2 -5
  204. data/spec/extensions/pg_range_spec.rb +6 -2
  205. data/spec/extensions/pg_row_ops_spec.rb +2 -0
  206. data/spec/extensions/prepared_statements_associations_spec.rb +26 -5
  207. data/spec/extensions/rcte_tree_spec.rb +15 -15
  208. data/spec/extensions/schema_dumper_spec.rb +0 -1
  209. data/spec/extensions/schema_spec.rb +9 -9
  210. data/spec/extensions/serialization_modification_detection_spec.rb +1 -1
  211. data/spec/extensions/serialization_spec.rb +18 -29
  212. data/spec/extensions/set_overrides_spec.rb +4 -0
  213. data/spec/extensions/{many_to_one_pk_lookup_spec.rb → shared_caching_spec.rb} +1 -4
  214. data/spec/extensions/single_table_inheritance_spec.rb +4 -4
  215. data/spec/extensions/spec_helper.rb +8 -9
  216. data/spec/extensions/sql_expr_spec.rb +2 -0
  217. data/spec/extensions/string_date_time_spec.rb +2 -0
  218. data/spec/extensions/string_stripper_spec.rb +2 -0
  219. data/spec/extensions/tactical_eager_loading_spec.rb +12 -12
  220. data/spec/extensions/thread_local_timezones_spec.rb +2 -0
  221. data/spec/extensions/timestamps_spec.rb +1 -1
  222. data/spec/extensions/to_dot_spec.rb +1 -1
  223. data/spec/extensions/touch_spec.rb +24 -24
  224. data/spec/extensions/tree_spec.rb +7 -7
  225. data/spec/extensions/typecast_on_load_spec.rb +8 -1
  226. data/spec/extensions/update_primary_key_spec.rb +10 -10
  227. data/spec/extensions/validation_class_methods_spec.rb +10 -39
  228. data/spec/extensions/validation_helpers_spec.rb +29 -47
  229. data/spec/extensions/xml_serializer_spec.rb +1 -23
  230. data/spec/integration/associations_test.rb +231 -40
  231. data/spec/integration/database_test.rb +1 -1
  232. data/spec/integration/dataset_test.rb +64 -64
  233. data/spec/integration/eager_loader_test.rb +28 -28
  234. data/spec/integration/migrator_test.rb +1 -1
  235. data/spec/integration/model_test.rb +2 -2
  236. data/spec/integration/plugin_test.rb +21 -21
  237. data/spec/integration/prepared_statement_test.rb +7 -7
  238. data/spec/integration/schema_test.rb +115 -110
  239. data/spec/integration/spec_helper.rb +17 -27
  240. data/spec/integration/timezone_test.rb +1 -1
  241. data/spec/integration/transaction_test.rb +10 -10
  242. data/spec/integration/type_test.rb +2 -2
  243. data/spec/model/association_reflection_spec.rb +2 -28
  244. data/spec/model/associations_spec.rb +239 -188
  245. data/spec/model/base_spec.rb +27 -68
  246. data/spec/model/dataset_methods_spec.rb +4 -4
  247. data/spec/model/eager_loading_spec.rb +160 -172
  248. data/spec/model/hooks_spec.rb +62 -79
  249. data/spec/model/model_spec.rb +36 -51
  250. data/spec/model/plugins_spec.rb +5 -19
  251. data/spec/model/record_spec.rb +125 -151
  252. data/spec/model/spec_helper.rb +8 -6
  253. data/spec/model/validations_spec.rb +4 -17
  254. data/spec/spec_config.rb +2 -10
  255. metadata +50 -56
  256. data/lib/sequel/deprecated_core_extensions.rb +0 -135
  257. data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -185
  258. data/lib/sequel/extensions/pg_statement_cache.rb +0 -318
  259. data/lib/sequel/plugins/identity_map.rb +0 -260
  260. data/lib/sequel_core.rb +0 -2
  261. data/lib/sequel_model.rb +0 -2
  262. data/spec/extensions/association_autoreloading_spec.rb +0 -102
  263. data/spec/extensions/identity_map_spec.rb +0 -337
  264. data/spec/extensions/pg_auto_parameterize_spec.rb +0 -70
  265. data/spec/extensions/pg_statement_cache_spec.rb +0 -208
  266. data/spec/rcov.opts +0 -8
  267. data/spec/spec_config.rb.example +0 -10
@@ -1,260 +0,0 @@
1
- module Sequel
2
- module Plugins
3
- # The identity_map plugin allows the user to create temporary identity maps
4
- # via the with_identity_map method, which takes a block. Inside the block,
5
- # objects have a 1-1 correspondence with rows in the database.
6
- #
7
- # For example, the following is true, and wouldn't be true if you weren't
8
- # using the identity map:
9
- # Sequel::Model.with_identity_map do
10
- # Album.filter{(id > 0) & (id < 2)}.first.object_id == Album.first(:id=>1).object_id
11
- # end
12
- #
13
- # In addition to providing a 1-1 correspondence, the identity_map plugin
14
- # also provides a cached looked up of records in two cases:
15
- # * Model.[] (e.g. Album[1])
16
- # * Model.many_to_one accessor methods (e.g. album.artist)
17
- #
18
- # If the object you are looking up, using one of those two methods, is already
19
- # in the identity map, the record is returned without a database query being
20
- # issued.
21
- #
22
- # Identity maps are thread-local and only persist for the duration of the block,
23
- # so they should only be considered as a possible performance enhancer.
24
- #
25
- # The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
26
- #
27
- # Usage:
28
- #
29
- # # Use an identity map that will affect all model classes (called before loading subclasses)
30
- # Sequel::Model.plugin :identity_map
31
- #
32
- # # Use an identity map just for the Album class
33
- # Album.plugin :identity_map
34
- # # would need to do Album.with_identity_map{} to use the identity map
35
- module IdentityMap
36
- def self.apply(mod)
37
- Sequel::Deprecation.deprecate('The identity_map plugin', 'Please stop loading it') unless defined?(SEQUEL_EXTENSIONS_NO_DEPRECATION_WARNING)
38
- end
39
-
40
- module ClassMethods
41
- # Override the default :eager_loader option for many_*_many associations to work
42
- # with an identity_map. If the :eager_graph association option is used, you'll probably have to use
43
- # :uniq=>true on the current association and :cartesian_product_number=>2 on the association
44
- # mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
45
- # getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
46
- # they'll probably be lost. Making that work correctly would require changing a lot of the core
47
- # architecture, such as how graphing and eager graphing work.
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
151
-
152
- # Returns the current thread-local identity map. Should be a hash if
153
- # there is an active identity map, and nil otherwise.
154
- def identity_map
155
- Thread.current[:sequel_identity_map]
156
- end
157
-
158
- # The identity map key for an object of the current class with the given pk.
159
- # May not always be correct for a class which uses STI.
160
- def identity_map_key(pk)
161
- pk = Array(pk)
162
- "#{self}:#{pk.join(',')}" unless pk.compact.empty?
163
- end
164
-
165
- # If the identity map is in use, check it for a current copy of the object.
166
- # If a copy does not exist, create a new object and add it to the identity map.
167
- # If a copy exists, add any values in the given row that aren't currently
168
- # in the object to the object's values. This allows you to only request
169
- # certain fields in an initial query, make modifications to some of those
170
- # fields and request other, potentially overlapping fields in a new query,
171
- # and not have the second query override fields you modified.
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
184
-
185
- # Take a block and inside that block use an identity map to ensure a 1-1
186
- # correspondence of objects to the database row they represent.
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
196
-
197
- private
198
-
199
- # Set the thread local identity map to the given value.
200
- def identity_map=(v)
201
- Thread.current[:sequel_identity_map] = v
202
- end
203
-
204
- # Check the current identity map if it exists for the object with
205
- # the matching pk. If one is found, return it, otherwise call super.
206
- def primary_key_lookup(pk)
207
- ((idm = identity_map) && (k = identity_map_key(pk)) && (o = idm[k])) ? o : super
208
- end
209
- end
210
-
211
- module InstanceMethods
212
- # Remove instances from the identity map cache if they are deleted.
213
- def delete
214
- super
215
- if (idm = model.identity_map) && (k = model.identity_map_key(pk))
216
- idm.delete(k)
217
- end
218
- self
219
- end
220
-
221
- # Merge the current values into the values provided in the row, ensuring
222
- # that current values are not overridden by new values.
223
- def merge_db_update(row)
224
- @values = row.merge(@values)
225
- end
226
-
227
- private
228
-
229
- # The primary keys values of the associated object, given the foreign
230
- # key columns(s).
231
- def _associated_object_pk(fk)
232
- fk.is_a?(Array) ? fk.map{|c| send(c)} : send(fk)
233
- end
234
-
235
- # If the association is a many_to_one and it has a :key option and the
236
- # key option has a value and the association uses the primary key of
237
- # the associated class as the :primary_key option, check the identity
238
- # map for the associated object and return it if present.
239
- def _load_associated_object(opts, dynamic_opts)
240
- klass = opts.associated_class
241
- cache_lookup = opts.fetch(:idm_cache_lookup) do
242
- opts[:idm_cache_lookup] = klass.respond_to?(:identity_map) &&
243
- opts[:type] == :many_to_one &&
244
- opts[:key] &&
245
- opts.primary_key == klass.primary_key
246
- end
247
- if cache_lookup &&
248
- !dynamic_opts[:callback] &&
249
- (idm = klass.identity_map) &&
250
- (k = klass.identity_map_key(_associated_object_pk(opts[:key]))) &&
251
- (o = idm[k])
252
- o
253
- else
254
- super
255
- end
256
- end
257
- end
258
- end
259
- end
260
- end
@@ -1,2 +0,0 @@
1
- require 'sequel/core'
2
- Sequel::Deprecation.deprecate('require "sequel_core"', 'Switch to require "sequel/core"')
@@ -1,2 +0,0 @@
1
- require 'sequel/model'
2
- Sequel::Deprecation.deprecate('require "sequel_model"', 'Switch to require "sequel"')
@@ -1,102 +0,0 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
-
3
- describe "AssociationAutoreloading plugin" do
4
- before do
5
- @c = Class.new(Sequel::Model)
6
- @c.plugin :association_autoreloading
7
- @Artist = Class.new(@c).set_dataset(:artists)
8
- @Artist.dataset._fetch = {:id=>2, :name=>'Ar'}
9
- @Album = Class.new(@c).set_dataset(:albums)
10
- @Artist.columns :id, :name
11
- @Album.columns :id, :name, :artist_id
12
- @Album.db_schema[:artist_id][:type] = :integer
13
- @Album.many_to_one :artist, :class=>@Artist
14
- MODEL_DB.reset
15
- end
16
-
17
- specify "should reload many_to_one association when foreign key is modified" do
18
- album = @Album.load(:id => 1, :name=>'Al', :artist_id=>2)
19
- album.artist
20
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1']
21
-
22
- album.artist_id = 1
23
- album.artist
24
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 1) LIMIT 1']
25
- end
26
-
27
- specify "should handle multiple many_to_one association with the same foreign key" do
28
- @Album.many_to_one :artist2, :key=>:artist_id, :class=>@Artist
29
- album = @Album.load(:id => 1, :name=>'Al', :artist_id=>2)
30
- album.artist
31
- album.artist2
32
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1'] * 2
33
-
34
- album.artist
35
- album.artist2
36
- MODEL_DB.sqls.should == []
37
-
38
- album.artist_id = 1
39
- album.artist
40
- album.artist2
41
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 1) LIMIT 1'] * 2
42
- end
43
-
44
- specify "should not reload when value has not changed" do
45
- album = @Album.load(:id => 1, :name=>'Al', :artist_id=>2)
46
- album.artist
47
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1']
48
-
49
- album.artist_id = 2
50
- album.artist
51
- MODEL_DB.sqls.should == []
52
-
53
- album.artist_id = "2"
54
- album.artist
55
- MODEL_DB.sqls.should == []
56
- end
57
-
58
- specify "should reload all associations which use the foreign key" do
59
- @Album.many_to_one :other_artist, :key => :artist_id, :foreign_key => :id, :class => @Artist
60
- album = @Album.load(:id => 1, :name=>'Al', :artist_id=>2)
61
- album.artist
62
- album.other_artist
63
- MODEL_DB.reset
64
-
65
- album.artist_id = 1
66
- album.artist
67
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 1) LIMIT 1']
68
-
69
- album.other_artist
70
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 1) LIMIT 1']
71
- end
72
-
73
- specify "should work with composite keys" do
74
- @Album.many_to_one :composite_artist, :key => [:artist_id, :name], :primary_key => [:id, :name], :class => @Artist
75
- album = @Album.load(:id => 1, :name=>'Al', :artist_id=>2)
76
- album.composite_artist
77
- MODEL_DB.reset
78
-
79
- album.artist_id = 1
80
- album.composite_artist
81
- MODEL_DB.sqls.should == ["SELECT * FROM artists WHERE ((artists.id = 1) AND (artists.name = 'Al')) LIMIT 1"]
82
-
83
- album.name = 'Al2'
84
- album.composite_artist
85
- MODEL_DB.sqls.should == ["SELECT * FROM artists WHERE ((artists.id = 1) AND (artists.name = 'Al2')) LIMIT 1"]
86
- end
87
-
88
- specify "should work with subclasses" do
89
- salbum = Class.new(@Album)
90
- oartist = Class.new(@c).set_dataset(:oartist)
91
- oartist.columns :id, :name
92
- salbum.many_to_one :artist2, :class=>oartist, :key=>:artist_id
93
- album = salbum.load(:id => 1, :name=>'Al', :artist_id=>2)
94
- album.artist
95
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1']
96
-
97
- album.artist_id = 1
98
- album.artist
99
- MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 1) LIMIT 1']
100
- end
101
-
102
- end
@@ -1,337 +0,0 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
-
3
- describe "Sequel::Plugins::IdentityMap" do
4
- before do
5
- class ::IdentityMapModel < Sequel::Model
6
- plugin :identity_map
7
- attr_accessor :foo
8
- dataset._fetch = proc do |sql|
9
- sql =~ /WHERE \(?(\w+\.)?(\w+) = (\d)\)?/
10
- {$2.to_sym=>$3.to_i}
11
- end
12
- def self.waw_identity_map(&block) # with and without
13
- with_identity_map(&block)
14
- db.reset
15
- yield
16
- end
17
- end
18
- class ::IdentityMapAlbum < ::IdentityMapModel
19
- columns :artist_id
20
- end
21
- class ::IdentityMapArtist < ::IdentityMapModel
22
- end
23
- @c = ::IdentityMapModel
24
- @c1 = ::IdentityMapAlbum
25
- @c2 = ::IdentityMapArtist
26
- MODEL_DB.reset
27
- end
28
- after do
29
- Object.send(:remove_const, :IdentityMapAlbum)
30
- Object.send(:remove_const, :IdentityMapArtist)
31
- Object.send(:remove_const, :IdentityMapModel)
32
- end
33
-
34
- it "#identity_map should return a hash if an identity map is currently being used" do
35
- @c.with_identity_map{@c.identity_map.should == {}}
36
- end
37
-
38
- it "#identity_map should return nil if an identity map is not currently being used" do
39
- @c.identity_map.should == nil
40
- end
41
-
42
- it "#identity_map_key should be the same for the same class and pk" do
43
- @c.identity_map_key(1).should == @c.identity_map_key(1)
44
- end
45
-
46
- it "#identity_map_key should be different for a different class" do
47
- @c1.identity_map_key(1).should_not == @c2.identity_map_key(1)
48
- end
49
-
50
- it "#identity_map_key should be different for different anonymous classes" do
51
- Class.new(@c).identity_map_key(1).should_not == Class.new(@c).identity_map_key(1)
52
- end
53
-
54
- it "#identity_map_key should be different for a different pk" do
55
- @c.identity_map_key(1).should_not == @c.identity_map_key(2)
56
- end
57
-
58
- it "#identity_map_key should be nil for an empty pk values" do
59
- @c.identity_map_key(nil).should == nil
60
- @c.identity_map_key([]).should == nil
61
- @c.identity_map_key([nil]).should == nil
62
- end
63
-
64
- it "#load should work even if model doesn't have a primary key" do
65
- c = Class.new(@c)
66
- c.no_primary_key
67
- proc{c.with_identity_map{c.load({})}}.should_not raise_error
68
- c.with_identity_map{c.load({}).should_not equal(c.load({}))}
69
- end
70
-
71
- it "#load should return an object if there is no current identity map" do
72
- o = @c.load(:id=>1)
73
- o.should be_a_kind_of(@c)
74
- o.values.should == {:id=>1}
75
- end
76
-
77
- it "#load should return an object if there is a current identity map" do
78
- @c.with_identity_map do
79
- o = @c.load(:id=>1)
80
- o.should be_a_kind_of(@c)
81
- o.values.should == {:id=>1}
82
- end
83
- end
84
-
85
- it "#load should store the object in the current identity map if it isn't already there" do
86
- @c.with_identity_map do
87
- @c.identity_map[@c.identity_map_key(1)].should == nil
88
- o = @c.load(:id=>1)
89
- @c.identity_map[@c.identity_map_key(1)].should == o
90
- end
91
- end
92
-
93
- it "#load should update the record in the current identity map if new fields if it is already there" do
94
- @c.with_identity_map do
95
- o = @c.load(:id=>1, :a=>2)
96
- o.values.should == {:id=>1, :a=>2}
97
- o = @c.load(:id=>1, :b=>3)
98
- o.values.should == {:id=>1, :a=>2, :b=>3}
99
- end
100
- end
101
-
102
- it "#load should not update existing fields in the record if the record is in the current identity map" do
103
- @c.with_identity_map do
104
- o = @c.load(:id=>1, :a=>2)
105
- o.values.should == {:id=>1, :a=>2}
106
- o = @c.load(:id=>1, :a=>4)
107
- o.values.should == {:id=>1, :a=>2}
108
- end
109
- end
110
-
111
- it "should use the identity map as a lookup cache in Model.[] to save on database queries" do
112
- @c.with_identity_map do
113
- MODEL_DB.sqls.length.should == 0
114
- o = @c[1]
115
- MODEL_DB.sqls.length.should == 1
116
- o.foo = 1
117
- @c[1].foo.should == o.foo
118
- MODEL_DB.sqls.length.should == 0
119
- @c[2].foo.should_not == o.foo
120
- MODEL_DB.sqls.length.should == 1
121
- end
122
- end
123
-
124
- it "should remove instances from the identity map if they are deleted or destroyed" do
125
- @c.with_identity_map do
126
- MODEL_DB.sqls.length.should == 0
127
- o = @c[1]
128
- MODEL_DB.sqls.length.should == 1
129
- o.foo = 1
130
- @c[1].should == o
131
- MODEL_DB.sqls.length.should == 0
132
- o.destroy
133
- MODEL_DB.sqls.length.should == 1
134
- @c[1].foo.should_not == o.foo
135
- MODEL_DB.sqls.length.should == 1
136
-
137
- MODEL_DB.reset
138
- o = @c[2]
139
- MODEL_DB.sqls.length.should == 1
140
- o.foo = 1
141
- @c[2].should == o
142
- MODEL_DB.sqls.length.should == 0
143
- o.delete
144
- MODEL_DB.sqls.length.should == 1
145
- @c[2].foo.should_not == o.foo
146
- MODEL_DB.sqls.length.should == 1
147
- end
148
- end
149
-
150
- it "should use the identity map as a lookup cache when retrieving many_to_one associated records via a composite key" do
151
- @c1.columns :another_id
152
- @c1.many_to_one :artist, :class=>@c2, :key=>[:id, :another_id]
153
- @c.with_identity_map do
154
- MODEL_DB.sqls.length.should == 0
155
- o = @c1.load(:id=>1, :another_id=>1, :artist_id=>2)
156
- a = o.artist
157
- a.should be_a_kind_of(@c2)
158
- MODEL_DB.sqls.length.should == 1
159
- o = @c1.load(:id=>1, :another_id=>2, :artist_id=>2)
160
- o.artist.should == a
161
- MODEL_DB.sqls.length.should == 0
162
- o = @c1.load(:id=>3, :another_id=>3, :artist_id=>3)
163
- o.artist.should_not == a
164
- MODEL_DB.sqls.length.should == 1
165
- end
166
- end
167
-
168
- it "should use the identity map as a lookup cache when retrieving many_to_one associated records" do
169
- @c1.many_to_one :artist, :class=>@c2
170
- @c.with_identity_map do
171
- MODEL_DB.sqls.length.should == 0
172
- o = @c1.load(:id=>1, :artist_id=>2)
173
- a = o.artist
174
- a.should be_a_kind_of(@c2)
175
- MODEL_DB.sqls.length.should == 1
176
- o = @c1.load(:id=>2, :artist_id=>2)
177
- o.artist.should == a
178
- MODEL_DB.sqls.length.should == 0
179
- o = @c1.load(:id=>3, :artist_id=>3)
180
- o.artist.should_not == a
181
- MODEL_DB.sqls.length.should == 1
182
- end
183
- end
184
-
185
- it "should not use the identity map as a lookup cache for a one_to_one association" do
186
- @c2.one_to_one :artist, :class=>@c1, :key=>:artist_id
187
- @c.with_identity_map do
188
- MODEL_DB.sqls.length.should == 0
189
- o = @c2.load(:id=>2)
190
- a = o.artist
191
- a.should be_a_kind_of(@c1)
192
- MODEL_DB.sqls.length.should == 1
193
- o.reload
194
- MODEL_DB.sqls.length.should == 1
195
- o.artist.should == a
196
- MODEL_DB.sqls.length.should == 1
197
- end
198
- end
199
-
200
- it "should not use the identity map as a lookup cache if the assocation has a nil :key option" do
201
- c = @c2
202
- @c1.many_to_one :artist, :class=>@c2, :key=>nil, :dataset=>proc{c.filter(:artist_id=>artist_id)}
203
- @c.with_identity_map do
204
- MODEL_DB.sqls.length.should == 0
205
- o = @c1.load(:id=>1, :artist_id=>2)
206
- a = o.artist
207
- a.should be_a_kind_of(@c2)
208
- MODEL_DB.sqls.length.should == 1
209
- o = @c1.load(:id=>2, :artist_id=>2)
210
- o.artist.should == a
211
- MODEL_DB.sqls.length.should == 1
212
- end
213
- end
214
-
215
- it "should not use the identity map as a lookup cache if the assocation's :primary_key option doesn't match the primary key of the associated class" do
216
- @c1.many_to_one :artist, :class=>@c2, :primary_key=>:artist_id
217
- @c.with_identity_map do
218
- MODEL_DB.sqls.length.should == 0
219
- o = @c1.load(:id=>1, :artist_id=>2)
220
- a = o.artist
221
- a.should be_a_kind_of(@c2)
222
- MODEL_DB.sqls.length.should == 1
223
- o = @c1.load(:id=>2, :artist_id=>2)
224
- o.artist.should == a
225
- MODEL_DB.sqls.length.should == 1
226
- end
227
- end
228
-
229
- it "should not use the identity map as a lookup cache if a dynamic callback is used" do
230
- @c1.many_to_one :artist, :class=>@c2
231
- @c.with_identity_map do
232
- MODEL_DB.sqls.length.should == 0
233
- o = @c1.load(:id=>1, :artist_id=>2)
234
- a = o.artist
235
- a.should be_a_kind_of(@c2)
236
- MODEL_DB.sqls.length.should == 1
237
- o = @c1.load(:id=>2, :artist_id=>2)
238
- o.artist.should == a
239
- MODEL_DB.sqls.length.should == 0
240
- o = @c1.load(:id=>3, :artist_id=>3)
241
- o.artist.should_not == a
242
- MODEL_DB.sqls.length.should == 1
243
- end
244
- end
245
-
246
- it "should not override custom :eager_loaders for many_to_many associations" do
247
- @c1.columns :id
248
- @c2.columns :id
249
- c = @c2
250
- @c1.many_to_many :artists, :class=>@c2, :left_key=>:album_id, :right_key=>:artist_id, :join_table=>:aa, :eager_loader=>(proc do |eo|
251
- eo[:rows].each{|object| object.associations[:artists] = [c.load(:id=>object.id)]}
252
- end)
253
- @c1.dataset._fetch = [{:id=>1}, {:id=>2}, {:id=>3}]
254
-
255
- @c.waw_identity_map do
256
- MODEL_DB.sqls.length.should == 0
257
- a = @c1.eager(:artists).all
258
- MODEL_DB.sqls.length.should == 1
259
- a.should == [@c1.load(:id=>1), @c1.load(:id=>2), @c1.load(:id=>3)]
260
- a.map{|x| x.artists}.should == [[@c2.load(:id=>1)], [@c2.load(:id=>2)], [@c2.load(:id=>3)]]
261
- MODEL_DB.sqls.length.should == 0
262
- end
263
- end
264
-
265
- it "should work correctly when eagerly loading many_to_many associations" do
266
- @c1.columns :id
267
- @c2.columns :id
268
- @c1.many_to_many :artists, :class=>@c2, :left_key=>:album_id, :right_key=>:artist_id, :join_table=>:aa
269
- @c1.dataset._fetch = [{:id=>1}, {:id=>2}, {:id=>3}]
270
- @c2.dataset._fetch = [{:id=>1, :x_foreign_key_x=>1}, {:id=>1, :x_foreign_key_x=>2}, {:id=>2, :x_foreign_key_x=>1}, {:id=>2, :x_foreign_key_x=>2}, {:id=>3, :x_foreign_key_x=>1}, {:id=>3, :x_foreign_key_x=>1}]
271
-
272
- @c.waw_identity_map do
273
- MODEL_DB.sqls.length.should == 0
274
- a = @c1.eager(:artists).all
275
- MODEL_DB.sqls.length.should == 2
276
- a.should == [@c1.load(:id=>1), @c1.load(:id=>2), @c1.load(:id=>3)]
277
- a.map{|x| x.artists}.should == [[@c2.load(:id=>1), @c2.load(:id=>2), @c2.load(:id=>3), @c2.load(:id=>3)], [@c2.load(:id=>1), @c2.load(:id=>2)], []]
278
- MODEL_DB.sqls.length.should == 0
279
- end
280
- end
281
-
282
- it "should work correctly when eagerly loading many_to_many associations with composite keys" do
283
- @c1.columns :id, :id2
284
- @c2.columns :id, :id2
285
- @c1.set_primary_key [:id, :id2]
286
- @c2.set_primary_key [:id, :id2]
287
- @c1.many_to_many :artists, :class=>@c2, :left_key=>[:album_id1, :album_id2], :right_key=>[:artist_id1, :artist_id2], :join_table=>:aa
288
- @c1.dataset._fetch = [{:id=>1, :id2=>4}, {:id=>2, :id2=>5}, {:id=>3, :id2=>6}]
289
- @c2.dataset._fetch = [ {:id=>1, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}, {:id=>1, :x_foreign_key_0_x=>2, :x_foreign_key_1_x=>5}, {:id=>2, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}, {:id=>2, :x_foreign_key_0_x=>2, :x_foreign_key_1_x=>5}, {:id=>3, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}, {:id=>3, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}]
290
-
291
- @c.waw_identity_map do
292
- MODEL_DB.sqls.length.should == 0
293
- a = @c1.eager(:artists).all
294
- MODEL_DB.sqls.length.should == 2
295
- a.should == [@c1.load(:id=>1, :id2=>4), @c1.load(:id=>2, :id2=>5), @c1.load(:id=>3, :id2=>6)]
296
- a.map{|x| x.artists}.should == [[@c2.load(:id=>1), @c2.load(:id=>2), @c2.load(:id=>3), @c2.load(:id=>3)], [@c2.load(:id=>1), @c2.load(:id=>2)], []]
297
- MODEL_DB.sqls.length.should == 0
298
- end
299
- end
300
-
301
- it "should work correctly when eagerly loading many_through_many associations" do
302
- @c1.columns :id
303
- @c2.columns :id
304
- @c1.plugin :many_through_many
305
- @c1.many_through_many :artists, [[:aa, :album_id, :artist_id]], :class=>@c2
306
- @c1.dataset._fetch = [{:id=>1}, {:id=>2}, {:id=>3}]
307
- @c2.dataset._fetch = [{:id=>1, :x_foreign_key_x=>1}, {:id=>1, :x_foreign_key_x=>2}, {:id=>2, :x_foreign_key_x=>1}, {:id=>2, :x_foreign_key_x=>2}, {:id=>3, :x_foreign_key_x=>1}, {:id=>3, :x_foreign_key_x=>1}]
308
-
309
- @c.waw_identity_map do
310
- MODEL_DB.sqls.length.should == 0
311
- a = @c1.eager(:artists).all
312
- MODEL_DB.sqls.length.should == 2
313
- a.should == [@c1.load(:id=>1), @c1.load(:id=>2), @c1.load(:id=>3)]
314
- a.map{|x| x.artists}.should == [[@c2.load(:id=>1), @c2.load(:id=>2), @c2.load(:id=>3), @c2.load(:id=>3)], [@c2.load(:id=>1), @c2.load(:id=>2)], []]
315
- MODEL_DB.sqls.length.should == 0
316
- end
317
- end
318
-
319
- it "should work correctly when eagerly loading many_to_many associations with composite keys" do
320
- @c1.columns :id, :id2
321
- @c2.columns :id
322
- @c1.set_primary_key [:id, :id2]
323
- @c1.plugin :many_through_many
324
- @c1.many_through_many :artists, [[:aa, [:album_id1, :album_id2], :artist_id]], :class=>@c2
325
- @c1.dataset._fetch = [{:id=>1, :id2=>4}, {:id=>2, :id2=>5}, {:id=>3, :id2=>6}]
326
- @c2.dataset._fetch = [ {:id=>1, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}, {:id=>1, :x_foreign_key_0_x=>2, :x_foreign_key_1_x=>5}, {:id=>2, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}, {:id=>2, :x_foreign_key_0_x=>2, :x_foreign_key_1_x=>5}, {:id=>3, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}, {:id=>3, :x_foreign_key_0_x=>1, :x_foreign_key_1_x=>4}]
327
-
328
- @c.waw_identity_map do
329
- MODEL_DB.sqls.length.should == 0
330
- a = @c1.eager(:artists).all
331
- MODEL_DB.sqls.length.should == 2
332
- a.should == [@c1.load(:id=>1, :id2=>4), @c1.load(:id=>2, :id2=>5), @c1.load(:id=>3, :id2=>6)]
333
- a.map{|x| x.artists}.should == [[@c2.load(:id=>1), @c2.load(:id=>2), @c2.load(:id=>3), @c2.load(:id=>3)], [@c2.load(:id=>1), @c2.load(:id=>2)], []]
334
- MODEL_DB.sqls.length.should == 0
335
- end
336
- end
337
- end