activerecord 7.1.3.4 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2133
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. metadata +17 -12
@@ -73,7 +73,7 @@ module ActiveRecord
73
73
  end
74
74
  self.configurations = {}
75
75
 
76
- # Returns fully resolved ActiveRecord::DatabaseConfigurations object
76
+ # Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
77
77
  def self.configurations
78
78
  @@configurations
79
79
  end
@@ -102,6 +102,9 @@ module ActiveRecord
102
102
 
103
103
  class_attribute :shard_selector, instance_accessor: false, default: nil
104
104
 
105
+ # Specifies the attributes that will be included in the output of the #inspect method
106
+ class_attribute :attributes_for_inspect, instance_accessor: false, default: [:id]
107
+
105
108
  def self.application_record_class? # :nodoc:
106
109
  if ActiveRecord.application_record_class
107
110
  self == ActiveRecord.application_record_class
@@ -351,7 +354,7 @@ module ActiveRecord
351
354
  elsif abstract_class?
352
355
  "#{super}(abstract)"
353
356
  elsif !connected?
354
- "#{super} (call '#{super}.connection' to establish a connection)"
357
+ "#{super} (call '#{super}.lease_connection' to establish a connection)"
355
358
  elsif table_exists?
356
359
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
357
360
  "#{super}(#{attr_list})"
@@ -373,7 +376,7 @@ module ActiveRecord
373
376
  TypeCaster::Map.new(self)
374
377
  end
375
378
 
376
- def cached_find_by_statement(key, &block) # :nodoc:
379
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
377
380
  cache = @find_by_statement_cache[connection.prepared_statements]
378
381
  cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
379
382
  end
@@ -396,8 +399,8 @@ module ActiveRecord
396
399
  @arel_table = nil
397
400
  @predicate_builder = nil
398
401
  @inspection_filter = nil
399
- @filter_attributes = nil
400
- @generated_association_methods = nil
402
+ @filter_attributes ||= nil
403
+ @generated_association_methods ||= nil
401
404
  end
402
405
  end
403
406
 
@@ -416,21 +419,23 @@ module ActiveRecord
416
419
  end
417
420
 
418
421
  def cached_find_by(keys, values)
419
- statement = cached_find_by_statement(keys) { |params|
420
- wheres = keys.index_with do |key|
421
- if key.is_a?(Array)
422
- [key.map { params.bind }]
423
- else
424
- params.bind
422
+ with_connection do |connection|
423
+ statement = cached_find_by_statement(connection, keys) { |params|
424
+ wheres = keys.index_with do |key|
425
+ if key.is_a?(Array)
426
+ [key.map { params.bind }]
427
+ else
428
+ params.bind
429
+ end
425
430
  end
426
- end
427
- where(wheres).limit(1)
428
- }
431
+ where(wheres).limit(1)
432
+ }
429
433
 
430
- begin
431
- statement.execute(values.flatten, connection).first
432
- rescue TypeError
433
- raise ActiveRecord::StatementInvalid
434
+ begin
435
+ statement.execute(values.flatten, connection, allow_retry: true).first
436
+ rescue TypeError
437
+ raise ActiveRecord::StatementInvalid
438
+ end
434
439
  end
435
440
  end
436
441
  end
@@ -450,7 +455,7 @@ module ActiveRecord
450
455
  init_internals
451
456
  initialize_internals_callback
452
457
 
453
- assign_attributes(attributes) if attributes
458
+ super
454
459
 
455
460
  yield self if block_given?
456
461
  _run_initialize_callbacks
@@ -701,6 +706,11 @@ module ActiveRecord
701
706
  @strict_loading_mode == :n_plus_one_only
702
707
  end
703
708
 
709
+ # Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
710
+ def strict_loading_all?
711
+ @strict_loading_mode == :all
712
+ end
713
+
704
714
  # Marks this record as read only.
705
715
  #
706
716
  # customer = Customer.first
