activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  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 +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  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 +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  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 +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  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 +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  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 +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  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 +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  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 +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  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 +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  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 +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  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 +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  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 +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  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 +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,134 +1,168 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class AssociationScope #:nodoc:
4
- include JoinHelper
5
-
6
- attr_reader :association, :alias_tracker
6
+ def self.scope(association)
7
+ INSTANCE.scope(association)
8
+ end
7
9
 
8
- delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
- delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
10
+ def self.create(&block)
11
+ block ||= lambda { |val| val }
12
+ new(block)
13
+ end
10
14
 
11
- def initialize(association)
12
- @association = association
13
- @alias_tracker = AliasTracker.new klass.connection
15
+ def initialize(value_transformation)
16
+ @value_transformation = value_transformation
14
17
  end
15
18
 
16
- def scope
19
+ INSTANCE = create
20
+
21
+ def scope(association)
22
+ klass = association.klass
23
+ reflection = association.reflection
17
24
  scope = klass.unscoped
18
- scope = scope.extending(*Array.wrap(options[:extend]))
25
+ owner = association.owner
26
+ chain = get_chain(reflection, association, scope.alias_tracker)
27
+
28
+ scope.extending! reflection.extensions
29
+ add_constraints(scope, owner, chain)
30
+ end
19
31
 
20
- # It's okay to just apply all these like this. The options will only be present if the
21
- # association supports that option; this is enforced by the association builder.
22
- scope = scope.apply_finder_options(options.slice(
23
- :readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
32
+ def self.get_bind_values(owner, chain)
33
+ binds = []
34
+ last_reflection = chain.last
24
35
 
25
- if options[:through] && !options[:include]
26
- scope = scope.includes(source_options[:include])
36
+ binds << last_reflection.join_id_for(owner)
37
+ if last_reflection.type
38
+ binds << owner.class.polymorphic_name
27
39
  end
28
40
 
29
- scope = scope.uniq if options[:uniq]
30
-
31
- add_constraints(scope)
41
+ chain.each_cons(2).each do |reflection, next_reflection|
42
+ if reflection.type
43
+ binds << next_reflection.klass.polymorphic_name
44
+ end
45
+ end
46
+ binds
32
47
  end
33
48
 
34
- 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
35
52
 
36
- def add_constraints(scope)
37
- tables = construct_tables
53
+ attr_reader :value_transformation
38
54
 
39
- chain.each_with_index do |reflection, i|
40
- table, foreign_table = tables.shift, tables.first
55
+ private
56
+ def join(table, constraint)
57
+ table.create_join(table, table.create_on(constraint))
58
+ end
41
59
 
42
- if reflection.source_macro == :has_and_belongs_to_many
43
- join_table = tables.shift
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
44
64
 
45
- scope = scope.joins(join(
46
- join_table,
47
- table[reflection.association_primary_key].
48
- eq(join_table[reflection.association_foreign_key])
49
- ))
65
+ table = reflection.aliased_table
66
+ value = transform_value(owner[foreign_key])
67
+ scope = apply_scope(scope, table, key, value)
50
68
 
51
- table, foreign_table = join_table, tables.first
69
+ if reflection.type
70
+ polymorphic_type = transform_value(owner.class.polymorphic_name)
71
+ scope = apply_scope(scope, table, reflection.type, polymorphic_type)
52
72
  end
53
73
 
54
- if reflection.source_macro == :belongs_to
55
- if reflection.options[:polymorphic]
56
- key = reflection.association_primary_key(klass)
57
- else
58
- key = reflection.association_primary_key
59
- end
74
+ scope
75
+ end
60
76
 
61
- foreign_key = reflection.foreign_key
62
- else
63
- key = reflection.foreign_key
64
- foreign_key = reflection.active_record_primary_key
65
- end
77
+ def transform_value(value)
78
+ value_transformation.call(value)
79
+ end
66
80
 
67
- conditions = self.conditions[i]
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
68
85
 
69
- if reflection == chain.last
70
- scope = scope.where(table[key].eq(owner[foreign_key]))
86
+ table = reflection.aliased_table
87
+ foreign_table = next_reflection.aliased_table
88
+ constraint = table[key].eq(foreign_table[foreign_key])
71
89
 
72
- if reflection.type
73
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
74
- end
90
+ if reflection.type
91
+ value = transform_value(next_reflection.klass.polymorphic_name)
92
+ scope = apply_scope(scope, table, reflection.type, value)
93
+ end
75
94
 
76
- conditions.each do |condition|
77
- if options[:through] && condition.is_a?(Hash)
78
- condition = disambiguate_condition(table, condition)
79
- end
95
+ scope.joins!(join(foreign_table, constraint))
96
+ end
80
97
 
81
- scope = scope.where(interpolate(condition))
82
- end
83
- else
84
- constraint = table[key].eq(foreign_table[foreign_key])
98
+ class ReflectionProxy < SimpleDelegator # :nodoc:
99
+ attr_reader :aliased_table
85
100
 
86
- if reflection.type
87
- type = chain[i + 1].klass.base_class.name
88
- constraint = constraint.and(table[reflection.type].eq(type))
89
- end
101
+ def initialize(reflection, aliased_table)
102
+ super(reflection)
103
+ @aliased_table = aliased_table
104
+ end
90
105
 
91
- scope = scope.joins(join(foreign_table, constraint))
106
+ def all_includes; nil; end
107
+ end
92
108
 
93
- unless conditions.empty?
94
- scope = scope.where(sanitize(conditions, table))
95
- end
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)
96
119
  end
120
+ chain
97
121
  end
98
122
 
99
- scope
100
- end
123
+ def add_constraints(scope, owner, chain)
124
+ scope = last_chain_scope(scope, chain.last, owner)
101
125
 
102
- def alias_suffix
103
- reflection.name
104
- end
126
+ chain.each_cons(2) do |reflection, next_reflection|
127
+ scope = next_chain_scope(scope, reflection, next_reflection)
128
+ end
105
129
 
106
- def table_name_for(reflection)
107
- if reflection == self.reflection
108
- # If this is a polymorphic belongs_to, we want to get the klass from the
109
- # association because it depends on the polymorphic_type attribute of
110
- # the owner
111
- klass.table_name
112
- else
113
- reflection.table_name
114
- end
115
- end
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)
136
+
137
+ if scope_chain_item == chain_head.scope
138
+ scope.merge! item.except(:where, :includes, :unscope, :order)
139
+ end
116
140
 
