activerecord 6.0.0.beta2 → 6.0.2.rc1

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +471 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +10 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +6 -2
  16. data/lib/active_record/associations/collection_proxy.rb +2 -2
  17. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  18. data/lib/active_record/associations/join_dependency.rb +14 -9
  19. data/lib/active_record/associations/join_dependency/join_association.rb +12 -3
  20. data/lib/active_record/associations/preloader.rb +13 -8
  21. data/lib/active_record/associations/preloader/association.rb +34 -30
  22. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  23. data/lib/active_record/attribute_methods.rb +3 -53
  24. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  25. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  26. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  27. data/lib/active_record/attribute_methods/query.rb +2 -3
  28. data/lib/active_record/attribute_methods/read.rb +3 -9
  29. data/lib/active_record/attribute_methods/write.rb +6 -12
  30. data/lib/active_record/attributes.rb +13 -0
  31. data/lib/active_record/autosave_association.rb +21 -7
  32. data/lib/active_record/base.rb +0 -1
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -11
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +88 -61
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -4
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  39. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  40. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +79 -22
  41. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
  42. data/lib/active_record/connection_adapters/abstract_adapter.rb +114 -34
  43. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +78 -73
  44. data/lib/active_record/connection_adapters/column.rb +17 -13
  45. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  46. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  49. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +10 -7
  51. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  53. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  54. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  55. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  56. data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
  57. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  58. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  59. data/lib/active_record/connection_adapters/postgresql_adapter.rb +68 -27
  60. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  61. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  62. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  63. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  64. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -114
  66. data/lib/active_record/connection_handling.rb +31 -13
  67. data/lib/active_record/core.rb +23 -24
  68. data/lib/active_record/database_configurations.rb +73 -44
  69. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  70. data/lib/active_record/database_configurations/url_config.rb +12 -12
  71. data/lib/active_record/dynamic_matchers.rb +1 -1
  72. data/lib/active_record/enum.rb +15 -0
  73. data/lib/active_record/errors.rb +1 -1
  74. data/lib/active_record/fixtures.rb +11 -6
  75. data/lib/active_record/gem_version.rb +2 -2
  76. data/lib/active_record/insert_all.rb +179 -0
  77. data/lib/active_record/integration.rb +13 -1
  78. data/lib/active_record/internal_metadata.rb +5 -1
  79. data/lib/active_record/locking/optimistic.rb +3 -4
  80. data/lib/active_record/log_subscriber.rb +1 -1
  81. data/lib/active_record/middleware/database_selector.rb +3 -3
  82. data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
  83. data/lib/active_record/migration.rb +62 -44
  84. data/lib/active_record/migration/command_recorder.rb +28 -14
  85. data/lib/active_record/migration/compatibility.rb +10 -0
  86. data/lib/active_record/model_schema.rb +3 -0
  87. data/lib/active_record/persistence.rb +206 -13
  88. data/lib/active_record/querying.rb +17 -12
  89. data/lib/active_record/railtie.rb +0 -1
  90. data/lib/active_record/railties/databases.rake +127 -25
  91. data/lib/active_record/reflection.rb +3 -3
  92. data/lib/active_record/relation.rb +99 -20
  93. data/lib/active_record/relation/calculations.rb +38 -40
  94. data/lib/active_record/relation/delegation.rb +22 -30
  95. data/lib/active_record/relation/finder_methods.rb +17 -12
  96. data/lib/active_record/relation/merger.rb +11 -16
  97. data/lib/active_record/relation/query_methods.rb +228 -76
  98. data/lib/active_record/relation/where_clause.rb +9 -5
  99. data/lib/active_record/sanitization.rb +33 -4
  100. data/lib/active_record/schema.rb +1 -1
  101. data/lib/active_record/schema_dumper.rb +10 -1
  102. data/lib/active_record/schema_migration.rb +1 -1
  103. data/lib/active_record/scoping/default.rb +6 -7
  104. data/lib/active_record/scoping/named.rb +3 -2
  105. data/lib/active_record/statement_cache.rb +2 -2
  106. data/lib/active_record/store.rb +48 -0
  107. data/lib/active_record/table_metadata.rb +9 -13
  108. data/lib/active_record/tasks/database_tasks.rb +109 -6
  109. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  110. data/lib/active_record/test_databases.rb +1 -16
  111. data/lib/active_record/test_fixtures.rb +1 -0
  112. data/lib/active_record/timestamp.rb +26 -16
  113. data/lib/active_record/touch_later.rb +4 -2
  114. data/lib/active_record/transactions.rb +56 -46
  115. data/lib/active_record/type_caster/connection.rb +16 -10
  116. data/lib/active_record/validations.rb +1 -0
  117. data/lib/active_record/validations/uniqueness.rb +3 -5
  118. data/lib/arel.rb +12 -5
  119. data/lib/arel/insert_manager.rb +3 -3
  120. data/lib/arel/nodes.rb +2 -1
  121. data/lib/arel/nodes/comment.rb +29 -0
  122. data/lib/arel/nodes/select_core.rb +16 -12
  123. data/lib/arel/nodes/unary.rb +1 -0
  124. data/lib/arel/nodes/values_list.rb +2 -17
  125. data/lib/arel/select_manager.rb +10 -10
  126. data/lib/arel/visitors/depth_first.rb +7 -2
  127. data/lib/arel/visitors/dot.rb +7 -2
  128. data/lib/arel/visitors/ibm_db.rb +13 -0
  129. data/lib/arel/visitors/informix.rb +6 -0
  130. data/lib/arel/visitors/mssql.rb +15 -1
  131. data/lib/arel/visitors/oracle12.rb +4 -5
  132. data/lib/arel/visitors/postgresql.rb +4 -10
  133. data/lib/arel/visitors/to_sql.rb +107 -131
  134. data/lib/arel/visitors/visitor.rb +9 -5
  135. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  136. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  137. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  138. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  139. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  140. metadata +16 -12
  141. data/lib/active_record/collection_cache_key.rb +0 -53
  142. data/lib/arel/nodes/values.rb +0 -16
