activerecord 4.2.0 → 5.2.8.1

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 (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +640 -928
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -324
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +136 -90
  133. data/lib/active_record/errors.rb +180 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +117 -35
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -339
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +208 -123
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +30 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,19 +1,19 @@
1
- require 'active_record/associations/join_dependency/join_part'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/associations/join_dependency/join_part"
2
4
 
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  class JoinDependency # :nodoc:
6
8
  class JoinAssociation < JoinPart # :nodoc:
7
- # The reflection of the association represented
8
- attr_reader :reflection
9
-
10
- attr_accessor :tables
9
+ attr_reader :reflection, :tables
10
+ attr_accessor :table
11
11
 
12
12
  def initialize(reflection, children)
13
13
  super(reflection.klass, children)
14
14
 
15
- @reflection = reflection
16
- @tables = nil
15
+ @reflection = reflection
16
+ @tables = nil
17
17
  end
18
18
 
19
19
  def match?(other)
@@ -21,101 +21,60 @@ module ActiveRecord
21
21
  super && reflection == other.reflection
22
22
  end
23
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
24
+ def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
25
+ joins = []
33
26
 
34
27
  # The chain starts with the target table, but we want to end with it here (makes
35
28
  # more sense in this context), so we reverse
36
- chain.reverse_each do |reflection|
37
- table = tables.shift
29
+ reflection.chain.reverse_each.with_index(1) do |reflection, i|
30
+ table = tables[-i]
38
31
  klass = reflection.klass
39
32
 
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)
33
+ join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
45
34
 
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
35
+ arel = join_scope.arel(alias_tracker.aliases)
36
+ nodes = arel.constraints.first
60
37
 
61
- if rel && !rel.arel.constraints.empty?
62
- bind_values.concat rel.bind_values
63
- constraint = constraint.and rel.arel.constraints
38
+ others, children = nodes.children.partition do |node|
39
+ !fetch_arel_attribute(node) { |attr| attr.relation.name == table.name }
64
40
  end
41
+ nodes = table.create_and(children)
65
42
 
66
- if reflection.type
67
- value = foreign_klass.base_class.name
68
- column = klass.columns_hash[reflection.type.to_s]
43
+ joins << table.create_join(table, table.create_on(nodes), join_type)
69
44
 
70
- substitute = klass.connection.substitute_at(column)
71
- bind_values.push [column, value]
72
- constraint = constraint.and table[reflection.type].eq substitute
45
+ unless others.empty?
46
+ joins.concat arel.join_sources
47
+ append_constraints(joins.last, others)
73
48
  end
74
49
 
75
- joins << table.create_join(table, table.create_on(constraint), join_type)
76
-
77
50
  # The current table in this iteration becomes the foreign table in the next
78
51
  foreign_table, foreign_klass = table, klass
79
52
  end
80
53
 
81
- JoinInformation.new joins, bind_values
54
+ joins
82
55
  end
83
56
 
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
57
+ def tables=(tables)
58
+ @tables = tables
59
+ @table = tables.first
110
60
  end
111
61
 
112
- def table
113
- tables.first
114
- end
62
+ private
63
+ def fetch_arel_attribute(value)
64
+ case value
65
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
66
+ yield value.left.is_a?(Arel::Attributes::Attribute) ? value.left : value.right
67
+ end
68
+ end
115
69
 
116
- def aliased_table_name
117
- table.table_alias || table.name
118
- end
70
+ def append_constraints(join, constraints)
71
+ if join.is_a?(Arel::Nodes::StringJoin)
72
+ join_string = table.create_and(constraints.unshift(join.left))
73
+ join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
74
+ else
75
+ join.right.expr.children.concat(constraints)
76
+ end
77
+ end
119
78
  end
120
79
  end
121
80
  end
@@ -1,20 +1,21 @@
1
- require 'active_record/associations/join_dependency/join_part'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/associations/join_dependency/join_part"
2
4
 
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  class JoinDependency # :nodoc:
6
8
  class JoinBase < JoinPart # :nodoc:
7
- def match?(other)
8
- return true if self == other
9
- super && base_klass == other.base_klass
10
- end
9
+ attr_reader :table
11
10
 
12
- def table
13
- base_klass.arel_table
11
+ def initialize(base_klass, table, children)
12
+ super(base_klass, children)
13
+ @table = table
14
14
  end
15
15
 
16
- def aliased_table_name
17
- base_klass.table_name
16
+ def match?(other)
17
+ return true if self == other
18
+ super && base_klass == other.base_klass
18
19
  end
19
20
  end
20
21
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class JoinDependency # :nodoc:
@@ -15,17 +17,13 @@ module ActiveRecord
15
17
  # association.