@@ -714,21 +724,14 @@ module ActiveRecord
714
724
  self.class.connection_handler
715
725
  end
716
726
 
717
- # Returns the contents of the record as a nicely formatted string.
727
+ # Returns the attributes specified by <tt>.attributes_for_inspect</tt> as a nicely formatted string.
718
728
  def inspect
719
- # We check defined?(@attributes) not to issue warnings if the object is
720
- # allocated but not initialized.
721
- inspection = if defined?(@attributes) && @attributes
722
- attribute_names.filter_map do |name|
723
- if _has_attribute?(name)
724
- "#{name}: #{attribute_for_inspect(name)}"
725
- end
726
- end.join(", ")
727
- else
728
- "not initialized"
729
- end
729
+ inspect_with_attributes(attributes_for_inspect)
730
+ end
730
731
 
731
- "#<#{self.class} #{inspection}>"
732
+ # Returns the full contents of the record as a nicely formatted string.
733
+ def full_inspect
734
+ inspect_with_attributes(attribute_names)
732
735
  end
733
736
 
734
737
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -736,17 +739,17 @@ module ActiveRecord
736
739
  def pretty_print(pp)
737
740
  return super if custom_inspect_method_defined?
738
741
  pp.object_address_group(self) do
739
- if defined?(@attributes) && @attributes
740
- attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
742
+ if @attributes
743
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
741
744
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
745
+ attr_name = attr_name.to_s
742
746
  pp.breakable " "
743
747
  pp.group(1) do
744
748
  pp.text attr_name
745
749
  pp.text ":"
746
750
  pp.breakable
747
- value = _read_attribute(attr_name)
748
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
749
- pp.pp value
751
+ value = attribute_for_inspect(attr_name)
752
+ pp.text value
750
753
  end
751
754
  end
752
755
  else
@@ -784,7 +787,6 @@ module ActiveRecord
784
787
  @strict_loading_mode = :all
785
788
 
786
789
  klass.define_attribute_methods
787
- klass.generate_alias_attributes
788
790
  end
789
791
 
790
792
  def initialize_internals_callback
@@ -804,5 +806,24 @@ module ActiveRecord
804
806
  def inspection_filter
805
807
  self.class.inspection_filter
806
808
  end
809
+
810
+ def inspect_with_attributes(attributes_to_list)
811
+ inspection = if @attributes
812
+ attributes_to_list.filter_map do |name|
813
+ name = name.to_s
814
+ if _has_attribute?(name)
815
+ "#{name}: #{attribute_for_inspect(name)}"
816
+ end
817
+ end.join(", ")
818
+ else
819
+ "not initialized"
820
+ end
821
+
822
+ "#<#{self.class} #{inspection}>"
823
+ end
824
+
825
+ def attributes_for_inspect
826
+ self.class.attributes_for_inspect == :all ? attribute_names : self.class.attributes_for_inspect
827
+ end
807
828
  end
808
829
  end
@@ -7,6 +7,7 @@ module ActiveRecord
7
7
 
8
8
  included do
9
9
  class_attribute :_counter_cache_columns, instance_accessor: false, default: []
10
+ class_attribute :counter_cached_association_names, instance_writer: false, default: []
10
11
  end
11
12
 
12
13
  module ClassMethods
@@ -65,7 +66,7 @@ module ActiveRecord
65
66
  updates.merge!(touch_updates)
66
67
  end
67
68
 
68
- unscoped.where(primary_key => object.id).update_all(updates) if updates.any?
69
+ unscoped.where(primary_key => [object.id]).update_all(updates) if updates.any?
69
70
 
70
71
  true
71
72
  end
@@ -112,6 +113,7 @@ module ActiveRecord
112
113
  # # `updated_at` = '2016-10-13T09:59:23-05:00'
113
114
  # # WHERE id IN (10, 15)
114
115
  def update_counters(id, counters)
116
+ id = [id] if composite_primary_key? && id.is_a?(Array) && !id[0].is_a?(Array)
115
117
  unscoped.where!(primary_key => id).update_counters(counters)
