colincasey-sequel 2.10.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +7 -1
  2. data/doc/advanced_associations.rdoc +614 -0
  3. data/doc/cheat_sheet.rdoc +223 -0
  4. data/doc/dataset_filtering.rdoc +158 -0
  5. data/doc/prepared_statements.rdoc +104 -0
  6. data/doc/release_notes/1.0.txt +38 -0
  7. data/doc/release_notes/1.1.txt +143 -0
  8. data/doc/release_notes/1.3.txt +101 -0
  9. data/doc/release_notes/1.4.0.txt +53 -0
  10. data/doc/release_notes/1.5.0.txt +155 -0
  11. data/doc/release_notes/2.0.0.txt +298 -0
  12. data/doc/release_notes/2.1.0.txt +271 -0
  13. data/doc/release_notes/2.10.0.txt +328 -0
  14. data/doc/release_notes/2.2.0.txt +253 -0
  15. data/doc/release_notes/2.3.0.txt +88 -0
  16. data/doc/release_notes/2.4.0.txt +106 -0
  17. data/doc/release_notes/2.5.0.txt +137 -0
  18. data/doc/release_notes/2.6.0.txt +157 -0
  19. data/doc/release_notes/2.7.0.txt +166 -0
  20. data/doc/release_notes/2.8.0.txt +171 -0
  21. data/doc/release_notes/2.9.0.txt +97 -0
  22. data/doc/schema.rdoc +29 -0
  23. data/doc/sharding.rdoc +113 -0
  24. data/lib/sequel.rb +1 -0
  25. data/lib/sequel_core/adapters/ado.rb +89 -0
  26. data/lib/sequel_core/adapters/db2.rb +143 -0
  27. data/lib/sequel_core/adapters/dbi.rb +112 -0
  28. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  29. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  30. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  31. data/lib/sequel_core/adapters/do.rb +205 -0
  32. data/lib/sequel_core/adapters/firebird.rb +298 -0
  33. data/lib/sequel_core/adapters/informix.rb +85 -0
  34. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  35. data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
  36. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  37. data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
  38. data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
  39. data/lib/sequel_core/adapters/jdbc.rb +491 -0
  40. data/lib/sequel_core/adapters/mysql.rb +369 -0
  41. data/lib/sequel_core/adapters/odbc.rb +174 -0
  42. data/lib/sequel_core/adapters/openbase.rb +68 -0
  43. data/lib/sequel_core/adapters/oracle.rb +107 -0
  44. data/lib/sequel_core/adapters/postgres.rb +456 -0
  45. data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
  46. data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
  47. data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
  48. data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
  49. data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
  50. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  51. data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
  52. data/lib/sequel_core/adapters/sqlite.rb +248 -0
  53. data/lib/sequel_core/connection_pool.rb +258 -0
  54. data/lib/sequel_core/core_ext.rb +217 -0
  55. data/lib/sequel_core/core_sql.rb +202 -0
  56. data/lib/sequel_core/database/schema.rb +164 -0
  57. data/lib/sequel_core/database.rb +691 -0
  58. data/lib/sequel_core/dataset/callback.rb +13 -0
  59. data/lib/sequel_core/dataset/convenience.rb +237 -0
  60. data/lib/sequel_core/dataset/pagination.rb +96 -0
  61. data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
  62. data/lib/sequel_core/dataset/query.rb +41 -0
  63. data/lib/sequel_core/dataset/schema.rb +15 -0
  64. data/lib/sequel_core/dataset/sql.rb +1010 -0
  65. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  66. data/lib/sequel_core/dataset/unsupported.rb +43 -0
  67. data/lib/sequel_core/dataset.rb +511 -0
  68. data/lib/sequel_core/deprecated.rb +26 -0
  69. data/lib/sequel_core/exceptions.rb +44 -0
  70. data/lib/sequel_core/migration.rb +212 -0
  71. data/lib/sequel_core/object_graph.rb +230 -0
  72. data/lib/sequel_core/pretty_table.rb +71 -0
  73. data/lib/sequel_core/schema/generator.rb +320 -0
  74. data/lib/sequel_core/schema/sql.rb +325 -0
  75. data/lib/sequel_core/schema.rb +2 -0
  76. data/lib/sequel_core/sql.rb +887 -0
  77. data/lib/sequel_core/version.rb +11 -0
  78. data/lib/sequel_core.rb +172 -0
  79. data/lib/sequel_model/association_reflection.rb +267 -0
  80. data/lib/sequel_model/associations.rb +499 -0
  81. data/lib/sequel_model/base.rb +523 -0
  82. data/lib/sequel_model/caching.rb +82 -0
  83. data/lib/sequel_model/dataset_methods.rb +26 -0
  84. data/lib/sequel_model/eager_loading.rb +370 -0
  85. data/lib/sequel_model/exceptions.rb +7 -0
  86. data/lib/sequel_model/hooks.rb +101 -0
  87. data/lib/sequel_model/inflector.rb +281 -0
  88. data/lib/sequel_model/plugins.rb +62 -0
  89. data/lib/sequel_model/record.rb +568 -0
  90. data/lib/sequel_model/schema.rb +49 -0
  91. data/lib/sequel_model/validations.rb +429 -0
  92. data/lib/sequel_model.rb +91 -0
  93. data/spec/adapters/ado_spec.rb +46 -0
  94. data/spec/adapters/firebird_spec.rb +376 -0
  95. data/spec/adapters/informix_spec.rb +96 -0
  96. data/spec/adapters/mysql_spec.rb +881 -0
  97. data/spec/adapters/oracle_spec.rb +244 -0
  98. data/spec/adapters/postgres_spec.rb +687 -0
  99. data/spec/adapters/spec_helper.rb +10 -0
  100. data/spec/adapters/sqlite_spec.rb +555 -0
  101. data/spec/integration/dataset_test.rb +134 -0
  102. data/spec/integration/eager_loader_test.rb +696 -0
  103. data/spec/integration/prepared_statement_test.rb +130 -0
  104. data/spec/integration/schema_test.rb +180 -0
  105. data/spec/integration/spec_helper.rb +58 -0
  106. data/spec/integration/type_test.rb +96 -0
  107. data/spec/rcov.opts +6 -0
  108. data/spec/sequel_core/connection_pool_spec.rb +526 -0
  109. data/spec/sequel_core/core_ext_spec.rb +156 -0
  110. data/spec/sequel_core/core_sql_spec.rb +522 -0
  111. data/spec/sequel_core/database_spec.rb +1188 -0
  112. data/spec/sequel_core/dataset_spec.rb +3481 -0
  113. data/spec/sequel_core/expression_filters_spec.rb +363 -0
  114. data/spec/sequel_core/migration_spec.rb +261 -0
  115. data/spec/sequel_core/object_graph_spec.rb +272 -0
  116. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  117. data/spec/sequel_core/schema_generator_spec.rb +167 -0
  118. data/spec/sequel_core/schema_spec.rb +780 -0
  119. data/spec/sequel_core/spec_helper.rb +55 -0
  120. data/spec/sequel_core/version_spec.rb +7 -0
  121. data/spec/sequel_model/association_reflection_spec.rb +93 -0
  122. data/spec/sequel_model/associations_spec.rb +1767 -0
  123. data/spec/sequel_model/base_spec.rb +419 -0
  124. data/spec/sequel_model/caching_spec.rb +215 -0
  125. data/spec/sequel_model/dataset_methods_spec.rb +78 -0
  126. data/spec/sequel_model/eager_loading_spec.rb +1165 -0
  127. data/spec/sequel_model/hooks_spec.rb +485 -0
  128. data/spec/sequel_model/inflector_spec.rb +119 -0
  129. data/spec/sequel_model/model_spec.rb +588 -0
  130. data/spec/sequel_model/plugins_spec.rb +80 -0
  131. data/spec/sequel_model/record_spec.rb +1184 -0
  132. data/spec/sequel_model/schema_spec.rb +90 -0
  133. data/spec/sequel_model/spec_helper.rb +78 -0
  134. data/spec/sequel_model/validations_spec.rb +1067 -0
  135. data/spec/spec.opts +0 -0
  136. data/spec/spec_config.rb.example +10 -0
  137. metadata +177 -3
