epugh-sequel 0.0.0

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