viking-sequel 3.10.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 (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,122 @@
1
+ # The sql_expr extension adds the sql_expr method to every object, which
2
+ # returns an object that works nicely with Sequel's DSL. This is
3
+ # best shown by example:
4
+ #
5
+ # 1.sql_expr < :a # 1 < a
6
+ # false.sql_expr & :a # FALSE AND a
7
+ # true.sql_expr | :a # TRUE OR a
8
+ # ~nil.sql_expr # NOT NULL
9
+ # "a".sql_expr + "b" # 'a' || 'b'
10
+
11
+ module Sequel
12
+ module SQL
13
+ # The GenericComplexExpression acts like a
14
+ # GenericExpression in terms of methods,
15
+ # but has an internal structure of a
16
+ # ComplexExpression. It is used by Object#sql_expr.
17
+ # Since we don't know what specific type of object
18
+ # we are dealing with it, we treat it similarly to
19
+ # how we treat symbols or literal strings, allowing
20
+ # many different types of methods.
21
+ class GenericComplexExpression < ComplexExpression
22
+ include AliasMethods
23
+ include BooleanMethods
24
+ include CastMethods
25
+ include ComplexExpressionMethods
26
+ include InequalityMethods
27
+ include NumericMethods
28
+ include OrderMethods
29
+ include StringMethods
30
+ include SubscriptMethods
31
+ end
32
+ end
33
+ end
34
+
35
+ class Object
36
+ # Return a copy of the object wrapped in a
37
+ # Sequel::SQL::GenericComplexExpression. Allows easy use
38
+ # of the Object with Sequel's DSL. You'll probably have
39
+ # to make sure that Sequel knows how to literalize the
40
+ # object properly, though.
41
+ def sql_expr
42
+ Sequel::SQL::GenericComplexExpression.new(:NOOP, self)
43
+ end
44
+ end
45
+
46
+ class FalseClass
47
+ # Returns a copy of the object wrapped in a
48
+ # Sequel::SQL::BooleanExpression, allowing easy use
49
+ # of Sequel's DSL:
50
+ #
51
+ # false.sql_expr & :a # FALSE AND a
52
+ def sql_expr
53
+ Sequel::SQL::BooleanExpression.new(:NOOP, self)
54
+ end
55
+ end
56
+
57
+ class NilClass
58
+ # Returns a copy of the object wrapped in a
59
+ # Sequel::SQL::BooleanExpression, allowing easy use
60
+ # of Sequel's DSL:
61
+ #
62
+ # ~nil.sql_expr # NOT NULL
63
+ def sql_expr
64
+ Sequel::SQL::BooleanExpression.new(:NOOP, self)
65
+ end
66
+ end
67
+
68
+ class Numeric
69
+ # Returns a copy of the object wrapped in a
70
+ # Sequel::SQL::NumericExpression, allowing easy use
71
+ # of Sequel's DSL:
72
+ #
73
+ # 1.sql_expr < :a # 1 < a
74
+ def sql_expr
75
+ Sequel::SQL::NumericExpression.new(:NOOP, self)
76
+ end
77
+ end
78
+
79
+ class Proc
80
+ # Evaluates the proc as a virtual row block.
81
+ # If a hash or array of two element arrays is returned,
82
+ # they are converted to a Sequel::SQL::BooleanExpression. Otherwise,
83
+ # unless the object returned is already an Sequel::SQL::Expression,
84
+ # convert the object to an Sequel::SQL::GenericComplexExpression.
85
+ #
86
+ # proc{a(b)}.sql_expr + 1 # a(b) + 1
87
+ # proc{{a=>b}}.sql_expr | true # (a = b) OR TRUE
88
+ # proc{1}.sql_expr + :a # 1 + a
89
+ def sql_expr
90
+ o = Sequel.virtual_row(&self)
91
+ if Sequel.condition_specifier?(o)
92
+ Sequel::SQL::BooleanExpression.from_value_pairs(o, :AND)
93
+ elsif o.is_a?(Sequel::SQL::Expression)
94
+ o
95
+ else
96
+ Sequel::SQL::GenericComplexExpression.new(:NOOP, o)
97
+ end
98
+ end
99
+ end
100
+
101
+ class String
102
+ # Returns a copy of the object wrapped in a
103
+ # Sequel::SQL::StringExpression, allowing easy use
104
+ # of Sequel's DSL:
105
+ #
106
+ # "a".sql_expr + :a # 'a' || a
107
+ def sql_expr
108
+ Sequel::SQL::StringExpression.new(:NOOP, self)
109
+ end
110
+ end
111
+
112
+ class TrueClass
113
+ # Returns a copy of the object wrapped in a
114
+ # Sequel::SQL::BooleanExpression, allowing easy use
115
+ # of Sequel's DSL:
116
+ #
117
+ # true.sql_expr | :a # TRUE OR a
118
+ def sql_expr
119
+ Sequel::SQL::BooleanExpression.new(:NOOP, self)
120
+ end
121
+ end
122
+
@@ -0,0 +1,46 @@
1
+ # The string_date_time extension provides String instance methods
2
+ # for converting the strings to a date (e.g. String#to_date), allowing
3
+ # for backwards compatibility with legacy Sequel code.
4
+
5
+ class String
6
+ # Converts a string into a Date object.
7
+ def to_date
8
+ begin
9
+ Date.parse(self, Sequel.convert_two_digit_years)
10
+ rescue => e
11
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
12
+ end
13
+ end
14
+
15
+ # Converts a string into a DateTime object.
16
+ def to_datetime
17
+ begin
18
+ DateTime.parse(self, Sequel.convert_two_digit_years)
19
+ rescue => e
20
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
21
+ end
22
+ end
23
+
24
+ # Converts a string into a Time or DateTime object, depending on the
25
+ # value of Sequel.datetime_class
26
+ def to_sequel_time
27
+ begin
28
+ if Sequel.datetime_class == DateTime
29
+ DateTime.parse(self, Sequel.convert_two_digit_years)
30
+ else
31
+ Sequel.datetime_class.parse(self)
32
+ end
33
+ rescue => e
34
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
35
+ end
36
+ end
37
+
38
+ # Converts a string into a Time object.
39
+ def to_time
40
+ begin
41
+ Time.parse(self)
42
+ rescue => e
43
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ # The thread_local_timezones extension allows you to set a per-thread timezone that
2
+ # will override the default global timezone while the thread is executing. The
3
+ # main use case is for web applications that execute each request in its own thread,
4
+ # and want to set the timezones based on the request. The most common example
5
+ # is having the database always store time in UTC, but have the application deal
6
+ # with the timezone of the current user. That can be done with:
7
+ #
8
+ # Sequel.database_timezone = :utc
9
+ # # In each thread:
10
+ # Sequel.thread_application_timezone = current_user.timezone
11
+ #
12
+ # This extension is designed to work with the named_timezones extension.
13
+ #
14
+ # This extension adds the thread_application_timezone=, thread_database_timezone=,
15
+ # and thread_typecast_timezone= methods to the Sequel module. It overrides
16
+ # the application_timezone, database_timezone, and typecast_timezone
17
+ # methods to check the related thread local timezone first, and use it if present.
18
+ # If the related thread local timezone is not present, it falls back to the
19
+ # default global timezone.
20
+ #
21
+ # There is one special case of note. If you have a default global timezone
22
+ # and you want to have a nil thread local timezone, you have to set the thread
23
+ # local value to :nil instead of nil:
24
+ #
25
+ # Sequel.application_timezone = :utc
26
+ # Sequel.thread_application_timezone = nil
27
+ # Sequel.application_timezone # => :utc
28
+ # Sequel.thread_application_timezone = :nil
29
+ # Sequel.application_timezone # => nil
30
+
31
+ module Sequel
32
+ module ThreadLocalTimezones
33
+ %w'application database typecast'.each do |t|
34
+ class_eval("def thread_#{t}_timezone=(tz); Thread.current[:#{t}_timezone] = convert_timezone_setter_arg(tz); end", __FILE__, __LINE__)
35
+ class_eval(<<END, __FILE__, __LINE__)
36
+ def #{t}_timezone
37
+ if tz = Thread.current[:#{t}_timezone]
38
+ tz unless tz == :nil
39
+ else
40
+ super
41
+ end
42
+ end
43
+ END
44
+ end
45
+ end
46
+
47
+ extend ThreadLocalTimezones
48
+ end
@@ -0,0 +1,9 @@
1
+ module Sequel
2
+ # Contains meta_def method for adding methods to objects via blocks, used by some of Sequel's classes and objects.
3
+ module Metaprogramming
4
+ # Define a method with the given name and block body on the receiver.
5
+ def meta_def(name, &block)
6
+ (class << self; self end).send(:define_method, name, &block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,120 @@
1
+ require 'sequel/core'
2
+
3
+ module Sequel
4
+ # Lets you create a Model subclass with its dataset already set.
5
+ # source can be an existing dataset or a symbol (in which case
6
+ # it will create a dataset using the default database with
7
+ # the given symbol as the table name).
8
+ #
9
+ # The purpose of this method is to set the dataset automatically
10
+ # for a model class, if the table name doesn't match the implicit
11
+ # name. This is neater than using set_dataset inside the class,
12
+ # doesn't require a bogus query for the schema, and allows
13
+ # it to work correctly in a system that uses code reloading.
14
+ #
15
+ # Example:
16
+ # class Comment < Sequel::Model(:something)
17
+ # table_name # => :something
18
+ # end
19
+ def self.Model(source)
20
+ Model::ANONYMOUS_MODEL_CLASSES[source] ||= if source.is_a?(Database)
21
+ c = Class.new(Model)
22
+ c.db = source
23
+ c
24
+ else
25
+ Class.new(Model).set_dataset(source)
26
+ end
27
+ end
28
+
29
+ # Sequel::Model is an object relational mapper built on top of Sequel core. Each
30
+ # model class is backed by a dataset instance, and many dataset methods can be
31
+ # called directly on the class. Model datasets return rows as model instances,
32
+ # which have fairly standard ORM instance behavior.
33
+ #
34
+ # Sequel::Model is built completely out of plugins, the only method not part of a
35
+ # plugin is the plugin method itself. Plugins can override any class, instance, or
36
+ # dataset method defined by a previous plugin and call super to get the default
37
+ # behavior.
38
+ #
39
+ # You can set the SEQUEL_NO_ASSOCIATIONS constant or environment variable to
40
+ # make Sequel not load the associations plugin by default.
41
+ class Model
42
+ # Map that stores model classes created with Sequel::Model(), to allow the reopening
43
+ # of classes when dealing with code reloading.
44
+ ANONYMOUS_MODEL_CLASSES = {}
45
+
46
+ # Class methods added to model that call the method of the same name on the dataset
47
+ DATASET_METHODS = %w'<< add_graph_aliases all avg count cross_join delete distinct
48
+ each each_page each_server eager eager_graph empty? except exclude filter first for_update from from_self
49
+ full_join full_outer_join get graph grep group group_and_count group_by having import
50
+ inner_join insert insert_multiple intersect interval invert join join_table
51
+ last left_join left_outer_join limit lock_style map max min multi_insert naked
52
+ natural_full_join natural_join natural_left_join natural_right_join order order_by
53
+ order_more paginate print qualify query range reverse reverse_order right_join right_outer_join
54
+ select select_all select_append select_hash select_map select_more select_order_map
55
+ server set set_defaults set_graph_aliases set_overrides
56
+ single_value sum to_csv to_hash truncate unfiltered ungraphed ungrouped union unlimited unordered
57
+ update where with with_recursive with_sql'.map{|x| x.to_sym}
58
+
59
+ # Class instance variables to set to nil when a subclass is created, for -w compliance
60
+ EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
61
+
62
+ # Empty instance methods to create that the user can override to get hook/callback behavior.
63
+ # Just like any other method defined by Sequel, if you override one of these, you should
64
+ # call super to get the default behavior (while empty by default, they can also be defined
65
+ # by plugins).
66
+ HOOKS = [:after_initialize, :before_create, :after_create, :before_update,
67
+ :after_update, :before_save, :after_save, :before_destroy, :after_destroy,
68
+ :before_validation, :after_validation]
69
+
70
+ # Class instance variables that are inherited in subclasses. If the value is :dup, dup is called
71
+ # on the superclass's instance variable when creating the instance variable in the subclass.
72
+ # If the value is nil, the superclass's instance variable is used directly in the subclass.
73
+ INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@dataset_methods=>:dup,
74
+ :@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
75
+ :@raise_on_save_failure=>nil, :@require_modification=>nil,
76
+ :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
77
+ :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
78
+ :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
79
+ :@raise_on_typecast_failure=>nil, :@plugins=>:dup}
80
+
81
+ # Regexp that determines if a method name is normal in the sense that
82
+ # it could be called directly in ruby code without using send. Used to
83
+ # avoid problems when using eval with a string to define methods.
84
+ NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
85
+
86
+ # The setter methods (methods ending with =) that are never allowed
87
+ # to be called automatically via set/update/new/etc..
88
+ RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
89
+
90
+ # Regular expression that determines if the method is a valid setter name
91
+ # (i.e. it ends with =).
92
+ SETTER_METHOD_REGEXP = /=\z/
93
+
94
+ @allowed_columns = nil
95
+ @db = nil
96
+ @db_schema = nil
97
+ @dataset_method_modules = []
98
+ @dataset_methods = {}
99
+ @overridable_methods_module = nil
100
+ @plugins = []
101
+ @primary_key = :id
102
+ @raise_on_save_failure = true
103
+ @raise_on_typecast_failure = true
104
+ @require_modification = nil
105
+ @restrict_primary_key = true
106
+ @restricted_columns = nil
107
+ @simple_pk = nil
108
+ @simple_table = nil
109
+ @strict_param_setting = true
110
+ @typecast_empty_string_to_nil = true
111
+ @typecast_on_assignment = true
112
+ @use_transactions = true
113
+ end
114
+
115
+ require %w"default_inflections inflections plugins base exceptions errors", "model"
116
+ if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
117
+ require 'associations', 'model'
118
+ Model.plugin Model::Associations
119
+ end
120
+ end
@@ -0,0 +1,1514 @@
1
+ module Sequel
2
+ class Model
3
+ # Associations are used in order to specify relationships between model classes
4
+ # that reflect relations between tables in the database using foreign keys.
5
+ module Associations
6
+ # Map of association type symbols to association reflection classes.
7
+ ASSOCIATION_TYPES = {}
8
+
9
+ # Set an empty association reflection hash in the model
10
+ def self.apply(model)
11
+ model.instance_variable_set(:@association_reflections, {})
12
+ end
13
+
14
+ # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
15
+ # provides methods to reduce internal code duplication. It should not
16
+ # be instantiated by the user.
17
+ class AssociationReflection < Hash
18
+ include Sequel::Inflections
19
+
20
+ # Name symbol for the _add internal association method
21
+ def _add_method
22
+ :"_add_#{singularize(self[:name])}"
23
+ end
24
+
25
+ # Name symbol for the _dataset association method
26
+ def _dataset_method
27
+ :"_#{self[:name]}_dataset"
28
+ end
29
+
30
+ # Name symbol for the _remove_all internal association method
31
+ def _remove_all_method
32
+ :"_remove_all_#{self[:name]}"
33
+ end
34
+
35
+ # Name symbol for the _remove internal association method
36
+ def _remove_method
37
+ :"_remove_#{singularize(self[:name])}"
38
+ end
39
+
40
+ # Name symbol for the _setter association method
41
+ def _setter_method
42
+ :"_#{self[:name]}="
43
+ end
44
+
45
+ # Name symbol for the add association method
46
+ def add_method
47
+ :"add_#{singularize(self[:name])}"
48
+ end
49
+
50
+ # Name symbol for association method, the same as the name of the association.
51
+ def association_method
52
+ self[:name]
53
+ end
54
+
55
+ # The class associated to the current model class via this association
56
+ def associated_class
57
+ self[:class] ||= constantize(self[:class_name])
58
+ end
59
+
60
+ # Whether this association can have associated objects, given the current
61
+ # object. Should be false if obj cannot have associated objects because
62
+ # the necessary key columns are NULL.
63
+ def can_have_associated_objects?(obj)
64
+ true
65
+ end
66
+
67
+ # Name symbol for the dataset association method
68
+ def dataset_method
69
+ :"#{self[:name]}_dataset"
70
+ end
71
+
72
+ # Name symbol for the _helper internal association method
73
+ def dataset_helper_method
74
+ :"_#{self[:name]}_dataset_helper"
75
+ end
76
+
77
+ # Whether the dataset needs a primary key to function, true by default.
78
+ def dataset_need_primary_key?
79
+ true
80
+ end
81
+
82
+ # By default associations do not need to select a key in an associated table
83
+ # to eagerly load.
84
+ def eager_loading_use_associated_key?
85
+ false
86
+ end
87
+
88
+ # Whether to eagerly graph a lazy dataset, true by default. If this
89
+ # is false, the association won't respect the :eager_graph option
90
+ # when loading the association for a single record.
91
+ def eager_graph_lazy_dataset?
92
+ true
93
+ end
94
+
95
+ # Whether the associated object needs a primary key to be added/removed,
96
+ # false by default.
97
+ def need_associated_primary_key?
98
+ false
99
+ end
100
+
101
+ # Returns the reciprocal association variable, if one exists. The reciprocal
102
+ # association is the association in the associated class that is the opposite
103
+ # of the current association. For example, Album.many_to_one :artist and
104
+ # Artist.one_to_many :albums are reciprocal associations. This information is
105
+ # to populate reciprocal associations. For example, when you do this_artist.add_album(album)
106
+ # it sets album.artist to this_artist.
107
+ def reciprocal
108
+ return self[:reciprocal] if include?(:reciprocal)
109
+ r_types = Array(reciprocal_type)
110
+ keys = self[:keys]
111
+ associated_class.all_association_reflections.each do |assoc_reflect|
112
+ if r_types.include?(assoc_reflect[:type]) && assoc_reflect[:keys] == keys && assoc_reflect.associated_class == self[:model]
113
+ self[:reciprocal_type] = assoc_reflect[:type]
114
+ return self[:reciprocal] = assoc_reflect[:name]
115
+ end
116
+ end
117
+ self[:reciprocal] = nil
118
+ end
119
+
120
+ # Whether the reciprocal of this association returns an array of objects instead of a single object,
121
+ # true by default.
122
+ def reciprocal_array?
123
+ true
124
+ end
125
+
126
+ # Name symbol for the remove_all_ association method
127
+ def remove_all_method
128
+ :"remove_all_#{self[:name]}"
129
+ end
130
+
131
+ # Name symbol for the remove_ association method
132
+ def remove_method
133
+ :"remove_#{singularize(self[:name])}"
134
+ end
135
+
136
+ # Whether to check that an object to be disassociated is already associated to this object, false by default.
137
+ def remove_should_check_existing?
138
+ false
139
+ end
140
+
141
+ # Whether this association returns an array of objects instead of a single object,
142
+ # true by default.
143
+ def returns_array?
144
+ true
145
+ end
146
+
147
+ # The columns to select when loading the association, nil by default.
148
+ def select
149
+ self[:select]
150
+ end
151
+
152
+ # Whether to set the reciprocal association to self when loading associated
153
+ # records, false by default.
154
+ def set_reciprocal_to_self?
155
+ false
156
+ end
157
+
158
+ # Name symbol for the setter association method
159
+ def setter_method
160
+ :"#{self[:name]}="
161
+ end
162
+ end
163
+
164
+ class ManyToOneAssociationReflection < AssociationReflection
165
+ ASSOCIATION_TYPES[:many_to_one] = self
166
+
167
+ # many_to_one associations can only have associated objects if none of
168
+ # the :keys options have a nil value.
169
+ def can_have_associated_objects?(obj)
170
+ !self[:keys].any?{|k| obj.send(k).nil?}
171
+ end
172
+
173
+ # Whether the dataset needs a primary key to function, false for many_to_one associations.
174
+ def dataset_need_primary_key?
175
+ false
176
+ end
177
+
178
+ # Default foreign key name symbol for foreign key in current model's table that points to
179
+ # the given association's table's primary key.
180
+ def default_key
181
+ :"#{self[:name]}_id"
182
+ end
183
+
184
+ # Whether to eagerly graph a lazy dataset, true for many_to_one associations
185
+ # only if the key is nil.
186
+ def eager_graph_lazy_dataset?
187
+ self[:key].nil?
188
+ end
189
+
190
+ # The key to use for the key hash when eager loading
191
+ def eager_loader_key
192
+ self[:eager_loader_key] ||= self[:key]
193
+ end
194
+
195
+ # The column(s) in the associated table that the key in the current table references (either a symbol or an array).
196
+ def primary_key
197
+ self[:primary_key] ||= associated_class.primary_key
198
+ end
199
+
200
+ # The columns in the associated table that the key in the current table references (always an array).
201
+ def primary_keys
202
+ self[:primary_keys] ||= Array(primary_key)
203
+ end
204
+ alias associated_object_keys primary_keys
205
+
206
+ # True only if the reciprocal is a one_to_many association.
207
+ def reciprocal_array?
208
+ !set_reciprocal_to_self?
209
+ end
210
+
211
+ # Whether this association returns an array of objects instead of a single object,
212
+ # false for a many_to_one association.
213
+ def returns_array?
214
+ false
215
+ end
216
+
217
+ # True only if the reciprocal is a one_to_one association.
218
+ def set_reciprocal_to_self?
219
+ reciprocal
220
+ self[:reciprocal_type] == :one_to_one
221
+ end
222
+
223
+ private
224
+
225
+ # The reciprocal type of a many_to_one association is either
226
+ # a one_to_many or a one_to_one association.
227
+ def reciprocal_type
228
+ self[:reciprocal_type] ||= [:one_to_many, :one_to_one]
229
+ end
230
+ end
231
+
232
+ class OneToManyAssociationReflection < AssociationReflection
233
+ ASSOCIATION_TYPES[:one_to_many] = self
234
+
235
+ # The keys in the associated model's table related to this association
236
+ def associated_object_keys
237
+ self[:keys]
238
+ end
239
+
240
+ # one_to_many associations can only have associated objects if none of
241
+ # the :keys options have a nil value.
242
+ def can_have_associated_objects?(obj)
243
+ !self[:primary_keys].any?{|k| obj.send(k).nil?}
244
+ end
245
+
246
+ # Default foreign key name symbol for key in associated table that points to
247
+ # current table's primary key.
248
+ def default_key
249
+ :"#{underscore(demodulize(self[:model].name))}_id"
250
+ end
251
+
252
+ # The key to use for the key hash when eager loading
253
+ def eager_loader_key
254
+ self[:eager_loader_key] ||= primary_key
255
+ end
256
+
257
+ # The column in the current table that the key in the associated table references.
258
+ def primary_key
259
+ self[:primary_key] ||= self[:model].primary_key
260
+ end
261
+
262
+ # One to many associations set the reciprocal to self when loading associated records.
263
+ def set_reciprocal_to_self?
264
+ true
265
+ end
266
+
267
+ # Whether the reciprocal of this association returns an array of objects instead of a single object,
268
+ # false for a one_to_many association.
269
+ def reciprocal_array?
270
+ false
271
+ end
272
+
273
+ # The one_to_many association needs to check that an object to be removed already is associated.
274
+ def remove_should_check_existing?
275
+ true
276
+ end
277
+
278
+ private
279
+
280
+ # The reciprocal type of a one_to_many association is a many_to_one association.
281
+ def reciprocal_type
282
+ :many_to_one
283
+ end
284
+ end
285
+
286
+ class OneToOneAssociationReflection < OneToManyAssociationReflection
287
+ ASSOCIATION_TYPES[:one_to_one] = self
288
+
289
+ # one_to_one associations return a single object, not an array
290
+ def returns_array?
291
+ false
292
+ end
293
+ end
294
+
295
+ class ManyToManyAssociationReflection < AssociationReflection
296
+ ASSOCIATION_TYPES[:many_to_many] = self
297
+
298
+ # The alias to use for the associated key when eagerly loading
299
+ def associated_key_alias
300
+ self[:left_key_alias]
301
+ end
302
+
303
+ # The column to use for the associated key when eagerly loading
304
+ def associated_key_column
305
+ self[:left_key]
306
+ end
307
+
308
+ # The table containing the column to use for the associated key when eagerly loading
309
+ def associated_key_table
310
+ self[:join_table]
311
+ end
312
+
313
+ # many_to_many associations can only have associated objects if none of
314
+ # the :left_primary_keys options have a nil value.
315
+ def can_have_associated_objects?(obj)
316
+ !self[:left_primary_keys].any?{|k| obj.send(k).nil?}
317
+ end
318
+
319
+ # The default associated key alias(es) to use when eager loading
320
+ # associations via eager.
321
+ def default_associated_key_alias
322
+ self[:uses_left_composite_keys] ? (0...self[:left_keys].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
323
+ end
324
+
325
+ # Default name symbol for the join table.
326
+ def default_join_table
327
+ [self[:class_name], self[:model].name].map{|i| underscore(pluralize(demodulize(i)))}.sort.join('_').to_sym
328
+ end
329
+
330
+ # Default foreign key name symbol for key in join table that points to
331
+ # current table's primary key (or :left_primary_key column).
332
+ def default_left_key
333
+ :"#{underscore(demodulize(self[:model].name))}_id"
334
+ end
335
+
336
+ # Default foreign key name symbol for foreign key in join table that points to
337
+ # the association's table's primary key (or :right_primary_key column).
338
+ def default_right_key
339
+ :"#{singularize(self[:name])}_id"
340
+ end
341
+
342
+ # The key to use for the key hash when eager loading
343
+ def eager_loader_key
344
+ self[:eager_loader_key] ||= self[:left_primary_key]
345
+ end
346
+
347
+ # many_to_many associations need to select a key in an associated table to eagerly load
348
+ def eager_loading_use_associated_key?
349
+ true
350
+ end
351
+
352
+ # Whether the associated object needs a primary key to be added/removed,
353
+ # true for many_to_many associations.
354
+ def need_associated_primary_key?
355
+ true
356
+ end
357
+
358
+ # Returns the reciprocal association symbol, if one exists.
359
+ def reciprocal
360
+ return self[:reciprocal] if include?(:reciprocal)
361
+ left_keys = self[:left_keys]
362
+ right_keys = self[:right_keys]
363
+ join_table = self[:join_table]
364
+ associated_class.all_association_reflections.each do |assoc_reflect|
365
+ if assoc_reflect[:type] == :many_to_many && assoc_reflect[:left_keys] == right_keys &&
366
+ assoc_reflect[:right_keys] == left_keys && assoc_reflect[:join_table] == join_table &&
367
+ assoc_reflect.associated_class == self[:model]
368
+ return self[:reciprocal] = assoc_reflect[:name]
369
+ end
370
+ end
371
+ self[:reciprocal] = nil
372
+ end
373
+
374
+ # The primary key column(s) to use in the associated table (can be symbol or array).
375
+ def right_primary_key
376
+ self[:right_primary_key] ||= associated_class.primary_key
377
+ end
378
+
379
+ # The primary key columns to use in the associated table (always array).
380
+ def right_primary_keys
381
+ self[:right_primary_keys] ||= Array(right_primary_key)
382
+ end
383
+ alias associated_object_keys right_primary_keys
384
+
385
+ # The columns to select when loading the association, associated_class.table_name.* by default.
386
+ def select
387
+ return self[:select] if include?(:select)
388
+ self[:select] ||= Sequel::SQL::ColumnAll.new(associated_class.table_name)
389
+ end
390
+ end
391
+
392
+ # This module contains methods added to all association datasets
393
+ module AssociationDatasetMethods
394
+ # The model object that created the association dataset
395
+ attr_accessor :model_object
396
+
397
+ # The association reflection related to the association dataset
398
+ attr_accessor :association_reflection
399
+ end
400
+
401
+ # Each kind of association adds a number of instance methods to the model class which
402
+ # are specialized according to the association type and optional parameters
403
+ # given in the definition. Example:
404
+ #
405
+ # class Project < Sequel::Model
406
+ # many_to_one :portfolio
407
+ # # or: one_to_one :portfolio
408
+ # one_to_many :milestones
409
+ # # or: many_to_many :milestones
410
+ # end
411
+ #
412
+ # The project class now has the following instance methods:
413
+ # portfolio :: Returns the associated portfolio.
414
+ # portfolio=(obj) :: Sets the associated portfolio to the object,
415
+ # but the change is not persisted until you save the record (for many_to_one associations).
416
+ # portfolio_dataset :: Returns a dataset that would return the associated
417
+ # portfolio, only useful in fairly specific circumstances.
418
+ # milestones :: Returns an array of associated milestones
419
+ # add_milestone(obj) :: Associates the passed milestone with this object.
420
+ # remove_milestone(obj) :: Removes the association with the passed milestone.
421
+ # remove_all_milestones :: Removes associations with all associated milestones.
422
+ # milestones_dataset :: Returns a dataset that would return the associated
423
+ # milestones, allowing for further filtering/limiting/etc.
424
+ #
425
+ # If you want to override the behavior of the add_/remove_/remove_all_/ methods
426
+ # or the association setter method, there are private instance methods created that are prepended
427
+ # with an underscore (e.g. _add_milestone or _portfolio=). The private instance methods can be
428
+ # easily overridden, but you shouldn't override the public instance methods without
429
+ # calling super, as they deal with callbacks and caching.
430
+ #
431
+ # By default the classes for the associations are inferred from the association
432
+ # name, so for example the Project#portfolio will return an instance of
433
+ # Portfolio, and Project#milestones will return an array of Milestone
434
+ # instances. You can use the :class option to change which class is used.
435
+ #
436
+ # Association definitions are also reflected by the class, e.g.:
437
+ #
438
+ # Project.associations
439
+ # => [:portfolio, :milestones]
440
+ # Project.association_reflection(:portfolio)
441
+ # => {:type => :many_to_one, :name => :portfolio, :class_name => "Portfolio"}
442
+ #
443
+ # For examples of advanced usage, see the {Advanced Associations page}[link:files/doc/advanced_associations_rdoc.html].
444
+ module ClassMethods
445
+ # All association reflections defined for this model (default: none).
446
+ attr_reader :association_reflections
447
+
448
+ # Array of all association reflections for this model class
449
+ def all_association_reflections
450
+ association_reflections.values
451
+ end
452
+
453
+ # Associates a related model with the current model. The following types are
454
+ # supported:
455
+ #
456
+ # * :many_to_one - Foreign key in current model's table points to
457
+ # associated model's primary key. Each associated model object can
458
+ # be associated with more than one current model objects. Each current
459
+ # model object can be associated with only one associated model object.
460
+ # * :one_to_many - Foreign key in associated model's table points to this
461
+ # model's primary key. Each current model object can be associated with
462
+ # more than one associated model objects. Each associated model object
463
+ # can be associated with only one current model object.
464
+ # * :one_to_one - Similar to one_to_many in terms of foreign keys, but
465
+ # only one object is associated to the current object through the
466
+ # association. The methods created are similar to many_to_one, except
467
+ # that the one_to_one setter method saves the passed object.
468
+ # * :many_to_many - A join table is used that has a foreign key that points
469
+ # to this model's primary key and a foreign key that points to the
470
+ # associated model's primary key. Each current model object can be
471
+ # associated with many associated model objects, and each associated
472
+ # model object can be associated with many current model objects.
473
+ #
474
+ # The following options can be supplied:
475
+ # * *ALL types*:
476
+ # - :after_add - Symbol, Proc, or array of both/either specifying a callback to call
477
+ # after a new item is added to the association.
478
+ # - :after_load - Symbol, Proc, or array of both/either specifying a callback to call
479
+ # after the associated record(s) have been retrieved from the database. Not called
480
+ # when eager loading via eager_graph, but called when eager loading via eager.
481
+ # - :after_remove - Symbol, Proc, or array of both/either specifying a callback to call
482
+ # after an item is removed from the association.
483
+ # - :after_set - Symbol, Proc, or array of both/either specifying a callback to call
484
+ # after an item is set using the association setter method.
485
+ # - :allow_eager - If set to false, you cannot load the association eagerly
486
+ # via eager or eager_graph
487
+ # - :before_add - Symbol, Proc, or array of both/either specifying a callback to call
488
+ # before a new item is added to the association.
489
+ # - :before_remove - Symbol, Proc, or array of both/either specifying a callback to call
490
+ # before an item is removed from the association.
491
+ # - :before_set - Symbol, Proc, or array of both/either specifying a callback to call
492
+ # before an item is set using the association setter method.
493
+ # - :cartesian_product_number - the number of joins completed by this association that could cause more
494
+ # than one row for each row in the current table (default: 0 for many_to_one associations,
495
+ # 1 for *_to_many associations).
496
+ # - :class - The associated class or its name. If not
497
+ # given, uses the association's name, which is camelized (and
498
+ # singularized unless the type is :many_to_one)
499
+ # - :clone - Merge the current options and block into the options and block used in defining
500
+ # the given association. Can be used to DRY up a bunch of similar associations that
501
+ # all share the same options such as :class and :key, while changing the order and block used.
502
+ # - :conditions - The conditions to use to filter the association, can be any argument passed to filter.
503
+ # - :dataset - A proc that is instance_evaled to get the base dataset
504
+ # to use for the _dataset method (before the other options are applied).
505
+ # - :distinct - Use the DISTINCT clause when selecting associating object, both when
506
+ # lazy loading and eager loading via .eager (but not when using .eager_graph).
507
+ # - :eager - The associations to eagerly load via #eager when loading the associated object(s).
508
+ # For many_to_one associations, this is ignored unless this association is
509
+ # being eagerly loaded, as it doesn't save queries unless multiple objects
510
+ # can be loaded at once.
511
+ # - :eager_block - If given, use the block instead of the default block when
512
+ # eagerly loading. To not use a block when eager loading (when one is used normally),
513
+ # set to nil.
514
+ # - :eager_graph - The associations to eagerly load via #eager_graph when loading the associated object(s).
515
+ # For many_to_one associations, this is ignored unless this association is
516
+ # being eagerly loaded, as it doesn't save queries unless multiple objects
517
+ # can be loaded at once.
518
+ # - :eager_grapher - A proc to use to implement eager loading via eager graph, overriding the default.
519
+ # Takes three arguments, a dataset, an alias to use for the table to graph for this association,
520
+ # and the alias that was used for the current table (since you can cascade associations),
521
+ # Should return a copy of the dataset with the association graphed into it.
522
+ # - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes three arguments,
523
+ # a key hash (used solely to enhance performance), an array of records,
524
+ # and a hash of dependent associations. The associated records should
525
+ # be queried from the database and the associations cache for each
526
+ # record should be populated for this to work correctly.
527
+ # - :eager_loader_key - A symbol for the key column to use to populate the key hash
528
+ # for the eager loader.
529
+ # - :extend - A module or array of modules to extend the dataset with.
530
+ # - :graph_block - The block to pass to join_table when eagerly loading
531
+ # the association via eager_graph.
532
+ # - :graph_conditions - The additional conditions to use on the SQL join when eagerly loading
533
+ # the association via eager_graph. Should be a hash or an array of all two pairs. If not
534
+ # specified, the :conditions option is used if it is a hash or array of all two pairs.
535
+ # - :graph_join_type - The type of SQL join to use when eagerly loading the association via
536
+ # eager_graph. Defaults to :left_outer.
537
+ # - :graph_only_conditions - The conditions to use on the SQL join when eagerly loading
538
+ # the association via eager_graph, instead of the default conditions specified by the
539
+ # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
540
+ # - :graph_select - A column or array of columns to select from the associated table
541
+ # when eagerly loading the association via eager_graph. Defaults to all
542
+ # columns in the associated table.
543
+ # - :limit - Limit the number of records to the provided value. Use
544
+ # an array with two arguments for the value to specify a limit and an offset.
545
+ # - :order - the column(s) by which to order the association dataset. Can be a
546
+ # singular column or an array.
547
+ # - :order_eager_graph - Whether to add the order to the dataset's order when graphing
548
+ # via eager graph. Defaults to true, so set to false to disable.
549
+ # - :read_only - Do not add a setter method (for many_to_one or one_to_many with :one_to_one),
550
+ # or add_/remove_/remove_all_ methods (for one_to_many, many_to_many)
551
+ # - :reciprocal - the symbol name of the reciprocal association,
552
+ # if it exists. By default, sequel will try to determine it by looking at the
553
+ # associated model's assocations for a association that matches
554
+ # the current association's key(s). Set to nil to not use a reciprocal.
555
+ # - :select - the attributes to select. Defaults to the associated class's
556
+ # table_name.* in a many_to_many association, which means it doesn't include the attributes from the
557
+ # join table. If you want to include the join table attributes, you can
558
+ # use this option, but beware that the join table attributes can clash with
559
+ # attributes from the model table, so you should alias any attributes that have
560
+ # the same name in both the join table and the associated table.
561
+ # - :validate - Set to false to not validate when implicitly saving any associated object.
562
+ # * :many_to_one:
563
+ # - :key - foreign_key in current model's table that references
564
+ # associated model's primary key, as a symbol. Defaults to :"#{name}_id". Can use an
565
+ # array of symbols for a composite key association.
566
+ # - :primary_key - column in the associated table that :key option references, as a symbol.
567
+ # Defaults to the primary key of the associated table. Can use an
568
+ # array of symbols for a composite key association.
569
+ # * :one_to_many:
570
+ # - :key - foreign key in associated model's table that references
571
+ # current model's primary key, as a symbol. Defaults to
572
+ # :"#{self.name.underscore}_id". Can use an
573
+ # array of symbols for a composite key association.
574
+ # - :primary_key - column in the current table that :key option references, as a symbol.
575
+ # Defaults to primary key of the current table. Can use an
576
+ # array of symbols for a composite key association.
577
+ # * :many_to_many:
578
+ # - :graph_join_table_block - The block to pass to join_table for
579
+ # the join table when eagerly loading the association via eager_graph.
580
+ # - :graph_join_table_conditions - The additional conditions to use on the SQL join for
581
+ # the join table when eagerly loading the association via eager_graph. Should be a hash
582
+ # or an array of all two pairs.
583
+ # - :graph_join_table_join_type - The type of SQL join to use for the join table when eagerly
584
+ # loading the association via eager_graph. Defaults to the :graph_join_type option or
585
+ # :left_outer.
586
+ # - :graph_join_table_only_conditions - The conditions to use on the SQL join for the join
587
+ # table when eagerly loading the association via eager_graph, instead of the default
588
+ # conditions specified by the foreign/primary keys. This option causes the
589
+ # :graph_join_table_conditions option to be ignored.
590
+ # - :join_table - name of table that includes the foreign keys to both
591
+ # the current model and the associated model, as a symbol. Defaults to the name
592
+ # of current model and name of associated model, pluralized,
593
+ # underscored, sorted, and joined with '_'.
594
+ # - :left_key - foreign key in join table that points to current model's
595
+ # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
596
+ # Can use an array of symbols for a composite key association.
597
+ # - :left_primary_key - column in current table that :left_key points to, as a symbol.
598
+ # Defaults to primary key of current table. Can use an
599
+ # array of symbols for a composite key association.
600
+ # - :right_key - foreign key in join table that points to associated
601
+ # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
602
+ # Can use an array of symbols for a composite key association.
603
+ # - :right_primary_key - column in associated table that :right_key points to, as a symbol.
604
+ # Defaults to primary key of the associated table. Can use an
605
+ # array of symbols for a composite key association.
606
+ # - :uniq - Adds a after_load callback that makes the array of objects unique.
607
+ def associate(type, name, opts = {}, &block)
608
+ raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
609
+ raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
610
+ raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
611
+
612
+ # merge early so we don't modify opts
613
+ orig_opts = opts.dup
614
+ orig_opts = association_reflection(opts[:clone])[:orig_opts].merge(orig_opts) if opts[:clone]
615
+ opts = orig_opts.merge(:type => type, :name => name, :cache => true, :model => self)
616
+ opts[:block] = block if block
617
+ opts = assoc_class.new.merge!(opts)
618
+ opts[:eager_block] = block unless opts.include?(:eager_block)
619
+ opts[:graph_join_type] ||= :left_outer
620
+ opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
621
+ conds = opts[:conditions]
622
+ opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
623
+ opts[:graph_conditions] = opts.fetch(:graph_conditions, []).to_a
624
+ opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
625
+ [:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set, :extend].each do |cb_type|
626
+ opts[cb_type] = Array(opts[cb_type])
627
+ end
628
+ late_binding_class_option(opts, opts.returns_array? ? singularize(name) : name)
629
+
630
+ send(:"def_#{type}", opts)
631
+
632
+ orig_opts.delete(:clone)
633
+ orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>block)
634
+ opts[:orig_opts] = orig_opts
635
+ # don't add to association_reflections until we are sure there are no errors
636
+ association_reflections[name] = opts
637
+ end
638
+
639
+ # The association reflection hash for the association of the given name.
640
+ def association_reflection(name)
641
+ association_reflections[name]
642
+ end
643
+
644
+ # Array of association name symbols
645
+ def associations
646
+ association_reflections.keys
647
+ end
648
+
649
+ # Modify and return eager loading dataset based on association options. Options:
650
+ def eager_loading_dataset(opts, ds, select, associations)
651
+ ds = ds.select(*select) if select
652
+ if c = opts[:conditions]
653
+ ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
654
+ end
655
+ ds = ds.order(*opts[:order]) if opts[:order]
656
+ ds = ds.eager(opts[:eager]) if opts[:eager]
657
+ ds = ds.distinct if opts[:distinct]
658
+ if opts[:eager_graph]
659
+ ds = ds.eager_graph(opts[:eager_graph])
660
+ ds = ds.add_graph_aliases(opts.associated_key_alias=>[opts.associated_class.table_name, opts.associated_key_alias, SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column)]) if opts.eager_loading_use_associated_key?
661
+ elsif opts.eager_loading_use_associated_key?
662
+ ds = if opts[:uses_left_composite_keys]
663
+ t = opts.associated_key_table
664
+ ds.select_more(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
665
+ else
666
+ ds.select_more(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column), opts.associated_key_alias))
667
+ end
668
+ end
669
+ ds = ds.eager(associations) unless Array(associations).empty?
670
+ ds = opts[:eager_block].call(ds) if opts[:eager_block]
671
+ ds
672
+ end
673
+
674
+ # Copy the association reflections to the subclass
675
+ def inherited(subclass)
676
+ super
677
+ subclass.instance_variable_set(:@association_reflections, @association_reflections.dup)
678
+ end
679
+
680
+ # Shortcut for adding a many_to_many association, see associate
681
+ def many_to_many(name, opts={}, &block)
682
+ associate(:many_to_many, name, opts, &block)
683
+ end
684
+
685
+ # Shortcut for adding a many_to_one association, see associate
686
+ def many_to_one(name, opts={}, &block)
687
+ associate(:many_to_one, name, opts, &block)
688
+ end
689
+
690
+ # Shortcut for adding a one_to_many association, see associate
691
+ def one_to_many(name, opts={}, &block)
692
+ associate(:one_to_many, name, opts, &block)
693
+ end
694
+
695
+ # Shortcut for adding a one_to_one association, see associate.
696
+ def one_to_one(name, opts={}, &block)
697
+ associate(:one_to_one, name, opts, &block)
698
+ end
699
+
700
+ private
701
+
702
+ # Add a method to the module included in the class, so the method
703
+ # can be easily overridden in the class itself while allowing for
704
+ # super to be called.
705
+ def association_module_def(name, &block)
706
+ overridable_methods_module.module_eval{define_method(name, &block)}
707
+ end
708
+
709
+ # Add a private method to the module included in the class.
710
+ def association_module_private_def(name, &block)
711
+ association_module_def(name, &block)
712
+ overridable_methods_module.send(:private, name)
713
+ end
714
+
715
+ # Add the add_ instance method
716
+ def def_add_method(opts)
717
+ association_module_def(opts.add_method){|o,*args| add_associated_object(opts, o, *args)}
718
+ end
719
+
720
+ # Adds methods related to the association's dataset to the module included in the class.
721
+ def def_association_dataset_methods(opts)
722
+ # If a block is given, define a helper method for it, because it takes
723
+ # an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
724
+ association_module_private_def(opts.dataset_helper_method, &opts[:block]) if opts[:block]
725
+ association_module_private_def(opts._dataset_method, &opts[:dataset])
726
+ association_module_def(opts.dataset_method){_dataset(opts)}
727
+ def_association_method(opts)
728
+ end
729
+
730
+ # Adds method for retrieving the associated objects to the module included in the class.
731
+ def def_association_method(opts)
732
+ association_module_def(opts.association_method){|*reload| load_associated_objects(opts, reload[0])}
733
+ end
734
+
735
+ # Adds many_to_many association instance methods
736
+ def def_many_to_many(opts)
737
+ name = opts[:name]
738
+ model = self
739
+ left = (opts[:left_key] ||= opts.default_left_key)
740
+ lcks = opts[:left_keys] = Array(left)
741
+ right = (opts[:right_key] ||= opts.default_right_key)
742
+ rcks = opts[:right_keys] = Array(right)
743
+ left_pk = (opts[:left_primary_key] ||= self.primary_key)
744
+ lcpks = opts[:left_primary_keys] = Array(left_pk)
745
+ raise(Error, 'mismatched number of left composite keys') unless lcks.length == lcpks.length
746
+ raise(Error, 'mismatched number of right composite keys') if opts[:right_primary_key] && rcks.length != Array(opts[:right_primary_key]).length
747
+ uses_lcks = opts[:uses_left_composite_keys] = lcks.length > 1
748
+ uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
749
+ opts[:cartesian_product_number] ||= 1
750
+ join_table = (opts[:join_table] ||= opts.default_join_table)
751
+ left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
752
+ graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
753
+ opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
754
+ opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
755
+ opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
756
+ database = db
757
+
758
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
759
+ h = key_hash[left_pk]
760
+ records.each{|object| object.associations[name] = []}
761
+ r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
762
+ l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, SQL::SQLArray.new(h.keys)]] : [[left, h.keys]]
763
+ model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), associations).all do |assoc_record|
764
+ hash_key = if uses_lcks
765
+ left_key_alias.map{|k| assoc_record.values.delete(k)}
766
+ else
767
+ assoc_record.values.delete(left_key_alias)
768
+ end
769
+ next unless objects = h[hash_key]
770
+ objects.each{|object| object.associations[name].push(assoc_record)}
771
+ end
772
+ end
773
+
774
+ join_type = opts[:graph_join_type]
775
+ select = opts[:graph_select]
776
+ use_only_conditions = opts.include?(:graph_only_conditions)
777
+ only_conditions = opts[:graph_only_conditions]
778
+ conditions = opts[:graph_conditions]
779
+ graph_block = opts[:graph_block]
780
+ use_jt_only_conditions = opts.include?(:graph_join_table_only_conditions)
781
+ jt_only_conditions = opts[:graph_join_table_only_conditions]
782
+ jt_join_type = opts[:graph_join_table_join_type]
783
+ jt_graph_block = opts[:graph_join_table_block]
784
+ opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
785
+ ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table), :join_type=>jt_join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
786
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
787
+ end
788
+
789
+ def_association_dataset_methods(opts)
790
+
791
+ return if opts[:read_only]
792
+
793
+ association_module_private_def(opts._add_method) do |o|
794
+ h = {}
795
+ lcks.zip(lcpks).each{|k, pk| h[k] = send(pk)}
796
+ rcks.zip(opts.right_primary_keys).each{|k, pk| h[k] = o.send(pk)}
797
+ database.dataset.from(join_table).insert(h)
798
+ end
799
+ association_module_private_def(opts._remove_method) do |o|
800
+ database.dataset.from(join_table).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_keys.map{|k| o.send(k)})).delete
801
+ end
802
+ association_module_private_def(opts._remove_all_method) do
803
+ _apply_association_options(opts, database.dataset.from(join_table).filter(lcks.zip(lcpks.map{|k| send(k)}))).delete
804
+ end
805
+
806
+ def_add_method(opts)
807
+ def_remove_methods(opts)
808
+ end
809
+
810
+ # Adds many_to_one association instance methods
811
+ def def_many_to_one(opts)
812
+ name = opts[:name]
813
+ model = self
814
+ opts[:key] = opts.default_key unless opts.include?(:key)
815
+ key = opts[:key]
816
+ cks = opts[:keys] = Array(opts[:key])
817
+ raise(Error, 'mismatched number of composite keys') if opts[:primary_key] && cks.length != Array(opts[:primary_key]).length
818
+ uses_cks = opts[:uses_composite_keys] = cks.length > 1
819
+ opts[:cartesian_product_number] ||= 0
820
+ opts[:dataset] ||= proc do
821
+ klass = opts.associated_class
822
+ klass.filter(opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cks.map{|k| send(k)}))
823
+ end
824
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
825
+ h = key_hash[key]
826
+ keys = h.keys
827
+ # Default the cached association to nil, so any object that doesn't have it
828
+ # populated will have cached the negative lookup.
829
+ records.each{|object| object.associations[name] = nil}
830
+ # Skip eager loading if no objects have a foreign key for this association
831
+ unless keys.empty?
832
+ klass = opts.associated_class
833
+ model.eager_loading_dataset(opts, klass.filter(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, associations).all do |assoc_record|
834
+ hash_key = uses_cks ? opts.primary_keys.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key)
835
+ next unless objects = h[hash_key]
836
+ objects.each{|object| object.associations[name] = assoc_record}
837
+ end
838
+ end
839
+ end
840
+
841
+ join_type = opts[:graph_join_type]
842
+ select = opts[:graph_select]
843
+ use_only_conditions = opts.include?(:graph_only_conditions)
844
+ only_conditions = opts[:graph_only_conditions]
845
+ conditions = opts[:graph_conditions]
846
+ graph_block = opts[:graph_block]
847
+ opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
848
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : opts.primary_keys.zip(cks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &graph_block)
849
+ end
850
+
851
+ def_association_dataset_methods(opts)
852
+
853
+ return if opts[:read_only]
854
+
855
+ association_module_private_def(opts._setter_method){|o| cks.zip(opts.primary_keys).each{|k, pk| send(:"#{k}=", (o.send(pk) if o))}}
856
+ association_module_def(opts.setter_method){|o| set_associated_object(opts, o)}
857
+ end
858
+
859
+ # Adds one_to_many association instance methods
860
+ def def_one_to_many(opts)
861
+ one_to_one = opts[:type] == :one_to_one
862
+ name = opts[:name]
863
+ model = self
864
+ key = (opts[:key] ||= opts.default_key)
865
+ cks = opts[:keys] = Array(key)
866
+ primary_key = (opts[:primary_key] ||= self.primary_key)
867
+ cpks = opts[:primary_keys] = Array(primary_key)
868
+ raise(Error, 'mismatched number of composite keys') unless cks.length == cpks.length
869
+ uses_cks = opts[:uses_composite_keys] = cks.length > 1
870
+ opts[:dataset] ||= proc do
871
+ klass = opts.associated_class
872
+ klass.filter(cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cpks.map{|k| send(k)}))
873
+ end
874
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
875
+ h = key_hash[primary_key]
876
+ if one_to_one
877
+ records.each{|object| object.associations[name] = nil}
878
+ else
879
+ records.each{|object| object.associations[name] = []}
880
+ end
881
+ reciprocal = opts.reciprocal
882
+ klass = opts.associated_class
883
+ model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(h.keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, associations).all do |assoc_record|
884
+ hash_key = uses_cks ? cks.map{|k| assoc_record.send(k)} : assoc_record.send(key)
885
+ next unless objects = h[hash_key]
886
+ if one_to_one
887
+ objects.each do |object|
888
+ object.associations[name] = assoc_record
889
+ assoc_record.associations[reciprocal] = object if reciprocal
890
+ end
891
+ else
892
+ objects.each do |object|
893
+ object.associations[name].push(assoc_record)
894
+ assoc_record.associations[reciprocal] = object if reciprocal
895
+ end
896
+ end
897
+ end
898
+ end
899
+
900
+ join_type = opts[:graph_join_type]
901
+ select = opts[:graph_select]
902
+ use_only_conditions = opts.include?(:graph_only_conditions)
903
+ only_conditions = opts[:graph_only_conditions]
904
+ conditions = opts[:graph_conditions]
905
+ opts[:cartesian_product_number] ||= one_to_one ? 0 : 1
906
+ graph_block = opts[:graph_block]
907
+ opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
908
+ ds = ds.graph(opts.associated_class, use_only_conditions ? only_conditions : cks.zip(cpks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &graph_block)
909
+ # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
910
+ ds.opts[:eager_graph][:reciprocals][assoc_alias] = opts.reciprocal
911
+ ds
912
+ end
913
+
914
+ def_association_dataset_methods(opts)
915
+
916
+ ck_nil_hash ={}
917
+ cks.each{|k| ck_nil_hash[k] = nil}
918
+
919
+ unless opts[:read_only]
920
+ validate = opts[:validate]
921
+
922
+ if one_to_one
923
+ association_module_private_def(opts._setter_method) do |o|
924
+ up_ds = _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)})))
925
+ if o
926
+ up_ds = up_ds.exclude(o.pk_hash)
927
+ cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
928
+ end
929
+ update_database = lambda do
930
+ up_ds.update(ck_nil_hash)
931
+ o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save") if o
932
+ end
933
+ use_transactions && o ? db.transaction(opts){update_database.call} : update_database.call
934
+ end
935
+ association_module_def(opts.setter_method){|o| set_one_to_one_associated_object(opts, o)}
936
+ else
937
+ association_module_private_def(opts._add_method) do |o|
938
+ cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
939
+ o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
940
+ end
941
+ def_add_method(opts)
942
+
943
+ association_module_private_def(opts._remove_method) do |o|
944
+ cks.each{|k| o.send(:"#{k}=", nil)}
945
+ o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
946
+ end
947
+ association_module_private_def(opts._remove_all_method) do
948
+ _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)}))).update(ck_nil_hash)
949
+ end
950
+ def_remove_methods(opts)
951
+ end
952
+ end
953
+ end
954
+ alias def_one_to_one def_one_to_many
955
+
956
+ # Add the remove_ and remove_all instance methods
957
+ def def_remove_methods(opts)
958
+ association_module_def(opts.remove_method){|o,*args| remove_associated_object(opts, o, *args)}
959
+ association_module_def(opts.remove_all_method){|*args| remove_all_associated_objects(opts, *args)}
960
+ end
961
+ end
962
+
963
+ # Private instance methods used to implement the associations support.
964
+ module InstanceMethods
965
+ # The currently cached associations. A hash with the keys being the
966
+ # association name symbols and the values being the associated object
967
+ # or nil (many_to_one), or the array of associated objects (*_to_many).
968
+ def associations
969
+ @associations ||= {}
970
+ end
971
+
972
+ # Used internally by the associations code, like pk but doesn't raise
973
+ # an Error if the model has no primary key.
974
+ def pk_or_nil
975
+ key = primary_key
976
+ key.is_a?(Array) ? key.map{|k| @values[k]} : @values[key]
977
+ end
978
+
979
+ private
980
+
981
+ def _apply_association_options(opts, ds)
982
+ ds.extend(AssociationDatasetMethods)
983
+ ds.model_object = self
984
+ ds.association_reflection = opts
985
+ opts[:extend].each{|m| ds.extend(m)}
986
+ ds = ds.select(*opts.select) if opts.select
987
+ if c = opts[:conditions]
988
+ ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
989
+ end
990
+ ds = ds.order(*opts[:order]) if opts[:order]
991
+ ds = ds.limit(*opts[:limit]) if opts[:limit]
992
+ ds = ds.limit(1) if !opts.returns_array? && opts[:key]
993
+ ds = ds.eager(*opts[:eager]) if opts[:eager]
994
+ ds = ds.distinct if opts[:distinct]
995
+ ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
996
+ ds = send(opts.dataset_helper_method, ds) if opts[:block]
997
+ ds
998
+ end
999
+
1000
+ # Backbone behind association dataset methods
1001
+ def _dataset(opts)
1002
+ raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
1003
+ _apply_association_options(opts, send(opts._dataset_method))
1004
+ end
1005
+
1006
+ # Return the associated objects from the dataset, without callbacks, reciprocals, and caching.
1007
+ def _load_associated_objects(opts)
1008
+ if opts.returns_array?
1009
+ opts.can_have_associated_objects?(self) ? send(opts.dataset_method).all : []
1010
+ else
1011
+ if opts.can_have_associated_objects?(self)
1012
+ send(opts.dataset_method).all.first
1013
+ end
1014
+ end
1015
+ end
1016
+
1017
+ # Clear the associations cache when refreshing
1018
+ def _refresh(dataset)
1019
+ associations.clear
1020
+ super
1021
+ end
1022
+
1023
+ # Add the given associated object to the given association
1024
+ def add_associated_object(opts, o, *args)
1025
+ klass = opts.associated_class
1026
+ if o.is_a?(Hash)
1027
+ o = klass.new(o)
1028
+ elsif !o.is_a?(klass)
1029
+ raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
1030
+ end
1031
+ raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
1032
+ if opts.need_associated_primary_key?
1033
+ o.save(:validate=>opts[:validate]) if o.new?
1034
+ raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk
1035
+ end
1036
+ return if run_association_callbacks(opts, :before_add, o) == false
1037
+ send(opts._add_method, o, *args)
1038
+ if array = associations[opts[:name]] and !array.include?(o)
1039
+ array.push(o)
1040
+ end
1041
+ add_reciprocal_object(opts, o)
1042
+ run_association_callbacks(opts, :after_add, o)
1043
+ o
1044
+ end
1045
+
1046
+ # Add/Set the current object to/as the given object's reciprocal association.
1047
+ def add_reciprocal_object(opts, o)
1048
+ return unless reciprocal = opts.reciprocal
1049
+ if opts.reciprocal_array?
1050
+ if array = o.associations[reciprocal] and !array.include?(self)
1051
+ array.push(self)
1052
+ end
1053
+ else
1054
+ o.associations[reciprocal] = self
1055
+ end
1056
+ end
1057
+
1058
+ # Call uniq! on the given array. This is used by the :uniq option,
1059
+ # and is an actual method for memory reasons.
1060
+ def array_uniq!(a)
1061
+ a.uniq!
1062
+ end
1063
+
1064
+ # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
1065
+ def load_associated_objects(opts, reload=false)
1066
+ name = opts[:name]
1067
+ if associations.include?(name) and !reload
1068
+ associations[name]
1069
+ else
1070
+ objs = _load_associated_objects(opts)
1071
+ run_association_callbacks(opts, :after_load, objs)
1072
+ if opts.set_reciprocal_to_self?
1073
+ if opts.returns_array?
1074
+ objs.each{|o| add_reciprocal_object(opts, o)}
1075
+ elsif objs
1076
+ add_reciprocal_object(opts, objs)
1077
+ end
1078
+ end
1079
+ associations[name] = objs
1080
+ end
1081
+ end
1082
+
1083
+ # Remove all associated objects from the given association
1084
+ def remove_all_associated_objects(opts, *args)
1085
+ raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
1086
+ send(opts._remove_all_method, *args)
1087
+ ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
1088
+ associations[opts[:name]] = []
1089
+ ret
1090
+ end
1091
+
1092
+ # Remove the given associated object from the given association
1093
+ def remove_associated_object(opts, o, *args)
1094
+ klass = opts.associated_class
1095
+ if o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
1096
+ key = o
1097
+ pkh = klass.primary_key_hash(key)
1098
+ raise(Sequel::Error, "no object with key(s) #{key} is currently associated to #{inspect}") unless o = (opts.remove_should_check_existing? ? send(opts.dataset_method) : klass).first(pkh)
1099
+ elsif !o.is_a?(klass)
1100
+ raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
1101
+ elsif opts.remove_should_check_existing? && send(opts.dataset_method).filter(o.pk_hash).empty?
1102
+ raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}")
1103
+ end
1104
+ raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
1105
+ raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
1106
+ return if run_association_callbacks(opts, :before_remove, o) == false
1107
+ send(opts._remove_method, o, *args)
1108
+ associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
1109
+ remove_reciprocal_object(opts, o)
1110
+ run_association_callbacks(opts, :after_remove, o)
1111
+ o
1112
+ end
1113
+
1114
+ # Remove/unset the current object from/as the given object's reciprocal association.
1115
+ def remove_reciprocal_object(opts, o)
1116
+ return unless reciprocal = opts.reciprocal
1117
+ if opts.reciprocal_array?
1118
+ if array = o.associations[reciprocal]
1119
+ array.delete_if{|x| self === x}
1120
+ end
1121
+ else
1122
+ o.associations[reciprocal] = nil
1123
+ end
1124
+ end
1125
+
1126
+ # Run the callback for the association with the object.
1127
+ def run_association_callbacks(reflection, callback_type, object)
1128
+ raise_error = raise_on_save_failure || !reflection.returns_array?
1129
+ stop_on_false = [:before_add, :before_remove, :before_set].include?(callback_type)
1130
+ reflection[callback_type].each do |cb|
1131
+ res = case cb
1132
+ when Symbol
1133
+ send(cb, object)
1134
+ when Proc
1135
+ cb.call(self, object)
1136
+ else
1137
+ raise Error, "callbacks should either be Procs or Symbols"
1138
+ end
1139
+ if res == false and stop_on_false
1140
+ raise(BeforeHookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false") if raise_error
1141
+ return false
1142
+ end
1143
+ end
1144
+ end
1145
+
1146
+ # Set the given object as the associated object for the given association
1147
+ def set_associated_object(opts, o)
1148
+ raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
1149
+ run_association_callbacks(opts, :before_set, o)
1150
+ if a = associations[opts[:name]]
1151
+ remove_reciprocal_object(opts, a)
1152
+ end
1153
+ send(opts._setter_method, o)
1154
+ associations[opts[:name]] = o
1155
+ add_reciprocal_object(opts, o) if o
1156
+ run_association_callbacks(opts, :after_set, o)
1157
+ o
1158
+ end
1159
+
1160
+ # Set the given object as the associated object for the given association
1161
+ def set_one_to_one_associated_object(opts, o)
1162
+ raise(Error, "object #{inspect} does not have a primary key") unless pk
1163
+ run_association_callbacks(opts, :before_set, o)
1164
+ if a = associations[opts[:name]]
1165
+ remove_reciprocal_object(opts, a)
1166
+ end
1167
+ send(opts._setter_method, o)
1168
+ associations[opts[:name]] = o
1169
+ add_reciprocal_object(opts, o) if o
1170
+ run_association_callbacks(opts, :after_set, o)
1171
+ o
1172
+ end
1173
+ end
1174
+
1175
+ # Eager loading makes it so that you can load all associated records for a
1176
+ # set of objects in a single query, instead of a separate query for each object.
1177
+ #
1178
+ # Two separate implementations are provided. #eager should be used most of the
1179
+ # time, as it loads associated records using one query per association. However,
1180
+ # it does not allow you the ability to filter based on columns in associated tables. #eager_graph loads
1181
+ # all records in one query. Using #eager_graph you can filter based on columns in associated
1182
+ # tables. However, #eager_graph can be slower than #eager, especially if multiple
1183
+ # *_to_many associations are joined.
1184
+ #
1185
+ # You can cascade the eager loading (loading associations' associations)
1186
+ # with no limit to the depth of the cascades. You do this by passing a hash to #eager or #eager_graph
1187
+ # with the keys being associations of the current model and values being
1188
+ # associations of the model associated with the current model via the key.
1189
+ #
1190
+ # The arguments can be symbols or hashes with symbol keys (for cascaded
1191
+ # eager loading). Examples:
1192
+ #
1193
+ # Album.eager(:artist).all
1194
+ # Album.eager_graph(:artist).all
1195
+ # Album.eager(:artist, :genre).all
1196
+ # Album.eager_graph(:artist, :genre).all
1197
+ # Album.eager(:artist).eager(:genre).all
1198
+ # Album.eager_graph(:artist).eager(:genre).all
1199
+ # Artist.eager(:albums=>:tracks).all
1200
+ # Artist.eager_graph(:albums=>:tracks).all
1201
+ # Artist.eager(:albums=>{:tracks=>:genre}).all
1202
+ # Artist.eager_graph(:albums=>{:tracks=>:genre}).all
1203
+ module DatasetMethods
1204
+ # Add the #eager! and #eager_graph! mutation methods to the dataset.
1205
+ def self.extended(obj)
1206
+ obj.def_mutation_method(:eager, :eager_graph)
1207
+ end
1208
+
1209
+ # The preferred eager loading method. Loads all associated records using one
1210
+ # query for each association.
1211
+ #
1212
+ # The basic idea for how it works is that the dataset is first loaded normally.
1213
+ # Then it goes through all associations that have been specified via eager.
1214
+ # It loads each of those associations separately, then associates them back
1215
+ # to the original dataset via primary/foreign keys. Due to the necessity of
1216
+ # all objects being present, you need to use .all to use eager loading, as it
1217
+ # can't work with .each.
1218
+ #
1219
+ # This implementation avoids the complexity of extracting an object graph out
1220
+ # of a single dataset, by building the object graph out of multiple datasets,
1221
+ # one for each association. By using a separate dataset for each association,
1222
+ # it avoids problems such as aliasing conflicts and creating cartesian product
1223
+ # result sets if multiple *_to_many eager associations are requested.
1224
+ #
1225
+ # One limitation of using this method is that you cannot filter the dataset
1226
+ # based on values of columns in an associated table, since the associations are loaded
1227
+ # in separate queries. To do that you need to load all associations in the
1228
+ # same query, and extract an object graph from the results of that query. If you
1229
+ # need to filter based on columns in associated tables, look at #eager_graph
1230
+ # or join the tables you need to filter on manually.
1231
+ #
1232
+ # Each association's order, if defined, is respected. Eager also works
1233
+ # on a limited dataset, but does not use any :limit options for associations.
1234
+ # If the association uses a block or has an :eager_block argument, it is used.
1235
+ def eager(*associations)
1236
+ opt = @opts[:eager]
1237
+ opt = opt ? opt.dup : {}
1238
+ associations.flatten.each do |association|
1239
+ case association
1240
+ when Symbol
1241
+ check_association(model, association)
1242
+ opt[association] = nil
1243
+ when Hash
1244
+ association.keys.each{|assoc| check_association(model, assoc)}
1245
+ opt.merge!(association)
1246
+ else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1247
+ end
1248
+ end
1249
+ clone(:eager=>opt)
1250
+ end
1251
+
1252
+ # The secondary eager loading method. Loads all associations in a single query. This
1253
+ # method should only be used if you need to filter based on columns in associated tables.
1254
+ #
1255
+ # This method builds an object graph using Dataset#graph. Then it uses the graph
1256
+ # to build the associations, and finally replaces the graph with a simple array
1257
+ # of model objects.
1258
+ #
1259
+ # Be very careful when using this with multiple *_to_many associations, as you can
1260
+ # create large cartesian products. If you must graph multiple *_to_many associations,
1261
+ # make sure your filters are specific if you have a large database.
1262
+ #
1263
+ # Each association's order, if definied, is respected. #eager_graph probably
1264
+ # won't work correctly on a limited dataset, unless you are
1265
+ # only graphing many_to_one associations.
1266
+ #
1267
+ # Does not use the block defined for the association, since it does a single query for
1268
+ # all objects. You can use the :graph_* association options to modify the SQL query.
1269
+ #
1270
+ # Like eager, you need to call .all on the dataset for the eager loading to work. If you just
1271
+ # call each, you will get a normal graphed result back (a hash with model object values).
1272
+ def eager_graph(*associations)
1273
+ ds = if @opts[:eager_graph]
1274
+ self
1275
+ else
1276
+ # Each of the following have a symbol key for the table alias, with the following values:
1277
+ # :reciprocals - the reciprocal instance variable to use for this association
1278
+ # :requirements - array of requirements for this association
1279
+ # :alias_association_type_map - the type of association for this association
1280
+ # :alias_association_name_map - the name of the association for this association
1281
+ clone(:eager_graph=>{:requirements=>{}, :master=>alias_symbol(first_source), :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
1282
+ end
1283
+ ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
1284
+ end
1285
+
1286
+ # Do not attempt to split the result set into associations,
1287
+ # just return results as simple objects. This is useful if you
1288
+ # want to use eager_graph as a shortcut to have all of the joins
1289
+ # and aliasing set up, but want to do something else with the dataset.
1290
+ def ungraphed
1291
+ super.clone(:eager_graph=>nil)
1292
+ end
1293
+
1294
+ protected
1295
+
1296
+ # Call graph on the association with the correct arguments,
1297
+ # update the eager_graph data structure, and recurse into
1298
+ # eager_graph_associations if there are any passed in associations
1299
+ # (which would be dependencies of the current association)
1300
+ #
1301
+ # Arguments:
1302
+ # * ds - Current dataset
1303
+ # * model - Current Model
1304
+ # * ta - table_alias used for the parent association
1305
+ # * requirements - an array, used as a stack for requirements
1306
+ # * r - association reflection for the current association
1307
+ # * *associations - any associations dependent on this one
1308
+ def eager_graph_association(ds, model, ta, requirements, r, *associations)
1309
+ klass = r.associated_class
1310
+ assoc_name = r[:name]
1311
+ assoc_table_alias = ds.unused_table_alias(assoc_name)
1312
+ ds = r[:eager_grapher].call(ds, assoc_table_alias, ta)
1313
+ ds = ds.order_more(*qualified_expression(r[:order], assoc_table_alias)) if r[:order] and r[:order_eager_graph]
1314
+ eager_graph = ds.opts[:eager_graph]
1315
+ eager_graph[:requirements][assoc_table_alias] = requirements.dup
1316
+ eager_graph[:alias_association_name_map][assoc_table_alias] = assoc_name
1317
+ eager_graph[:alias_association_type_map][assoc_table_alias] = r.returns_array?
1318
+ eager_graph[:cartesian_product_number] += r[:cartesian_product_number] || 2
1319
+ ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
1320
+ ds
1321
+ end
1322
+
1323
+ # Check the associations are valid for the given model.
1324
+ # Call eager_graph_association on each association.
1325
+ #
1326
+ # Arguments:
1327
+ # * ds - Current dataset
1328
+ # * model - Current Model
1329
+ # * ta - table_alias used for the parent association
1330
+ # * requirements - an array, used as a stack for requirements
1331
+ # * *associations - the associations to add to the graph
1332
+ def eager_graph_associations(ds, model, ta, requirements, *associations)
1333
+ return ds if associations.empty?
1334
+ associations.flatten.each do |association|
1335
+ ds = case association
1336
+ when Symbol
1337
+ ds.eager_graph_association(ds, model, ta, requirements, check_association(model, association))
1338
+ when Hash
1339
+ association.each do |assoc, assoc_assocs|
1340
+ ds = ds.eager_graph_association(ds, model, ta, requirements, check_association(model, assoc), assoc_assocs)
1341
+ end
1342
+ ds
1343
+ else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1344
+ end
1345
+ end
1346
+ ds
1347
+ end
1348
+
1349
+ # Build associations out of the array of returned object graphs.
1350
+ def eager_graph_build_associations(record_graphs)
1351
+ eager_graph = @opts[:eager_graph]
1352
+ master = eager_graph[:master]
1353
+ requirements = eager_graph[:requirements]
1354
+ alias_map = eager_graph[:alias_association_name_map]
1355
+ type_map = eager_graph[:alias_association_type_map]
1356
+ reciprocal_map = eager_graph[:reciprocals]
1357
+
1358
+ # Make dependency map hash out of requirements array for each association.
1359
+ # This builds a tree of dependencies that will be used for recursion
1360
+ # to ensure that all parts of the object graph are loaded into the
1361
+ # appropriate subordinate association.
1362
+ dependency_map = {}
1363
+ # Sort the associations by requirements length, so that
1364
+ # requirements are added to the dependency hash before their
1365
+ # dependencies.
1366
+ requirements.sort_by{|a| a[1].length}.each do |ta, deps|
1367
+ if deps.empty?
1368
+ dependency_map[ta] = {}
1369
+ else
1370
+ deps = deps.dup
1371
+ hash = dependency_map[deps.shift]
1372
+ deps.each do |dep|
1373
+ hash = hash[dep]
1374
+ end
1375
+ hash[ta] = {}
1376
+ end
1377
+ end
1378
+
1379
+ # This mapping is used to make sure that duplicate entries in the
1380
+ # result set are mapped to a single record. For example, using a
1381
+ # single one_to_many association with 10 associated records,
1382
+ # the main object will appear in the object graph 10 times.
1383
+ # We map by primary key, if available, or by the object's entire values,
1384
+ # if not. The mapping must be per table, so create sub maps for each table
1385
+ # alias.
1386
+ records_map = {master=>{}}
1387
+ alias_map.keys.each{|ta| records_map[ta] = {}}
1388
+
1389
+ # This will hold the final record set that we will be replacing the object graph with.
1390
+ records = []
1391
+ record_graphs.each do |record_graph|
1392
+ primary_record = record_graph[master]
1393
+ key = primary_record.pk_or_nil || primary_record.values.sort_by{|x| x[0].to_s}
1394
+ if cached_pr = records_map[master][key]
1395
+ primary_record = cached_pr
1396
+ else
1397
+ records_map[master][key] = primary_record
1398
+ # Only add it to the list of records to return if it is a new record
1399
+ records.push(primary_record)
1400
+ end
1401
+ # Build all associations for the current object and it's dependencies
1402
+ eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, primary_record, record_graph)
1403
+ end
1404
+
1405
+ # Remove duplicate records from all associations if this graph could possibly be a cartesian product
1406
+ eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map) if eager_graph[:cartesian_product_number] > 1
1407
+
1408
+ # Replace the array of object graphs with an array of model objects
1409
+ record_graphs.replace(records)
1410
+ end
1411
+
1412
+ private
1413
+
1414
+ # Make sure the association is valid for this model, and return the related AssociationReflection.
1415
+ def check_association(model, association)
1416
+ raise(Sequel::Error, "Invalid association #{association} for #{model.name}") unless reflection = model.association_reflection(association)
1417
+ raise(Sequel::Error, "Eager loading is not allowed for #{model.name} association #{association}") if reflection[:allow_eager] == false
1418
+ reflection
1419
+ end
1420
+
1421
+ # Build associations for the current object. This is called recursively
1422
+ # to build all dependencies.
1423
+ def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
1424
+ return if dependency_map.empty?
1425
+ # Don't clobber the instance variable array for *_to_many associations if it has already been setup
1426
+ dependency_map.keys.each do |ta|
1427
+ assoc_name = alias_map[ta]
1428
+ current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
1429
+ end
1430
+ dependency_map.each do |ta, deps|
1431
+ next unless rec = record_graph[ta]
1432
+ key = rec.pk_or_nil || rec.values.sort_by{|x| x[0].to_s}
1433
+ if cached_rec = records_map[ta][key]
1434
+ rec = cached_rec
1435
+ else
1436
+ records_map[ta][key] = rec
1437
+ end
1438
+ assoc_name = alias_map[ta]
1439
+ if type_map[ta]
1440
+ current.associations[assoc_name].push(rec)
1441
+ if reciprocal = reciprocal_map[ta]
1442
+ rec.associations[reciprocal] = current
1443
+ end
1444
+ else
1445
+ current.associations[assoc_name] = rec
1446
+ end
1447
+ # Recurse into dependencies of the current object
1448
+ eager_graph_build_associations_graph(deps, alias_map, type_map, reciprocal_map, records_map, rec, record_graph)
1449
+ end
1450
+ end
1451
+
1452
+ # If the result set is the result of a cartesian product, then it is possible that
1453
+ # there are multiple records for each association when there should only be one.
1454
+ # In that case, for each object in all associations loaded via #eager_graph, run
1455
+ # uniq! on the association to make sure no duplicate records show up.
1456
+ # Note that this can cause legitimate duplicate records to be removed.
1457
+ def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
1458
+ records.each do |record|
1459
+ dependency_map.each do |ta, deps|
1460
+ list = record.send(alias_map[ta])
1461
+ list = if type_map[ta]
1462
+ list.uniq!
1463
+ else
1464
+ [list] if list
1465
+ end
1466
+ # Recurse into dependencies
1467
+ eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
1468
+ end
1469
+ end
1470
+ end
1471
+
1472
+ # Eagerly load all specified associations
1473
+ def eager_load(a, eager_assoc=@opts[:eager])
1474
+ return if a.empty?
1475
+ # Key is foreign/primary key name symbol
1476
+ # Value is hash with keys being foreign/primary key values (generally integers)
1477
+ # and values being an array of current model objects with that
1478
+ # specific foreign/primary key
1479
+ key_hash = {}
1480
+ # Reflections for all associations to eager load
1481
+ reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc)}
1482
+
1483
+ # Populate keys to monitor
1484
+ reflections.each{|reflection| key_hash[reflection.eager_loader_key] ||= Hash.new{|h,k| h[k] = []}}
1485
+
1486
+ # Associate each object with every key being monitored
1487
+ a.each do |rec|
1488
+ key_hash.each do |key, id_map|
1489
+ case key
1490
+ when Array
1491
+ id_map[key.map{|k| rec[k]}] << rec if key.all?{|k| rec[k]}
1492
+ when Symbol
1493
+ id_map[rec[key]] << rec if rec[key]
1494
+ end
1495
+ end
1496
+ end
1497
+
1498
+ reflections.each do |r|
1499
+ r[:eager_loader].call(key_hash, a, eager_assoc[r[:name]])
1500
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
1501
+ end
1502
+ end
1503
+
1504
+ # Build associations from the graph if #eager_graph was used,
1505
+ # and/or load other associations if #eager was used.
1506
+ def post_load(all_records)
1507
+ eager_graph_build_associations(all_records) if @opts[:eager_graph]
1508
+ eager_load(all_records) if @opts[:eager]
1509
+ super
1510
+ end
1511
+ end
1512
+ end
1513
+ end
1514
+ end