116
118
  end
117
119
 
@@ -180,14 +182,26 @@ module ActiveRecord
180
182
  def counter_cache_column?(name) # :nodoc:
181
183
  _counter_cache_columns.include?(name)
182
184
  end
185
+
186
+ def load_schema! # :nodoc:
187
+ super
188
+
189
+ association_names = _reflections.filter_map do |name, reflection|
190
+ next unless reflection.belongs_to? && reflection.counter_cache_column
191
+
192
+ name.to_sym
193
+ end
194
+
195
+ self.counter_cached_association_names |= association_names
196
+ end
183
197
  end
184
198
 
185
199
  private
186
200
  def _create_record(attribute_names = self.attribute_names)
187
201
  id = super
188
202
 
189
- each_counter_cached_associations do |association|
190
- association.increment_counters
203
+ counter_cached_association_names.each do |association_name|
204
+ association(association_name).increment_counters
191
205
  end
192
206
 
193
207
  id
@@ -197,9 +211,10 @@ module ActiveRecord
197
211
  affected_rows = super
198
212
 
199
213
  if affected_rows > 0
200
- each_counter_cached_associations do |association|
201
- foreign_key = association.reflection.foreign_key.to_sym
202
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
214
+ counter_cached_association_names.each do |association_name|
215
+ association = association(association_name)
216
+
217
+ unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
203
218
  association.decrement_counters
204
219
  end
205
220
  end
@@ -208,10 +223,8 @@ module ActiveRecord
208
223
  affected_rows
209
224
  end
210
225
 
211
- def each_counter_cached_associations
212
- _reflections.each do |name, reflection|
213
- yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
214
- end
226
+ def _foreign_keys_equal?(fkey1, fkey2)
227
+ fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
215
228
  end
216
229
  end
217
230
  end
@@ -25,8 +25,7 @@ module ActiveRecord
25
25
  def initialize(url)
26
26
  raise "Database URL cannot be empty" if url.blank?
27
27
  @uri = uri_parser.parse(url)
28
- @adapter = @uri.scheme && @uri.scheme.tr("-", "_")
29
- @adapter = "postgresql" if @adapter == "postgres"
28
+ @adapter = resolved_adapter
30
29
 
31
30
  if @uri.opaque
32
31
  @uri.opaque, @query = @uri.opaque.split("?", 2)
@@ -80,6 +79,12 @@ module ActiveRecord
80
79
  end
81
80
  end
82
81
 
82
+ def resolved_adapter
83
+ adapter = uri.scheme && @uri.scheme.tr("-", "_")
84
+ adapter = ActiveRecord.protocol_adapters[adapter] || adapter
85
+ adapter
86
+ end
87
+
83
88
  # Returns name of the database.
84
89
  def database_from_path
85
90
  if @adapter == "sqlite3"
@@ -11,14 +11,21 @@ module ActiveRecord
11
11
  def initialize(env_name, name)
12
12
  @env_name = env_name
13
13
  @name = name
14
+ @adapter_class = nil
14
15
  end
15
16
 
16
- def adapter_method
17
- "#{adapter}_connection"
17
+ def adapter_class
18
+ @adapter_class ||= ActiveRecord::ConnectionAdapters.resolve(adapter)
18
19
  end
19
20
 
20
- def adapter_class_method
21
- "#{adapter}_adapter_class"
21
+ def new_connection
22
+ adapter_class.new(configuration_hash)
23
+ end
24
+
25
+ def validate!
26
+ adapter_class if adapter
27
+
28
+ true
22
29
  end
23
30
 
24
31
  def host
@@ -84,6 +91,10 @@ module ActiveRecord
84
91
  def schema_cache_path
85
92
  raise NotImplementedError
86
93
  end
94
+
95
+ def use_metadata_table?
96
+ raise NotImplementedError
97
+ end
87
98
  end
88
99
  end
