activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Has One Through Association
6
+ class HasOneThroughAssociation < HasOneAssociation #:nodoc:
7
+ include ThroughAssociation
8
+
9
+ def replace(record, save = true)
10
+ create_through_record(record, save)
11
+ self.target = record
12
+ end
13
+
14
+ private
15
+ def create_through_record(record, save)
16
+ ensure_not_nested
17
+
18
+ through_proxy = through_association
19
+ through_record = through_proxy.load_target
20
+
21
+ if through_record && !record
22
+ through_record.destroy
23
+ elsif record
24
+ attributes = construct_join_attributes(record)
25
+
26
+ if through_record && through_record.destroyed?
27
+ through_record = through_proxy.tap(&:reload).target
28
+ end
29
+
30
+ if through_record
31
+ if through_record.new_record?
32
+ through_record.assign_attributes(attributes)
33
+ else
34
+ through_record.update(attributes)
35
+ end
36
+ elsif owner.new_record? || !save
37
+ through_proxy.build(attributes)
38
+ else
39
+ through_proxy.create(attributes)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class JoinDependency # :nodoc:
6
+ autoload :JoinBase, "active_record/associations/join_dependency/join_base"
7
+ autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
8
+
9
+ class Aliases # :nodoc:
10
+ def initialize(tables)
11
+ @tables = tables
12
+ @alias_cache = tables.each_with_object({}) { |table, h|
13
+ h[table.node] = table.columns.each_with_object({}) { |column, i|
14
+ i[column.name] = column.alias
15
+ }
16
+ }
17
+ @name_and_alias_cache = tables.each_with_object({}) { |table, h|
18
+ h[table.node] = table.columns.map { |column|
19
+ [column.name, column.alias]
20
+ }
21
+ }
22
+ end
23
+
24
+ def columns
25
+ @tables.flat_map(&:column_aliases)
26
+ end
27
+
28
+ # An array of [column_name, alias] pairs for the table
29
+ def column_aliases(node)
30
+ @name_and_alias_cache[node]
31
+ end
32
+
33
+ def column_alias(node, column)
34
+ @alias_cache[node][column]
35
+ end
36
+
37
+ Table = Struct.new(:node, :columns) do # :nodoc:
38
+ def column_aliases
39
+ t = node.table
40
+ columns.map { |column| t[column.name].as Arel.sql column.alias }
41
+ end
42
+ end
43
+ Column = Struct.new(:name, :alias)
44
+ end
45
+
46
+ def self.make_tree(associations)
47
+ hash = {}
48
+ walk_tree associations, hash
49
+ hash
50
+ end
51
+
52
+ def self.walk_tree(associations, hash)
53
+ case associations
54
+ when Symbol, String
55
+ hash[associations.to_sym] ||= {}
56
+ when Array
57
+ associations.each do |assoc|
58
+ walk_tree assoc, hash
59
+ end
60
+ when Hash
61
+ associations.each do |k, v|
62
+ cache = hash[k] ||= {}
63
+ walk_tree v, cache
64
+ end
65
+ else
66
+ raise ConfigurationError, associations.inspect
67
+ end
68
+ end
69
+
70
+ def initialize(base, table, associations)
71
+ tree = self.class.make_tree associations
72
+ @join_root = JoinBase.new(base, table, build(tree, base))
73
+ end
74
+
75
+ def reflections
76
+ join_root.drop(1).map!(&:reflection)
77
+ end
78
+
79
+ def join_constraints(joins_to_add, join_type, alias_tracker)
80
+ @alias_tracker = alias_tracker
81
+
82
+ construct_tables!(join_root)
83
+ joins = make_join_constraints(join_root, join_type)
84
+
85
+ joins.concat joins_to_add.flat_map { |oj|
86
+ construct_tables!(oj.join_root)
87
+ if join_root.match? oj.join_root
88
+ walk join_root, oj.join_root
89
+ else
90
+ make_join_constraints(oj.join_root, join_type)
91
+ end
92
+ }
93
+ end
94
+
95
+ def instantiate(result_set, &block)
96
+ primary_key = aliases.column_alias(join_root, join_root.primary_key)
97
+
98
+ seen = Hash.new { |i, object_id|
99
+ i[object_id] = Hash.new { |j, child_class|
100
+ j[child_class] = {}
101
+ }
102
+ }
103
+
104
+ model_cache = Hash.new { |h, klass| h[klass] = {} }
105
+ parents = model_cache[join_root]
106
+ column_aliases = aliases.column_aliases join_root
107
+
108
+ message_bus = ActiveSupport::Notifications.instrumenter
109
+
110
+ payload = {
111
+ record_count: result_set.length,
112
+ class_name: join_root.base_klass.name
113
+ }
114
+
115
+ message_bus.instrument("instantiation.active_record", payload) do
116
+ result_set.each { |row_hash|
117
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
118
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
119
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
120
+ }
121
+ end
122
+
123
+ parents.values
124
+ end
125
+
126
+ def apply_column_aliases(relation)
127
+ relation._select!(-> { aliases.columns })
128
+ end
129
+
130
+ protected
131
+ attr_reader :alias_tracker, :join_root
132
+
133
+ private
134
+ def aliases
135
+ @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
136
+ columns = join_part.column_names.each_with_index.map { |column_name, j|
137
+ Aliases::Column.new column_name, "t#{i}_r#{j}"
138
+ }
139
+ Aliases::Table.new(join_part, columns)
140
+ }
141
+ end
142
+
143
+ def construct_tables!(join_root)
144
+ join_root.each_children do |parent, child|
145
+ child.tables = table_aliases_for(parent, child)
146
+ end
147
+ end
148
+
149
+ def make_join_constraints(join_root, join_type)
150
+ join_root.children.flat_map do |child|
151
+ make_constraints(join_root, child, join_type)
152
+ end
153
+ end
154
+
155
+ def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
156
+ foreign_table = parent.table
157
+ foreign_klass = parent.base_klass
158
+ joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
159
+ joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
160
+ end
161
+
162
+ def table_aliases_for(parent, node)
163
+ node.reflection.chain.map { |reflection|
164
+ alias_tracker.aliased_table_for(
165
+ reflection.table_name,
166
+ table_alias_for(reflection, parent, reflection != node.reflection),
167
+ reflection.klass.type_caster
168
+ )
169
+ }
170
+ end
171
+
172
+ def table_alias_for(reflection, parent, join)
173
+ name = "#{reflection.plural_name}_#{parent.table_name}"
174
+ join ? "#{name}_join" : name
175
+ end
176
+
177
+ def walk(left, right)
178
+ intersection, missing = right.children.map { |node1|
179
+ [left.children.find { |node2| node1.match? node2 }, node1]
180
+ }.partition(&:first)
181
+
182
+ joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
183
+ joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
184
+ end
185
+
186
+ def find_reflection(klass, name)
187
+ klass._reflect_on_association(name) ||
188
+ raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?")
189
+ end
190
+
191
+ def build(associations, base_klass)
192
+ associations.map do |name, right|
193
+ reflection = find_reflection base_klass, name
194
+ reflection.check_validity!
195
+ reflection.check_eager_loadable!
196
+
197
+ if reflection.polymorphic?
198
+ raise EagerLoadPolymorphicError.new(reflection)
199
+ end
200
+
201
+ JoinAssociation.new(reflection, build(right, reflection.klass))
202
+ end
203
+ end
204
+
205
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
206
+ return if ar_parent.nil?
207
+
208
+ parent.children.each do |node|
209
+ if node.reflection.collection?
210
+ other = ar_parent.association(node.reflection.name)
211
+ other.loaded!
212
+ elsif ar_parent.association_cached?(node.reflection.name)
213
+ model = ar_parent.association(node.reflection.name).target
214
+ construct(model, node, row, rs, seen, model_cache, aliases)
215
+ next
216
+ end
217
+
218
+ key = aliases.column_alias(node, node.primary_key)
219
+ id = row[key]
220
+ if id.nil?
221
+ nil_association = ar_parent.association(node.reflection.name)
222
+ nil_association.loaded!
223
+ next
224
+ end
225
+
226
+ model = seen[ar_parent.object_id][node][id]
227
+
228
+ if model
229
+ construct(model, node, row, rs, seen, model_cache, aliases)
230
+ else
231
+ model = construct_model(ar_parent, node, row, model_cache, id, aliases)
232
+
233
+ if node.reflection.scope &&
234
+ node.reflection.scope_for(node.base_klass.unscoped).readonly_value
235
+ model.readonly!
236
+ end
237
+
238
+ seen[ar_parent.object_id][node][id] = model
239
+ construct(model, node, row, rs, seen, model_cache, aliases)
240
+ end
241
+ end
242
+ end
243
+
244
+ def construct_model(record, node, row, model_cache, id, aliases)
245
+ other = record.association(node.reflection.name)
246
+
247
+ model = model_cache[node][id] ||=
248
+ node.instantiate(row, aliases.column_aliases(node)) do |m|
249
+ other.set_inverse_instance(m)
250
+ end
251
+
252
+ if node.reflection.collection?
253
+ other.target.push(model)
254
+ else
255
+ other.target = model
256
+ end
257
+
258
+ model
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/associations/join_dependency/join_part"
4
+
5
+ module ActiveRecord
6
+ module Associations
7
+ class JoinDependency # :nodoc:
8
+ class JoinAssociation < JoinPart # :nodoc:
9
+ attr_reader :reflection, :tables
10
+ attr_accessor :table
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
+ def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
25
+ joins = []
26
+
27
+ # The chain starts with the target table, but we want to end with it here (makes
28
+ # more sense in this context), so we reverse
29
+ reflection.chain.reverse_each.with_index(1) do |reflection, i|
30
+ table = tables[-i]
31
+ klass = reflection.klass
32
+
33
+ constraint = reflection.build_join_constraint(table, foreign_table)
34
+
35
+ joins << table.create_join(table, table.create_on(constraint), join_type)
36
+
37
+ join_scope = reflection.join_scope(table, foreign_klass)
38
+ arel = join_scope.arel(alias_tracker.aliases)
39
+
40
+ if arel.constraints.any?
41
+ joins.concat arel.join_sources
42
+ right = joins.last.right
43
+ right.expr = right.expr.and(arel.constraints)
44
+ end
45
+
46
+ # The current table in this iteration becomes the foreign table in the next
47
+ foreign_table, foreign_klass = table, klass
48
+ end
49
+
50
+ joins
51
+ end
52
+
53
+ def tables=(tables)
54
+ @tables = tables
55
+ @table = tables.first
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/associations/join_dependency/join_part"
4
+
5
+ module ActiveRecord
6
+ module Associations
7
+ class JoinDependency # :nodoc:
8
+ class JoinBase < JoinPart # :nodoc:
9
+ attr_reader :table
10
+
11
+ def initialize(base_klass, table, children)
12
+ super(base_klass, children)
13
+ @table = table
14
+ end
15
+
16
+ def match?(other)
17
+ return true if self == other
18
+ super && base_klass == other.base_klass
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class JoinDependency # :nodoc:
6
+ # A JoinPart represents a part of a JoinDependency. It is inherited
7
+ # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
8
+ # everything else is being joined onto. A JoinAssociation represents an association which
9
+ # is joining to the base. A JoinAssociation may result in more than one actual join
10
+ # operations (for example a has_and_belongs_to_many JoinAssociation would result in
11
+ # two; one for the join table and one for the target table).
12
+ class JoinPart # :nodoc:
13
+ include Enumerable
14
+
15
+ # The Active Record class which this join part is associated 'about'; for a JoinBase
16
+ # this is the actual base model, for a JoinAssociation this is the target model of the
17
+ # association.
18
+ attr_reader :base_klass, :children
19
+
20
+ delegate :table_name, :column_names, :primary_key, to: :base_klass
21
+
22
+ def initialize(base_klass, children)
23
+ @base_klass = base_klass
24
+ @children = children
25
+ end
26
+
27
+ def match?(other)
28
+ self.class == other.class
29
+ end
30
+
31
+ def each(&block)
32
+ yield self
33
+ children.each { |child| child.each(&block) }
34
+ end
35
+
36
+ def each_children(&block)
37
+ children.each do |child|
38
+ yield self, child
39
+ child.each_children(&block)
40
+ end
41
+ end
42
+
43
+ # An Arel::Table for the active_record
44
+ def table
45
+ raise NotImplementedError
46
+ end
47
+
48
+ def extract_record(row, column_names_with_alias)
49
+ # This code is performance critical as it is called per row.
50
+ # see: https://github.com/rails/rails/pull/12185
51
+ hash = {}
52
+
53
+ index = 0
54
+ length = column_names_with_alias.length
55
+
56
+ while index < length
57
+ column_name, alias_name = column_names_with_alias[index]
58
+ hash[column_name] = row[alias_name]
59
+ index += 1
60
+ end
61
+
62
+ hash
63
+ end
64
+
65
+ def instantiate(row, aliases, &block)
66
+ base_klass.instantiate(extract_record(row, aliases), &block)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end