activerecord 6.0.6 → 6.1.0

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

Potentially problematic release.


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

Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +783 -910
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +22 -14
  7. data/lib/active_record/associations/alias_tracker.rb +19 -15
  8. data/lib/active_record/associations/association.rb +43 -26
  9. data/lib/active_record/associations/association_scope.rb +11 -15
  10. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  12. data/lib/active_record/associations/builder/association.rb +9 -3
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +19 -13
  20. data/lib/active_record/associations/collection_proxy.rb +12 -5
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -2
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +29 -14
  26. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +63 -49
  28. data/lib/active_record/associations/preloader/association.rb +13 -5
  29. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  30. data/lib/active_record/associations/preloader.rb +5 -3
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations.rb +114 -11
  33. data/lib/active_record/attribute_assignment.rb +10 -8
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  35. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  36. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  37. data/lib/active_record/attribute_methods/query.rb +3 -6
  38. data/lib/active_record/attribute_methods/read.rb +8 -11
  39. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  41. data/lib/active_record/attribute_methods/write.rb +12 -20
  42. data/lib/active_record/attribute_methods.rb +64 -54
  43. data/lib/active_record/attributes.rb +32 -7
  44. data/lib/active_record/autosave_association.rb +47 -30
  45. data/lib/active_record/base.rb +2 -14
  46. data/lib/active_record/callbacks.rb +152 -22
  47. data/lib/active_record/coders/yaml_column.rb +2 -24
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +185 -134
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +110 -30
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +49 -72
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
  61. data/lib/active_record/connection_adapters/column.rb +15 -1
  62. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  63. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -24
  65. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  68. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  69. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -3
  71. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  73. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  74. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  75. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +12 -53
  77. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  78. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  80. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  91. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql_adapter.rb +72 -55
  93. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  94. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  95. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +30 -5
  96. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  97. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  98. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
  99. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
  100. data/lib/active_record/connection_adapters.rb +50 -0
  101. data/lib/active_record/connection_handling.rb +210 -71
  102. data/lib/active_record/core.rb +223 -66
  103. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  104. data/lib/active_record/database_configurations/database_config.rb +52 -9
  105. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  106. data/lib/active_record/database_configurations/url_config.rb +15 -40
  107. data/lib/active_record/database_configurations.rb +124 -85
  108. data/lib/active_record/delegated_type.rb +209 -0
  109. data/lib/active_record/destroy_association_async_job.rb +36 -0
  110. data/lib/active_record/enum.rb +27 -10
  111. data/lib/active_record/errors.rb +47 -12
  112. data/lib/active_record/explain.rb +9 -4
  113. data/lib/active_record/explain_subscriber.rb +1 -1
  114. data/lib/active_record/fixture_set/file.rb +10 -17
  115. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  116. data/lib/active_record/fixture_set/render_context.rb +1 -1
  117. data/lib/active_record/fixture_set/table_row.rb +2 -2
  118. data/lib/active_record/fixtures.rb +54 -8
  119. data/lib/active_record/gem_version.rb +2 -2
  120. data/lib/active_record/inheritance.rb +40 -18
  121. data/lib/active_record/insert_all.rb +34 -5
  122. data/lib/active_record/integration.rb +3 -5
  123. data/lib/active_record/internal_metadata.rb +16 -7
  124. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  125. data/lib/active_record/locking/optimistic.rb +13 -16
  126. data/lib/active_record/locking/pessimistic.rb +6 -2
  127. data/lib/active_record/log_subscriber.rb +26 -8
  128. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  129. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/migration/command_recorder.rb +47 -27
  132. data/lib/active_record/migration/compatibility.rb +67 -17
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/model_schema.rb +88 -13
  135. data/lib/active_record/nested_attributes.rb +2 -3
  136. data/lib/active_record/no_touching.rb +1 -1
  137. data/lib/active_record/persistence.rb +50 -45
  138. data/lib/active_record/query_cache.rb +15 -5
  139. data/lib/active_record/querying.rb +11 -6
  140. data/lib/active_record/railtie.rb +64 -44
  141. data/lib/active_record/railties/databases.rake +266 -95
  142. data/lib/active_record/readonly_attributes.rb +4 -0
  143. data/lib/active_record/reflection.rb +60 -44
  144. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  145. data/lib/active_record/relation/batches.rb +38 -31
  146. data/lib/active_record/relation/calculations.rb +100 -43
  147. data/lib/active_record/relation/finder_methods.rb +44 -14
  148. data/lib/active_record/relation/from_clause.rb +1 -1
  149. data/lib/active_record/relation/merger.rb +20 -23
  150. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  151. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  152. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  153. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  154. data/lib/active_record/relation/predicate_builder.rb +57 -33
  155. data/lib/active_record/relation/query_methods.rb +318 -195
  156. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  157. data/lib/active_record/relation/spawn_methods.rb +8 -7
  158. data/lib/active_record/relation/where_clause.rb +104 -57
  159. data/lib/active_record/relation.rb +90 -64
  160. data/lib/active_record/result.rb +41 -33
  161. data/lib/active_record/runtime_registry.rb +2 -2
  162. data/lib/active_record/sanitization.rb +6 -17
  163. data/lib/active_record/schema_dumper.rb +34 -4
  164. data/lib/active_record/schema_migration.rb +2 -8
  165. data/lib/active_record/scoping/named.rb +1 -17
  166. data/lib/active_record/secure_token.rb +16 -8
  167. data/lib/active_record/serialization.rb +5 -3
  168. data/lib/active_record/signed_id.rb +116 -0
  169. data/lib/active_record/statement_cache.rb +20 -4
  170. data/lib/active_record/store.rb +2 -2
  171. data/lib/active_record/suppressor.rb +2 -2
  172. data/lib/active_record/table_metadata.rb +39 -51
  173. data/lib/active_record/tasks/database_tasks.rb +139 -113
  174. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  175. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  176. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  177. data/lib/active_record/test_databases.rb +5 -4
  178. data/lib/active_record/test_fixtures.rb +36 -33
  179. data/lib/active_record/timestamp.rb +4 -6
  180. data/lib/active_record/touch_later.rb +21 -21
  181. data/lib/active_record/transactions.rb +15 -64
  182. data/lib/active_record/type/serialized.rb +6 -2
  183. data/lib/active_record/type.rb +8 -1
  184. data/lib/active_record/type_caster/connection.rb +0 -1
  185. data/lib/active_record/type_caster/map.rb +8 -5
  186. data/lib/active_record/validations/associated.rb +1 -1
  187. data/lib/active_record/validations/numericality.rb +35 -0
  188. data/lib/active_record/validations/uniqueness.rb +24 -4
  189. data/lib/active_record/validations.rb +1 -0
  190. data/lib/active_record.rb +7 -14
  191. data/lib/arel/attributes/attribute.rb +4 -0
  192. data/lib/arel/collectors/bind.rb +5 -0
  193. data/lib/arel/collectors/composite.rb +8 -0
  194. data/lib/arel/collectors/sql_string.rb +7 -0
  195. data/lib/arel/collectors/substitute_binds.rb +7 -0
  196. data/lib/arel/nodes/binary.rb +82 -8
  197. data/lib/arel/nodes/bind_param.rb +8 -0
  198. data/lib/arel/nodes/casted.rb +21 -9
  199. data/lib/arel/nodes/equality.rb +6 -9
  200. data/lib/arel/nodes/grouping.rb +3 -0
  201. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  202. data/lib/arel/nodes/in.rb +8 -1
  203. data/lib/arel/nodes/infix_operation.rb +13 -1
  204. data/lib/arel/nodes/join_source.rb +1 -1
  205. data/lib/arel/nodes/node.rb +7 -6
  206. data/lib/arel/nodes/ordering.rb +27 -0
  207. data/lib/arel/nodes/sql_literal.rb +3 -0
  208. data/lib/arel/nodes/table_alias.rb +7 -3
  209. data/lib/arel/nodes/unary.rb +0 -1
  210. data/lib/arel/nodes.rb +3 -1
  211. data/lib/arel/predications.rb +12 -18
  212. data/lib/arel/select_manager.rb +1 -2
  213. data/lib/arel/table.rb +13 -5
  214. data/lib/arel/visitors/dot.rb +14 -2
  215. data/lib/arel/visitors/mysql.rb +11 -1
  216. data/lib/arel/visitors/postgresql.rb +15 -4
  217. data/lib/arel/visitors/to_sql.rb +89 -78
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel.rb +5 -13
  220. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  221. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  222. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  224. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  225. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  226. metadata +28 -30
  227. data/lib/active_record/advisory_lock_base.rb +0 -18
  228. data/lib/active_record/attribute_decorators.rb +0 -88
  229. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  230. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  231. data/lib/active_record/define_callbacks.rb +0 -22
  232. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  233. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  234. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  235. data/lib/arel/attributes.rb +0 -22
  236. data/lib/arel/visitors/depth_first.rb +0 -203
  237. data/lib/arel/visitors/ibm_db.rb +0 -34
  238. data/lib/arel/visitors/informix.rb +0 -62
  239. data/lib/arel/visitors/mssql.rb +0 -156
  240. data/lib/arel/visitors/oracle.rb +0 -158
  241. data/lib/arel/visitors/oracle12.rb +0 -65
  242. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/inquiry"