89
100
  end
@@ -1,53 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActiveRecord
4
6
  class DatabaseConfigurations
5
- # = Active Record Database Hash Config
7
+ # # Active Record Database Hash Config
6
8
  #
7
- # A +HashConfig+ object is created for each database configuration entry that
8
- # is created from a hash.
9
+ # A `HashConfig` object is created for each database configuration entry that is
10
+ # created from a hash.
9
11
  #
10
12
  # A hash config:
11
13
  #
12
- # { "development" => { "database" => "db_name" } }
14
+ # { "development" => { "database" => "db_name" } }
13
15
  #
14
16
  # Becomes:
15
17
  #
16
- # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
17
- # @env_name="development", @name="primary", @config={database: "db_name"}>
18
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
19
+ # @env_name="development", @name="primary", @config={database: "db_name"}>
18
20
  #
19
21
  # See ActiveRecord::DatabaseConfigurations for more info.
20
22
  class HashConfig < DatabaseConfig
21
23
  attr_reader :configuration_hash
22
24
 
23
-
24
- # Initialize a new +HashConfig+ object
25
+ # Initialize a new `HashConfig` object
25
26
  #
26
- # ==== Options
27
+ # #### Parameters
28
+ #
29
+ # * `env_name` - The Rails environment, i.e. "development".
30
+ # * `name` - The db config name. In a standard two-tier database configuration
31
+ # this will default to "primary". In a multiple database three-tier database
32
+ # configuration this corresponds to the name used in the second tier, for
33
+ # example "primary_readonly".
34
+ # * `configuration_hash` - The config hash. This is the hash that contains the
35
+ # database adapter, name, and other important information for database
36
+ # connections.
27
37
  #
28
- # * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
29
- # * <tt>:name</tt> - The db config name. In a standard two-tier
30
- # database configuration this will default to "primary". In a multiple
31
- # database three-tier database configuration this corresponds to the name
32
- # used in the second tier, for example "primary_readonly".
33
- # * <tt>:config</tt> - The config hash. This is the hash that contains the
34
- # database adapter, name, and other important information for database
35
- # connections.
36
38
  def initialize(env_name, name, configuration_hash)
37
39
  super(env_name, name)
38
40
  @configuration_hash = configuration_hash.symbolize_keys.freeze
39
41
  end
40
42
 
41
43
  # Determines whether a database configuration is for a replica / readonly
42
- # connection. If the +replica+ key is present in the config, +replica?+ will
43
- # return +true+.
44
+ # connection. If the `replica` key is present in the config, `replica?` will
45
+ # return `true`.
44
46
  def replica?
45
47
  configuration_hash[:replica]
46
48
  end
47
49
 
48
- # The migrations paths for a database configuration. If the
49
- # +migrations_paths+ key is present in the config, +migrations_paths+
50
- # will return its value.
50
+ # The migrations paths for a database configuration. If the `migrations_paths`
51
+ # key is present in the config, `migrations_paths` will return its value.
51
52
  def migrations_paths
52
53
  configuration_hash[:migrations_paths]
53
54
  end
@@ -92,8 +93,8 @@ module ActiveRecord
92
93
  (configuration_hash[:checkout_timeout] || 5).to_f
93
94
  end
94
95
 
95
- # +reaping_frequency+ is configurable mostly for historical reasons, but it could
96
- # also be useful if someone wants a very low +idle_timeout+.
96
+ # `reaping_frequency` is configurable mostly for historical reasons, but it
97
+ # could also be useful if someone wants a very low `idle_timeout`.
97
98
  def reaping_frequency
98
99
  configuration_hash.fetch(:reaping_frequency, 60)&.to_f
99
100
  end
@@ -104,18 +105,21 @@ module ActiveRecord
104
105
  end
105
106
 
106
107
  def adapter
107
- configuration_hash[:adapter]
108
+ configuration_hash[:adapter]&.to_s
108
109
  end
109
110
 
