sequel 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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