@@ -85,14 +85,14 @@ module ActiveRecord
85
85
  # based on the requested role:
86
86
  #
87
87
  # ActiveRecord::Base.connected_to(role: :writing) do
88
- # Dog.create! # creates dog using dog connection
88
+ # Dog.create! # creates dog using dog writing connection
89
89
  # end
90
90
  #
91
91
  # ActiveRecord::Base.connected_to(role: :reading) do
92
92
  # Dog.create! # throws exception because we're on a replica
93
93
  # end
94
94
  #
95
- # ActiveRecord::Base.connected_to(role: :unknown_ode) do
95
+ # ActiveRecord::Base.connected_to(role: :unknown_role) do
96
96
  # # raises exception due to non-existent role
97
97
  # end
98
98
  #
@@ -100,31 +100,44 @@ module ActiveRecord
100
100
  # you can use +connected_to+ with a +database+ argument. The +database+ argument
101
101
  # expects a symbol that corresponds to the database key in your config.
102
102
  #
103
- # This will connect to a new database for the queries inside the block.
104
- #
105
103
  # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
106
104
  # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
107
105
  # end
108
- def connected_to(database: nil, role: nil, &blk)
106
+ #
107
+ # This will connect to a new database for the queries inside the block. By
108
+ # default the `:writing` role will be used since all connections must be assigned
109
+ # a role. If you would like to use a different role you can pass a hash to database:
110
+ #
111
+ # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
112
+ # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
113
+ # Dog.run_a_long_query
114
+ # end
115
+ #
116
+ # When using the database key a new connection will be established every time. It is not
117
+ # recommended to use this outside of one-off scripts.
118
+ def connected_to(database: nil, role: nil, prevent_writes: false, &blk)
109
119
  if database && role
110
120
  raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
111
121
  elsif database
112
122
  if database.is_a?(Hash)
113
123
  role, database = database.first
114
124
  role = role.to_sym
115
- else
116
- role = database.to_sym
117
125
  end
118
126
 
119
127
  config_hash = resolve_config_for_connection(database)