4
+
5
+ module ActiveRecord
6
+ # == Delegated types
7
+ #
8
+ # Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
9
+ # purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
10
+ # where all attributes from all levels of the hierarchy are represented in a single table. Both have their
11
+ # places, but neither are without their drawbacks.
12
+ #
13
+ # The problem with purely abstract classes is that all concrete subclasses must persist all the shared
14
+ # attributes themselves in their own tables (also known as class-table inheritance). This makes it hard to
15
+ # do queries across the hierarchy. For example, imagine you have the following hierarchy:
16
+ #
17
+ # Entry < ApplicationRecord
18
+ # Message < Entry
19
+ # Comment < Entry
20
+ #
21
+ # How do you show a feed that has both +Message+ and +Comment+ records, which can be easily paginated?
22
+ # Well, you can't! Messages are backed by a messages table and comments by a comments table. You can't
23
+ # pull from both tables at once and use a consistent OFFSET/LIMIT scheme.
24
+ #
25
+ # You can get around the pagination problem by using single-table inheritance, but now you're forced into
26
+ # a single mega table with all the attributes from all subclasses. No matter how divergent. If a Message
27
+ # has a subject, but the comment does not, well, now the comment does anyway! So STI works best when there's
28
+ # little divergence between the subclasses and their attributes.
29
+ #
30
+ # But there's a third way: Delegated types. With this approach, the "superclass" is a concrete class
31
+ # that is represented by its own table, where all the superclass attributes that are shared amongst all the
32
+ # "subclasses" are stored. And then each of the subclasses have their own individual tables for additional
33
+ # attributes that are particular to their implementation. This is similar to what's called multi-table
34
+ # inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the
35
+ # hierarchy and share responsibilities.
36
+ #
37
+ # Let's look at that entry/message/comment example using delegated types:
38
+ #
39
+ # # Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
40
+ # class Entry < ApplicationRecord
41
+ # belongs_to :account
42
+ # belongs_to :creator
43
+ # delegated_type :entryable, types: %w[ Message Comment ]
44
+ # end
45
+ #
46
+ # module Entryable
47
+ # extend ActiveSupport::Concern
48
+ #
49
+ # included do
50
+ # has_one :entry, as: :entryable, touch: true
51
+ # end
52
+ # end
53
+ #
54
+ # # Schema: messages[ id, subject ]
55
+ # class Message < ApplicationRecord
56
+ # include Entryable
57
+ # has_rich_text :content
58
+ # end
59
+ #
60
+ # # Schema: comments[ id, content ]
61
+ # class Comment < ApplicationRecord
62
+ # include Entryable
63
+ # end
64
+ #
65
+ # As you can see, neither +Message+ nor +Comment+ are meant to stand alone. Crucial metadata for both classes
66
+ # resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
67
+ # in particular. You can now easily do things like:
68
+ #
69
+ # Account.entries.order(created_at: :desc).limit(50)
70
+ #
71
+ # Which is exactly what you want when displaying both comments and messages together. The entry itself can
72
+ # be rendered as its delegated type easily, like so:
73
+ #
74
+ # # entries/_entry.html.erb
75
+ # <%= render "entries/entryables/#{entry.entryable_name}", entry: entry %>
76
+ #
77
+ # # entries/entryables/_message.html.erb
78
+ # <div class="message">
79
+ # Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %>
80
+ # </div>
81
+ #
82
+ # # entries/entryables/_comment.html.erb
83
+ # <div class="comment">
84
+ # <%= entry.creator.name %> said: <%= entry.comment.content %>
85
+ # </div>
86
+ #
87
+ # == Sharing behavior with concerns and controllers
88
+ #
89
+ # The entry "superclass" also serves as a perfect place to put all that shared logic that applies to both
90
+ # messages and comments, and which acts primarily on the shared attributes. Imagine:
91
+ #
92
+ # class Entry < ApplicationRecord
93
+ # include Eventable, Forwardable, Redeliverable
94
+ # end
95
+ #
96
+ # Which allows you to have controllers for things like +ForwardsController+ and +RedeliverableController+
97
+ # that both act on entries, and thus provide the shared functionality to both messages and comments.
98
+ #
99
+ # == Creating new records
100
+ #
101
+ # You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
102
+ # like so:
103
+ #
104
+ # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
105
+ #
106
+ # If you need more complicated composition, or you need to perform dependent validation, you should build a factory
107
+ # method or class to take care of the complicated needs. This could be as simple as:
108
+ #
109
+ # class Entry < ApplicationRecord
110
+ # def self.create_with_comment(content, creator: Current.user)
111
+ # create! entryable: Comment.new(content: content), creator: creator
112
+ # end
113
+ # end
114
+ #
115
+ # == Adding further delegation
116
+ #
117
+ # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
118
+ # an anti-pattern most of the time. The reason you're building this hierarchy is to take advantage of polymorphism.
119
+ # So here's a simple example of that:
120
+ #
121
+ # class Entry < ApplicationRecord
122
+ # delegated_type :entryable, types: %w[ Message Comment ]
123
+ # delegate :title, to: :entryable
124
+ # end
125
+ #
126
+ # class Message < ApplicationRecord
127
+ # def title
128
+ # subject
129
+ # end
130
+ # end
131
+ #
132
+ # class Comment < ApplicationRecord
133
+ # def title
134
+ # content.truncate(20)
135
+ # end
136
+ # end
137
+ #
138
+ # Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
139
+ module DelegatedType
140
+ # Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
141
+ # That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
142
+ # type convenience methods:
143
+ #
144
+ # class Entry < ApplicationRecord
145
+ # delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
146
+ # end
147
+ #
148
+ # Entry#entryable_class # => +Message+ or +Comment+
149
+ # Entry#entryable_name # => "message" or "comment"
150
+ # Entry.messages # => Entry.where(entryable_type: "Message")
151
+ # Entry#message? # => true when entryable_type == "Message"
152
+ # Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
153
+ # Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
154
+ # Entry.comments # => Entry.where(entryable_type: "Comment")
155
+ # Entry#comment? # => true when entryable_type == "Comment"
156
+ # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
157
+ # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
158
+ #
159
+ # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
160
+ #
161
+ # You can also declare namespaced types:
162
+ #
163
+ # class Entry < ApplicationRecord
164
+ # delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy
165
+ # end
166
+ #
167
+ # Entry.access_notice_messages
168
+ # entry.access_notice_message
169
+ # entry.access_notice_message?
170
+ def delegated_type(role, types:, **options)
171
+ belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
172
+ define_delegated_type_methods role, types: types
173
+ end
174
+
175
+ private
176
+ def define_delegated_type_methods(role, types:)
177
+ role_type = "#{role}_type"
178
+ role_id = "#{role}_id"
179
+
180
+ define_method "#{role}_class" do
181
+ public_send("#{role}_type").constantize
182
+ end
183
+
184
+ define_method "#{role}_name" do
185
+ public_send("#{role}_class").model_name.singular.inquiry
186
+ end
187
+
188
+ types.each do |type|
189
+ scope_name = type.tableize.gsub("/", "_")
190
+ singular = scope_name.singularize
191
+ query = "#{singular}?"
192
+
193
+ scope scope_name, -> { where(role_type => type) }
194
+
195
+ define_method query do
196
+ public_send(role_type) == type
197
+ end
198
+
199
+ define_method singular do
200
+ public_send(role) if public_send(query)
201
+ end
202
+
203
+ define_method "#{singular}_id" do
204
+ public_send(role_id) if public_send(query)
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DestroyAssociationAsyncError < StandardError
5
+ end
6
+
7
+ # Job to destroy the records associated with a destroyed record in background.
8
+ class DestroyAssociationAsyncJob < ActiveJob::Base
9
+ queue_as { ActiveRecord::Base.queues[:destroy] }
10
+
11
+ discard_on ActiveJob::DeserializationError
12
+
13
+ def perform(
14
+ owner_model_name: nil, owner_id: nil,
15
+ association_class: nil, association_ids: nil, association_primary_key_column: nil,
16
+ ensuring_owner_was_method: nil
17
+ )
18
+ association_model = association_class.constantize
19
+ owner_class = owner_model_name.constantize
20
+ owner = owner_class.find_by(owner_class.primary_key.to_sym => owner_id)
21
+
22
+ if !owner_destroyed?(owner, ensuring_owner_was_method)
23
+ raise DestroyAssociationAsyncError, "owner record not destroyed"
24
+ end
25
+
26
+ association_model.where(association_primary_key_column => association_ids).find_each do |r|
27
+ r.destroy
28
+ end
29
+ end
30
+
31
+ private
32
+ def owner_destroyed?(owner, ensuring_owner_was_method)
33
+ !owner || (ensuring_owner_was_method && owner.public_send(ensuring_owner_was_method))
34
+ end
35
+ end
36
+ end
@@ -41,13 +41,20 @@ module ActiveRecord
41
41
  # Conversation.where(status: [:active, :archived])
