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
@@ -1,82 +0,0 @@
1
- module Sequel
2
- class Model
3
- metaattr_reader :cache_store, :cache_ttl
4
-
5
- ### Public Class Methods ###
6
-
7
- # Set the cache store for the model, as well as the caching before_* hooks.
8
- #
9
- # The cache store should implement the following API:
10
- #
11
- # cache_store.set(key, obj, time) # Associate the obj with the given key
12
- # # in the cache for the time (specified
13
- # # in seconds)
14
- # cache_store.get(key) => obj # Returns object set with same key
15
- # cache_store.get(key2) => nil # nil returned if there isn't an object
16
- # # currently in the cache with that key
17
- def self.set_cache(store, opts = {})
18
- @cache_store = store
19
- @cache_ttl = opts[:ttl] || 3600
20
- before_update :cache_delete
21
- before_update_values :cache_delete
22
- before_delete :cache_delete
23
- end
24
-
25
- # Set the time to live for the cache store, in seconds (default is 3600,
26
- # so 1 hour).
27
- def self.set_cache_ttl(ttl)
28
- @cache_ttl = ttl
29
- end
30
-
31
- ### Private Class Methods ###
32
-
33
- # Delete the entry with the matching key from the cache
34
- def self.cache_delete(key) # :nodoc:
35
- @cache_store.delete(key)
36
- nil
37
- end
38
-
39
- # Return a key string for the pk
40
- def self.cache_key(pk) # :nodoc:
41
- "#{self}:#{Array(pk).join(',')}"
42
- end
43
-
44
- # Lookup the primary key in the cache.
45
- # If found, return the matching object.
46
- # Otherwise, get the matching object from the database and
47
- # update the cache with it.
48
- def self.cache_lookup(pk) # :nodoc:
49
- ck = cache_key(pk)
50
- unless obj = @cache_store.get(ck)
51
- obj = dataset[primary_key_hash(pk)]
52
- @cache_store.set(ck, obj, @cache_ttl)
53
- end
54
- obj
55
- end
56
-
57
- private_class_method :cache_delete, :cache_key, :cache_lookup
58
-
59
- ### Instance Methods ###
60
-
61
- # Return a key unique to the underlying record for caching, based on the
62
- # primary key value(s) for the object. If the model does not have a primary
63
- # key, raise an Error.
64
- def cache_key
65
- raise(Error, "No primary key is associated with this model") unless key = primary_key
66
- pk = case key
67
- when Array
68
- key.collect{|k| @values[k]}
69
- else
70
- @values[key] || (raise Error, 'no primary key for this record')
71
- end
72
- model.send(:cache_key, pk)
73
- end
74
-
75
- private
76
-
77
- # Delete this object from the cache
78
- def cache_delete
79
- model.send(:cache_delete, cache_key)
80
- end
81
- end
82
- end
@@ -1,26 +0,0 @@
1
- # Dataset methods are methods that the model class extends its dataset with in
2
- # the call to set_dataset.
3
- module Sequel::Model::DatasetMethods
4
- # Destroy each row in the dataset by instantiating it and then calling
5
- # destroy on the resulting model object. This isn't as fast as deleting
6
- # the object, which does a single SQL call, but this runs any destroy
7
- # hooks.
8
- def destroy
9
- raise(Error, "No model associated with this dataset") unless @opts[:models]
10
- count = 0
11
- @db.transaction{all{|r| count += 1; r.destroy}}
12
- count
13
- end
14
-
15
- # This allows you to call to_hash without any arguments, which will
16
- # result in a hash with the primary key value being the key and the
17
- # model object being the value.
18
- def to_hash(key_column=nil, value_column=nil)
19
- if key_column
20
- super
21
- else
22
- raise(Sequel::Error, "No primary key for model") unless pk = @opts[:models][nil].primary_key
23
- super(pk, value_column)
24
- end
25
- end
26
- end
@@ -1,370 +0,0 @@
1
- # Eager loading makes it so that you can load all associated records for a
2
- # set of objects in a single query, instead of a separate query for each object.
3
- #
4
- # Two separate implementations are provided. #eager should be used most of the
5
- # time, as it loads associated records using one query per association. However,
6
- # it does not allow you the ability to filter based on columns in associated tables. #eager_graph loads
7
- # all records in one query. Using #eager_graph you can filter based on columns in associated
8
- # tables. However, #eager_graph can be much slower than #eager, especially if multiple
9
- # *_to_many associations are joined.
10
- #
11
- # You can cascade the eager loading (loading associations' associations)
12
- # with no limit to the depth of the cascades. You do this by passing a hash to #eager or #eager_graph
13
- # with the keys being associations of the current model and values being
14
- # associations of the model associated with the current model via the key.
15
- #
16
- # The arguments can be symbols or hashes with symbol keys (for cascaded
17
- # eager loading). Examples:
18
- #
19
- # Album.eager(:artist).all
20
- # Album.eager_graph(:artist).all
21
- # Album.eager(:artist, :genre).all
22
- # Album.eager_graph(:artist, :genre).all
23
- # Album.eager(:artist).eager(:genre).all
24
- # Album.eager_graph(:artist).eager(:genre).all
25
- # Artist.eager(:albums=>:tracks).all
26
- # Artist.eager_graph(:albums=>:tracks).all
27
- # Artist.eager(:albums=>{:tracks=>:genre}).all
28
- # Artist.eager_graph(:albums=>{:tracks=>:genre}).all
29
- module Sequel::Model::Associations::EagerLoading
30
- # Add the #eager! and #eager_graph! mutation methods to the dataset.
31
- def self.extended(obj)
32
- obj.def_mutation_method(:eager, :eager_graph)
33
- end
34
-
35
- # The preferred eager loading method. Loads all associated records using one
36
- # query for each association.
37
- #
38
- # The basic idea for how it works is that the dataset is first loaded normally.
39
- # Then it goes through all associations that have been specified via eager.
40
- # It loads each of those associations separately, then associates them back
41
- # to the original dataset via primary/foreign keys. Due to the necessity of
42
- # all objects being present, you need to use .all to use eager loading, as it
43
- # can't work with .each.
44
- #
45
- # This implementation avoids the complexity of extracting an object graph out
46
- # of a single dataset, by building the object graph out of multiple datasets,
47
- # one for each association. By using a separate dataset for each association,
48
- # it avoids problems such as aliasing conflicts and creating cartesian product
49
- # result sets if multiple *_to_many eager associations are requested.
50
- #
51
- # One limitation of using this method is that you cannot filter the dataset
52
- # based on values of columns in an associated table, since the associations are loaded
53
- # in separate queries. To do that you need to load all associations in the
54
- # same query, and extract an object graph from the results of that query. If you
55
- # need to filter based on columns in associated tables, look at #eager_graph
56
- # or join the tables you need to filter on manually.
57
- #
58
- # Each association's order, if defined, is respected. Eager also works
59
- # on a limited dataset, but does not use any :limit options for associations.
60
- # If the association uses a block or has an :eager_block argument, it is used.
61
- def eager(*associations)
62
- model = check_model
63
- opt = @opts[:eager]
64
- opt = opt ? opt.dup : {}
65
- associations.flatten.each do |association|
66
- case association
67
- when Symbol
68
- check_association(model, association)
69
- opt[association] = nil
70
- when Hash
71
- association.keys.each{|assoc| check_association(model, assoc)}
72
- opt.merge!(association)
73
- else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
74
- end
75
- end
76
- clone(:eager=>opt)
77
- end
78
-
79
- # The secondary eager loading method. Loads all associations in a single query. This
80
- # method should only be used if you need to filter based on columns in associated tables.
81
- #
82
- # This method builds an object graph using Dataset#graph. Then it uses the graph
83
- # to build the associations, and finally replaces the graph with a simple array
84
- # of model objects.
85
- #
86
- # Be very careful when using this with multiple *_to_many associations, as you can
87
- # create large cartesian products. If you must graph multiple *_to_many associations,
88
- # make sure your filters are specific if you have a large database.
89
- #
90
- # Each association's order, if definied, is respected. #eager_graph probably
91
- # won't work correctly on a limited dataset, unless you are
92
- # only graphing many_to_one associations.
93
- #
94
- # Does not use the block defined for the association, since it does a single query for
95
- # all objects. You can use the :graph_join_type, :graph_conditions, and :graph_join_table_conditions
96
- # association options to modify the SQL query.
97
- def eager_graph(*associations)
98
- model = check_model
99
- table_name = model.table_name
100
- ds = if @opts[:eager_graph]
101
- self
102
- else
103
- # Each of the following have a symbol key for the table alias, with the following values:
104
- # :reciprocals - the reciprocal instance variable to use for this association
105
- # :requirements - array of requirements for this association
106
- # :alias_association_type_map - the type of association for this association
107
- # :alias_association_name_map - the name of the association for this association
108
- clone(:eager_graph=>{:requirements=>{}, :master=>model.table_name, :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}})
109
- end
110
- ds.eager_graph_associations(ds, model, table_name, [], *associations)
111
- end
112
-
113
- protected
114
-
115
- # Call graph on the association with the correct arguments,
116
- # update the eager_graph data structure, and recurse into
117
- # eager_graph_associations if there are any passed in associations
118
- # (which would be dependencies of the current association)
119
- #
120
- # Arguments:
121
- # * ds - Current dataset
122
- # * model - Current Model
123
- # * ta - table_alias used for the parent association
124
- # * requirements - an array, used as a stack for requirements
125
- # * r - association reflection for the current association
126
- # * *associations - any associations dependent on this one
127
- def eager_graph_association(ds, model, ta, requirements, r, *associations)
128
- klass = r.associated_class
129
- assoc_name = r[:name]
130
- assoc_table_alias = ds.eager_unique_table_alias(ds, assoc_name)
131
- ds = r[:eager_grapher].call(ds, assoc_table_alias, ta)
132
- ds = ds.order_more(*Array(r[:order]).map{|c| eager_graph_qualify_order(assoc_table_alias, c)}) if r[:order] and r[:order_eager_graph]
133
- eager_graph = ds.opts[:eager_graph]
134
- eager_graph[:requirements][assoc_table_alias] = requirements.dup
135
- eager_graph[:alias_association_name_map][assoc_table_alias] = assoc_name
136
- eager_graph[:alias_association_type_map][assoc_table_alias] = r.returns_array?
137
- ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
138
- ds
139
- end
140
-
141
- # Check the associations are valid for the given model.
142
- # Call eager_graph_association on each association.
143
- #
144
- # Arguments:
145
- # * ds - Current dataset
146
- # * model - Current Model
147
- # * ta - table_alias used for the parent association
148
- # * requirements - an array, used as a stack for requirements
149
- # * *associations - the associations to add to the graph
150
- def eager_graph_associations(ds, model, ta, requirements, *associations)
151
- return ds if associations.empty?
152
- associations.flatten.each do |association|
153
- ds = case association
154
- when Symbol
155
- ds.eager_graph_association(ds, model, ta, requirements, check_association(model, association))
156
- when Hash
157
- association.each do |assoc, assoc_assocs|
158
- ds = ds.eager_graph_association(ds, model, ta, requirements, check_association(model, assoc), assoc_assocs)
159
- end
160
- ds
161
- else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
162
- end
163
- end
164
- ds
165
- end
166
-
167
- # Build associations out of the array of returned object graphs.
168
- def eager_graph_build_associations(record_graphs)
169
- eager_graph = @opts[:eager_graph]
170
- master = eager_graph[:master]
171
- requirements = eager_graph[:requirements]
172
- alias_map = eager_graph[:alias_association_name_map]
173
- type_map = eager_graph[:alias_association_type_map]
174
- reciprocal_map = eager_graph[:reciprocals]
175
-
176
- # Make dependency map hash out of requirements array for each association.
177
- # This builds a tree of dependencies that will be used for recursion
178
- # to ensure that all parts of the object graph are loaded into the
179
- # appropriate subordinate association.
180
- dependency_map = {}
181
- # Sort the associations be requirements length, so that
182
- # requirements are added to the dependency hash before their
183
- # dependencies.
184
- requirements.sort_by{|a| a[1].length}.each do |ta, deps|
185
- if deps.empty?
186
- dependency_map[ta] = {}
187
- else
188
- deps = deps.dup
189
- hash = dependency_map[deps.shift]
190
- deps.each do |dep|
191
- hash = hash[dep]
192
- end
193
- hash[ta] = {}
194
- end
195
- end
196
-
197
- # This mapping is used to make sure that duplicate entries in the
198
- # result set are mapped to a single record. For example, using a
199
- # single one_to_many association with 10 associated records,
200
- # the main object will appear in the object graph 10 times.
201
- # We map by primary key, if available, or by the object's entire values,
202
- # if not. The mapping must be per table, so create sub maps for each table
203
- # alias.
204
- records_map = {master=>{}}
205
- alias_map.keys.each{|ta| records_map[ta] = {}}
206
-
207
- # This will hold the final record set that we will be replacing the object graph with.
208
- records = []
209
- record_graphs.each do |record_graph|
210
- primary_record = record_graph[master]
211
- key = primary_record.pk || primary_record.values.sort_by{|x| x[0].to_s}
212
- if cached_pr = records_map[master][key]
213
- primary_record = cached_pr
214
- else
215
- records_map[master][key] = primary_record
216
- # Only add it to the list of records to return if it is a new record
217
- records.push(primary_record)
218
- end
219
- # Build all associations for the current object and it's dependencies
220
- eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, primary_record, record_graph)
221
- end
222
-
223
- # Remove duplicate records from all associations if this graph could possibly be a cartesian product
224
- eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map) if type_map.values.select{|v| v}.length > 1
225
-
226
- # Replace the array of object graphs with an array of model objects
227
- record_graphs.replace(records)
228
- end
229
-
230
- # Creates a unique table alias that hasn't already been used in the query.
231
- # Will either be the table_alias itself or table_alias_N for some integer
232
- # N (starting at 0 and increasing until an unused one is found).
233
- def eager_unique_table_alias(ds, table_alias)
234
- used_aliases = ds.opts[:from]
235
- graph = ds.opts[:graph]
236
- used_aliases += graph[:table_aliases].keys if graph
237
- if used_aliases.include?(table_alias)
238
- i = 0
239
- loop do
240
- ta = :"#{table_alias}_#{i}"
241
- return ta unless used_aliases.include?(ta)
242
- i += 1
243
- end
244
- end
245
- table_alias
246
- end
247
-
248
- private
249
-
250
- # Make sure this dataset is associated with a model, and return the default model for it.
251
- def check_model
252
- raise(Sequel::Error, 'No model for this dataset') unless @opts[:models] && model = @opts[:models][nil]
253
- model
254
- end
255
-
256
- # Make sure the association is valid for this model, and return the related AssociationReflection.
257
- def check_association(model, association)
258
- raise(Sequel::Error, 'Invalid association') unless reflection = model.association_reflection(association)
259
- raise(Sequel::Error, "Eager loading is not allowed for #{model.name} association #{association}") if reflection[:allow_eager] == false
260
- reflection
261
- end
262
-
263
- # Build associations for the current object. This is called recursively
264
- # to build object's dependencies.
265
- def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
266
- return if dependency_map.empty?
267
- # Don't clobber the instance variable array for *_to_many associations if it has already been setup
268
- dependency_map.keys.each do |ta|
269
- assoc_name = alias_map[ta]
270
- current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
271
- end
272
- dependency_map.each do |ta, deps|
273
- next unless rec = record_graph[ta]
274
- key = rec.pk || rec.values.sort_by{|x| x[0].to_s}
275
- if cached_rec = records_map[ta][key]
276
- rec = cached_rec
277
- else
278
- records_map[ta][rec.pk] = rec
279
- end
280
- assoc_name = alias_map[ta]
281
- case type_map[ta]
282
- when false
283
- current.associations[assoc_name] = rec
284
- else
285
- current.associations[assoc_name].push(rec)
286
- if reciprocal = reciprocal_map[ta]
287
- rec.associations[reciprocal] = current
288
- end
289
- end
290
- # Recurse into dependencies of the current object
291
- eager_graph_build_associations_graph(deps, alias_map, type_map, reciprocal_map, records_map, rec, record_graph)
292
- end
293
- end
294
-
295
- # If the result set is the result of a cartesian product, then it is possible that
296
- # there a multiple records for each association when there should only be one.
297
- # In that case, for each object in all associations loaded via #eager_graph, run
298
- # uniq! on the association instance variables to make sure no duplicate records show up.
299
- # Note that this can cause legitimate duplicate records to be removed.
300
- def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
301
- records.each do |record|
302
- dependency_map.each do |ta, deps|
303
- list = if !type_map[ta]
304
- item = record.send(alias_map[ta])
305
- [item] if item
306
- else
307
- list = record.send(alias_map[ta])
308
- list.uniq!
309
- end
310
- # Recurse into dependencies
311
- eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
312
- end
313
- end
314
- end
315
-
316
- # Qualify the given expression if necessary. The only expressions which are qualified are
317
- # unqualified symbols and identifiers, either of which may by sorted.
318
- def eager_graph_qualify_order(table_alias, expression)
319
- case expression
320
- when Symbol
321
- table, column, aliaz = split_symbol(expression)
322
- raise(Sequel::Error, "Can't use an aliased expression in the :order option") if aliaz
323
- table ? expression : Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
324
- when Sequel::SQL::Identifier
325
- Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
326
- when Sequel::SQL::OrderedExpression
327
- Sequel::SQL::OrderedExpression.new(eager_graph_qualify_order(table_alias, expression.expression), expression.descending)
328
- else
329
- expression
330
- end
331
- end
332
-
333
- # Eagerly load all specified associations
334
- def eager_load(a)
335
- return if a.empty?
336
- # Current model class
337
- model = @opts[:models][nil]
338
- # All associations to eager load
339
- eager_assoc = @opts[:eager]
340
- # Key is foreign/primary key name symbol
341
- # Value is hash with keys being foreign/primary key values (generally integers)
342
- # and values being an array of current model objects with that
343
- # specific foreign/primary key
344
- key_hash = {}
345
- # Reflections for all associations to eager load
346
- reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc)}
347
-
348
- # Populate keys to monitor
349
- reflections.each{|reflection| key_hash[reflection.eager_loader_key] ||= Hash.new{|h,k| h[k] = []}}
350
-
351
- # Associate each object with every key being monitored
352
- a.each do |rec|
353
- key_hash.each do |key, id_map|
354
- id_map[rec[key]] << rec if rec[key]
355
- end
356
- end
357
-
358
- reflections.each do |r|
359
- r[:eager_loader].call(key_hash, a, eager_assoc[r[:name]])
360
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
361
- end
362
- end
363
-
364
- # Build associations from the graph if #eager_graph was used,
365
- # and/or load other associations if #eager was used.
366
- def post_load(all_records)
367
- eager_graph_build_associations(all_records) if @opts[:eager_graph]
368
- eager_load(all_records) if @opts[:eager]
369
- end
370
- end