120
128
  handler = lookup_connection_handler(role)
121
129
 
122
- with_handler(role) do
123
- handler.establish_connection(config_hash)
124
- yield
125
- end
130
+ handler.establish_connection(config_hash)
131
+
132
+ with_handler(role, &blk)
126
133
  elsif role
127
- with_handler(role.to_sym, &blk)
134
+ if role == writing_role
135
+ with_handler(role.to_sym) do
136
+ connection_handler.while_preventing_writes(prevent_writes, &blk)
137
+ end
138
+ else
139
+ with_handler(role.to_sym, &blk)
140
+ end
128
141
  else
129
142
  raise ArgumentError, "must provide a `database` or a `role`."
130
143
  end
@@ -154,6 +167,7 @@ module ActiveRecord
154
167
  end
155
168
 
156
169
  def lookup_connection_handler(handler_key) # :nodoc:
170
+ handler_key ||= ActiveRecord::Base.writing_role
157
171
  connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
158
172
  end
159
173
 
@@ -166,7 +180,7 @@ module ActiveRecord
166
180
  raise "Anonymous class is not allowed." unless name
167
181
 
168
182
  config_or_env ||= DEFAULT_ENV.call.to_sym
169
- pool_name = self == Base ? "primary" : name
183
+ pool_name = primary_class? ? "primary" : name
170
184
  self.connection_specification_name = pool_name
171
185
 
172
186
  resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
@@ -202,6 +216,10 @@ module ActiveRecord
202
216
  @connection_specification_name
203
217
  end
204
218
 
219
+ def primary_class? # :nodoc:
220
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
221
+ end
222
+
205
223
  # Returns the configuration of the associated connection as a hash:
206
224
  #
207
225
  # ActiveRecord::Base.connection_config
@@ -101,7 +101,6 @@ module ActiveRecord
101
101
  # environment where dumping schema is rarely needed.
102
102
  mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
103
103
 
104
- mattr_accessor :database_selector, instance_writer: false
105
104
  ##
106
105
  # :singleton-method:
107
106
  # Specifies which database schemas to dump when calling db:structure:dump.
@@ -161,7 +160,7 @@ module ActiveRecord
161
160
  return super if block_given? ||
162
161
  primary_key.nil? ||
163
162
  scope_attributes? ||
164
- columns_hash.include?(inheritance_column)
163
+ columns_hash.key?(inheritance_column) && !base_class?
165
164
 
166
165
  id = ids.first
167
166
 
@@ -175,14 +174,14 @@ module ActiveRecord
175
174
 
176
175
  record = statement.execute([id], connection)&.first
177
176
  unless record
178
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
179
- name, primary_key, id)
177
+ raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
180
178
  end
181
179
  record
182
180
  end
183
181
 
184
182
  def find_by(*args) # :nodoc:
185
- return super if scope_attributes? || reflect_on_all_aggregations.any?
183
+ return super if scope_attributes? || reflect_on_all_aggregations.any? ||
184
+ columns_hash.key?(inheritance_column) && !base_class?
186
185
 
187
186
  hash = args.first
188
187
 
@@ -269,7 +268,8 @@ module ActiveRecord
269
268
  end
270
269
 
271
270
  def arel_attribute(name, table = arel_table) # :nodoc:
272
- name = attribute_alias(name) if attribute_alias?(name)
271
+ name = name.to_s
272
+ name = attribute_aliases[name] || name
273
273
  table[name]
274
274
  end
275
275
 
@@ -317,7 +317,7 @@ module ActiveRecord
317
317
  # # Instantiates a single new object
318
318
  # User.new(first_name: 'Jamie')
319
319
  def initialize(attributes = nil)
320
- self.class.define_attribute_methods
320
+ @new_record = true
321
321
  @attributes = self.class._default_attributes.deep_dup
322
322
 
323
323
  init_internals
@@ -354,12 +354,10 @@ module ActiveRecord
354
354
  # +attributes+ should be an attributes object, and unlike the
355
355
  # `initialize` method, no assignment calls are made per attribute.