42
42
  # Conversation.where.not(status: :active)
43
43
  #
44
- # You can set the default value from the database declaration, like:
44
+ # Defining scopes can be disabled by setting +:_scopes+ to +false+.
45
45
  #
46
- # create_table :conversations do |t|
47
- # t.column :status, :integer, default: 0
46
+ # class Conversation < ActiveRecord::Base
47
+ # enum status: [ :active, :archived ], _scopes: false
48
+ # end
49
+ #
50
+ # You can set the default enum value by setting +:_default+, like:
51
+ #
52
+ # class Conversation < ActiveRecord::Base
53
+ # enum status: [ :active, :archived ], _default: "active"
48
54
  # end
49
55
  #
50
- # Good practice is to let the first declared status be the default.
56
+ # conversation = Conversation.new
57
+ # conversation.status # => "active"
51
58
  #
52
59
  # Finally, it's also possible to explicitly map the relation between attribute and
53
60
  # database integer with a hash:
@@ -117,28 +124,31 @@ module ActiveRecord
117
124
  end
118
125
 
119
126
  def cast(value)
120
- return if value.blank?
121
-
122
127
  if mapping.has_key?(value)
123
128
  value.to_s
124
129
  elsif mapping.has_value?(value)
125
130
  mapping.key(value)
131
+ elsif value.blank?
132
+ nil
126
133
  else