@@ -0,0 +1,370 @@
1
+ # Eager loading makes it so that you can load all associated records for a
2
+ # set of objects in a single query, instead of a separate query for each object.
3
+ #
4
+ # Two separate implementations are provided. #eager should be used most of the
5
+ # time, as it loads associated records using one query per association. However,
6
+ # it does not allow you the ability to filter based on columns in associated tables. #eager_graph loads
7
+ # all records in one query. Using #eager_graph you can filter based on columns in associated
8
+ # tables. However, #eager_graph can be much slower than #eager, especially if multiple
9
+ # *_to_many associations are joined.
10
+ #
11
+ # You can cascade the eager loading (loading associations' associations)
12
+ # with no limit to the depth of the cascades. You do this by passing a hash to #eager or #eager_graph
13
+ # with the keys being associations of the current model and values being
14
+ # associations of the model associated with the current model via the key.
15
+ #
16
+ # The arguments can be symbols or hashes with symbol keys (for cascaded
17
+ # eager loading). Examples:
18
+ #
19
+ # Album.eager(:artist).all
20
+ # Album.eager_graph(:artist).all
21
+ # Album.eager(:artist, :genre).all
22
+ # Album.eager_graph(:artist, :genre).all
23
+ # Album.eager(:artist).eager(:genre).all
24
+ # Album.eager_graph(:artist).eager(:genre).all
25
+ # Artist.eager(:albums=>:tracks).all
26
+ # Artist.eager_graph(:albums=>:tracks).all
27
+ # Artist.eager(:albums=>{:tracks=>:genre}).all
28
+ # Artist.eager_graph(:albums=>{:tracks=>:genre}).all
29
+ module Sequel::Model::Associations::EagerLoading
30
+ # Add the #eager! and #eager_graph! mutation methods to the dataset.
31
+ def self.extended(obj)
32
+ obj.def_mutation_method(:eager, :eager_graph)
33
+ end
34
+
35
+ # The preferred eager loading method. Loads all associated records using one
36
+ # query for each association.
37
+ #
38
+ # The basic idea for how it works is that the dataset is first loaded normally.
39
+ # Then it goes through all associations that have been specified via eager.
40
+ # It loads each of those associations separately, then associates them back
41
+ # to the original dataset via primary/foreign keys. Due to the necessity of
42
+ # all objects being present, you need to use .all to use eager loading, as it
43
+ # can't work with .each.
44
+ #
45
+ # This implementation avoids the complexity of extracting an object graph out
46
+ # of a single dataset, by building the object graph out of multiple datasets,
47
+ # one for each association. By using a separate dataset for each association,
48
+ # it avoids problems such as aliasing conflicts and creating cartesian product
49
+ # result sets if multiple *_to_many eager associations are requested.
50
+ #
51
+ # One limitation of using this method is that you cannot filter the dataset
52
+ # based on values of columns in an associated table, since the associations are loaded
53
+ # in separate queries. To do that you need to load all associations in the
54
+ # same query, and extract an object graph from the results of that query. If you
55
+ # need to filter based on columns in associated tables, look at #eager_graph
56
+ # or join the tables you need to filter on manually.
57
+ #
58
+ # Each association's order, if defined, is respected. Eager also works
59
+ # on a limited dataset, but does not use any :limit options for associations.
60
+ # If the association uses a block or has an :eager_block argument, it is used.
61
+ def eager(*associations)
62
+ model = check_model
63
+ opt = @opts[:eager]
64
+ opt = opt ? opt.dup : {}
65
+ associations.flatten.each do |association|
66
+ case association
67
+ when Symbol
68
+ check_association(model, association)
69
+ opt[association] = nil
70
+ when Hash
71
+ association.keys.each{|assoc| check_association(model, assoc)}
72
+ opt.merge!(association)
73
+ else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
74
+ end
75
+ end
76
+ clone(:eager=>opt)
77
+ end
78
+
79
+ # The secondary eager loading method. Loads all associations in a single query. This
80
+ # method should only be used if you need to filter based on columns in associated tables.
81
+ #
82
+ # This method builds an object graph using Dataset#graph. Then it uses the graph
83
+ # to build the associations, and finally replaces the graph with a simple array
84
+ # of model objects.
85
+ #
86
+ # Be very careful when using this with multiple *_to_many associations, as you can
87
+ # create large cartesian products. If you must graph multiple *_to_many associations,
88
+ # make sure your filters are specific if you have a large database.
89
+ #
90
+ # Each association's order, if definied, is respected. #eager_graph probably
91
+ # won't work correctly on a limited dataset, unless you are
92
+ # only graphing many_to_one associations.
93
+ #
94
+ # Does not use the block defined for the association, since it does a single query for
95
+ # all objects. You can use the :graph_join_type, :graph_conditions, and :graph_join_table_conditions
96
+ # association options to modify the SQL query.
97
+ def eager_graph(*associations)
98
+ model = check_model
99
+ table_name = model.table_name
100
+ ds = if @opts[:eager_graph]
101
+ self
102
+ else
103
+ # Each of the following have a symbol key for the table alias, with the following values:
104
+ # :reciprocals - the reciprocal instance variable to use for this association
105
+ # :requirements - array of requirements for this association
106
+ # :alias_association_type_map - the type of association for this association
107
+ # :alias_association_name_map - the name of the association for this association
108
+ clone(:eager_graph=>{:requirements=>{}, :master=>model.table_name, :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}})
109
+ end
110
+ ds.eager_graph_associations(ds, model, table_name, [], *associations)
111
+ end
112
+
113
+ protected
114
+
115
+ # Call graph on the association with the correct arguments,
116
+ # update the eager_graph data structure, and recurse into
117
+ # eager_graph_associations if there are any passed in associations
118
+ # (which would be dependencies of the current association)
119
+ #
120
+ # Arguments:
121
+ # * ds - Current dataset
122
+ # * model - Current Model
123
+ # * ta - table_alias used for the parent association
124
+ # * requirements - an array, used as a stack for requirements
125
+ # * r - association reflection for the current association
126
+ # * *associations - any associations dependent on this one
127
+ def eager_graph_association(ds, model, ta, requirements, r, *associations)
128
+ klass = r.associated_class
129
+ assoc_name = r[:name]
130
+ assoc_table_alias = ds.eager_unique_table_alias(ds, assoc_name)
131
+ ds = r[:eager_grapher].call(ds, assoc_table_alias, ta)
132
+ ds = ds.order_more(*Array(r[:order]).map{|c| eager_graph_qualify_order(assoc_table_alias, c)}) if r[:order] and r[:order_eager_graph]
133
+ eager_graph = ds.opts[:eager_graph]
134
+ eager_graph[:requirements][assoc_table_alias] = requirements.dup
135
+ eager_graph[:alias_association_name_map][assoc_table_alias] = assoc_name
136
+ eager_graph[:alias_association_type_map][assoc_table_alias] = r.returns_array?
137
+ ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
138
+ ds
139
+ end
140
+
141
+ # Check the associations are valid for the given model.
142
+ # Call eager_graph_association on each association.
143
+ #
144
+ # Arguments:
145
+ # * ds - Current dataset
146
+ # * model - Current Model
147
+ # * ta - table_alias used for the parent association
148
+ # * requirements - an array, used as a stack for requirements
149
+ # * *associations - the associations to add to the graph
150
+ def eager_graph_associations(ds, model, ta, requirements, *associations)
151
+ return ds if associations.empty?
152
+ associations.flatten.each do |association|
153
+ ds = case association
154
+ when Symbol
155
+ ds.eager_graph_association(ds, model, ta, requirements, check_association(model, association))
156
+ when Hash
157
+ association.each do |assoc, assoc_assocs|
158
+ ds = ds.eager_graph_association(ds, model, ta, requirements, check_association(model, assoc), assoc_assocs)
159
+ end
160
+ ds
161
+ else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
162
+ end
163
+ end
164
+ ds
165
+ end
166
+
167
+ # Build associations out of the array of returned object graphs.
168
+ def eager_graph_build_associations(record_graphs)
169
+ eager_graph = @opts[:eager_graph]
170
+ master = eager_graph[:master]
171
+ requirements = eager_graph[:requirements]
172
+ alias_map = eager_graph[:alias_association_name_map]
173
+ type_map = eager_graph[:alias_association_type_map]
174
+ reciprocal_map = eager_graph[:reciprocals]
175
+
176
+ # Make dependency map hash out of requirements array for each association.
177
+ # This builds a tree of dependencies that will be used for recursion
178
+ # to ensure that all parts of the object graph are loaded into the
179
+ # appropriate subordinate association.
180
+ dependency_map = {}
181
+ # Sort the associations be requirements length, so that
182
+ # requirements are added to the dependency hash before their
183
+ # dependencies.
184
+ requirements.sort_by{|a| a[1].length}.each do |ta, deps|
185
+ if deps.empty?
186
+ dependency_map[ta] = {}
187
+ else
188
+ deps = deps.dup
189
+ hash = dependency_map[deps.shift]
190
+ deps.each do |dep|
191
+ hash = hash[dep]
192
+ end
193
+ hash[ta] = {}
194
+ end
195
+ end
196
+
197
+ # This mapping is used to make sure that duplicate entries in the
198
+ # result set are mapped to a single record. For example, using a
199
+ # single one_to_many association with 10 associated records,
200
+ # the main object will appear in the object graph 10 times.
201
+ # We map by primary key, if available, or by the object's entire values,
202
+ # if not. The mapping must be per table, so create sub maps for each table
203
+ # alias.
204
+ records_map = {master=>{}}
205
+ alias_map.keys.each{|ta| records_map[ta] = {}}
206
+
207
+ # This will hold the final record set that we will be replacing the object graph with.
208
+ records = []
209
+ record_graphs.each do |record_graph|
210
+ primary_record = record_graph[master]
211
+ key = primary_record.pk || primary_record.values.sort_by{|x| x[0].to_s}
212
+ if cached_pr = records_map[master][key]
213
+ primary_record = cached_pr
214
+ else
215
+ records_map[master][key] = primary_record
216
+ # Only add it to the list of records to return if it is a new record
217
+ records.push(primary_record)
218
+ end
219
+ # Build all associations for the current object and it's dependencies
220
+ eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, primary_record, record_graph)
221
+ end
222
+
223
+ # Remove duplicate records from all associations if this graph could possibly be a cartesian product
224
+ eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map) if type_map.values.select{|v| v}.length > 1
225
+
226
+ # Replace the array of object graphs with an array of model objects
227
+ record_graphs.replace(records)
228
+ end
229
+
230
+ # Creates a unique table alias that hasn't already been used in the query.
231
+ # Will either be the table_alias itself or table_alias_N for some integer
232
+ # N (starting at 0 and increasing until an unused one is found).
233
+ def eager_unique_table_alias(ds, table_alias)
234
+ used_aliases = ds.opts[:from]
235
+ graph = ds.opts[:graph]
236
+ used_aliases += graph[:table_aliases].keys if graph
237
+ if used_aliases.include?(table_alias)
238
+ i = 0
239
+ loop do
240
+ ta = :"#{table_alias}_#{i}"
241
+ return ta unless used_aliases.include?(ta)
242
+ i += 1
243
+ end
244
+ end
245
+ table_alias
246
+ end
247
+
248
+ private
249
+
250
+ # Make sure this dataset is associated with a model, and return the default model for it.
251
+ def check_model
252
+ raise(Sequel::Error, 'No model for this dataset') unless @opts[:models] && model = @opts[:models][nil]
253
+ model
254
+ end
255
+
256
+ # Make sure the association is valid for this model, and return the related AssociationReflection.
257
+ def check_association(model, association)
258
+ raise(Sequel::Error, 'Invalid association') unless reflection = model.association_reflection(association)
259
+ raise(Sequel::Error, "Eager loading is not allowed for #{model.name} association #{association}") if reflection[:allow_eager] == false
260
+ reflection
261
+ end
262
+
263
+ # Build associations for the current object. This is called recursively
264
+ # to build object's dependencies.
265
+ def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
266
+ return if dependency_map.empty?
267
+ # Don't clobber the instance variable array for *_to_many associations if it has already been setup
268
+ dependency_map.keys.each do |ta|
269
+ assoc_name = alias_map[ta]
270
+ current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
271
+ end
272
+ dependency_map.each do |ta, deps|
273
+ next unless rec = record_graph[ta]
274
+ key = rec.pk || rec.values.sort_by{|x| x[0].to_s}
275
+ if cached_rec = records_map[ta][key]
276
+ rec = cached_rec
277
+ else
278
+ records_map[ta][rec.pk] = rec
279
+ end
280
+ assoc_name = alias_map[ta]
281
+ case type_map[ta]
282
+ when false
283
+ current.associations[assoc_name] = rec
284
+ else
285
+ current.associations[assoc_name].push(rec)
286
+ if reciprocal = reciprocal_map[ta]
287
+ rec.associations[reciprocal] = current
288
+ end
289
+ end
290
+ # Recurse into dependencies of the current object
291
+ eager_graph_build_associations_graph(deps, alias_map, type_map, reciprocal_map, records_map, rec, record_graph)
292
+ end
293
+ end
294
+
295
+ # If the result set is the result of a cartesian product, then it is possible that
296
+ # there a multiple records for each association when there should only be one.
297
+ # In that case, for each object in all associations loaded via #eager_graph, run
298
+ # uniq! on the association instance variables to make sure no duplicate records show up.
299
+ # Note that this can cause legitimate duplicate records to be removed.
300
+ def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
301
+ records.each do |record|
302
+ dependency_map.each do |ta, deps|
303
+ list = if !type_map[ta]
304
+ item = record.send(alias_map[ta])
305
+ [item] if item
306
+ else
307
+ list = record.send(alias_map[ta])
308
+ list.uniq!
309
+ end
310
+ # Recurse into dependencies
311
+ eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
312
+ end
313
+ end
314
+ end
315
+
316
+ # Qualify the given expression if necessary. The only expressions which are qualified are
317
+ # unqualified symbols and identifiers, either of which may by sorted.
318
+ def eager_graph_qualify_order(table_alias, expression)
319
+ case expression
320
+ when Symbol
321
+ table, column, aliaz = split_symbol(expression)
322
+ raise(Sequel::Error, "Can't use an aliased expression in the :order option") if aliaz
323
+ table ? expression : Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
324
+ when Sequel::SQL::Identifier
325
+ Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
326
+ when Sequel::SQL::OrderedExpression
327
+ Sequel::SQL::OrderedExpression.new(eager_graph_qualify_order(table_alias, expression.expression), expression.descending)
328
+ else
329
+ expression
330
+ end
331
+ end
332
+
333
+ # Eagerly load all specified associations
334
+ def eager_load(a)
335
+ return if a.empty?
336
+ # Current model class
337
+ model = @opts[:models][nil]
338
+ # All associations to eager load
339
+ eager_assoc = @opts[:eager]
340
+ # Key is foreign/primary key name symbol
341
+ # Value is hash with keys being foreign/primary key values (generally integers)
342
+ # and values being an array of current model objects with that
343
+ # specific foreign/primary key
344
+ key_hash = {}
345
+ # Reflections for all associations to eager load
346
+ reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc)}
347
+
348
+ # Populate keys to monitor
349
+ reflections.each{|reflection| key_hash[reflection.eager_loader_key] ||= Hash.new{|h,k| h[k] = []}}
350
+
351
+ # Associate each object with every key being monitored
352
+ a.each do |rec|
353
+ key_hash.each do |key, id_map|
354
+ id_map[rec[key]] << rec if rec[key]
355
+ end
356
+ end
357
+
358
+ reflections.each do |r|
359
+ r[:eager_loader].call(key_hash, a, eager_assoc[r[:name]])
360
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
361
+ end
362
+ end
363
+
364
+ # Build associations from the graph if #eager_graph was used,
365
+ # and/or load other associations if #eager was used.
366
+ def post_load(all_records)
367
+ eager_graph_build_associations(all_records) if @opts[:eager_graph]
368
+ eager_load(all_records) if @opts[:eager]
369
+ end
370
+ end
@@ -0,0 +1,7 @@
1
+ module Sequel
2
+ # This exception will be raised when raise_on_save_failure is set and a validation fails
3
+ class ValidationFailed < Error;end
4
+
5
+ # This exception will be raised when raise_on_save_failure is set and a before hook fails
6
+ class BeforeHookFailed < Error;end
7
+ end
@@ -0,0 +1,101 @@
1
+ module Sequel
2
+ class Model
3
+ # Hooks that are safe for public use
4
+ HOOKS = [:after_initialize, :before_create, :after_create, :before_update,
5
+ :after_update, :before_save, :after_save, :before_destroy, :after_destroy,
6
+ :before_validation, :after_validation]
7
+
8
+ # Hooks that are only for internal use
9
+ PRIVATE_HOOKS = [:before_update_values, :before_delete]
10
+
11
+ # This adds a new hook type. It will define both a class
12
+ # method that you can use to add hooks, as well as an instance method
13
+ # that you can use to call all hooks of that type. The class method
14
+ # can be called with a symbol or a block or both. If a block is given and
15
+ # and symbol is not, it adds the hook block to the hook type. If a block
16
+ # and symbol are both given, it replaces the hook block associated with
17
+ # that symbol for a given hook type, or adds it if there is no hook block
18
+ # with that symbol for that hook type. If no block is given, it assumes
19
+ # the symbol specifies an instance method to call and adds it to the hook
20
+ # type.
21
+ #
22
+ # If any hook block returns false, the instance method will return false
23
+ # immediately without running the rest of the hooks of that type.
24
+ #
25
+ # It is recommended that you always provide a symbol to this method,
26
+ # for descriptive purposes. It's only necessary to do so when you
27
+ # are using a system that reloads code.
28
+ #
29
+ # All of Sequel's standard hook types are also implemented using this
30
+ # method.
31
+ #
32
+ # Example of usage:
33
+ #
34
+ # class MyModel
35
+ # define_hook :before_move_to
36
+ # before_move_to(:check_move_allowed){|o| o.allow_move?}
37
+ # def move_to(there)
38
+ # return if before_move_to == false
39
+ # # move MyModel object to there
40
+ # end
41
+ # end
42
+ def self.add_hook_type(*hooks)
43
+ hooks.each do |hook|
44
+ @hooks[hook] = []
45
+ instance_eval("def #{hook}(method = nil, &block); define_hook_instance_method(:#{hook}); add_hook(:#{hook}, method, &block) end")
46
+ class_eval("def #{hook}; end")
47
+ end
48
+ end
49
+
50
+ # Returns true if there are any hook blocks for the given hook.
51
+ def self.has_hooks?(hook)
52
+ !@hooks[hook].empty?
53
+ end
54
+
55
+ # Yield every block related to the given hook.
56
+ def self.hook_blocks(hook)
57
+ @hooks[hook].each{|k,v| yield v}
58
+ end
59
+
60
+ ### Private Class Methods ###
61
+
62
+ # Add a hook block to the list of hook methods.
63
+ # If a non-nil tag is given and it already is in the list of hooks,
64
+ # replace it with the new block.
65
+ def self.add_hook(hook, tag, &block) #:nodoc:
66
+ unless block
67
+ (raise Error, 'No hook method specified') unless tag
68
+ block = proc {send tag}
69
+ end
70
+ h = @hooks[hook]
71
+ if tag && (old = h.find{|x| x[0] == tag})
72
+ old[1] = block
73
+ else
74
+ if hook.to_s =~ /^before/
75
+ h.unshift([tag,block])
76
+ else
77
+ h << [tag, block]
78
+ end
79
+ end
80
+ end
81
+
82
+ # Define a hook instance method that calls the run_hooks instance method.
83
+ def self.define_hook_instance_method(hook) #:nodoc:
84
+ class_eval("def #{hook}; run_hooks(:#{hook}); end")
85
+ end
86
+
87
+ private_class_method :add_hook, :define_hook_instance_method
88
+
89
+ private
90
+
91
+ # Runs all hook blocks of given hook type on this object.
92
+ # Stops running hook blocks and returns false if any hook block returns false.
93
+ def run_hooks(hook)
94
+ model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
95
+ end
96
+
97
+ # For performance reasons, we define empty hook instance methods, which are
98
+ # overwritten with real hook instance methods whenever the hook class method is called.
99
+ add_hook_type(*(HOOKS + PRIVATE_HOOKS))
100
+ end
101
+ end