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,49 +1,41 @@
1
- require 'active_support/core_ext/string/conversions'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/conversions"
2
4
 
3
5
  module ActiveRecord
4
6
  module Associations
5
- # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
6
- # ActiveRecord::Associations::ThroughAssociationScope
7
+ # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
7
8
  class AliasTracker # :nodoc:
8
- attr_reader :aliases, :connection
9
-
10
- def self.empty(connection)
11
- new connection, Hash.new(0)
12
- end
13
-
14
- def self.create(connection, table_joins)
15
- if table_joins.empty?
16
- empty connection
9
+ def self.create(connection, initial_table, joins)
10
+ if joins.empty?
11
+ aliases = Hash.new(0)
17
12
  else
18
- aliases = Hash.new { |h,k|
19
- h[k] = initial_count_for(connection, k, table_joins)
13
+ aliases = Hash.new { |h, k|
14
+ h[k] = initial_count_for(connection, k, joins)
20
15
  }
21
- new connection, aliases
22
16
  end
17
+ aliases[initial_table] = 1
18
+ new(connection, aliases)
23
19
  end
24
20
 
25
21
  def self.initial_count_for(connection, name, table_joins)
26
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
27
- quoted_name = connection.quote_table_name(name).downcase
22
+ quoted_name = nil
28
23
 
29
24
  counts = table_joins.map do |join|
30
25
  if join.is_a?(Arel::Nodes::StringJoin)
26
+ # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase
27
+ quoted_name ||= connection.quote_table_name(name)
28
+
31
29
  # Table names + table aliases
32
- join.left.downcase.scan(
33
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
30
+ join.left.scan(
31
+ /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
34
32
  ).size
35
- elsif join.respond_to? :left
36
- join.left.table_name == name ? 1 : 0
33
+ elsif join.is_a?(Arel::Nodes::Join)
34
+ join.left.name == name ? 1 : 0
35
+ elsif join.is_a?(Hash)
36
+ join[name]
37
37
  else
38
- # this branch is reached by two tests:
39
- #
40
- # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
41
- # with :posts
42
- #
43
- # activerecord/test/cases/associations/eager_test.rb:1133
44
- # with :comments
45
- #
46
- 0
38
+ raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
47
39
  end
48
40
  end
49
41
 
@@ -56,14 +48,14 @@ module ActiveRecord
56
48
  @connection = connection
57
49
  end
58
50
 
59
- def aliased_table_for(table_name, aliased_name)
51
+ def aliased_table_for(table_name, aliased_name, type_caster)
60
52
  if aliases[table_name].zero?
61
53
  # If it's zero, we can have our table_name
62
54
  aliases[table_name] = 1
63
- Arel::Table.new(table_name)
55
+ Arel::Table.new(table_name, type_caster: type_caster)
64
56
  else
65
57
  # Otherwise, we need to use an alias
66
- aliased_name = connection.table_alias_for(aliased_name)
58
+ aliased_name = @connection.table_alias_for(aliased_name)
67
59
 
68
60
  # Update the count
69
61
  aliases[aliased_name] += 1
@@ -73,14 +65,16 @@ module ActiveRecord
73
65
  else
74
66
  aliased_name
75
67
  end
76
- Arel::Table.new(table_name).alias(table_alias)
68
+ Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias)
77
69
  end
78
70
  end
79
71
 
72
+ attr_reader :aliases
73
+
80
74
  private
81
75
 
82
76
  def truncate(name)
83
- name.slice(0, connection.table_alias_length - 2)
77
+ name.slice(0, @connection.table_alias_length - 2)
84
78
  end
85
79
  end
86
80
  end
@@ -1,4 +1,6 @@
1
- require 'active_support/core_ext/array/wrap'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/wrap"
2
4
 
3
5
  module ActiveRecord
4
6
  module Associations
@@ -8,18 +10,17 @@ module ActiveRecord
8
10
  #
9
11
  # Association
10
12
  # SingularAssociation
11
- # HasOneAssociation
13
+ # HasOneAssociation + ForeignAssociation
12
14
  # HasOneThroughAssociation + ThroughAssociation
13
15
  # BelongsToAssociation
14
16
  # BelongsToPolymorphicAssociation
15
17
  # CollectionAssociation
16
- # HasManyAssociation
18
+ # HasManyAssociation + ForeignAssociation
17
19
  # HasManyThroughAssociation + ThroughAssociation
18
20
  class Association #:nodoc:
19
21
  attr_reader :owner, :target, :reflection
20
- attr_accessor :inversed
21
22
 
22
- delegate :options, :to => :reflection
23
+ delegate :options, to: :reflection
23
24
 
24
25
  def initialize(owner, reflection)
25
26
  reflection.check_validity!
@@ -30,14 +31,6 @@ module ActiveRecord
30
31
  reset_scope
31
32
  end
32
33
 
33
- # Returns the name of the table of the associated class:
34
- #
35
- # post.comments.aliased_table_name # => "comments"
36
- #
37
- def aliased_table_name
38
- klass.table_name
39
- end
40
-
41
34
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
42
35
  def reset
43
36
  @loaded = false
@@ -73,7 +66,7 @@ module ActiveRecord
73
66
  #
74
67
  # Note that if the target has not been loaded, it is not considered stale.
75
68
  def stale_target?
76
- !inversed && loaded? && @stale_state != stale_state
69
+ !@inversed && loaded? && @stale_state != stale_state
77
70
  end
78
71
 
79
72
  # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
@@ -83,7 +76,7 @@ module ActiveRecord
83
76
  end
84
77
 
85
78
  def scope
86
- target_scope.merge(association_scope)
79
+ target_scope.merge!(association_scope)
87
80
  end
88
81
 
89
82
  # The scope for this association.
@@ -94,7 +87,7 @@ module ActiveRecord
94
87
  # actually gets built.
95
88
  def association_scope
96
89
  if klass
97
- @association_scope ||= AssociationScope.scope(self, klass.connection)
90
+ @association_scope ||= AssociationScope.scope(self)
98
91
  end
99
92
  end
100
93
 
@@ -104,14 +97,32 @@ module ActiveRecord
104
97
 
105
98
  # Set the inverse association, if possible
106
99
  def set_inverse_instance(record)
107
- if invertible_for?(record)
108
- inverse = record.association(inverse_reflection_for(record).name)
109
- inverse.target = owner
110
- inverse.inversed = true
100
+ if inverse = inverse_association_for(record)
101
+ inverse.inversed_from(owner)
102
+ end
103
+ record
104
+ end
105
+
106
+ def set_inverse_instance_from_queries(record)
107
+ if inverse = inverse_association_for(record)
108
+ inverse.inversed_from_queries(owner)
111
109
  end
112
110
  record
113
111
  end
114
112
 
113
+ # Remove the inverse association, if possible
114
+ def remove_inverse_instance(record)
115
+ if inverse = inverse_association_for(record)
116
+ inverse.inversed_from(nil)
117
+ end
118
+ end
119
+
120
+ def inversed_from(record)
121
+ self.target = record
122
+ @inversed = !!record
123
+ end
124
+ alias :inversed_from_queries :inversed_from
125
+
115
126
  # Returns the class of the target. belongs_to polymorphic overrides this to look at the
116
127
  # polymorphic_type field on the owner.
117
128
  def klass
@@ -121,7 +132,17 @@ module ActiveRecord
121
132
  # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
122
133
  # through association's scope)
123
134
  def target_scope
124
- AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
135
+ AssociationRelation.create(klass, self).merge!(klass.all)
136
+ end
137
+
138
+ def extensions
139
+ extensions = klass.default_extensions | reflection.extensions
140
+
141
+ if reflection.scope
142
+ extensions |= reflection.scope_for(klass.unscoped, owner).extensions
143
+ end
144
+
145
+ extensions
125
146
  end
126
147
 
127
148
  # Loads the \target if needed and returns it.
@@ -143,17 +164,9 @@ module ActiveRecord
143
164
  reset
144
165
  end
145
166
 
146
- def interpolate(sql, record = nil)
147
- if sql.respond_to?(:to_proc)
148
- owner.instance_exec(record, &sql)
149
- else
150
- sql
151
- end
152
- end
153
-
154
- # We can't dump @reflection since it contains the scope proc
167
+ # We can't dump @reflection and @through_reflection since it contains the scope proc
155
168
  def marshal_dump
156
- ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
169
+ ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
157
170
  [@reflection.name, ivars]
158
171
  end
159
172
 
@@ -163,14 +176,28 @@ module ActiveRecord
163
176
  @reflection = @owner.class._reflect_on_association(reflection_name)
164
177
  end
165
178
 
166
- def initialize_attributes(record) #:nodoc:
179
+ def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
180
+ except_from_scope_attributes ||= {}
167
181
  skip_assign = [reflection.foreign_key, reflection.type].compact
168
- attributes = create_scope.except(*(record.changed - skip_assign))
169
- record.assign_attributes(attributes)
182
+ assigned_keys = record.changed_attribute_names_to_save
183
+ assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
184
+ attributes = scope_for_create.except!(*(assigned_keys - skip_assign))
185
+ record.send(:_assign_attributes, attributes) if attributes.any?
170
186
  set_inverse_instance(record)
171
187
  end
172
188
 
189
+ def create(attributes = {}, &block)
190
+ _create_record(attributes, &block)
191
+ end
192
+
193
+ def create!(attributes = {}, &block)
194
+ _create_record(attributes, true, &block)
195
+ end
196
+
173
197
  private
198
+ def scope_for_create
199
+ scope.scope_for_create
200
+ end
174
201
 
175
202
  def find_target?
176
203
  !loaded? && (!owner.new_record? || foreign_key_present?) && klass
@@ -182,8 +209,8 @@ module ActiveRecord
182
209
  if (reflection.has_one? || reflection.collection?) && !options[:through]
183
210
  attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
184
211
 
185
- if reflection.options[:as]
186
- attributes[reflection.type] = owner.class.base_class.name
212
+ if reflection.type
213
+ attributes[reflection.type] = owner.class.polymorphic_name
187
214
  end
188
215
  end
189
216
 
@@ -211,9 +238,19 @@ module ActiveRecord
211
238
  # the kind of the class of the associated objects. Meant to be used as
212
239
  # a sanity check when you are about to assign an associated record.
213
240
  def raise_on_type_mismatch!(record)
214
- unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
215
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
216
- raise ActiveRecord::AssociationTypeMismatch, message
241
+ unless record.is_a?(reflection.klass)
242
+ fresh_class = reflection.class_name.safe_constantize
243
+ unless fresh_class && record.is_a?(fresh_class)
244
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
245
+ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
246
+ raise ActiveRecord::AssociationTypeMismatch, message
247
+ end
248
+ end
249
+ end
250
+
251
+ def inverse_association_for(record)
252
+ if invertible_for?(record)
253
+ record.association(inverse_reflection_for(record).name)
217
254
  end
218
255
  end
219
256
 
@@ -239,15 +276,24 @@ module ActiveRecord
239
276
  # so that when stale_state is different from the value stored on the last find_target,
240
277
  # the target is stale.
241
278
  #
242
- # This is only relevant to certain associations, which is why it returns nil by default.
279
+ # This is only relevant to certain associations, which is why it returns +nil+ by default.
243
280
  def stale_state
244
281
  end
245
282
 
246
283
  def build_record(attributes)
247
284
  reflection.build_association(attributes) do |record|
248
- initialize_attributes(record)
285
+ initialize_attributes(record, attributes)
286
+ yield(record) if block_given?
249
287
  end
250
288
  end
289
+
290
+ # Returns true if statement cache should be skipped on the association reader.
291
+ def skip_statement_cache?(scope)
292
+ reflection.has_scope? ||
293
+ scope.eager_loading? ||
294
+ klass.scope_attributes? ||
295
+ reflection.source_reflection.active_record.default_scopes.any?
296
+ end
251
297
  end
252
298
  end
253
299
  end
@@ -1,46 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class AssociationScope #:nodoc:
4
- def self.scope(association, connection)
5
- INSTANCE.scope association, connection
6
- end
7
-
8
- class BindSubstitution
9
- def initialize(block)
10
- @block = block
11
- end
12
-
13
- def bind_value(scope, column, value, alias_tracker)
14
- substitute = alias_tracker.connection.substitute_at(column)
15
- scope.bind_values += [[column, @block.call(value)]]
16
- substitute
17
- end
6
+ def self.scope(association)
7
+ INSTANCE.scope(association)
18
8
  end
19
9
 
20
10
  def self.create(&block)
21
- block = block ? block : lambda { |val| val }
22
- new BindSubstitution.new(block)
11
+ block ||= lambda { |val| val }
12
+ new(block)
23
13
  end
24
14
 
25
- def initialize(bind_substitution)
26
- @bind_substitution = bind_substitution
15
+ def initialize(value_transformation)
16
+ @value_transformation = value_transformation
27
17
  end
28
18
 
29
19
  INSTANCE = create
30
20
 
31
- def scope(association, connection)
32
- klass = association.klass
33
- reflection = association.reflection
34
- scope = klass.unscoped
35
- owner = association.owner
36
- alias_tracker = AliasTracker.empty connection
21
+ def scope(association)
22
+ klass = association.klass
23
+ reflection = association.reflection
24
+ scope = klass.unscoped
25
+ owner = association.owner
26
+ chain = get_chain(reflection, association, scope.alias_tracker)
37
27
 
38
- scope.extending! Array(reflection.options[:extend])
39
- add_constraints(scope, owner, klass, reflection, alias_tracker)
40
- end
41
-
42
- def join_type
43
- Arel::Nodes::InnerJoin
28
+ scope.extending! reflection.extensions
29
+ add_constraints(scope, owner, chain)
44
30
  end
45
31
 
46
32
  def self.get_bind_values(owner, chain)
@@ -49,146 +35,134 @@ module ActiveRecord
49
35
 
50
36
  binds << last_reflection.join_id_for(owner)
51
37
  if last_reflection.type
52
- binds << owner.class.base_class.name
38
+ binds << owner.class.polymorphic_name
53
39
  end
54
40
 
55
41
  chain.each_cons(2).each do |reflection, next_reflection|
56
42
  if reflection.type
57
- binds << next_reflection.klass.base_class.name
43
+ binds << next_reflection.klass.polymorphic_name
58
44
  end
59
45
  end
60
46
  binds
61
47
  end
62
48
 
63
- private
49
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
50
+ # Workaround for Ruby 2.2 "private attribute?" warning.
51
+ protected
64
52
 
65
- def construct_tables(chain, klass, refl, alias_tracker)
66
- chain.map do |reflection|
67
- alias_tracker.aliased_table_for(
68
- table_name_for(reflection, klass, refl),
69
- table_alias_for(reflection, refl, reflection != refl)
70
- )
71
- end
72
- end
53
+ attr_reader :value_transformation
73
54
 
74
- def table_alias_for(reflection, refl, join = false)
75
- name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
76
- name << "_join" if join
77
- name
78
- end
79
-
80
- def join(table, constraint)
81
- table.create_join(table, table.create_on(constraint), join_type)
82
- end
83
-
84
- def column_for(table_name, column_name, alias_tracker)
85
- columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
86
- columns[column_name]
87
- end
88
-
89
- def bind_value(scope, column, value, alias_tracker)
90
- @bind_substitution.bind_value scope, column, value, alias_tracker
91
- end
55
+ private
56
+ def join(table, constraint)
57
+ table.create_join(table, table.create_on(constraint))
58
+ end
92
59
 
93
- def bind(scope, table_name, column_name, value, tracker)
94
- column = column_for table_name, column_name, tracker
95
- bind_value scope, column, value, tracker
96
- end
60
+ def last_chain_scope(scope, reflection, owner)
61
+ join_keys = reflection.join_keys
62
+ key = join_keys.key
63
+ foreign_key = join_keys.foreign_key
97
64
 
98
- def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
99
- join_keys = reflection.join_keys(assoc_klass)
100
- key = join_keys.key
101
- foreign_key = join_keys.foreign_key
65
+ table = reflection.aliased_table
66
+ value = transform_value(owner[foreign_key])
67
+ scope = apply_scope(scope, table, key, value)
102
68
 
103
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
104
- scope = scope.where(table[key].eq(bind_val))
69
+ if reflection.type
70
+ polymorphic_type = transform_value(owner.class.polymorphic_name)
71
+ scope = apply_scope(scope, table, reflection.type, polymorphic_type)
72
+ end
105
73
 
106
- if reflection.type
107
- value = owner.class.base_class.name
108
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
109
- scope = scope.where(table[reflection.type].eq(bind_val))
110
- else
111
74
  scope
112
75
  end
113
- end
114
76
 
115
- def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
116
- join_keys = reflection.join_keys(assoc_klass)
117
- key = join_keys.key
118
- foreign_key = join_keys.foreign_key
77
+ def transform_value(value)
78
+ value_transformation.call(value)
79
+ end
80
+
81
+ def next_chain_scope(scope, reflection, next_reflection)
82
+ join_keys = reflection.join_keys
83
+ key = join_keys.key
84
+ foreign_key = join_keys.foreign_key
119
85
 
120
- constraint = table[key].eq(foreign_table[foreign_key])
86
+ table = reflection.aliased_table
87
+ foreign_table = next_reflection.aliased_table
88
+ constraint = table[key].eq(foreign_table[foreign_key])
89
+
90
+ if reflection.type
91
+ value = transform_value(next_reflection.klass.polymorphic_name)
92
+ scope = apply_scope(scope, table, reflection.type, value)
93
+ end
121
94
 
122
- if reflection.type
123
- value = next_reflection.klass.base_class.name
124
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
125
- scope = scope.where(table[reflection.type].eq(bind_val))
95
+ scope.joins!(join(foreign_table, constraint))
126
96
  end
127
97
 
128
- scope = scope.joins(join(foreign_table, constraint))
129
- end
98
+ class ReflectionProxy < SimpleDelegator # :nodoc:
99
+ attr_reader :aliased_table
130
100
 
131
- def add_constraints(scope, owner, assoc_klass, refl, tracker)
132
- chain = refl.chain
133
- scope_chain = refl.scope_chain
101
+ def initialize(reflection, aliased_table)
102
+ super(reflection)
103
+ @aliased_table = aliased_table
104
+ end
134
105
 
135
- tables = construct_tables(chain, assoc_klass, refl, tracker)
106
+ def all_includes; nil; end
107
+ end
136
108
 
137
- owner_reflection = chain.last
138
- table = tables.last
139
- scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
109
+ def get_chain(reflection, association, tracker)
110
+ name = reflection.name
111
+ chain = [Reflection::RuntimeReflection.new(reflection, association)]
112
+ reflection.chain.drop(1).each do |refl|
113
+ aliased_table = tracker.aliased_table_for(
114
+ refl.table_name,
115
+ refl.alias_candidate(name),
116
+ refl.klass.type_caster
117
+ )
118
+ chain << ReflectionProxy.new(refl, aliased_table)
119
+ end
120
+ chain
121
+ end
140
122
 
141
- chain.each_with_index do |reflection, i|
142
- table, foreign_table = tables.shift, tables.first
123
+ def add_constraints(scope, owner, chain)
124
+ scope = last_chain_scope(scope, chain.last, owner)
143
125
 
144
- unless reflection == chain.last
145
- next_reflection = chain[i + 1]
146
- scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
126
+ chain.each_cons(2) do |reflection, next_reflection|
127
+ scope = next_chain_scope(scope, reflection, next_reflection)
147
128
  end
148
129
 
149
- is_first_chain = i == 0
150
- klass = is_first_chain ? assoc_klass : reflection.klass
130
+ chain_head = chain.first
131
+ chain.reverse_each do |reflection|
132
+ # Exclude the scope of the association itself, because that
133
+ # was already merged in the #scope method.
134
+ reflection.constraints.each do |scope_chain_item|
135
+ item = eval_scope(reflection, scope_chain_item, owner)
151
136
 
152
- # Exclude the scope of the association itself, because that
153
- # was already merged in the #scope method.
154
- scope_chain[i].each do |scope_chain_item|
155
- item = eval_scope(klass, scope_chain_item, owner)
137
+ if scope_chain_item == chain_head.scope
138
+ scope.merge! item.except(:where, :includes, :unscope, :order)
139
+ end
156
140
 
157
- if scope_chain_item == refl.scope
158
- scope.merge! item.except(:where, :includes, :bind)
159
- end
141
+ reflection.all_includes do
142
+ scope.includes! item.includes_values
143
+ end
160
144
 
161
- if is_first_chain
162
- scope.includes! item.includes_values
145
+ scope.unscope!(*item.unscope_values)
146
+ scope.where_clause += item.where_clause
147
+ scope.order_values = item.order_values | scope.order_values
163
148
  end
164
-
165
- scope.where_values += item.where_values
166
- scope.bind_values += item.bind_values
167
- scope.order_values |= item.order_values
168
149
  end
169
- end
170
-
171
- scope
172
- end
173
150
 
174
- def alias_suffix(refl)
175
- refl.name
176
- end
151
+ scope
152
+ end
177
153
 
178
- def table_name_for(reflection, klass, refl)
179
- if reflection == refl
180
- # If this is a polymorphic belongs_to, we want to get the klass from the
181
- # association because it depends on the polymorphic_type attribute of
182
- # the owner
183
- klass.table_name
184
- else
185
- reflection.table_name
154
+ def apply_scope(scope, table, key, value)
155
+ if scope.table == table
156
+ scope.where!(key => value)
157
+ else
158
+ scope.where!(table.name => { key => value })
159
+ end
186
160
  end
187
- end
188
161
 
189
- def eval_scope(klass, scope, owner)
190
- klass.unscoped.instance_exec(owner, &scope)
191
- end
162
+ def eval_scope(reflection, scope, owner)
163
+ relation = reflection.build_scope(reflection.aliased_table)
164
+ relation.instance_exec(owner, &scope) || relation
165
+ end
192
166
  end
193
167
  end
194
168
  end