127
134
  assert_valid_value(value)
128
135
  end
129
136
  end
130
137
 
131
138
  def deserialize(value)
132
- return if value.nil?
133
139
  mapping.key(subtype.deserialize(value))
134
140
  end
135
141
 
142
+ def serializable?(value)
143
+ (value.blank? || mapping.has_key?(value) || mapping.has_value?(value)) && super
144
+ end
145
+
136
146
  def serialize(value)
137
147
  mapping.fetch(value, value)
138
148
  end
139
149
 
140
150
  def assert_valid_value(value)
141
- unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
151
+ unless serializable?(value)
142
152
  raise ArgumentError, "'#{value}' is not a valid #{name}"
143
153
  end
144
154
  end
@@ -149,9 +159,14 @@ module ActiveRecord
149
159
 
150
160
  def enum(definitions)
151
161
  klass = self
162
+
152
163
  enum_prefix = definitions.delete(:_prefix)
153
164
  enum_suffix = definitions.delete(:_suffix)
154
165
  enum_scopes = definitions.delete(:_scopes)
166
+
167
+ default = {}
168
+ default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
169
+
155
170
  definitions.each do |name, values|
156
171
  assert_valid_enum_definition_values(values)
157
172
  # statuses = { }
@@ -167,7 +182,8 @@ module ActiveRecord
167
182
  detect_enum_conflict!(name, "#{name}=")