356
356
  def init_with_attributes(attributes, new_record = false) # :nodoc:
357
- init_internals
358
-
359
357
  @new_record = new_record
360
358
  @attributes = attributes
361
359
 
362
- self.class.define_attribute_methods
360
+ init_internals
363
361
 
364
362
  yield self if block_given?
365
363
 
@@ -398,13 +396,13 @@ module ActiveRecord
398
396
  ##
399
397
  def initialize_dup(other) # :nodoc:
400
398
  @attributes = @attributes.deep_dup
401
- @attributes.reset(self.class.primary_key)
399
+ @attributes.reset(@primary_key)
402
400
 
403
401
  _run_initialize_callbacks
404
402
 
405
403
  @new_record = true
406
404
  @destroyed = false
407
- @_start_transaction_state = {}
405
+ @_start_transaction_state = nil
408
406
  @transaction_state = nil
409
407
 
410
408
  super
@@ -465,6 +463,7 @@ module ActiveRecord
465
463
 
466
464
  # Returns +true+ if the attributes hash has been frozen.
467
465
  def frozen?
466
+ sync_with_transaction_state if @transaction_state&.finalized?
468
467
  @attributes.frozen?
469
468
  end
470
469
 
@@ -569,34 +568,34 @@ module ActiveRecord
569
568
  end
570
569
 
571
570
  def init_internals
571
+ @primary_key = self.class.primary_key
572
572
  @readonly = false
573
573
  @destroyed = false
574
574
  @marked_for_destruction = false
575
575
  @destroyed_by_association = nil
576
- @new_record = true
577
- @_start_transaction_state = {}
576
+ @_start_transaction_state = nil
578
577
  @transaction_state = nil
579
- end
580
578
 
581
- def initialize_internals_callback
579
+ self.class.define_attribute_methods
582
580
  end
583
581
 
584
- def thaw
585
- if frozen?
586
- @attributes = @attributes.dup
587
- end
582
+ def initialize_internals_callback
588
583
  end
589
584
 
590
585
  def custom_inspect_method_defined?
591
586
  self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
592
587
  end
593
588
 
589
+ class InspectionMask < DelegateClass(::String)
590
+ def pretty_print(pp)
591
+ pp.text __getobj__
592
+ end
593
+ end
594
+ private_constant :InspectionMask
595
+
594
596
  def inspection_filter
595
597
  @inspection_filter ||= begin
596
- mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
597
- def mask.pretty_print(pp)
598
- pp.text __getobj__
599
- end
598
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
600
599
  ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
601
600
  end
602
601
  end
@@ -7,8 +7,10 @@ require "active_record/database_configurations/url_config"
7
7
  module ActiveRecord
8
8
  # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
9
9
  # objects (either a HashConfig or UrlConfig) that are constructed from the
10
- # application's database configuration hash or url string.
10
+ # application's database configuration hash or URL string.
11
11
  class DatabaseConfigurations
12
+ class InvalidConfigurationError < StandardError; end
13
+
12
14
  attr_reader :configurations
13
15
  delegate :any?, to: :configurations
14
16
 
@@ -17,22 +19,22 @@ module ActiveRecord
17
19
  end
18
20
 
19
21
  # Collects the configs for the environment and optionally the specification
20
- # name passed in. To include replica configurations pass `include_replicas: true`.
22
+ # name passed in. To include replica configurations pass <tt>include_replicas: true</tt>.
21
23
  #
22
24
  # If a spec name is provided a single DatabaseConfig object will be
23
25
  # returned, otherwise an array of DatabaseConfig objects will be
24
26
  # returned that corresponds with the environment and type requested.
25
27
  #
26
- # Options:
28
+ # ==== Options
27
29
  #