117
- def disambiguate_condition(table, condition)
118
- if condition.is_a?(Hash)
119
- Hash[
120
- condition.map do |k, v|
121
- if v.is_a?(Hash)
122
- [k, v]
123
- else
124
- [table.table_alias || table.name, { k => v }]
141
+ reflection.all_includes do
142
+ scope.includes! item.includes_values
125
143
  end
144
+
145
+ scope.unscope!(*item.unscope_values)
146
+ scope.where_clause += item.where_clause
147
+ scope.order_values = item.order_values | scope.order_values
126
148
  end
127
- ]
128
- else
129
- condition
149
+ end
150
+
151
+ scope
152
+ end
153
+
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
160
+ end
161
+
162
+ def eval_scope(reflection, scope, owner)
163
+ relation = reflection.build_scope(reflection.aliased_table)
164
+ relation.instance_exec(owner, &scope) || relation
130
165
  end
131
- end
132
166
  end
133
167
  end
134
168
  end
@@ -1,78 +1,129 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
- # = Active Record Belongs To Associations
3
4
  module Associations
5
+ # = Active Record Belongs To Association
4
6
  class BelongsToAssociation < SingularAssociation #:nodoc:
7
+ def handle_dependency
8
+ return unless load_target
9
+
10
+ case options[:dependent]
11
+ when :destroy
12
+ target.destroy
13
+ raise ActiveRecord::Rollback unless target.destroyed?
14
+ else
15
+ target.send(options[:dependent])
16
+ end
17
+ end
18
+
5
19
  def replace(record)
6
- raise_on_type_mismatch(record) if record
20
+ if record
21
+ raise_on_type_mismatch!(record)
22
+ update_counters_on_replace(record)
23
+ set_inverse_instance(record)
24
+ @updated = true
25
+ else
26
+ decrement_counters
27
+ end
7
28
 
8
- update_counters(record)
9
29
  replace_keys(record)
10
- set_inverse_instance(record)
11
-
12
- @updated = true if record
13
30
 
14
31
  self.target = record
15
32
  end
16
33
 
34
+ def inversed_from(record)
35
+ replace_keys(record)
36
+ super
37
+ end
38
+
39
+ def default(&block)
40
+ writer(owner.instance_exec(&block)) if reader.nil?
41
+ end
42
+
43
+ def reset
44
+ super
45
+ @updated = false
46
+ end
47
+
17
48
  def updated?
18
49
  @updated
19
50
  end
20
51
 