16
18
  attr_reader :base_klass, :children
17
19
 
18
- delegate :table_name, :column_names, :primary_key, :to => :base_klass
20
+ delegate :table_name, :column_names, :primary_key, to: :base_klass
19
21
 
20
22
  def initialize(base_klass, children)
21
23
  @base_klass = base_klass
22
24
  @children = children
23
25
  end
24
26
 
25
- def name
26
- reflection.name
27
- end
28
-
29
27
  def match?(other)
30
28
  self.class == other.class
31
29
  end
@@ -35,13 +33,15 @@ module ActiveRecord
35
33
  children.each { |child| child.each(&block) }
36
34
  end
37
35
 
38
- # An Arel::Table for the active_record
39
- def table
40
- raise NotImplementedError
36
+ def each_children(&block)
37
+ children.each do |child|
38
+ yield self, child
39
+ child.each_children(&block)
40
+ end
41
41
  end
42
42
 
43
- # The alias for the active_record's table
44
- def aliased_table_name
43
+ # An Arel::Table for the active_record
44
+ def table
45
45
  raise NotImplementedError
46
46
  end
47
47
 
@@ -62,8 +62,8 @@ module ActiveRecord
62
62
  hash
63
63
  end
64
64
 
65
- def instantiate(row, aliases)
66
- base_klass.instantiate(extract_record(row, aliases))
65
+ def instantiate(row, aliases, &block)
66
+ base_klass.instantiate(extract_record(row, aliases), &block)
67
67
  end
68
68
  end
69
69
  end
@@ -1,18 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class JoinDependency # :nodoc:
4
- autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
5
- autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
6
+ autoload :JoinBase, "active_record/associations/join_dependency/join_base"
7
+ autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
6
8
 
7
9
  class Aliases # :nodoc:
8
10
  def initialize(tables)
9
11
  @tables = tables
10
- @alias_cache = tables.each_with_object({}) { |table,h|
11
- h[table.node] = table.columns.each_with_object({}) { |column,i|
12
+ @alias_cache = tables.each_with_object({}) { |table, h|
13
+ h[table.node] = table.columns.each_with_object({}) { |column, i|
12
14
  i[column.name] = column.alias
13
15
  }
14
16
  }
15
- @name_and_alias_cache = tables.each_with_object({}) { |table,h|
17
+ @name_and_alias_cache = tables.each_with_object({}) { |table, h|
16
18
  h[table.node] = table.columns.map { |column|
17
19
  [column.name, column.alias]
18
20
  }
@@ -20,7 +22,7 @@ module ActiveRecord
20
22
  end
21
23
 
22
24
  def columns
23
- @tables.flat_map { |t| t.column_aliases }
25
+ @tables.flat_map(&:column_aliases)
24
26
  end
25
27
 
26
28
  # An array of [column_name, alias] pairs for the table
@@ -32,21 +34,15 @@ module ActiveRecord
32
34
  @alias_cache[node][column]
33
35
  end
34
36
 
35
- class Table < Struct.new(:node, :columns)
36
- def table
37
- Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
38
- end
39
-
37
+ Table = Struct.new(:node, :columns) do # :nodoc:
40
38
  def column_aliases
41
- t = table
39
+ t = node.table
42
40
  columns.map { |column| t[column.name].as Arel.sql column.alias }
43
41
  end
44
42
  end
45
43
  Column = Struct.new(:name, :alias)
46
44
  end
47
45
 
48
- attr_reader :alias_tracker, :base_klass, :join_root
49
-
50
46
  def self.make_tree(associations)
51
47
  hash = {}
52
48
  walk_tree associations, hash
@@ -62,7 +58,7 @@ module ActiveRecord
62
58
  walk_tree assoc, hash
63
59
  end
64
60
  when Hash
65
- associations.each do |k,v|
61
+ associations.each do |k, v|
66
62
  cache = hash[k] ||= {}
67
63
  walk_tree v, cache
68
64
  end
@@ -71,74 +67,41 @@ module ActiveRecord
71
67
  end
72
68
  end
73
69
 
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
70
+ def initialize(base, table, associations)
98
71
  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 }
72
+ @join_root = JoinBase.new(base, table, build(tree, base))
101
73
  end
102
74
 
103
75
  def reflections
104
76
  join_root.drop(1).map!(&:reflection)
105
77
  end
106
78
 
107
- def join_constraints(outer_joins)
108
- joins = join_root.children.flat_map { |child|
109
- make_inner_joins join_root, child
110
- }
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)
111
84
 
112
- joins.concat outer_joins.flat_map { |oj|
85
+ joins.concat joins_to_add.flat_map { |oj|
86
+ construct_tables!(oj.join_root)
113
87
  if join_root.match? oj.join_root
114
88
  walk join_root, oj.join_root
115
89
  else
116
- oj.join_root.children.flat_map { |child|
117
- make_outer_joins oj.join_root, child
118
- }
90
+ make_join_constraints(oj.join_root, join_type)
119
91
  end
120
92
  }
121
93
  end
122
94
 
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)
95
+ def instantiate(result_set, &block)
133
96
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
134
97
 
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] = {} }
98
+ seen = Hash.new { |i, object_id|
99
+ i[object_id] = Hash.new { |j, child_class|
100
+ j[child_class] = {}
138
101
  }
139
102
  }