28
- # <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
29
- # configs for all environments.
30
- # <tt>spec_name:</tt> The specification name (i.e. primary, animals, etc.). Defaults
31
- # to +nil+.
32
- # <tt>include_replicas:</tt> Determines whether to include replicas in
33
- # the returned list. Most of the time we're only iterating over the write
34
- # connection (i.e. migrations don't need to run for the write and read connection).
35
- # Defaults to +false+.
30
+ # * <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
31
+ # configs for all environments.
32
+ # * <tt>spec_name:</tt> The specification name (i.e. primary, animals, etc.). Defaults
33
+ # to +nil+.
34
+ # * <tt>include_replicas:</tt> Determines whether to include replicas in
35
+ # the returned list. Most of the time we're only iterating over the write
36
+ # connection (i.e. migrations don't need to run for the write and read connection).
37
+ # Defaults to +false+.
36
38
  def configs_for(env_name: nil, spec_name: nil, include_replicas: false)
37
39
  configs = env_with_configs(env_name)
38
40
 
@@ -53,7 +55,7 @@ module ActiveRecord
53
55
 
54
56
  # Returns the config hash that corresponds with the environment
55
57
  #
56
- # If the application has multiple databases `default_hash` will
58
+ # If the application has multiple databases +default_hash+ will
57
59
  # return the first config hash for the environment.
58
60
  #
59
61
  # { database: "my_db", adapter: "mysql2" }
@@ -65,7 +67,7 @@ module ActiveRecord
65
67
 
66
68
  # Returns a single DatabaseConfig object based on the requested environment.
67
69
  #
68
- # If the application has multiple databases `find_db_config` will return
70
+ # If the application has multiple databases +find_db_config+ will return
69
71
  # the first DatabaseConfig for the environment.
70
72
  def find_db_config(env)
71
73
  configurations.find do |db_config|
@@ -91,6 +93,19 @@ module ActiveRecord
91
93
  end
92
94
  alias :blank? :empty?
93
95
 
96
+ def each
97
+ throw_getter_deprecation(:each)
98
+ configurations.each { |config|
99
+ yield [config.env_name, config.config]
100
+ }
101
+ end
102
+
103
+ def first
104
+ throw_getter_deprecation(:first)
105
+ config = configurations.first
106
+ [config.env_name, config.config]
107
+ end
108
+
94
109
  private
95
110
  def env_with_configs(env = nil)
96
111
  if env
@@ -104,34 +119,48 @@ module ActiveRecord
104
119
  return configs.configurations if configs.is_a?(DatabaseConfigurations)
105
120
  return configs if configs.is_a?(Array)
106
121
 
107
- build_db_config = configs.each_pair.flat_map do |env_name, config|
108
- walk_configs(env_name.to_s, "primary", config)
109
- end.compact
122
+ db_configs = configs.flat_map do |env_name, config|
123
+ if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
124
+ walk_configs(env_name.to_s, config)
125
+ else
126
+ build_db_config_from_raw_config(env_name.to_s, "primary", config)
127
+ end
128
+ end
129
+
130
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
110
131
 
111
- if url = ENV["DATABASE_URL"]
112
- build_url_config(url, build_db_config)
113
- else
114
- build_db_config
132
+ unless db_configs.find(&:for_current_env?)
133
+ db_configs << environment_url_config(current_env, "primary", {})
134
+ end
135
+
136
+ merge_db_environment_variables(current_env, db_configs.compact)
137
+ end
138
+
139
+ def walk_configs(env_name, config)
140
+ config.map do |spec_name, sub_config|
141
+ build_db_config_from_raw_config(env_name, spec_name.to_s, sub_config)
115
142
  end
116
143
  end
117
144
 
118
- def walk_configs(env_name, spec_name, config)
145
+ def build_db_config_from_raw_config(env_name, spec_name, config)
119
146
  case config
120
147
  when String
121
148
  build_db_config_from_string(env_name, spec_name, config)
122
149
  when Hash
123
150
  build_db_config_from_hash(env_name, spec_name, config.stringify_keys)
151
+ else
152
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
124
153
  end
125
154
  end
126
155
 
127
156
  def build_db_config_from_string(env_name, spec_name, config)
128
157
  url = config
129
158
  uri = URI.parse(url)
130
- if uri.try(:scheme)
159
+ if uri.scheme
131
160
  ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url)
161
+ else
162
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
132
163
  end
133
- rescue URI::InvalidURIError
134
- ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
135
164
  end
136
165
 