52
+ def decrement_counters # :nodoc:
53
+ update_counters(-1)
54
+ end
55
+
56
+ def increment_counters # :nodoc:
57
+ update_counters(1)
58
+ end
59
+
60
+ def target_changed?
61
+ owner.saved_change_to_attribute?(reflection.foreign_key)
62
+ end
63
+
21
64
  private
22
65
 
66
+ def update_counters(by)
67
+ if require_counter_update? && foreign_key_present?
68
+ if target && !stale_target?
69
+ target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
70
+ else
71
+ klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch])
72
+ end
73
+ end
74
+ end
75
+
23
76
  def find_target?
24
77
  !loaded? && foreign_key_present? && klass
25
78
  end
26
79
 
27
- def update_counters(record)
28
- counter_cache_name = reflection.counter_cache_column
29
-
30
- if counter_cache_name && owner.persisted? && different_target?(record)
31
- if record
32
- record.class.increment_counter(counter_cache_name, record.id)
33
- end
80
+ def require_counter_update?
81
+ reflection.counter_cache_column && owner.persisted?
82
+ end
34
83
 
35
- if foreign_key_present?
36
- klass.decrement_counter(counter_cache_name, target_id)
37
- end
84
+ def update_counters_on_replace(record)
85
+ if require_counter_update? && different_target?(record)
86
+ owner.instance_variable_set :@_after_replace_counter_called, true
87
+ record.increment!(reflection.counter_cache_column, touch: reflection.options[:touch])
88
+ decrement_counters
38
89
  end
39
90
  end
40
91
 
41
92
  # Checks whether record is different to the current target, without loading it
42
93
  def different_target?(record)
43
- record.nil? && owner[reflection.foreign_key] ||
44
- record && record.id != owner[reflection.foreign_key]
94
+ record._read_attribute(primary_key(record)) != owner._read_attribute(reflection.foreign_key)
45
95
  end
46
96
 
47
97
  def replace_keys(record)
48
- if record
49
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
50
- else
51
- owner[reflection.foreign_key] = nil
52
- end
98
+ owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record)) : nil
99
+ end
100
+
101
+ def primary_key(record)
102
+ reflection.association_primary_key(record.class)
53
103
  end
54
104
 
55
105
  def foreign_key_present?
56
- owner[reflection.foreign_key]
106
+ owner._read_attribute(reflection.foreign_key)
57
107
  end
58
108
 
59
109
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
60
110
  # has_one associations.
61
111
  def invertible_for?(record)
62
112
  inverse = inverse_reflection_for(record)
63
- inverse && inverse.macro == :has_one
113
+ inverse && inverse.has_one?
64
114
  end
65
115
 
66
116
  def target_id
67
117
  if options[:primary_key]
68
118
  owner.send(reflection.name).try(:id)
69
119
  else
70
- owner[reflection.foreign_key]
120
+ owner._read_attribute(reflection.foreign_key)
71
121
  end
72
122
  end
73
123
 
74
124
  def stale_state
75
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
125
+ result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
126
+ result && result.to_s
76
127
  end
77
128
  end
78
129
  end
@@ -1,17 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
- # = Active Record Belongs To Polymorphic Association
3
4
  module Associations
5
+ # = Active Record Belongs To Polymorphic Association
4
6
  class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
5
7
  def klass
6
8
  type = owner[reflection.foreign_type]
7
9
  type.presence && type.constantize
8
10
  end
9
11
 
10
- private
12
+ def target_changed?
13
+ super || owner.saved_change_to_attribute?(reflection.foreign_type)
14
+ end
11
15
 
16
+ private
12
17
  def replace_keys(record)
13
18
  super
14
- owner[reflection.foreign_type] = record && record.class.base_class.name
19
+ owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
15
20
  end
16
21
 
17
22
  def different_target?(record)
@@ -22,7 +27,7 @@ module ActiveRecord
22
27
  reflection.polymorphic_inverse_of(record.class)
23
28
  end
24
29
 
25
- def raise_on_type_mismatch(record)
30
+ def raise_on_type_mismatch!(record)
26
31
  # A polymorphic association cannot have a type mismatch, by definition
27
32
  end
28
33
 
@@ -1,55 +1,140 @@
1
- module ActiveRecord::Associations::Builder
1
+ # frozen_string_literal: true
2
+
3
+ # This is the parent Association class which defines the variables
4
+ # used by all associations.
5
+ #
6
+ # The hierarchy is defined as follows:
7
+ # Association
8
+ # - SingularAssociation
9
+ # - BelongsToAssociation
10
+ # - HasOneAssociation
11
+ # - CollectionAssociation
12
+ # - HasManyAssociation
13
+
14
+ module ActiveRecord::Associations::Builder # :nodoc:
2
15
  class Association #:nodoc:
3
- class_attribute :valid_options
4
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate]
16
+ class << self
17
+ attr_accessor :extensions
18
+ end
19
+ self.extensions = []
20
+
21
+ VALID_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate] # :nodoc:
22
+
23
+ def self.build(model, name, scope, options, &block)
24
+ if model.dangerous_attribute_method?(name)
25
+ raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
26
+ "this will conflict with a method #{name} already defined by Active Record. " \
27
+ "Please choose a different association name."
28
+ end
29
+
30
+ extension = define_extensions model, name, &block
31
+ reflection = create_reflection model, name, scope, options, extension
32
+ define_accessors model, reflection
33
+ define_callbacks model, reflection
34
+ define_validations model, reflection
35
+ reflection
36
+ end
5
37
 
6
- # Set by subclasses
7
- class_attribute :macro
38
+ def self.create_reflection(model, name, scope, options, extension = nil)
39
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
8
40
 
9
- attr_reader :model, :name, :options, :reflection
41
+ validate_options(options)
10
42
 
11
- def self.build(model, name, options)
12
- new(model, name, options).build
43
+ scope = build_scope(scope, extension)
44
+
45
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
13
46
  end
14
47
 
15
- def initialize(model, name, options)
16
- @model, @name, @options = model, name, options
48
+ def self.build_scope(scope, extension)
49
+ new_scope = scope
50
+
51
+ if scope && scope.arity == 0
52
+ new_scope = proc { instance_exec(&scope) }
53
+ end
54
+
55
+ if extension
56
+ new_scope = wrap_scope new_scope, extension
57
+ end
58
+
59
+ new_scope
17
60
  end
18
61
 
19
- def mixin
20
- @model.generated_feature_methods
62
+ def self.wrap_scope(scope, extension)
63
+ scope
21
64
  end
22
65
 
23
- def build
24
- validate_options
25
- reflection = model.create_reflection(self.class.macro, name, options, model)
26
- define_accessors
27
- reflection
66
+ def self.macro
67
+ raise NotImplementedError
28
68
  end
29
69
 
30
- private
70
+ def self.valid_options(options)
71
+ VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
72
+ end
31
73
 
32
- def validate_options
33
- options.assert_valid_keys(self.class.valid_options)
74
+ def self.validate_options(options)
75
+ options.assert_valid_keys(valid_options(options))
76
+ end
77
+
78
+ def self.define_extensions(model, name)
79
+ end
80
+
81
+ def self.define_callbacks(model, reflection)
82
+ if dependent = reflection.options[:dependent]
83
+ check_dependent_options(dependent)
84
+ add_destroy_callbacks(model, reflection)
34
85
  end
35
86
 
36
- def define_accessors
37
- define_readers
38
- define_writers
87
+ Association.extensions.each do |extension|
88
+ extension.build model, reflection
39
89
  end
90
+ end
40
91
 
41
- def define_readers
42
- name = self.name
43
- mixin.redefine_method(name) do |*params|
44
- association(name).reader(*params)
92
+ # Defines the setter and getter methods for the association
93
+ # class Post < ActiveRecord::Base
94
+ # has_many :comments
95
+ # end
96
+ #
97
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
98
+ def self.define_accessors(model, reflection)
99
+ mixin = model.generated_association_methods
100
+ name = reflection.name
101
+ define_readers(mixin, name)
102
+ define_writers(mixin, name)
103
+ end
104
+
105
+ def self.define_readers(mixin, name)
106
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
107
+ def #{name}
108
+ association(:#{name}).reader
45
109
  end
46
- end
110
+ CODE
111
+ end
47
112
 
48
- def define_writers
49
- name = self.name
50
- mixin.redefine_method("#{name}=") do |value|
51
- association(name).writer(value)
113
+ def self.define_writers(mixin, name)
114
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
115
+ def #{name}=(value)
116
+ association(:#{name}).writer(value)
52
117
  end
118
+ CODE
119
+ end
120
+
121
+ def self.define_validations(model, reflection)
122
+ # noop
123
+ end
124
+
125
+ def self.valid_dependent_options
126
+ raise NotImplementedError
127
+ end
128
+
129
+ def self.check_dependent_options(dependent)
130
+ unless valid_dependent_options.include? dependent
131
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
53
132
  end
133
+ end
134
+
135
+ def self.add_destroy_callbacks(model, reflection)
136
+ name = reflection.name
137
+ model.before_destroy lambda { |o| o.association(name).handle_dependency }
138
+ end
54
139
  end
55
140
  end