140
103
 
141
- model_cache = Hash.new { |h,klass| h[klass] = {} }
104
+ model_cache = Hash.new { |h, klass| h[klass] = {} }
142
105
  parents = model_cache[join_root]
143
106
  column_aliases = aliases.column_aliases join_root
144
107
 
@@ -149,9 +112,10 @@ module ActiveRecord
149
112
  class_name: join_root.base_klass.name
150
113
  }
151
114
 
152
- message_bus.instrument('instantiation.active_record', payload) do
115
+ message_bus.instrument("instantiation.active_record", payload) do
153
116
  result_set.each { |row_hash|
154
- parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
117
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
118
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
155
119
  construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
156
120
  }
157
121
  end
@@ -159,124 +123,140 @@ module ActiveRecord
159
123
  parents.values
160
124
  end
161
125
 
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)
126
+ def apply_column_aliases(relation)
127
+ relation._select!(-> { aliases.columns })
169
128
  end
170
129
 
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
130
+ protected
131
+ attr_reader :alias_tracker, :join_root
175
132
 
176
- [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
177
- end
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
178
142
 
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
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
183
148
 
184
- [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
185
- end
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
186
154
 
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
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
195
161
 
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
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
200
171
 
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
172
+ def table_alias_for(reflection, parent, join)
173
+ name = "#{reflection.plural_name}_#{parent.table_name}"
174
+ join ? "#{name}_join" : name
175
+ end
206
176
 
207
- def walk(left, right)
208
- intersection, missing = right.children.map { |node1|
209
- [left.children.find { |node2| node1.match? node2 }, node1]
210
- }.partition(&:first)
177
+ def walk(left, right)
178
+ intersection, missing = right.children.map { |node1|
179
+ [left.children.find { |node2| node1.match? node2 }, node1]
180
+ }.partition(&:first)
211
181
 
212
- ojs = missing.flat_map { |_,n| make_outer_joins left, n }
213
- intersection.flat_map { |l,r| walk l, r }.concat ojs
214
- end
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
215
185
 
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
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
220
190
 
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!
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!
226
196
 
227
- if reflection.polymorphic?
228
- raise EagerLoadPolymorphicError.new(reflection)
229
- end
197
+ if reflection.polymorphic?
198
+ raise EagerLoadPolymorphicError.new(reflection)
199
+ end
230
200
 
231
- JoinAssociation.new reflection, build(right, reflection.klass)
201
+ JoinAssociation.new(reflection, build(right, reflection.klass))
202
+ end
232
203
  end
233
- end
234
204
 
235
- def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
236
- primary_id = ar_parent.id
205
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
206
+ return if ar_parent.nil?
237
207
 
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)
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)
244
213
  model = ar_parent.association(node.reflection.name).target
245
214
  construct(model, node, row, rs, seen, model_cache, aliases)
246
215
  next
247
216
  end
248
- end
249
217
 
250
- key = aliases.column_alias(node, node.primary_key)
251
- id = row[key]
252
- next if id.nil?
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
253
225
 
254
- model = seen[parent.base_klass][primary_id][node.base_klass][id]
226
+ model = seen[ar_parent.object_id][node][id]
255
227
 
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)
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
262
241
  end
263
242
  end
264
- end
265
243
 
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)
244
+ def construct_model(record, node, row, model_cache, id, aliases)
245
+ other = record.association(node.reflection.name)
270
246
 
271
- if node.reflection.collection?
272
- other.target.push(model)
273
- else
274
- other.target = model
275
- end
247
+ model = model_cache[node][id] ||=
248
+ node.instantiate(row, aliases.column_aliases(node)) do |m|
249
+ other.set_inverse_instance(m)
250
+ end
276
251
 
277
- other.set_inverse_instance(model)
278
- model
279
- end
252
+ if node.reflection.collection?
253
+ other.target.push(model)
254
+ else
255
+ other.target = model
256
+ end
257
+
258
+ model
259
+ end
280
260
  end
281
261
  end
282
262
  end