168
183
 
169
184
  attr = attribute_alias?(name) ? attribute_alias(name) : name
170
- decorate_attribute_type(attr, :enum) do |subtype|
185
+
186
+ decorate_attribute_type(attr, **default) do |subtype|
171
187
  EnumType.new(attr, enum_values, subtype)
172
188
  end
173
189
 
@@ -186,7 +202,8 @@ module ActiveRecord
186
202
  suffix = "_#{enum_suffix}"
187
203
  end
188
204
 
189
- value_method_name = "#{prefix}#{label}#{suffix}"
205
+ method_friendly_label = label.to_s.gsub(/\W+/, "_")
206
+ value_method_name = "#{prefix}#{method_friendly_label}#{suffix}"
190
207
  value_method_names << value_method_name
191
208
  enum_values[label] = value
192
209
  label = label.to_s
@@ -7,6 +7,10 @@ module ActiveRecord
7
7
  class ActiveRecordError < StandardError
8
8
  end
9
9
 
10
+ # Raised when trying to use a feature in Active Record which requires Active Job but the gem is not present.
11
+ class ActiveJobRequiredError < ActiveRecordError
12
+ end
13
+
10
14
  # Raised when the single-table inheritance mechanism fails to locate the subclass
11
15
  # (for example due to improper usage of column that
