activerecord 4.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,36 @@
1
+ module ActiveRecord
2
+ # = Active Record Has One Through Association
3
+ module Associations
4
+ class HasOneThroughAssociation < HasOneAssociation #:nodoc:
5
+ include ThroughAssociation
6
+
7
+ def replace(record)
8
+ create_through_record(record)
9
+ self.target = record
10
+ end
11
+
12
+ private
13
+
14
+ def create_through_record(record)
15
+ ensure_not_nested
16
+
17
+ through_proxy = owner.association(through_reflection.name)
18
+ through_record = through_proxy.send(:load_target)
19
+
20
+ if through_record && !record
21
+ through_record.destroy
22
+ elsif record
23
+ attributes = construct_join_attributes(record)
24
+
25
+ if through_record
26
+ through_record.update(attributes)
27
+ elsif owner.new_record?
28
+ through_proxy.build(attributes)
29
+ else
30
+ through_proxy.create(attributes)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,282 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
5
+ autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
6
+
7
+ class Aliases # :nodoc:
8
+ def initialize(tables)
9
+ @tables = tables
10
+ @alias_cache = tables.each_with_object({}) { |table,h|
11
+ h[table.node] = table.columns.each_with_object({}) { |column,i|
12
+ i[column.name] = column.alias
13
+ }
14
+ }
15
+ @name_and_alias_cache = tables.each_with_object({}) { |table,h|
16
+ h[table.node] = table.columns.map { |column|
17
+ [column.name, column.alias]
18
+ }
19
+ }
20
+ end
21
+
22
+ def columns
23
+ @tables.flat_map { |t| t.column_aliases }
24
+ end
25
+
26
+ # An array of [column_name, alias] pairs for the table
27
+ def column_aliases(node)
28
+ @name_and_alias_cache[node]
29
+ end
30
+
31
+ def column_alias(node, column)
32
+ @alias_cache[node][column]
33
+ end
34
+
35
+ class Table < Struct.new(:node, :columns)
36
+ def table
37
+ Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
38
+ end
39
+
40
+ def column_aliases
41
+ t = table
42
+ columns.map { |column| t[column.name].as Arel.sql column.alias }
43
+ end
44
+ end
45
+ Column = Struct.new(:name, :alias)
46
+ end
47
+
48
+ attr_reader :alias_tracker, :base_klass, :join_root
49
+
50
+ def self.make_tree(associations)
51
+ hash = {}
52
+ walk_tree associations, hash
53
+ hash
54
+ end
55
+
56
+ def self.walk_tree(associations, hash)
57
+ case associations
58
+ when Symbol, String
59
+ hash[associations.to_sym] ||= {}
60
+ when Array
61
+ associations.each do |assoc|
62
+ walk_tree assoc, hash
63
+ end
64
+ when Hash
65
+ associations.each do |k,v|
66
+ cache = hash[k] ||= {}
67
+ walk_tree v, cache
68
+ end
69
+ else
70
+ raise ConfigurationError, associations.inspect
71
+ end
72
+ end
73
+
74
+ # base is the base class on which operation is taking place.
75
+ # associations is the list of associations which are joined using hash, symbol or array.
76
+ # joins is the list of all string join commands and arel nodes.
77
+ #
78
+ # Example :
79
+ #
80
+ # class Physician < ActiveRecord::Base
81
+ # has_many :appointments
82
+ # has_many :patients, through: :appointments
83
+ # end
84
+ #
85
+ # If I execute `@physician.patients.to_a` then
86
+ # base # => Physician
87
+ # associations # => []
88
+ # joins # => [#<Arel::Nodes::InnerJoin: ...]
89
+ #
90
+ # However if I execute `Physician.joins(:appointments).to_a` then
91
+ # base # => Physician
92
+ # associations # => [:appointments]
93
+ # joins # => []
94
+ #
95
+ def initialize(base, associations, joins)
96
+ @alias_tracker = AliasTracker.create(base.connection, joins)
97
+ @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
98
+ tree = self.class.make_tree associations
99
+ @join_root = JoinBase.new base, build(tree, base)
100
+ @join_root.children.each { |child| construct_tables! @join_root, child }
101
+ end
102
+
103
+ def reflections
104
+ join_root.drop(1).map!(&:reflection)
105
+ end
106
+
107
+ def join_constraints(outer_joins)
108
+ joins = join_root.children.flat_map { |child|
109
+ make_inner_joins join_root, child
110
+ }
111
+
112
+ joins.concat outer_joins.flat_map { |oj|
113
+ if join_root.match? oj.join_root
114
+ walk join_root, oj.join_root
115
+ else
116
+ oj.join_root.children.flat_map { |child|
117
+ make_outer_joins oj.join_root, child
118
+ }
119
+ end
120
+ }
121
+ end
122
+
123
+ def aliases
124
+ Aliases.new join_root.each_with_index.map { |join_part,i|
125
+ columns = join_part.column_names.each_with_index.map { |column_name,j|
126
+ Aliases::Column.new column_name, "t#{i}_r#{j}"
127
+ }
128
+ Aliases::Table.new(join_part, columns)
129
+ }
130
+ end
131
+
132
+ def instantiate(result_set, aliases)
133
+ primary_key = aliases.column_alias(join_root, join_root.primary_key)
134
+
135
+ seen = Hash.new { |h,parent_klass|
136
+ h[parent_klass] = Hash.new { |i,parent_id|
137
+ i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
138
+ }
139
+ }
140
+
141
+ model_cache = Hash.new { |h,klass| h[klass] = {} }
142
+ parents = model_cache[join_root]
143
+ column_aliases = aliases.column_aliases join_root
144
+
145
+ message_bus = ActiveSupport::Notifications.instrumenter
146
+
147
+ payload = {
148
+ record_count: result_set.length,
149
+ class_name: join_root.base_klass.name
150
+ }
151
+
152
+ message_bus.instrument('instantiation.active_record', payload) do
153
+ result_set.each { |row_hash|
154
+ parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
155
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
156
+ }
157
+ end
158
+
159
+ parents.values
160
+ end
161
+
162
+ private
163
+
164
+ def make_constraints(parent, child, tables, join_type)
165
+ chain = child.reflection.chain
166
+ foreign_table = parent.table
167
+ foreign_klass = parent.base_klass
168
+ child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
169
+ end
170
+
171
+ def make_outer_joins(parent, child)
172
+ tables = table_aliases_for(parent, child)
173
+ join_type = Arel::Nodes::OuterJoin
174
+ info = make_constraints parent, child, tables, join_type
175
+
176
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
177
+ end
178
+
179
+ def make_inner_joins(parent, child)
180
+ tables = child.tables
181
+ join_type = Arel::Nodes::InnerJoin
182
+ info = make_constraints parent, child, tables, join_type
183
+
184
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
185
+ end
186
+
187
+ def table_aliases_for(parent, node)
188
+ node.reflection.chain.map { |reflection|
189
+ alias_tracker.aliased_table_for(
190
+ reflection.table_name,
191
+ table_alias_for(reflection, parent, reflection != node.reflection)
192
+ )
193
+ }
194
+ end
195
+
196
+ def construct_tables!(parent, node)
197
+ node.tables = table_aliases_for(parent, node)
198
+ node.children.each { |child| construct_tables! node, child }
199
+ end
200
+
201
+ def table_alias_for(reflection, parent, join)
202
+ name = "#{reflection.plural_name}_#{parent.table_name}"
203
+ name << "_join" if join
204
+ name
205
+ end
206
+
207
+ def walk(left, right)
208
+ intersection, missing = right.children.map { |node1|
209
+ [left.children.find { |node2| node1.match? node2 }, node1]
210
+ }.partition(&:first)
211
+
212
+ ojs = missing.flat_map { |_,n| make_outer_joins left, n }
213
+ intersection.flat_map { |l,r| walk l, r }.concat ojs
214
+ end
215
+
216
+ def find_reflection(klass, name)
217
+ klass._reflect_on_association(name) or
218
+ raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
219
+ end
220
+
221
+ def build(associations, base_klass)
222
+ associations.map do |name, right|
223
+ reflection = find_reflection base_klass, name
224
+ reflection.check_validity!
225
+ reflection.check_eager_loadable!
226
+
227
+ if reflection.polymorphic?
228
+ raise EagerLoadPolymorphicError.new(reflection)
229
+ end
230
+
231
+ JoinAssociation.new reflection, build(right, reflection.klass)
232
+ end
233
+ end
234
+
235
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
236
+ primary_id = ar_parent.id
237
+
238
+ parent.children.each do |node|
239
+ if node.reflection.collection?
240
+ other = ar_parent.association(node.reflection.name)
241
+ other.loaded!
242
+ else
243
+ if ar_parent.association_cache.key?(node.reflection.name)
244
+ model = ar_parent.association(node.reflection.name).target
245
+ construct(model, node, row, rs, seen, model_cache, aliases)
246
+ next
247
+ end
248
+ end
249
+
250
+ key = aliases.column_alias(node, node.primary_key)
251
+ id = row[key]
252
+ next if id.nil?
253
+
254
+ model = seen[parent.base_klass][primary_id][node.base_klass][id]
255
+
256
+ if model
257
+ construct(model, node, row, rs, seen, model_cache, aliases)
258
+ else
259
+ model = construct_model(ar_parent, node, row, model_cache, id, aliases)
260
+ seen[parent.base_klass][primary_id][node.base_klass][id] = model
261
+ construct(model, node, row, rs, seen, model_cache, aliases)
262
+ end
263
+ end
264
+ end
265
+
266
+ def construct_model(record, node, row, model_cache, id, aliases)
267
+ model = model_cache[node][id] ||= node.instantiate(row,
268
+ aliases.column_aliases(node))
269
+ other = record.association(node.reflection.name)
270
+
271
+ if node.reflection.collection?
272
+ other.target.push(model)
273
+ else
274
+ other.target = model
275
+ end
276
+
277
+ other.set_inverse_instance(model)
278
+ model
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,122 @@
1
+ require 'active_record/associations/join_dependency/join_part'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class JoinDependency # :nodoc:
6
+ class JoinAssociation < JoinPart # :nodoc:
7
+ # The reflection of the association represented
8
+ attr_reader :reflection
9
+
10
+ attr_accessor :tables
11
+
12
+ def initialize(reflection, children)
13
+ super(reflection.klass, children)
14
+
15
+ @reflection = reflection
16
+ @tables = nil
17
+ end
18
+
19
+ def match?(other)
20
+ return true if self == other
21
+ super && reflection == other.reflection
22
+ end
23
+
24
+ JoinInformation = Struct.new :joins, :binds
25
+
26
+ def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
27
+ joins = []
28
+ bind_values = []
29
+ tables = tables.reverse
30
+
31
+ scope_chain_index = 0
32
+ scope_chain = scope_chain.reverse
33
+
34
+ # The chain starts with the target table, but we want to end with it here (makes
35
+ # more sense in this context), so we reverse
36
+ chain.reverse_each do |reflection|
37
+ table = tables.shift
38
+ klass = reflection.klass
39
+
40
+ join_keys = reflection.join_keys(klass)
41
+ key = join_keys.key
42
+ foreign_key = join_keys.foreign_key
43
+
44
+ constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
45
+
46
+ scope_chain_items = scope_chain[scope_chain_index].map do |item|
47
+ if item.is_a?(Relation)
48
+ item
49
+ else
50
+ ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
51
+ end
52
+ end
53
+ scope_chain_index += 1
54
+
55
+ scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
56
+
57
+ rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
58
+ left.merge right
59
+ end
60
+
61
+ if rel && !rel.arel.constraints.empty?
62
+ bind_values.concat rel.bind_values
63
+ constraint = constraint.and rel.arel.constraints
64
+ end
65
+
66
+ if reflection.type
67
+ value = foreign_klass.base_class.name
68
+ column = klass.columns_hash[reflection.type.to_s]
69
+
70
+ substitute = klass.connection.substitute_at(column)
71
+ bind_values.push [column, value]
72
+ constraint = constraint.and table[reflection.type].eq substitute
73
+ end
74
+
75
+ joins << table.create_join(table, table.create_on(constraint), join_type)
76
+
77
+ # The current table in this iteration becomes the foreign table in the next
78
+ foreign_table, foreign_klass = table, klass
79
+ end
80
+
81
+ JoinInformation.new joins, bind_values
82
+ end
83
+
84
+ # Builds equality condition.
85
+ #
86
+ # Example:
87
+ #
88
+ # class Physician < ActiveRecord::Base
89
+ # has_many :appointments
90
+ # end
91
+ #
92
+ # If I execute `Physician.joins(:appointments).to_a` then
93
+ # klass # => Physician
94
+ # table # => #<Arel::Table @name="appointments" ...>
95
+ # key # => physician_id
96
+ # foreign_table # => #<Arel::Table @name="physicians" ...>
97
+ # foreign_key # => id
98
+ #
99
+ def build_constraint(klass, table, key, foreign_table, foreign_key)
100
+ constraint = table[key].eq(foreign_table[foreign_key])
101
+
102
+ if klass.finder_needs_type_condition?
103
+ constraint = table.create_and([
104
+ constraint,
105
+ klass.send(:type_condition, table)
106
+ ])
107
+ end
108
+
109
+ constraint
110
+ end
111
+
112
+ def table
113
+ tables.first
114
+ end
115
+
116
+ def aliased_table_name
117
+ table.table_alias || table.name
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,22 @@
1
+ require 'active_record/associations/join_dependency/join_part'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class JoinDependency # :nodoc:
6
+ class JoinBase < JoinPart # :nodoc:
7
+ def match?(other)
8
+ return true if self == other
9
+ super && base_klass == other.base_klass
10
+ end
11
+
12
+ def table
13
+ base_klass.arel_table
14
+ end
15
+
16
+ def aliased_table_name
17
+ base_klass.table_name
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end