sequel 2.11.0 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. data/CHANGELOG +168 -0
  2. data/README.rdoc +77 -95
  3. data/Rakefile +100 -80
  4. data/bin/sequel +2 -1
  5. data/doc/advanced_associations.rdoc +23 -32
  6. data/doc/cheat_sheet.rdoc +23 -40
  7. data/doc/dataset_filtering.rdoc +6 -6
  8. data/doc/prepared_statements.rdoc +22 -22
  9. data/doc/release_notes/2.12.0.txt +534 -0
  10. data/doc/schema.rdoc +3 -1
  11. data/doc/sharding.rdoc +8 -8
  12. data/doc/virtual_rows.rdoc +65 -0
  13. data/lib/sequel.rb +1 -1
  14. data/lib/{sequel_core → sequel}/adapters/ado.rb +3 -3
  15. data/lib/{sequel_core → sequel}/adapters/db2.rb +0 -0
  16. data/lib/{sequel_core → sequel}/adapters/dbi.rb +1 -1
  17. data/lib/{sequel_core → sequel}/adapters/do.rb +9 -5
  18. data/lib/{sequel_core → sequel}/adapters/do/mysql.rb +1 -1
  19. data/lib/{sequel_core → sequel}/adapters/do/postgres.rb +1 -1
  20. data/lib/{sequel_core → sequel}/adapters/do/sqlite.rb +1 -1
  21. data/lib/{sequel_core → sequel}/adapters/firebird.rb +84 -80
  22. data/lib/{sequel_core → sequel}/adapters/informix.rb +1 -1
  23. data/lib/{sequel_core → sequel}/adapters/jdbc.rb +21 -14
  24. data/lib/{sequel_core → sequel}/adapters/jdbc/h2.rb +14 -13
  25. data/lib/{sequel_core → sequel}/adapters/jdbc/mysql.rb +1 -1
  26. data/lib/{sequel_core → sequel}/adapters/jdbc/oracle.rb +1 -1
  27. data/lib/{sequel_core → sequel}/adapters/jdbc/postgresql.rb +1 -1
  28. data/lib/{sequel_core → sequel}/adapters/jdbc/sqlite.rb +1 -1
  29. data/lib/{sequel_core → sequel}/adapters/mysql.rb +60 -39
  30. data/lib/{sequel_core → sequel}/adapters/odbc.rb +8 -4
  31. data/lib/{sequel_core → sequel}/adapters/openbase.rb +0 -0
  32. data/lib/{sequel_core → sequel}/adapters/oracle.rb +38 -7
  33. data/lib/{sequel_core → sequel}/adapters/postgres.rb +24 -24
  34. data/lib/{sequel_core → sequel}/adapters/shared/mssql.rb +5 -5
  35. data/lib/{sequel_core → sequel}/adapters/shared/mysql.rb +126 -71
  36. data/lib/{sequel_core → sequel}/adapters/shared/oracle.rb +7 -10
  37. data/lib/{sequel_core → sequel}/adapters/shared/postgres.rb +159 -125
  38. data/lib/{sequel_core → sequel}/adapters/shared/progress.rb +1 -2
  39. data/lib/{sequel_core → sequel}/adapters/shared/sqlite.rb +72 -67
  40. data/lib/{sequel_core → sequel}/adapters/sqlite.rb +11 -7
  41. data/lib/{sequel_core → sequel}/adapters/utils/date_format.rb +0 -0
  42. data/lib/{sequel_core → sequel}/adapters/utils/stored_procedures.rb +0 -0
  43. data/lib/{sequel_core → sequel}/adapters/utils/unsupported.rb +19 -0
  44. data/lib/{sequel_core → sequel}/connection_pool.rb +7 -5
  45. data/lib/sequel/core.rb +221 -0
  46. data/lib/{sequel_core → sequel}/core_sql.rb +91 -49
  47. data/lib/{sequel_core → sequel}/database.rb +264 -149
  48. data/lib/{sequel_core/schema/generator.rb → sequel/database/schema_generator.rb} +6 -2
  49. data/lib/{sequel_core/database/schema.rb → sequel/database/schema_methods.rb} +12 -12
  50. data/lib/sequel/database/schema_sql.rb +224 -0
  51. data/lib/{sequel_core → sequel}/dataset.rb +78 -236
  52. data/lib/{sequel_core → sequel}/dataset/convenience.rb +99 -61
  53. data/lib/{sequel_core/object_graph.rb → sequel/dataset/graph.rb} +16 -14
  54. data/lib/{sequel_core → sequel}/dataset/prepared_statements.rb +1 -1
  55. data/lib/{sequel_core → sequel}/dataset/sql.rb +150 -99
  56. data/lib/sequel/deprecated.rb +593 -0
  57. data/lib/sequel/deprecated_migration.rb +91 -0
  58. data/lib/sequel/exceptions.rb +48 -0
  59. data/lib/sequel/extensions/blank.rb +42 -0
  60. data/lib/{sequel_model → sequel/extensions}/inflector.rb +8 -1
  61. data/lib/{sequel_core → sequel/extensions}/migration.rb +1 -1
  62. data/lib/{sequel_core/dataset → sequel/extensions}/pagination.rb +0 -0
  63. data/lib/{sequel_core → sequel/extensions}/pretty_table.rb +7 -0
  64. data/lib/{sequel_core/dataset → sequel/extensions}/query.rb +7 -0
  65. data/lib/sequel/extensions/string_date_time.rb +47 -0
  66. data/lib/sequel/metaprogramming.rb +43 -0
  67. data/lib/sequel/model.rb +110 -0
  68. data/lib/sequel/model/associations.rb +1300 -0
  69. data/lib/sequel/model/base.rb +937 -0
  70. data/lib/sequel/model/deprecated.rb +204 -0
  71. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  72. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  73. data/lib/sequel/model/deprecated_validations.rb +388 -0
  74. data/lib/sequel/model/errors.rb +39 -0
  75. data/lib/{sequel_model → sequel/model}/exceptions.rb +4 -4
  76. data/lib/sequel/model/inflections.rb +208 -0
  77. data/lib/sequel/model/plugins.rb +76 -0
  78. data/lib/sequel/plugins/caching.rb +122 -0
  79. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  80. data/lib/sequel/plugins/schema.rb +53 -0
  81. data/lib/sequel/plugins/serialization.rb +117 -0
  82. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  83. data/lib/sequel/plugins/validation_class_methods.rb +384 -0
  84. data/lib/sequel/plugins/validation_helpers.rb +150 -0
  85. data/lib/{sequel_core → sequel}/sql.rb +125 -190
  86. data/lib/{sequel_core → sequel}/version.rb +2 -1
  87. data/lib/sequel_core.rb +1 -172
  88. data/lib/sequel_model.rb +1 -91
  89. data/spec/adapters/firebird_spec.rb +5 -5
  90. data/spec/adapters/informix_spec.rb +1 -1
  91. data/spec/adapters/mysql_spec.rb +128 -42
  92. data/spec/adapters/oracle_spec.rb +47 -19
  93. data/spec/adapters/postgres_spec.rb +64 -52
  94. data/spec/adapters/spec_helper.rb +1 -1
  95. data/spec/adapters/sqlite_spec.rb +12 -17
  96. data/spec/{sequel_core → core}/connection_pool_spec.rb +10 -10
  97. data/spec/{sequel_core → core}/core_ext_spec.rb +19 -19
  98. data/spec/{sequel_core → core}/core_sql_spec.rb +68 -71
  99. data/spec/{sequel_core → core}/database_spec.rb +135 -99
  100. data/spec/{sequel_core → core}/dataset_spec.rb +398 -242
  101. data/spec/{sequel_core → core}/expression_filters_spec.rb +13 -13
  102. data/spec/core/migration_spec.rb +263 -0
  103. data/spec/{sequel_core → core}/object_graph_spec.rb +10 -10
  104. data/spec/{sequel_core → core}/pretty_table_spec.rb +2 -2
  105. data/spec/{sequel_core → core}/schema_generator_spec.rb +0 -0
  106. data/spec/{sequel_core → core}/schema_spec.rb +8 -10
  107. data/spec/{sequel_core → core}/spec_helper.rb +29 -2
  108. data/spec/{sequel_core → core}/version_spec.rb +0 -0
  109. data/spec/extensions/blank_spec.rb +67 -0
  110. data/spec/extensions/caching_spec.rb +201 -0
  111. data/spec/{sequel_model/hooks_spec.rb → extensions/hook_class_methods_spec.rb} +8 -23
  112. data/spec/{sequel_model → extensions}/inflector_spec.rb +3 -0
  113. data/spec/{sequel_core → extensions}/migration_spec.rb +4 -4
  114. data/spec/extensions/pagination_spec.rb +99 -0
  115. data/spec/extensions/pretty_table_spec.rb +91 -0
  116. data/spec/extensions/query_spec.rb +85 -0
  117. data/spec/{sequel_model → extensions}/schema_spec.rb +22 -1
  118. data/spec/extensions/serialization_spec.rb +109 -0
  119. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  120. data/spec/{sequel_model → extensions}/spec_helper.rb +13 -4
  121. data/spec/extensions/string_date_time_spec.rb +93 -0
  122. data/spec/{sequel_model/validations_spec.rb → extensions/validation_class_methods_spec.rb} +15 -103
  123. data/spec/extensions/validation_helpers_spec.rb +291 -0
  124. data/spec/integration/dataset_test.rb +31 -0
  125. data/spec/integration/eager_loader_test.rb +17 -30
  126. data/spec/integration/schema_test.rb +8 -5
  127. data/spec/integration/spec_helper.rb +17 -0
  128. data/spec/integration/transaction_test.rb +68 -0
  129. data/spec/{sequel_model → model}/association_reflection_spec.rb +0 -0
  130. data/spec/{sequel_model → model}/associations_spec.rb +23 -10
  131. data/spec/{sequel_model → model}/base_spec.rb +29 -20
  132. data/spec/{sequel_model → model}/caching_spec.rb +16 -14
  133. data/spec/{sequel_model → model}/dataset_methods_spec.rb +0 -0
  134. data/spec/{sequel_model → model}/eager_loading_spec.rb +8 -8
  135. data/spec/model/hooks_spec.rb +472 -0
  136. data/spec/model/inflector_spec.rb +126 -0
  137. data/spec/{sequel_model → model}/model_spec.rb +25 -20
  138. data/spec/model/plugins_spec.rb +142 -0
  139. data/spec/{sequel_model → model}/record_spec.rb +121 -62
  140. data/spec/model/schema_spec.rb +92 -0
  141. data/spec/model/spec_helper.rb +124 -0
  142. data/spec/model/validations_spec.rb +1080 -0
  143. metadata +136 -107
  144. data/lib/sequel_core/core_ext.rb +0 -217
  145. data/lib/sequel_core/dataset/callback.rb +0 -13
  146. data/lib/sequel_core/dataset/schema.rb +0 -15
  147. data/lib/sequel_core/deprecated.rb +0 -26
  148. data/lib/sequel_core/exceptions.rb +0 -44
  149. data/lib/sequel_core/schema.rb +0 -2
  150. data/lib/sequel_core/schema/sql.rb +0 -325
  151. data/lib/sequel_model/association_reflection.rb +0 -267
  152. data/lib/sequel_model/associations.rb +0 -499
  153. data/lib/sequel_model/base.rb +0 -539
  154. data/lib/sequel_model/caching.rb +0 -82
  155. data/lib/sequel_model/dataset_methods.rb +0 -26
  156. data/lib/sequel_model/eager_loading.rb +0 -370
  157. data/lib/sequel_model/hooks.rb +0 -101
  158. data/lib/sequel_model/plugins.rb +0 -62
  159. data/lib/sequel_model/record.rb +0 -568
  160. data/lib/sequel_model/schema.rb +0 -49
  161. data/lib/sequel_model/validations.rb +0 -429
  162. data/spec/sequel_model/plugins_spec.rb +0 -80