110
- # The path to the schema cache dump file for a database.
111
- # If omitted, the filename will be read from ENV or a
112
- # default will be derived.
111
+ # The path to the schema cache dump file for a database. If omitted, the
112
+ # filename will be read from ENV or a default will be derived.
113
113
  def schema_cache_path
114
114
  configuration_hash[:schema_cache_path]
115
115
  end
116
116
 
117
- def default_schema_cache_path
118
- "db/schema_cache.yml"
117
+ def default_schema_cache_path(db_dir = "db")
118
+ if primary?
119
+ File.join(db_dir, "schema_cache.yml")
120
+ else
121
+ File.join(db_dir, "#{name}_schema_cache.yml")
122
+ end
119
123
  end
120
124
 
121
125
  def lazy_schema_cache_path
@@ -126,14 +130,14 @@ module ActiveRecord
126
130
  Base.configurations.primary?(name)
127
131
  end
128
132
 
129
- # Determines whether to dump the schema/structure files and the
130
- # filename that should be used.
133
+ # Determines whether to dump the schema/structure files and the filename that
134
+ # should be used.
131
135
  #
132
- # If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
133
- # the schema will not be dumped.
136
+ # If `configuration_hash[:schema_dump]` is set to `false` or `nil` the schema
137
+ # will not be dumped.
134
138
  #
135
- # If the config option is set that will be used. Otherwise \Rails
136
- # will generate the filename from the database config name.
139
+ # If the config option is set that will be used. Otherwise Rails will generate
140
+ # the filename from the database config name.
137
141
  def schema_dump(format = ActiveRecord.schema_format)
138
142
  if configuration_hash.key?(:schema_dump)
139
143
  if config = configuration_hash[:schema_dump]
@@ -150,6 +154,10 @@ module ActiveRecord
150
154
  !replica? && !!configuration_hash.fetch(:database_tasks, true)
151
155
  end
152
156
 
157
+ def use_metadata_table? # :nodoc:
158
+ configuration_hash.fetch(:use_metadata_table, true)
159
+ end
160
+
153
161
  private
154
162
  def schema_file_type(format)
155
163
  case format
@@ -41,10 +41,29 @@ module ActiveRecord
41
41
  super(env_name, name, configuration_hash)
42
42
 
43
43
  @url = url
44
- @configuration_hash = @configuration_hash.merge(build_url_hash).freeze
44
+ @configuration_hash = @configuration_hash.merge(build_url_hash)
45
+
46
+ if @configuration_hash[:schema_dump] == "false"
47
+ @configuration_hash[:schema_dump] = false
48
+ end
49
+
50
+ if @configuration_hash[:query_cache] == "false"
51
+ @configuration_hash[:query_cache] = false
52
+ end
53
+
54
+ to_boolean!(@configuration_hash, :replica)
55
+ to_boolean!(@configuration_hash, :database_tasks)
56
+
57
+ @configuration_hash.freeze
45
58
  end
46
59
 
47
60
  private
61
+ def to_boolean!(configuration_hash, key)
62
+ if configuration_hash[key].is_a?(String)
63
+ configuration_hash[key] = configuration_hash[key] != "false"
64
+ end
65
+ end
66
+
48
67
  # Return a Hash that can be merged into the main config that represents
49
68
  # the passed in url
50
69
  def build_url_hash
@@ -113,7 +113,7 @@ module ActiveRecord
113
113
 
114
114
  if name
115
115
  configs.find do |db_config|
116
- db_config.name == name
116
+ db_config.name == name.to_s
117
117
  end
118
118
  else
119
119
  configs
@@ -36,7 +36,7 @@ module ActiveRecord
36
36
  #
37
37
  # Let's look at that entry/message/comment example using delegated types:
38
38
  #
39
- # # Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
39
+ # # Schema: entries[ id, account_id, creator_id, entryable_type, entryable_id, created_at, updated_at ]
40
40
  # class Entry < ApplicationRecord
41
41
  # belongs_to :account