12
16
  # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
@@ -38,6 +42,10 @@ module ActiveRecord
38
42
  class AdapterNotSpecified < ActiveRecordError
39
43
  end
40
44
 
45
+ # Raised when a model makes a query but it has not specified an associated table.
46
+ class TableNotSpecified < ActiveRecordError
47
+ end
48
+
41
49
  # Raised when Active Record cannot find database adapter specified in
42
50
  # +config/database.yml+ or programmatically.
43
51
  class AdapterNotFound < ActiveRecordError
@@ -49,6 +57,19 @@ module ActiveRecord
49
57
  class ConnectionNotEstablished < ActiveRecordError
50
58
  end
51
59
 
60
+ # Raised when a connection could not be obtained within the connection
61
+ # acquisition timeout period: because max connections in pool
62
+ # are in use.
63
+ class ConnectionTimeoutError < ConnectionNotEstablished
64
+ end
65
+
66
+ # Raised when a pool was unable to get ahold of all its connections
67
+ # to perform a "group" action such as
68
+ # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
69
+ # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
70
+ class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
71
+ end
72
+
52
73
  # Raised when a write to the database is attempted on a read only connection.
53
74
  class ReadOnlyError < ActiveRecordError
54
75
  end
@@ -102,7 +123,7 @@ module ActiveRecord
102
123
  # Wraps the underlying database error as +cause+.
103
124
  class StatementInvalid < ActiveRecordError
104
125
  def initialize(message = nil, sql: nil, binds: nil)
105
- super(message || $!.try(:message))
126
+ super(message || $!&.message)
106
127
  @sql = sql
107
128
  @binds = binds
108
129
  end
@@ -169,9 +190,9 @@ module ActiveRecord
169
190
  class RangeError < StatementInvalid