137
166
  def build_db_config_from_hash(env_name, spec_name, config)
@@ -141,36 +170,36 @@ module ActiveRecord
141
170
  config_without_url.delete "url"
142
171
 
143
172
  ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
144
- elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
145
- ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
146
173
  else
147
- config.each_pair.map do |sub_spec_name, sub_config|
148
- walk_configs(env_name, sub_spec_name, sub_config)
149
- end
174
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
150
175
  end
151
176
  end
152
177
 
153
- def build_url_config(url, configs)
154
- env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
178
+ def merge_db_environment_variables(current_env, configs)
179
+ configs.map do |config|
180
+ next config if config.url_config? || config.env_name != current_env
155
181
 
156
- if original_config = configs.find(&:for_current_env?)
157
- if original_config.url_config?
158
- configs
159
- else
160
- configs.map do |config|
161
- ActiveRecord::DatabaseConfigurations::UrlConfig.new(config.env_name, config.spec_name, url, config.config)
162
- end
163
- end
164
- else
165
- configs + [ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, "primary", url)]
182
+ url_config = environment_url_config(current_env, config.spec_name, config.config)
183
+ url_config || config
166
184
  end
167
185
  end
168
186
 
187
+ def environment_url_config(env, spec_name, config)
188
+ url = environment_value_for(spec_name)
189
+ return unless url
190
+
191
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, spec_name, url, config)
192
+ end
193
+
194
+ def environment_value_for(spec_name)
195
+ spec_env_key = "#{spec_name.upcase}_DATABASE_URL"
196
+ url = ENV[spec_env_key]
197
+ url ||= ENV["DATABASE_URL"] if spec_name == "primary"
198
+ url
199
+ end
200
+
169
201
  def method_missing(method, *args, &blk)
170
202
  case method
171
- when :each, :first
172
- throw_getter_deprecation(method)
173
- configurations.send(method, *args, &blk)
174
203
  when :fetch
175
204
  throw_getter_deprecation(method)
176
205
  configs_for(env_name: args.first)
@@ -14,16 +14,16 @@ module ActiveRecord
14
14
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
15
15
  # @env_name="development", @spec_name="primary", @config={"database"=>"db_name"}>
16
16
  #
17
- # Options are:
17
+ # ==== Options
18
18
  #
19
- # <tt>:env_name</tt> - The Rails environment, i.e. "development"
20
- # <tt>:spec_name</tt> - The specification name. In a standard two-tier
21
- # database configuration this will default to "primary". In a multiple
22
- # database three-tier database configuration this corresponds to the name
23
- # used in the second tier, for example "primary_readonly".
24
- # <tt>:config</tt> - The config hash. This is the hash that contains the
25
- # database adapter, name, and other important information for database
26
- # connections.
19
+ # * <tt>:env_name</tt> - The Rails environment, i.e. "development".
20
+ # * <tt>:spec_name</tt> - The specification name. In a standard two-tier
21
+ # database configuration this will default to "primary". In a multiple
22
+ # database three-tier database configuration this corresponds to the name
23
+ # used in the second tier, for example "primary_readonly".
24
+ # * <tt>:config</tt> - The config hash. This is the hash that contains the
25
+ # database adapter, name, and other important information for database
26
+ # connections.
27
27
  class HashConfig < DatabaseConfig
28
28
  attr_reader :config
29
29
 
@@ -33,14 +33,14 @@ module ActiveRecord
33
33
  end
34
34
 
35
35
  # Determines whether a database configuration is for a replica / readonly
36
- # connection. If the `replica` key is present in the config, `replica?` will
36
+ # connection. If the +replica+ key is present in the config, +replica?+ will
37
37
  # return +true+.
38
38
  def replica?
39
39
  config["replica"]
40
40
  end
41
41
 
42
42
  # The migrations paths for a database configuration. If the
43
- # `migrations_paths` key is present in the config, `migrations_paths`
43
+ # +migrations_paths+ key is present in the config, +migrations_paths+
44
44
  # will return its value.
45
45
  def migrations_paths
46
46
  config["migrations_paths"]