42
42
  # belongs_to :creator
@@ -51,12 +51,12 @@ module ActiveRecord
51
51
  # end
52
52
  # end
53
53
  #
54
- # # Schema: messages[ id, subject, body ]
54
+ # # Schema: messages[ id, subject, body, created_at, updated_at ]
55
55
  # class Message < ApplicationRecord
56
56
  # include Entryable
57
57
  # end
58
58
  #
59
- # # Schema: comments[ id, content ]
59
+ # # Schema: comments[ id, content, created_at, updated_at ]
60
60
  # class Comment < ApplicationRecord
61
61
  # include Entryable
62
62
  # end
@@ -102,17 +102,37 @@ module ActiveRecord
102
102
  # You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
103
103
  # like so:
104
104
  #
105
- # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
105
+ # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account
106
106
  #
107
107
  # If you need more complicated composition, or you need to perform dependent validation, you should build a factory
108
108
  # method or class to take care of the complicated needs. This could be as simple as:
109
109
  #
110
110
  # class Entry < ApplicationRecord
111
- # def self.create_with_comment(content, creator: Current.user)
112
- # create! entryable: Comment.new(content: content), creator: creator
111
+ # def self.create_with_comment(content, creator: Current.user, account: Current.account)
112
+ # create! entryable: Comment.new(content: content), creator: creator, account: account
113
113
  # end
114
114
  # end
115
115
  #
116
+ # == Querying across records
117
+ #
118
+ # A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
119
+ # tricky, but not impossible.
120
+ #
121
+ # The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
122
+ # in appropriate places:
123
+ #
124
+ # Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
125
+ #
126
+ # For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
127
+ # the method:
128
+ #
129
+ # # app/models/concerns/entryable.rb
130
+ # scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
131
+ #
132
+ # Now the query can be shortened significantly:
133
+ #
134
+ # Comment.where(content: 'Hello!').with_entry(creator: Current.user)
135
+ #
116
136
  # == Adding further delegation
117
137
  #
118
138
  # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
@@ -219,6 +239,10 @@ module ActiveRecord
219
239
  role_type = options[:foreign_type] || "#{role}_type"
220
240
  role_id = options[:foreign_key] || "#{role}_id"
221
241
 
242
+ define_singleton_method "#{role}_types" do
243
+ types.map(&:to_s)
244
+ end
245
+
222
246
  define_method "#{role}_class" do
223
247
  public_send(role_type).constantize
224
248
  end
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
  )
20
20
  association_model = association_class.constantize
21
21
  owner_class = owner_model_name.constantize
22
- owner = owner_class.find_by(owner_class.primary_key.to_sym => owner_id)
22
+ owner = owner_class.find_by(owner_class.primary_key => [owner_id])
23
23
 
24
24
  if !owner_destroyed?(owner, ensuring_owner_was_method)
25
25
  raise DestroyAssociationAsyncError, "owner record not destroyed"
@@ -12,12 +12,12 @@ module ActiveRecord
12
12
  end
13
13
  end
14
14
 
15
- def method_missing(name, *arguments, &block)
15
+ def method_missing(name, ...)
16
16
  match = Method.match(self, name)
17
17
 
18
18
  if match && match.valid?
19
19
  match.define
20
- send(name, *arguments, &block)
20
+ send(name, ...)
21
21
  else
22
22
  super
23
23
  end
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
85
85
  encrypted_attributes << name.to_sym
86
86
 
87
- attribute name do |cast_type|
87
+ decorate_attributes([name]) do |name, cast_type|
88
88
  scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
89
89
  downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
90
90
 
@@ -221,7 +221,7 @@ module ActiveRecord
221
221
  end
222
222
 
223
223
  def cant_modify_encrypted_attributes_when_frozen
224
- self.class&.encrypted_attributes.each do |attribute|
224
+ self.class.encrypted_attributes.each do |attribute|
225
225
  errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
226
226
  end
227
227
  end