@@ -0,0 +1,1300 @@
1
+ module Sequel
2
+ class Model
3
+ # Associations are used in order to specify relationships between model classes
4
+ # that reflect relations between tables in the database using foreign keys.
5
+ module Associations
6
+ # Map of association type symbols to association reflection classes.
7
+ ASSOCIATION_TYPES = {}
8
+
9
+ # Set an empty association reflection hash in the model
10
+ def self.apply(model)
11
+ model.instance_variable_set(:@association_reflections, {})
12
+ end
13
+
14
+ # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
15
+ # provides methods to reduce internal code duplication. It should not
16
+ # be instantiated by the user.
17
+ class AssociationReflection < Hash
18
+ include Sequel::Inflections
19
+
20
+ # Name symbol for the _add internal association method
21
+ def _add_method
22
+ :"_add_#{singularize(self[:name])}"
23
+ end
24
+
25
+ # Name symbol for the _dataset association method
26
+ def _dataset_method
27
+ :"_#{self[:name]}_dataset"
28
+ end
29
+
30
+ # Name symbol for the _remove_all internal association method
31
+ def _remove_all_method
32
+ :"_remove_all_#{self[:name]}"
33
+ end
34
+
35
+ # Name symbol for the _remove internal association method
36
+ def _remove_method
37
+ :"_remove_#{singularize(self[:name])}"
38
+ end
39
+
40
+ # Name symbol for the _setter association method
41
+ def _setter_method
42
+ :"_#{self[:name]}="
43
+ end
44
+
45
+ # Name symbol for the add association method
46
+ def add_method
47
+ :"add_#{singularize(self[:name])}"
48
+ end
49
+
50
+ # Name symbol for association method, the same as the name of the association.
51
+ def association_method
52
+ self[:name]
53
+ end
54
+
55
+ # The class associated to the current model class via this association
56
+ def associated_class
57
+ self[:class] ||= constantize(self[:class_name])
58
+ end
59
+
60
+ # Name symbol for the dataset association method
61
+ def dataset_method
62
+ :"#{self[:name]}_dataset"
63
+ end
64
+
65
+ # Name symbol for the _helper internal association method
66
+ def dataset_helper_method
67
+ :"_#{self[:name]}_dataset_helper"
68
+ end
69
+
70
+ # Whether the dataset needs a primary key to function, true by default.
71
+ def dataset_need_primary_key?
72
+ true
73
+ end
74
+
75
+ # Whether to eagerly graph a lazy dataset, true by default. If this
76
+ # is false, the association won't respect the :eager_graph option
77
+ # when loading the association for a single record.
78
+ def eager_graph_lazy_dataset?
79
+ true
80
+ end
81
+
82
+ # Whether the associated object needs a primary key to be added/removed,
83
+ # false by default.
84
+ def need_associated_primary_key?
85
+ false
86
+ end
87
+
88
+ # Returns the reciprocal association variable, if one exists. The reciprocal
89
+ # association is the association in the associated class that is the opposite
90
+ # of the current association. For example, Album.many_to_one :artist and
91
+ # Artist.one_to_many :albums are reciprocal associations. This information is
92
+ # to populate reciprocal associations. For example, when you do this_artist.add_album(album)
93
+ # it sets album.artist to this_artist.
94
+ def reciprocal
95
+ return self[:reciprocal] if include?(:reciprocal)
96
+ r_type = reciprocal_type
97
+ key = self[:key]
98
+ associated_class.all_association_reflections.each do |assoc_reflect|
99
+ if assoc_reflect[:type] == r_type && assoc_reflect[:key] == key
100
+ return self[:reciprocal] = assoc_reflect[:name]
101
+ end
102
+ end
103
+ self[:reciprocal] = nil
104
+ end
105
+
106
+ # Whether the reciprocal of this association returns an array of objects instead of a single object,
107
+ # true by default.
108
+ def reciprocal_array?
109
+ true
110
+ end
111
+
112
+ # Name symbol for the remove_all_ association method
113
+ def remove_all_method
114
+ :"remove_all_#{self[:name]}"
115
+ end
116
+
117
+ # Name symbol for the remove_ association method
118
+ def remove_method
119
+ :"remove_#{singularize(self[:name])}"
120
+ end
121
+
122
+ # Whether this association returns an array of objects instead of a single object,
123
+ # true by default.
124
+ def returns_array?
125
+ true
126
+ end
127
+
128
+ # The columns to select when loading the association, nil by default.
129
+ def select
130
+ self[:select]
131
+ end
132
+
133
+ # Whether to set the reciprocal association to self when loading associated
134
+ # records, false by default.
135
+ def set_reciprocal_to_self?
136
+ false
137
+ end
138
+
139
+ # Name symbol for the setter association method
140
+ def setter_method
141
+ :"#{self[:name]}="
142
+ end
143
+ end
144
+
145
+ class ManyToOneAssociationReflection < AssociationReflection
146
+ ASSOCIATION_TYPES[:many_to_one] = self
147
+
148
+ # Whether the dataset needs a primary key to function, false for many_to_one associations.
149
+ def dataset_need_primary_key?
150
+ false
151
+ end
152
+
153
+ # Default foreign key name symbol for foreign key in current model's table that points to
154
+ # the given association's table's primary key.
155
+ def default_key
156
+ :"#{self[:name]}_id"
157
+ end
158
+
159
+ # Whether to eagerly graph a lazy dataset, true for many_to_one associations
160
+ # only if the key is nil.
161
+ def eager_graph_lazy_dataset?
162
+ self[:key].nil?
163
+ end
164
+
165
+ # The key to use for the key hash when eager loading
166
+ def eager_loader_key
167
+ self[:key]
168
+ end
169
+
170
+ # The column in the associated table that the key in the current table references.
171
+ def primary_key
172
+ self[:primary_key] ||= associated_class.primary_key
173
+ end
174
+
175
+ # Whether this association returns an array of objects instead of a single object,
176
+ # false for a many_to_one association.
177
+ def returns_array?
178
+ false
179
+ end
180
+
181
+ private
182
+
183
+ # The reciprocal type of a many_to_one association is a one_to_many association.
184
+ def reciprocal_type
185
+ :one_to_many
186
+ end
187
+ end
188
+
189
+ class OneToManyAssociationReflection < AssociationReflection
190
+ ASSOCIATION_TYPES[:one_to_many] = self
191
+
192
+ # Default foreign key name symbol for key in associated table that points to
193
+ # current table's primary key.
194
+ def default_key
195
+ :"#{underscore(demodulize(self[:model].name))}_id"
196
+ end
197
+
198
+ # The column in the current table that the key in the associated table references.
199
+ def primary_key
200
+ self[:primary_key] ||= self[:model].primary_key
201
+ end
202
+ alias eager_loader_key primary_key
203
+
204
+ # One to many associations set the reciprocal to self when loading associated records.
205
+ def set_reciprocal_to_self?
206
+ true
207
+ end
208
+
209
+ # Whether the reciprocal of this association returns an array of objects instead of a single object,
210
+ # false for a one_to_many association.
211
+ def reciprocal_array?
212
+ false
213
+ end
214
+
215
+ private
216
+
217
+ # The reciprocal type of a one_to_many association is a many_to_one association.
218
+ def reciprocal_type
219
+ :many_to_one
220
+ end
221
+ end
222
+
223
+ class ManyToManyAssociationReflection < AssociationReflection
224
+ ASSOCIATION_TYPES[:many_to_many] = self
225
+
226
+ # Default name symbol for the join table.
227
+ def default_join_table
228
+ [self[:class_name], self[:model].name].map{|i| underscore(pluralize(demodulize(i)))}.sort.join('_').to_sym
229
+ end
230
+
231
+ # Default foreign key name symbol for key in join table that points to
232
+ # current table's primary key (or :left_primary_key column).
233
+ def default_left_key
234
+ :"#{underscore(demodulize(self[:model].name))}_id"
235
+ end
236
+
237
+ # Default foreign key name symbol for foreign key in join table that points to
238
+ # the association's table's primary key (or :right_primary_key column).
239
+ def default_right_key
240
+ :"#{singularize(self[:name])}_id"
241
+ end
242
+
243
+ # The key to use for the key hash when eager loading
244
+ def eager_loader_key
245
+ self[:left_primary_key]
246
+ end
247
+
248
+ # Whether the associated object needs a primary key to be added/removed,
249
+ # true for many_to_many associations.
250
+ def need_associated_primary_key?
251
+ true
252
+ end
253
+
254
+ # Returns the reciprocal association symbol, if one exists.
255
+ def reciprocal
256
+ return self[:reciprocal] if include?(:reciprocal)
257
+ left_key = self[:left_key]
258
+ right_key = self[:right_key]
259
+ join_table = self[:join_table]
260
+ associated_class.all_association_reflections.each do |assoc_reflect|
261
+ if assoc_reflect[:type] == :many_to_many && assoc_reflect[:left_key] == right_key \
262
+ && assoc_reflect[:right_key] == left_key && assoc_reflect[:join_table] == join_table
263
+ return self[:reciprocal] = assoc_reflect[:name]
264
+ end
265
+ end
266
+ self[:reciprocal] = nil
267
+ end
268
+
269
+ # The primary key column to use in the associated table.
270
+ def right_primary_key
271
+ self[:right_primary_key] ||= associated_class.primary_key
272
+ end
273
+
274
+ # The columns to select when loading the association, associated_class.table_name.* by default.
275
+ def select
276
+ return self[:select] if include?(:select)
277
+ self[:select] ||= Sequel::SQL::ColumnAll.new(associated_class.table_name)
278
+ end
279
+ end
280
+
281
+ # This module contains methods added to all association datasets
282
+ module AssociationDatasetMethods
283
+ # The model object that created the association dataset
284
+ attr_accessor :model_object
285
+
286
+ # The association reflection related to the association dataset
287
+ attr_accessor :association_reflection
288
+ end
289
+
290
+ # Each kind of association adds a number of instance methods to the model class which
291
+ # are specialized according to the association type and optional parameters
292
+ # given in the definition. Example:
293
+ #
294
+ # class Project < Sequel::Model
295
+ # many_to_one :portfolio
296
+ # one_to_many :milestones
297
+ # # or: many_to_many :milestones
298
+ # end
299
+ #
300
+ # The project class now has the following instance methods:
301
+ # * portfolio - Returns the associated portfolio.
302
+ # * portfolio=(obj) - Sets the associated portfolio to the object,
303
+ # but the change is not persisted until you save the record.
304
+ # * portfolio_dataset - Returns a dataset that would return the associated
305
+ # portfolio, only useful in fairly specific circumstances.
306
+ # * milestones - Returns an array of associated milestones
307
+ # * add_milestone(obj) - Associates the passed milestone with this object.
308
+ # * remove_milestone(obj) - Removes the association with the passed milestone.
309
+ # * remove_all_milestones - Removes associations with all associated milestones.
310
+ # * milestones_dataset - Returns a dataset that would return the associated
311
+ # milestones, allowing for further filtering/limiting/etc.
312
+ #
313
+ # If you want to override the behavior of the add_/remove_/remove_all_ methods,
314
+ # there are private instance methods created that a prepended with an
315
+ # underscore (e.g. _add_milestone). The private instance methods can be
316
+ # easily overridden, but you shouldn't override the public instance methods without
317
+ # calling super, as they deal with callbacks and caching.
318
+ #
319
+ # By default the classes for the associations are inferred from the association
320
+ # name, so for example the Project#portfolio will return an instance of
321
+ # Portfolio, and Project#milestones will return an array of Milestone
322
+ # instances.
323
+ #
324
+ # Association definitions are also reflected by the class, e.g.:
325
+ #
326
+ # Project.associations
327
+ # => [:portfolio, :milestones]
328
+ # Project.association_reflection(:portfolio)
329
+ # => {:type => :many_to_one, :name => :portfolio, :class_name => "Portfolio"}
330
+ module ClassMethods
331
+ # All association reflections defined for this model (default: none).
332
+ attr_reader :association_reflections
333
+
334
+ # Array of all association reflections for this model class
335
+ def all_association_reflections
336
+ association_reflections.values
337
+ end
338
+
339
+ # Associates a related model with the current model. The following types are
340
+ # supported:
341
+ #
342
+ # * :many_to_one - Foreign key in current model's table points to
343
+ # associated model's primary key. Each associated model object can
344
+ # be associated with more than one current model objects. Each current
345
+ # model object can be associated with only one associated model object.
346
+ # * :one_to_many - Foreign key in associated model's table points to this
347
+ # model's primary key. Each current model object can be associated with
348
+ # more than one associated model objects. Each associated model object
349
+ # can be associated with only one current model object.
350
+ # * :many_to_many - A join table is used that has a foreign key that points
351
+ # to this model's primary key and a foreign key that points to the
352
+ # associated model's primary key. Each current model object can be
353
+ # associated with many associated model objects, and each associated
354
+ # model object can be associated with many current model objects.
355
+ #
356
+ # A one to one relationship can be set up with a many_to_one association
357
+ # on the table with the foreign key, and a one_to_many association with the
358
+ # :one_to_one option specified on the table without the foreign key. The
359
+ # two associations will operate similarly, except that the many_to_one
360
+ # association setter doesn't update the database until you call save manually.
361
+ # Also, in most cases you need to specify the plural association name when using
362
+ # one_to_many with the :one_to_one option.
363
+ #
364
+ # The following options can be supplied:
365
+ # * *ALL types*:
366
+ # - :after_add - Symbol, Proc, or array of both/either specifying a callback to call
367
+ # after a new item is added to the association.
368
+ # - :after_load - Symbol, Proc, or array of both/either specifying a callback to call
369
+ # after the associated record(s) have been retrieved from the database. Not called
370
+ # when eager loading via eager_graph, but called when eager loading via eager.
371
+ # - :after_remove - Symbol, Proc, or array of both/either specifying a callback to call
372
+ # after an item is removed from the association.
373
+ # - :allow_eager - If set to false, you cannot load the association eagerly
374
+ # via eager or eager_graph
375
+ # - :before_add - Symbol, Proc, or array of both/either specifying a callback to call
376
+ # before a new item is added to the association.
377
+ # - :before_remove - Symbol, Proc, or array of both/either specifying a callback to call
378
+ # before an item is removed from the association.
379
+ # - :class - The associated class or its name. If not
380
+ # given, uses the association's name, which is camelized (and
381
+ # singularized unless the type is :many_to_one)
382
+ # - :clone - Merge the current options and block into the options and block used in defining
383
+ # the given association. Can be used to DRY up a bunch of similar associations that
384
+ # all share the same options such as :class and :key, while changing the order and block used.
385
+ # - :conditions - The conditions to use to filter the association, can be any argument passed to filter.
386
+ # - :dataset - A proc that is instance_evaled to get the base dataset
387
+ # to use for the _dataset method (before the other options are applied).
388
+ # - :eager - The associations to eagerly load via #eager when loading the associated object(s).
389
+ # For many_to_one associations, this is ignored unless this association is
390
+ # being eagerly loaded, as it doesn't save queries unless multiple objects
391
+ # can be loaded at once.
392
+ # - :eager_block - If given, use the block instead of the default block when
393
+ # eagerly loading. To not use a block when eager loading (when one is used normally),
394
+ # set to nil.
395
+ # - :eager_graph - The associations to eagerly load via #eager_graph when loading the associated object(s).
396
+ # For many_to_one associations, this is ignored unless this association is
397
+ # being eagerly loaded, as it doesn't save queries unless multiple objects
398
+ # can be loaded at once.
399
+ # - :eager_grapher - A proc to use to implement eager loading via eager graph, overriding the default.
400
+ # Takes three arguments, a dataset, an alias to use for the table to graph for this association,
401
+ # and the alias that was used for the current table (since you can cascade associations),
402
+ # Should return a copy of the dataset with the association graphed into it.
403
+ # - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes three arguments,
404
+ # a key hash (used solely to enhance performance), an array of records,
405
+ # and a hash of dependent associations. The associated records should
406
+ # be queried from the database and the associations cache for each
407
+ # record should be populated for this to work correctly.
408
+ # - :extend - A module or array of modules to extend the dataset with.
409
+ # - :graph_block - The block to pass to join_table when eagerly loading
410
+ # the association via eager_graph.
411
+ # - :graph_conditions - The additional conditions to use on the SQL join when eagerly loading
412
+ # the association via eager_graph. Should be a hash or an array of all two pairs. If not
413
+ # specified, the :conditions option is used if it is a hash or array of all two pairs.
414
+ # - :graph_join_type - The type of SQL join to use when eagerly loading the association via
415
+ # eager_graph. Defaults to :left_outer.
416
+ # - :graph_only_conditions - The conditions to use on the SQL join when eagerly loading
417
+ # the association via eager_graph, instead of the default conditions specified by the
418
+ # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
419
+ # - :graph_select - A column or array of columns to select from the associated table
420
+ # when eagerly loading the association via eager_graph. Defaults to all
421
+ # columns in the associated table.
422
+ # - :limit - Limit the number of records to the provided value. Use
423
+ # an array with two arguments for the value to specify a limit and an offset.
424
+ # - :order - the column(s) by which to order the association dataset. Can be a
425
+ # singular column or an array.
426
+ # - :order_eager_graph - Whether to add the order to the dataset's order when graphing
427
+ # via eager graph. Defaults to true, so set to false to disable.
428
+ # - :read_only - Do not add a setter method (for many_to_one or one_to_many with :one_to_one),
429
+ # or add_/remove_/remove_all_ methods (for one_to_many, many_to_many)
430
+ # - :reciprocal - the symbol name of the reciprocal association,
431
+ # if it exists. By default, sequel will try to determine it by looking at the
432
+ # associated model's assocations for a association that matches
433
+ # the current association's key(s). Set to nil to not use a reciprocal.
434
+ # - :select - the attributes to select. Defaults to the associated class's
435
+ # table_name.* in a many_to_many association, which means it doesn't include the attributes from the
436
+ # join table. If you want to include the join table attributes, you can
437
+ # use this option, but beware that the join table attributes can clash with
438
+ # attributes from the model table, so you should alias any attributes that have
439
+ # the same name in both the join table and the associated table.
440
+ # * :many_to_one:
441
+ # - :key - foreign_key in current model's table that references
442
+ # associated model's primary key, as a symbol. Defaults to :"#{name}_id".
443
+ # - :primary_key - column in the associated table that :key option references, as a symbol.
444
+ # Defaults to the primary key of the associated table.
445
+ # * :one_to_many:
446
+ # - :key - foreign key in associated model's table that references
447
+ # current model's primary key, as a symbol. Defaults to
448
+ # :"#{self.name.underscore}_id".
449
+ # - :one_to_one: Create a getter and setter similar to those of many_to_one
450
+ # associations. The getter returns a singular matching record, or raises an
451
+ # error if multiple records match. The setter updates the record given and removes
452
+ # associations with all other records. When this option is used, the other
453
+ # association methods usually added are either removed or made private,
454
+ # so using this is similar to using many_to_one, in terms of the methods
455
+ # it adds, the main difference is that the foreign key is in the associated
456
+ # table instead of the current table. Note that using this option still requires
457
+ # you to use a plural name when creating and using the association (e.g. for reflections, eager loading, etc.).
458
+ # - :primary_key - column in the current table that :key option references, as a symbol.
459
+ # Defaults to primary key of the current table.
460
+ # * :many_to_many:
461
+ # - :graph_join_table_block - The block to pass to join_table for
462
+ # the join table when eagerly loading the association via eager_graph.
463
+ # - :graph_join_table_conditions - The additional conditions to use on the SQL join for
464
+ # the join table when eagerly loading the association via eager_graph. Should be a hash
465
+ # or an array of all two pairs.
466
+ # - :graph_join_table_join_type - The type of SQL join to use for the join table when eagerly
467
+ # loading the association via eager_graph. Defaults to the :graph_join_type option or
468
+ # :left_outer.
469
+ # - :graph_join_table_only_conditions - The conditions to use on the SQL join for the join
470
+ # table when eagerly loading the association via eager_graph, instead of the default
471
+ # conditions specified by the foreign/primary keys. This option causes the
472
+ # :graph_join_table_conditions option to be ignored.
473
+ # - :join_table - name of table that includes the foreign keys to both
474
+ # the current model and the associated model, as a symbol. Defaults to the name
475
+ # of current model and name of associated model, pluralized,
476
+ # underscored, sorted, and joined with '_'.
477
+ # - :left_key - foreign key in join table that points to current model's
478
+ # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
479
+ # - :left_primary_key - column in current table that :left_key points to, as a symbol.
480
+ # Defaults to primary key of current table.
481
+ # - :right_key - foreign key in join table that points to associated
482
+ # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
483
+ # - :right_primary_key - column in associated table that :right_key points to, as a symbol.
484
+ # Defaults to primary key of the associated table.
485
+ # - :uniq - Adds a after_load callback that makes the array of objects unique.
486
+ def associate(type, name, opts = {}, &block)
487
+ raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
488
+ raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
489
+
490
+ # merge early so we don't modify opts
491
+ orig_opts = opts.dup
492
+ orig_opts = association_reflection(opts[:clone])[:orig_opts].merge(orig_opts) if opts[:clone]
493
+ opts = orig_opts.merge(:type => type, :name => name, :cache => true, :model => self)
494
+ opts[:block] = block if block
495
+ opts = assoc_class.new.merge!(opts)
496
+ opts[:eager_block] = block unless opts.include?(:eager_block)
497
+ opts[:graph_join_type] ||= :left_outer
498
+ opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
499
+ conds = opts[:conditions]
500
+ opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
501
+ opts[:graph_conditions] = opts[:graph_conditions] ? opts[:graph_conditions].to_a : []
502
+ opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
503
+ [:before_add, :before_remove, :after_add, :after_remove, :after_load, :extend].each do |cb_type|
504
+ opts[cb_type] = Array(opts[cb_type])
505
+ end
506
+
507
+ # find class
508
+ case opts[:class]
509
+ when String, Symbol
510
+ # Delete :class to allow late binding
511
+ opts[:class_name] ||= opts.delete(:class).to_s
512
+ when Class
513
+ opts[:class_name] ||= opts[:class].name
514
+ end
515
+
516
+ send(:"def_#{type}", opts)
517
+
518
+ orig_opts.delete(:clone)
519
+ orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>block)
520
+ opts[:orig_opts] = orig_opts
521
+ # don't add to association_reflections until we are sure there are no errors
522
+ association_reflections[name] = opts
523
+ end
524
+
525
+ # The association reflection hash for the association of the given name.
526
+ def association_reflection(name)
527
+ association_reflections[name]
528
+ end
529
+
530
+ # Array of association name symbols
531
+ def associations
532
+ association_reflections.keys
533
+ end
534
+
535
+ # Modify and return eager loading dataset based on association options
536
+ def eager_loading_dataset(opts, ds, select, associations)
537
+ ds = ds.select(*select) if select
538
+ ds = ds.filter(opts[:conditions]) if opts[:conditions]
539
+ ds = ds.order(*opts[:order]) if opts[:order]
540
+ ds = ds.eager(opts[:eager]) if opts[:eager]
541
+ ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph]
542
+ ds = ds.eager(associations) unless Array(associations).empty?
543
+ ds = opts[:eager_block].call(ds) if opts[:eager_block]
544
+ ds
545
+ end
546
+
547
+ # Copy the association reflections to the subclass
548
+ def inherited(subclass)
549
+ super
550
+ subclass.instance_variable_set(:@association_reflections, @association_reflections.dup)
551
+ end
552
+
553
+ # Shortcut for adding a many_to_many association, see associate
554
+ def many_to_many(*args, &block)
555
+ associate(:many_to_many, *args, &block)
556
+ end
557
+
558
+ # Shortcut for adding a many_to_one association, see associate
559
+ def many_to_one(*args, &block)
560
+ associate(:many_to_one, *args, &block)
561
+ end
562
+
563
+ # Shortcut for adding a one_to_many association, see associate
564
+ def one_to_many(*args, &block)
565
+ associate(:one_to_many, *args, &block)
566
+ end
567
+
568
+ private
569
+
570
+ # Add a method to the module included in the class, so the method
571
+ # can be easily overridden in the class itself while allowing for
572
+ # super to be called.
573
+ def association_module_def(name, &block)
574
+ overridable_methods_module.module_eval{define_method(name, &block)}
575
+ end
576
+
577
+ # Add a private method to the module included in the class.
578
+ def association_module_private_def(name, &block)
579
+ association_module_def(name, &block)
580
+ overridable_methods_module.send(:private, name)
581
+ end
582
+
583
+ # Add the add_ instance method
584
+ def def_add_method(opts)
585
+ association_module_def(opts.add_method){|o| add_associated_object(opts, o)}
586
+ end
587
+
588
+ # Adds methods to the module included in the class related to getting the
589
+ # dataset and associated object(s).
590
+ def def_association_dataset_methods(opts)
591
+ # If a block is given, define a helper method for it, because it takes
592
+ # an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
593
+ association_module_private_def(opts.dataset_helper_method, &opts[:block]) if opts[:block]
594
+ association_module_private_def(opts._dataset_method, &opts[:dataset])
595
+ association_module_def(opts.dataset_method){_dataset(opts)}
596
+ association_module_def(opts.association_method){|*reload| load_associated_objects(opts, reload[0])}
597
+ end
598
+
599
+ # Adds many_to_many association instance methods
600
+ def def_many_to_many(opts)
601
+ name = opts[:name]
602
+ model = self
603
+ left = (opts[:left_key] ||= opts.default_left_key)
604
+ right = (opts[:right_key] ||= opts.default_right_key)
605
+ left_pk = (opts[:left_primary_key] ||= self.primary_key)
606
+ opts[:class_name] ||= camelize(singularize(name))
607
+ join_table = (opts[:join_table] ||= opts.default_join_table)
608
+ left_key_alias = opts[:left_key_alias] ||= :x_foreign_key_x
609
+ left_key_select = opts[:left_key_select] ||= SQL::QualifiedIdentifier.new(join_table, left).as(opts[:left_key_alias])
610
+ graph_jt_conds = opts[:graph_join_table_conditions] = opts[:graph_join_table_conditions] ? opts[:graph_join_table_conditions].to_a : []
611
+ opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
612
+ opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
613
+ opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, [[right, opts.right_primary_key], [left, send(left_pk)]])}
614
+ database = db
615
+
616
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
617
+ h = key_hash[left_pk]
618
+ records.each{|object| object.associations[name] = []}
619
+ model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, [[right, opts.right_primary_key], [left, h.keys]]), Array(opts.select) + Array(left_key_select), associations).all do |assoc_record|
620
+ next unless objects = h[assoc_record.values.delete(left_key_alias)]
621
+ objects.each{|object| object.associations[name].push(assoc_record)}
622
+ end
623
+ end
624
+
625
+ join_type = opts[:graph_join_type]
626
+ select = opts[:graph_select]
627
+ use_only_conditions = opts.include?(:graph_only_conditions)
628
+ only_conditions = opts[:graph_only_conditions]
629
+ conditions = opts[:graph_conditions]
630
+ graph_block = opts[:graph_block]
631
+ use_jt_only_conditions = opts.include?(:graph_join_table_only_conditions)
632
+ jt_only_conditions = opts[:graph_join_table_only_conditions]
633
+ jt_join_type = opts[:graph_join_table_join_type]
634
+ jt_graph_block = opts[:graph_join_table_block]
635
+ opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
636
+ ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : [[left, left_pk]] + graph_jt_conds, :select=>false, :table_alias=>ds.send(:eager_unique_table_alias, ds, join_table), :join_type=>jt_join_type, :implicit_qualifier=>table_alias, &jt_graph_block)
637
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : [[opts.right_primary_key, right]] + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
638
+ end
639
+
640
+ def_association_dataset_methods(opts)
641
+
642
+ return if opts[:read_only]
643
+
644
+ association_module_private_def(opts._add_method) do |o|
645
+ database.dataset.from(join_table).insert(left=>send(left_pk), right=>o.send(opts.right_primary_key))
646
+ end
647
+ association_module_private_def(opts._remove_method) do |o|
648
+ database.dataset.from(join_table).filter([[left, send(left_pk)], [right, o.send(opts.right_primary_key)]]).delete
649
+ end
650
+ association_module_private_def(opts._remove_all_method) do
651
+ database.dataset.from(join_table).filter(left=>send(left_pk)).delete
652
+ end
653
+
654
+ def_add_method(opts)
655
+ def_remove_methods(opts)
656
+ end
657
+
658
+ # Adds many_to_one association instance methods
659
+ def def_many_to_one(opts)
660
+ name = opts[:name]
661
+ model = self
662
+ opts[:key] = opts.default_key unless opts.include?(:key)
663
+ key = opts[:key]
664
+ opts[:class_name] ||= camelize(name)
665
+ opts[:dataset] ||= proc do
666
+ klass = opts.associated_class
667
+ klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>send(key))
668
+ end
669
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
670
+ h = key_hash[key]
671
+ keys = h.keys
672
+ # Default the cached association to nil, so any object that doesn't have it
673
+ # populated will have cached the negative lookup.
674
+ records.each{|object| object.associations[name] = nil}
675
+ # Skip eager loading if no objects have a foreign key for this association
676
+ unless keys.empty?
677
+ klass = opts.associated_class
678
+ model.eager_loading_dataset(opts, klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys), opts.select, associations).all do |assoc_record|
679
+ next unless objects = h[assoc_record.send(opts.primary_key)]
680
+ objects.each{|object| object.associations[name] = assoc_record}
681
+ end
682
+ end
683
+ end
684
+
685
+ join_type = opts[:graph_join_type]
686
+ select = opts[:graph_select]
687
+ use_only_conditions = opts.include?(:graph_only_conditions)
688
+ only_conditions = opts[:graph_only_conditions]
689
+ conditions = opts[:graph_conditions]
690
+ graph_block = opts[:graph_block]
691
+ opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
692
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : [[opts.primary_key, key]] + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, &graph_block)
693
+ end
694
+
695
+ def_association_dataset_methods(opts)
696
+
697
+ return if opts[:read_only]
698
+
699
+ association_module_private_def(opts._setter_method){|o| send(:"#{key}=", (o.send(opts.primary_key) if o))}
700
+ association_module_def(opts.setter_method){|o| set_associated_object(opts, o)}
701
+ end
702
+
703
+ # Adds one_to_many association instance methods
704
+ def def_one_to_many(opts)
705
+ name = opts[:name]
706
+ model = self
707
+ key = (opts[:key] ||= opts.default_key)
708
+ primary_key = (opts[:primary_key] ||= self.primary_key)
709
+ opts[:class_name] ||= camelize(singularize(name))
710
+ opts[:dataset] ||= proc do
711
+ klass = opts.associated_class
712
+ klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, key) => send(primary_key))
713
+ end
714
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
715
+ h = key_hash[primary_key]
716
+ records.each{|object| object.associations[name] = []}
717
+ reciprocal = opts.reciprocal
718
+ klass = opts.associated_class
719
+ model.eager_loading_dataset(opts, klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys), opts.select, associations).all do |assoc_record|
720
+ next unless objects = h[assoc_record[key]]
721
+ objects.each do |object|
722
+ object.associations[name].push(assoc_record)
723
+ assoc_record.associations[reciprocal] = object if reciprocal
724
+ end
725
+ end
726
+ end
727
+
728
+ join_type = opts[:graph_join_type]
729
+ select = opts[:graph_select]
730
+ use_only_conditions = opts.include?(:graph_only_conditions)
731
+ only_conditions = opts[:graph_only_conditions]
732
+ conditions = opts[:graph_conditions]
733
+ graph_block = opts[:graph_block]
734
+ opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
735
+ ds = ds.graph(opts.associated_class, use_only_conditions ? only_conditions : [[key, primary_key]] + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, &graph_block)
736
+ # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
737
+ ds.opts[:eager_graph][:reciprocals][assoc_alias] = opts.reciprocal
738
+ ds
739
+ end
740
+
741
+ def_association_dataset_methods(opts)
742
+
743
+ unless opts[:read_only]
744
+ association_module_private_def(opts._add_method) do |o|
745
+ o.send(:"#{key}=", send(primary_key))
746
+ o.save || raise(Sequel::Error, "invalid associated object, cannot save")
747
+ end
748
+ def_add_method(opts)
749
+
750
+ unless opts[:one_to_one]
751
+ association_module_private_def(opts._remove_method) do |o|
752
+ o.send(:"#{key}=", nil)
753
+ o.save || raise(Sequel::Error, "invalid associated object, cannot save")
754
+ end
755
+ association_module_private_def(opts._remove_all_method) do
756
+ opts.associated_class.filter(key=>send(primary_key)).update(key=>nil)
757
+ end
758
+ def_remove_methods(opts)
759
+ end
760
+ end
761
+ if opts[:one_to_one]
762
+ overridable_methods_module.send(:private, opts.association_method, opts.dataset_method)
763
+ n = singularize(name).to_sym
764
+ raise(Sequel::Error, "one_to_many association names should still be plural even when using the :one_to_one option") if n == name
765
+ association_module_def(n) do |*o|
766
+ objs = send(name, *o)
767
+ raise(Sequel::Error, "multiple values found for a one-to-one relationship") if objs.length > 1
768
+ objs.first
769
+ end
770
+ unless opts[:read_only]
771
+ overridable_methods_module.send(:private, opts.add_method)
772
+ association_module_def(:"#{n}=") do |o|
773
+ klass = opts.associated_class
774
+ update_database = lambda do
775
+ send(opts.add_method, o)
776
+ klass.filter(Sequel::SQL::BooleanExpression.new(:AND, {key=>send(primary_key)}, SQL::BooleanExpression.new(:'!=', klass.primary_key, o.pk))).update(key=>nil)
777
+ end
778
+ use_transactions ? db.transaction(opts){update_database.call} : update_database.call
779
+ end
780
+ end
781
+ end
782
+ end
783
+
784
+ # Add the remove_ and remove_all instance methods
785
+ def def_remove_methods(opts)
786
+ association_module_def(opts.remove_method){|o| remove_associated_object(opts, o)}
787
+ association_module_def(opts.remove_all_method){remove_all_associated_objects(opts)}
788
+ end
789
+ end
790
+
791
+ # Private instance methods used to implement the associations support.
792
+ module InstanceMethods
793
+ private
794
+
795
+ # Backbone behind association dataset methods
796
+ def _dataset(opts)
797
+ raise(Sequel::Error, "model object #{model} does not have a primary key") if opts.dataset_need_primary_key? && !pk
798
+ ds = send(opts._dataset_method)
799
+ ds.extend(AssociationDatasetMethods)
800
+ ds.model_object = self
801
+ ds.association_reflection = opts
802
+ opts[:extend].each{|m| ds.extend(m)}
803
+ ds = ds.select(*opts.select) if opts.select
804
+ ds = ds.filter(opts[:conditions]) if opts[:conditions]
805
+ ds = ds.order(*opts[:order]) if opts[:order]
806
+ ds = ds.limit(*opts[:limit]) if opts[:limit]
807
+ ds = ds.eager(*opts[:eager]) if opts[:eager]
808
+ ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
809
+ ds = send(opts.dataset_helper_method, ds) if opts[:block]
810
+ ds
811
+ end
812
+
813
+ # Add the given associated object to the given association
814
+ def add_associated_object(opts, o)
815
+ raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
816
+ raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
817
+ return if run_association_callbacks(opts, :before_add, o) == false
818
+ send(opts._add_method, o)
819
+ associations[opts[:name]].push(o) if associations.include?(opts[:name])
820
+ add_reciprocal_object(opts, o)
821
+ run_association_callbacks(opts, :after_add, o)
822
+ o
823
+ end
824
+
825
+ # Add/Set the current object to/as the given object's reciprocal association.
826
+ def add_reciprocal_object(opts, o)
827
+ return unless reciprocal = opts.reciprocal
828
+ if opts.reciprocal_array?
829
+ if array = o.associations[reciprocal] and !array.include?(self)
830
+ array.push(self)
831
+ end
832
+ else
833
+ o.associations[reciprocal] = self
834
+ end
835
+ end
836
+
837
+ # Call uniq! on the given array. This is used by the :uniq option,
838
+ # and is an actual method for memory reasons.
839
+ def array_uniq!(a)
840
+ a.uniq!
841
+ end
842
+
843
+ # Load the associated objects using the dataset
844
+ def load_associated_objects(opts, reload=false)
845
+ name = opts[:name]
846
+ if associations.include?(name) and !reload
847
+ associations[name]
848
+ else
849
+ objs = if opts.returns_array?
850
+ send(opts.dataset_method).all
851
+ else
852
+ if !opts[:key]
853
+ send(opts.dataset_method).all.first
854
+ elsif send(opts[:key])
855
+ send(opts.dataset_method).first
856
+ end
857
+ end
858
+ run_association_callbacks(opts, :after_load, objs)
859
+ objs.each{|o| add_reciprocal_object(opts, o)} if opts.set_reciprocal_to_self?
860
+ associations[name] = objs
861
+ end
862
+ end
863
+
864
+ # Remove all associated objects from the given association
865
+ def remove_all_associated_objects(opts)
866
+ raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
867
+ send(opts._remove_all_method)
868
+ ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
869
+ associations[opts[:name]] = []
870
+ ret
871
+ end
872
+
873
+ # Remove the given associated object from the given association
874
+ def remove_associated_object(opts, o)
875
+ raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
876
+ raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
877
+ return if run_association_callbacks(opts, :before_remove, o) == false
878
+ send(opts._remove_method, o)
879
+ associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
880
+ remove_reciprocal_object(opts, o)
881
+ run_association_callbacks(opts, :after_remove, o)
882
+ o
883
+ end
884
+
885
+ # Remove/unset the current object from/as the given object's reciprocal association.
886
+ def remove_reciprocal_object(opts, o)
887
+ return unless reciprocal = opts.reciprocal
888
+ if opts.reciprocal_array?
889
+ if array = o.associations[reciprocal]
890
+ array.delete_if{|x| self === x}
891
+ end
892
+ else
893
+ o.associations[reciprocal] = nil
894
+ end
895
+ end
896
+
897
+ # Run the callback for the association with the object.
898
+ def run_association_callbacks(reflection, callback_type, object)
899
+ raise_error = raise_on_save_failure || !reflection.returns_array?
900
+ stop_on_false = [:before_add, :before_remove].include?(callback_type)
901
+ reflection[callback_type].each do |cb|
902
+ res = case cb
903
+ when Symbol
904
+ send(cb, object)
905
+ when Proc
906
+ cb.call(self, object)
907
+ else
908
+ raise Error, "callbacks should either be Procs or Symbols"
909
+ end
910
+ if res == false and stop_on_false
911
+ raise(BeforeHookFailed, "Unable to modify association for record: one of the #{callback_type} hooks returned false") if raise_error
912
+ return false
913
+ end
914
+ end
915
+ end
916
+
917
+ # Set the given object as the associated object for the given association
918
+ def set_associated_object(opts, o)
919
+ raise(Sequel::Error, "model object #{model} does not have a primary key") if o && !o.pk
920
+ old_val = send(opts.association_method)
921
+ return o if old_val == o
922
+ return if old_val and run_association_callbacks(opts, :before_remove, old_val) == false
923
+ return if o and run_association_callbacks(opts, :before_add, o) == false
924
+ send(opts._setter_method, o)
925
+ associations[opts[:name]] = o
926
+ remove_reciprocal_object(opts, old_val) if old_val
927
+ if o
928
+ add_reciprocal_object(opts, o)
929
+ run_association_callbacks(opts, :after_add, o)
930
+ end
931
+ run_association_callbacks(opts, :after_remove, old_val) if old_val
932
+ o
933
+ end
934
+ end
935
+
936
+ # Eager loading makes it so that you can load all associated records for a
937
+ # set of objects in a single query, instead of a separate query for each object.
938
+ #
939
+ # Two separate implementations are provided. #eager should be used most of the
940
+ # time, as it loads associated records using one query per association. However,
941
+ # it does not allow you the ability to filter based on columns in associated tables. #eager_graph loads
942
+ # all records in one query. Using #eager_graph you can filter based on columns in associated
943
+ # tables. However, #eager_graph can be slower than #eager, especially if multiple
944
+ # *_to_many associations are joined.
945
+ #
946
+ # You can cascade the eager loading (loading associations' associations)
947
+ # with no limit to the depth of the cascades. You do this by passing a hash to #eager or #eager_graph
948
+ # with the keys being associations of the current model and values being
949
+ # associations of the model associated with the current model via the key.
950
+ #
951
+ # The arguments can be symbols or hashes with symbol keys (for cascaded
952
+ # eager loading). Examples:
953
+ #
954
+ # Album.eager(:artist).all
955
+ # Album.eager_graph(:artist).all
956
+ # Album.eager(:artist, :genre).all
957
+ # Album.eager_graph(:artist, :genre).all
958
+ # Album.eager(:artist).eager(:genre).all
959
+ # Album.eager_graph(:artist).eager(:genre).all
960
+ # Artist.eager(:albums=>:tracks).all
961
+ # Artist.eager_graph(:albums=>:tracks).all
962
+ # Artist.eager(:albums=>{:tracks=>:genre}).all
963
+ # Artist.eager_graph(:albums=>{:tracks=>:genre}).all
964
+ module DatasetMethods
965
+ # Add the #eager! and #eager_graph! mutation methods to the dataset.
966
+ def self.extended(obj)
967
+ obj.def_mutation_method(:eager, :eager_graph)
968
+ end
969
+
970
+ # The preferred eager loading method. Loads all associated records using one
971
+ # query for each association.
972
+ #
973
+ # The basic idea for how it works is that the dataset is first loaded normally.
974
+ # Then it goes through all associations that have been specified via eager.
975
+ # It loads each of those associations separately, then associates them back
976
+ # to the original dataset via primary/foreign keys. Due to the necessity of
977
+ # all objects being present, you need to use .all to use eager loading, as it
978
+ # can't work with .each.
979
+ #
980
+ # This implementation avoids the complexity of extracting an object graph out
981
+ # of a single dataset, by building the object graph out of multiple datasets,
982
+ # one for each association. By using a separate dataset for each association,
983
+ # it avoids problems such as aliasing conflicts and creating cartesian product
984
+ # result sets if multiple *_to_many eager associations are requested.
985
+ #
986
+ # One limitation of using this method is that you cannot filter the dataset
987
+ # based on values of columns in an associated table, since the associations are loaded
988
+ # in separate queries. To do that you need to load all associations in the
989
+ # same query, and extract an object graph from the results of that query. If you
990
+ # need to filter based on columns in associated tables, look at #eager_graph
991
+ # or join the tables you need to filter on manually.
992
+ #
993
+ # Each association's order, if defined, is respected. Eager also works
994
+ # on a limited dataset, but does not use any :limit options for associations.
995
+ # If the association uses a block or has an :eager_block argument, it is used.
996
+ def eager(*associations)
997
+ opt = @opts[:eager]
998
+ opt = opt ? opt.dup : {}
999
+ associations.flatten.each do |association|
1000
+ case association
1001
+ when Symbol
1002
+ check_association(model, association)
1003
+ opt[association] = nil
1004
+ when Hash
1005
+ association.keys.each{|assoc| check_association(model, assoc)}
1006
+ opt.merge!(association)
1007
+ else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1008
+ end
1009
+ end
1010
+ clone(:eager=>opt)
1011
+ end
1012
+
1013
+ # The secondary eager loading method. Loads all associations in a single query. This
1014
+ # method should only be used if you need to filter based on columns in associated tables.
1015
+ #
1016
+ # This method builds an object graph using Dataset#graph. Then it uses the graph
1017
+ # to build the associations, and finally replaces the graph with a simple array
1018
+ # of model objects.
1019
+ #
1020
+ # Be very careful when using this with multiple *_to_many associations, as you can
1021
+ # create large cartesian products. If you must graph multiple *_to_many associations,
1022
+ # make sure your filters are specific if you have a large database.
1023
+ #
1024
+ # Each association's order, if definied, is respected. #eager_graph probably
1025
+ # won't work correctly on a limited dataset, unless you are
1026
+ # only graphing many_to_one associations.
1027
+ #
1028
+ # Does not use the block defined for the association, since it does a single query for
1029
+ # all objects. You can use the :graph_* association options to modify the SQL query.
1030
+ #
1031
+ # Like eager, you need to call .all on the dataset for the eager loading to work. If you just
1032
+ # call each, you will get a normal graphed result back (a hash with model object values).
1033
+ def eager_graph(*associations)
1034
+ table_name = model.table_name
1035
+ ds = if @opts[:eager_graph]
1036
+ self
1037
+ else
1038
+ # Each of the following have a symbol key for the table alias, with the following values:
1039
+ # :reciprocals - the reciprocal instance variable to use for this association
1040
+ # :requirements - array of requirements for this association
1041
+ # :alias_association_type_map - the type of association for this association
1042
+ # :alias_association_name_map - the name of the association for this association
1043
+ clone(:eager_graph=>{:requirements=>{}, :master=>model.table_name, :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}})
1044
+ end
1045
+ ds.eager_graph_associations(ds, model, table_name, [], *associations)
1046
+ end
1047
+
1048
+ protected
1049
+
1050
+ # Call graph on the association with the correct arguments,
1051
+ # update the eager_graph data structure, and recurse into
1052
+ # eager_graph_associations if there are any passed in associations
1053
+ # (which would be dependencies of the current association)
1054
+ #
1055
+ # Arguments:
1056
+ # * ds - Current dataset
1057
+ # * model - Current Model
1058
+ # * ta - table_alias used for the parent association
1059
+ # * requirements - an array, used as a stack for requirements
1060
+ # * r - association reflection for the current association
1061
+ # * *associations - any associations dependent on this one
1062
+ def eager_graph_association(ds, model, ta, requirements, r, *associations)
1063
+ klass = r.associated_class
1064
+ assoc_name = r[:name]
1065
+ assoc_table_alias = ds.eager_unique_table_alias(ds, assoc_name)
1066
+ ds = r[:eager_grapher].call(ds, assoc_table_alias, ta)
1067
+ 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]
1068
+ eager_graph = ds.opts[:eager_graph]
1069
+ eager_graph[:requirements][assoc_table_alias] = requirements.dup
1070
+ eager_graph[:alias_association_name_map][assoc_table_alias] = assoc_name
1071
+ eager_graph[:alias_association_type_map][assoc_table_alias] = r.returns_array?
1072
+ ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
1073
+ ds
1074
+ end
1075
+
1076
+ # Check the associations are valid for the given model.
1077
+ # Call eager_graph_association on each association.
1078
+ #
1079
+ # Arguments:
1080
+ # * ds - Current dataset
1081
+ # * model - Current Model
1082
+ # * ta - table_alias used for the parent association
1083
+ # * requirements - an array, used as a stack for requirements
1084
+ # * *associations - the associations to add to the graph
1085
+ def eager_graph_associations(ds, model, ta, requirements, *associations)
1086
+ return ds if associations.empty?
1087
+ associations.flatten.each do |association|
1088
+ ds = case association
1089
+ when Symbol
1090
+ ds.eager_graph_association(ds, model, ta, requirements, check_association(model, association))
1091
+ when Hash
1092
+ association.each do |assoc, assoc_assocs|
1093
+ ds = ds.eager_graph_association(ds, model, ta, requirements, check_association(model, assoc), assoc_assocs)
1094
+ end
1095
+ ds
1096
+ else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1097
+ end
1098
+ end
1099
+ ds
1100
+ end
1101
+
1102
+ # Build associations out of the array of returned object graphs.
1103
+ def eager_graph_build_associations(record_graphs)
1104
+ eager_graph = @opts[:eager_graph]
1105
+ master = eager_graph[:master]
1106
+ requirements = eager_graph[:requirements]
1107
+ alias_map = eager_graph[:alias_association_name_map]
1108
+ type_map = eager_graph[:alias_association_type_map]
1109
+ reciprocal_map = eager_graph[:reciprocals]
1110
+
1111
+ # Make dependency map hash out of requirements array for each association.
1112
+ # This builds a tree of dependencies that will be used for recursion
1113
+ # to ensure that all parts of the object graph are loaded into the
1114
+ # appropriate subordinate association.
1115
+ dependency_map = {}
1116
+ # Sort the associations by requirements length, so that
1117
+ # requirements are added to the dependency hash before their
1118
+ # dependencies.
1119
+ requirements.sort_by{|a| a[1].length}.each do |ta, deps|
1120
+ if deps.empty?
1121
+ dependency_map[ta] = {}
1122
+ else
1123
+ deps = deps.dup
1124
+ hash = dependency_map[deps.shift]
1125
+ deps.each do |dep|
1126
+ hash = hash[dep]
1127
+ end
1128
+ hash[ta] = {}
1129
+ end
1130
+ end
1131
+
1132
+ # This mapping is used to make sure that duplicate entries in the
1133
+ # result set are mapped to a single record. For example, using a
1134
+ # single one_to_many association with 10 associated records,
1135
+ # the main object will appear in the object graph 10 times.
1136
+ # We map by primary key, if available, or by the object's entire values,
1137
+ # if not. The mapping must be per table, so create sub maps for each table
1138
+ # alias.
1139
+ records_map = {master=>{}}
1140
+ alias_map.keys.each{|ta| records_map[ta] = {}}
1141
+
1142
+ # This will hold the final record set that we will be replacing the object graph with.
1143
+ records = []
1144
+ record_graphs.each do |record_graph|
1145
+ primary_record = record_graph[master]
1146
+ key = primary_record.pk || primary_record.values.sort_by{|x| x[0].to_s}
1147
+ if cached_pr = records_map[master][key]
1148
+ primary_record = cached_pr
1149
+ else
1150
+ records_map[master][key] = primary_record
1151
+ # Only add it to the list of records to return if it is a new record
1152
+ records.push(primary_record)
1153
+ end
1154
+ # Build all associations for the current object and it's dependencies
1155
+ eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, primary_record, record_graph)
1156
+ end
1157
+
1158
+ # Remove duplicate records from all associations if this graph could possibly be a cartesian product
1159
+ eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map) if type_map.values.select{|v| v}.length > 1
1160
+
1161
+ # Replace the array of object graphs with an array of model objects
1162
+ record_graphs.replace(records)
1163
+ end
1164
+
1165
+ # Creates a unique table alias that hasn't already been used in the query.
1166
+ # Will either be the table_alias itself or table_alias_N for some integer
1167
+ # N (starting at 0 and increasing until an unused one is found).
1168
+ def eager_unique_table_alias(ds, table_alias)
1169
+ used_aliases = ds.opts[:from]
1170
+ graph = ds.opts[:graph]
1171
+ used_aliases += graph[:table_aliases].keys if graph
1172
+ if used_aliases.include?(table_alias)
1173
+ i = 0
1174
+ loop do
1175
+ ta = :"#{table_alias}_#{i}"
1176
+ return ta unless used_aliases.include?(ta)
1177
+ i += 1
1178
+ end
1179
+ end
1180
+ table_alias
1181
+ end
1182
+
1183
+ private
1184
+
1185
+ # Make sure the association is valid for this model, and return the related AssociationReflection.
1186
+ def check_association(model, association)
1187
+ raise(Sequel::Error, 'Invalid association') unless reflection = model.association_reflection(association)
1188
+ raise(Sequel::Error, "Eager loading is not allowed for #{model.name} association #{association}") if reflection[:allow_eager] == false
1189
+ reflection
1190
+ end
1191
+
1192
+ # Build associations for the current object. This is called recursively
1193
+ # to build all dependencies.
1194
+ def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
1195
+ return if dependency_map.empty?
1196
+ # Don't clobber the instance variable array for *_to_many associations if it has already been setup
1197
+ dependency_map.keys.each do |ta|
1198
+ assoc_name = alias_map[ta]
1199
+ current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
1200
+ end
1201
+ dependency_map.each do |ta, deps|
1202
+ next unless rec = record_graph[ta]
1203
+ key = rec.pk || rec.values.sort_by{|x| x[0].to_s}
1204
+ if cached_rec = records_map[ta][key]
1205
+ rec = cached_rec
1206
+ else
1207
+ records_map[ta][rec.pk] = rec
1208
+ end
1209
+ assoc_name = alias_map[ta]
1210
+ case type_map[ta]
1211
+ when false
1212
+ current.associations[assoc_name] = rec
1213
+ else
1214
+ current.associations[assoc_name].push(rec)
1215
+ if reciprocal = reciprocal_map[ta]
1216
+ rec.associations[reciprocal] = current
1217
+ end
1218
+ end
1219
+ # Recurse into dependencies of the current object
1220
+ eager_graph_build_associations_graph(deps, alias_map, type_map, reciprocal_map, records_map, rec, record_graph)
1221
+ end
1222
+ end
1223
+
1224
+ # If the result set is the result of a cartesian product, then it is possible that
1225
+ # there are multiple records for each association when there should only be one.
1226
+ # In that case, for each object in all associations loaded via #eager_graph, run
1227
+ # uniq! on the association to make sure no duplicate records show up.
1228
+ # Note that this can cause legitimate duplicate records to be removed.
1229
+ def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
1230
+ records.each do |record|
1231
+ dependency_map.each do |ta, deps|
1232
+ list = if !type_map[ta]
1233
+ item = record.send(alias_map[ta])
1234
+ [item] if item
1235
+ else
1236
+ list = record.send(alias_map[ta])
1237
+ list.uniq!
1238
+ end
1239
+ # Recurse into dependencies
1240
+ eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
1241
+ end
1242
+ end
1243
+ end
1244
+
1245
+ # Qualify the given expression if necessary. The only expressions which are qualified are
1246
+ # unqualified symbols and identifiers, either of which may by sorted.
1247
+ def eager_graph_qualify_order(table_alias, expression)
1248
+ case expression
1249
+ when Symbol
1250
+ table, column, aliaz = split_symbol(expression)
1251
+ raise(Sequel::Error, "Can't use an aliased expression in the :order option") if aliaz
1252
+ table ? expression : Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
1253
+ when Sequel::SQL::Identifier
1254
+ Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
1255
+ when Sequel::SQL::OrderedExpression
1256
+ Sequel::SQL::OrderedExpression.new(eager_graph_qualify_order(table_alias, expression.expression), expression.descending)
1257
+ else
1258
+ expression
1259
+ end
1260
+ end
1261
+
1262
+ # Eagerly load all specified associations
1263
+ def eager_load(a)
1264
+ return if a.empty?
1265
+ # All associations to eager load
1266
+ eager_assoc = @opts[:eager]
1267
+ # Key is foreign/primary key name symbol
1268
+ # Value is hash with keys being foreign/primary key values (generally integers)
1269
+ # and values being an array of current model objects with that
1270
+ # specific foreign/primary key
1271
+ key_hash = {}
1272
+ # Reflections for all associations to eager load
1273
+ reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc)}
1274
+
1275
+ # Populate keys to monitor
1276
+ reflections.each{|reflection| key_hash[reflection.eager_loader_key] ||= Hash.new{|h,k| h[k] = []}}
1277
+
1278
+ # Associate each object with every key being monitored
1279
+ a.each do |rec|
1280
+ key_hash.each do |key, id_map|
1281
+ id_map[rec[key]] << rec if rec[key]
1282
+ end
1283
+ end
1284
+
1285
+ reflections.each do |r|
1286
+ r[:eager_loader].call(key_hash, a, eager_assoc[r[:name]])
1287
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
1288
+ end
1289
+ end
1290
+
1291
+ # Build associations from the graph if #eager_graph was used,
1292
+ # and/or load other associations if #eager was used.
1293
+ def post_load(all_records)
1294
+ eager_graph_build_associations(all_records) if @opts[:eager_graph]
1295
+ eager_load(all_records) if @opts[:eager]
1296
+ end
1297
+ end
1298
+ end
1299
+ end
1300
+ end