170
191
  end
171
192
 
172
- # Raised when number of bind variables in statement given to +:condition+ key
173
- # (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method)
174
- # does not match number of expected values supplied.
193
+ # Raised when the number of placeholders in an SQL fragment passed to
194
+ # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]
195
+ # does not match the number of values supplied.
175
196
  #
176
197
  # For example, when there are two placeholders with only one value supplied:
177
198
  #
@@ -183,6 +204,10 @@ module ActiveRecord
183
204
  class NoDatabaseError < StatementInvalid
184
205
  end
185
206
 
207
+ # Raised when creating a database if it exists.
208
+ class DatabaseAlreadyExists < StatementInvalid
209
+ end
210
+
186
211
  # Raised when PostgreSQL returns 'cached plan must not change result type' and
187
212
  # we cannot retry gracefully (e.g. inside a transaction)
188
213
  class PreparedStatementCacheExpired < StatementInvalid
@@ -220,6 +245,10 @@ module ActiveRecord
220
245
  class ReadOnlyRecord < ActiveRecordError
221
246
  end
222
247
 
248
+ # Raised on attempt to lazily load records that are marked as strict loading.
249
+ class StrictLoadingViolationError < ActiveRecordError
250
+ end
251
+
223
252
  # {ActiveRecord::Base.transaction}[rdoc-ref:Transactions::ClassMethods#transaction]
224
253
  # uses this exception to distinguish a deliberate rollback from other exceptional situations.
225
254
  # Normally, raising an exception will cause the
@@ -330,7 +359,7 @@ module ActiveRecord
330
359
  # See the following:
331
360
  #
332
361
  # * https://www.postgresql.org/docs/current/static/transaction-iso.html
333
- # * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock
362
+ # * https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html#error_er_lock_deadlock
334
363
  class TransactionRollbackError < StatementInvalid
335
364
  end
336
365
 
@@ -349,30 +378,36 @@ module ActiveRecord
349
378
  class IrreversibleOrderError < ActiveRecordError
350
379
  end
351
380
 
381
+ # Superclass for errors that have been aborted (either by client or server).
382
+ class QueryAborted < StatementInvalid
383
+ end
384
+
352
385
  # LockWaitTimeout will be raised when lock wait timeout exceeded.
353
386
  class LockWaitTimeout < StatementInvalid
354
387
  end
355
388
 
356
389
  # StatementTimeout will be raised when statement timeout exceeded.
357
- class StatementTimeout < StatementInvalid
390
+ class StatementTimeout < QueryAborted
358
391
  end
359
392
 
360
393
  # QueryCanceled will be raised when canceling statement due to user request.
361
- class QueryCanceled < StatementInvalid
394
+ class QueryCanceled < QueryAborted
395
+ end
396
+
397
+ # AdapterTimeout will be raised when database clients times out while waiting from the server.
398
+ class AdapterTimeout < QueryAborted
362
399
  end
363
400
 
364
401
  # UnknownAttributeReference is raised when an unknown and potentially unsafe
365
- # value is passed to a query method when allow_unsafe_raw_sql is set to
366
- # :disabled. For example, passing a non column name value to a relation's
367
- # #order method might cause this exception.
402
+ # value is passed to a query method. For example, passing a non column name
403
+ # value to a relation's #order method might cause this exception.
368
404
  #
369
405
  # When working around this exception, caution should be taken to avoid SQL
370
406
  # injection vulnerabilities when passing user-provided values to query
371
407
  # methods. Known-safe values can be passed to query methods by wrapping them
372
408
  # in Arel.sql.
373
409
  #
374
- # For example, with allow_unsafe_raw_sql set to :disabled, the following
375
- # code would raise this exception:
410
+ # For example, the following code would raise this exception:
376
411
  #
377
412
  # Post.order("length(title)").first
378
413
  #
@@ -37,13 +37,18 @@ module ActiveRecord
37
37
 
38
38
  private
39
39
  def render_bind(attr)
40
- value = if attr.type.binary? && attr.value
41
- "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
40
+ if ActiveModel::Attribute === attr
41
+ value = if attr.type.binary? && attr.value
42
+ "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
43
+ else
44
+ connection.type_cast(attr.value_for_database)
45
+ end
42
46
  else
43
- connection.type_cast(attr.value_for_database)
47
+ value = connection.type_cast(attr)
48
+ attr = nil
44
49
  end
45
50
 
46
- [attr.name, value]
51
+ [attr&.name, value]
47
52
  end
48
53
  end
49
54
  end
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  payload[:exception] ||
27
27
  payload[:cached] ||
28
28
  IGNORED_PAYLOADS.include?(payload[:name]) ||
29
- payload[:sql] !~ EXPLAINED_SQLS
29
+ !payload[:sql].match?(EXPLAINED_SQLS)
30
30
  end
31
31
 
32
32
  ActiveSupport::Notifications.subscribe("sql.active_record", new)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
- require "yaml"
3
+ require "active_support/configuration_file"
5
4
 
6
5
  module ActiveRecord
7
6
  class FixtureSet
@@ -29,6 +28,10 @@ module ActiveRecord
29
28
  config_row["model_class"]
30
29
  end
31
30
 
31
+ def ignored_fixtures
32
+ config_row["ignore"]
33
+ end
34
+
32
35
  private
33
36
  def rows
34
37
  @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" }
@@ -40,31 +43,21 @@ module ActiveRecord
40
43
  if row
41
44
  row.last
42
45
  else
43
- { 'model_class': nil }
46
+ { 'model_class': nil, 'ignore': nil }
44
47
  end
45
48
  end
46
49
  end
47
50
 
48
51
  def raw_rows
49
52
  @raw_rows ||= begin
50
- data = YAML.load(render(IO.read(@file)))
53
+ data = ActiveSupport::ConfigurationFile.parse(@file, context:
54
+ ActiveRecord::FixtureSet::RenderContext.create_subclass.new.get_binding)
51
55
  data ? validate(data).to_a : []
52
- rescue ArgumentError, Psych::SyntaxError => error
53
- raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
56
+ rescue RuntimeError => error
57
+ raise Fixture::FormatError, error.message
54
58
  end
55
59
  end
56
60
 
57
- def prepare_erb(content)
58
- erb = ERB.new(content)
59
- erb.filename = @file
60
- erb
61
- end
62
-
63
- def render(content)
64
- context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
65
- prepare_erb(content).result(context.get_binding)
66
- end
67
-
68
61
  # Validate our unmarshalled data.
69
62
  def validate(data)
70
63
  unless Hash === data || YAML::Omap === data
@@ -21,8 +21,7 @@ module ActiveRecord
21
21
  end
22
22
 
23
23
  def timestamp_column_names
24
- @timestamp_column_names ||=
25
- %w(created_at created_on updated_at updated_on) & @model_class.column_names
24
+ @model_class.all_timestamp_attributes_in_model
26
25
  end
27
26
 
28
27
  def inheritance_column_name
@@ -10,7 +10,7 @@ class ActiveRecord::FixtureSet::RenderContext # :nodoc:
10
10
  end
11
11
 
12
12
  def binary(path)
13
- %(!!binary "#{Base64.strict_encode64(File.read(path, mode: 'rb'))}")
13
+ %(!!binary "#{Base64.strict_encode64(File.binread(path))}")
14
14
  end
15
15
  end
16
16
  end
@@ -112,12 +112,12 @@ module ActiveRecord
112
112
  case association.macro
113
113
  when :belongs_to
114
114
  # Do not replace association name with association foreign key if they are named the same
115
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
115
+ fk_name = association.join_foreign_key
116
116
 
117
117
  if association.name.to_s != fk_name && value = @row.delete(association.name.to_s)
118
118
  if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
119
119
  # support polymorphic belongs_to as "label (Type)"
120
- @row[association.foreign_type] = $1
120
+ @row[association.join_foreign_type] = $1
121
121
  end
122
122
 
123
123
  fk_type = reflection_class.type_for_attribute(fk_name).type