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
@@ -70,7 +70,15 @@ module ActiveRecord
70
70
  predicates == other.predicates
71
71
  end
72
72
 
73
- def invert
73
+ def invert(as = :nand)
74
+ if predicates.size == 1
75
+ inverted_predicates = [ invert_predicate(predicates.first) ]
76
+ elsif as == :nor
77
+ inverted_predicates = predicates.map { |node| invert_predicate(node) }
78
+ else
79
+ inverted_predicates = [ Arel::Nodes::Not.new(ast) ]
80
+ end
81
+
74
82
  WhereClause.new(inverted_predicates)
75
83
  end
76
84
 
@@ -115,10 +123,6 @@ module ActiveRecord
115
123
  node.respond_to?(:operator) && node.operator == :==
116
124
  end
117
125
 
118
- def inverted_predicates
119
- predicates.map { |node| invert_predicate(node) }
120
- end
121
-
122
126
  def invert_predicate(node)
123
127
  case node
124
128
  when NilClass
@@ -61,8 +61,9 @@ module ActiveRecord
61
61
  # # => "id ASC"
62
62
  def sanitize_sql_for_order(condition)
63
63
  if condition.is_a?(Array) && condition.first.to_s.include?("?")
64
- disallow_raw_sql!([condition.first],
65
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
64
+ disallow_raw_sql!(
65
+ [condition.first],
66
+ permit: connection.column_name_with_order_matcher
66
67
  )
67
68
 
68
69
  # Ensure we aren't dealing with a subclass of String that might
@@ -133,6 +134,33 @@ module ActiveRecord
133
134
  end
134
135
  end
135
136
 
137
+ def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
138
+ unexpected = nil
139
+ args.each do |arg|
140
+ next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s)
141
+ (unexpected ||= []) << arg
142
+ end
143
+
144
+ return unless unexpected
145
+
146
+ if allow_unsafe_raw_sql == :deprecated
147
+ ActiveSupport::Deprecation.warn(
148
+ "Dangerous query method (method whose arguments are used as raw " \
149
+ "SQL) called with non-attribute argument(s): " \
150
+ "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
151
+ "arguments will be disallowed in Rails 6.1. This method should " \
152
+ "not be called with user-provided values, such as request " \
153
+ "parameters or model attributes. Known-safe values can be passed " \
154
+ "by wrapping them in Arel.sql()."
155
+ )
156
+ else
157
+ raise(ActiveRecord::UnknownAttributeReference,
158
+ "Query method called with non-attribute argument(s): " +
159
+ unexpected.map(&:inspect).join(", ")
160
+ )
161
+ end
162
+ end
163
+
136
164
  private
137
165
  def replace_bind_variables(statement, values)
138
166
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
@@ -165,10 +193,11 @@ module ActiveRecord
165
193
 
166
194
  def quote_bound_value(value, c = connection)
167
195
  if value.respond_to?(:map) && !value.acts_like?(:string)
168
- if value.respond_to?(:empty?) && value.empty?
196
+ quoted = value.map { |v| c.quote(v) }
197
+ if quoted.empty?
169
198
  c.quote(nil)
170
199
  else
171
- value.map { |v| c.quote(v) }.join(",")
200
+ quoted.join(",")
172
201
  end
173
202
  else
174
203
  c.quote(value)
@@ -50,7 +50,7 @@ module ActiveRecord
50
50
  instance_eval(&block)
51
51
 
52
52
  if info[:version].present?
53
- ActiveRecord::SchemaMigration.create_table
53
+ connection.schema_migration.create_table
54
54
  connection.assume_migrated_upto_version(info[:version])
55
55
  end
56
56
 
@@ -47,6 +47,7 @@ module ActiveRecord
47
47
  end
48
48
 
49
49
  private
50
+ attr_accessor :table_name
50
51
 
51
52
  def initialize(connection, options = {})
52
53
  @connection = connection
@@ -110,6 +111,8 @@ HEADER
110
111
  def table(table, stream)
111
112
  columns = @connection.columns(table)
112
113
  begin
114
+ self.table_name = table
115
+
113
116
  tbl = StringIO.new
114
117
 
115
118
  # first dump primary key column
@@ -143,7 +146,11 @@ HEADER
143
146
  raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
144
147
  next if column.name == pk
145
148
  type, colspec = column_spec(column)
146
- tbl.print " t.#{type} #{column.name.inspect}"
149
+ if type.is_a?(Symbol)
150
+ tbl.print " t.#{type} #{column.name.inspect}"
151
+ else
152
+ tbl.print " t.column #{column.name.inspect}, #{type.inspect}"
153
+ end
147
154
  tbl.print ", #{format_colspec(colspec)}" if colspec.present?
148
155
  tbl.puts
149
156
  end
@@ -159,6 +166,8 @@ HEADER
159
166
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
160
167
  stream.puts "# #{e.message}"
161
168
  stream.puts
169
+ ensure
170
+ self.table_name = nil
162
171
  end
163
172
  end
164
173
 
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def table_name
22
- "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
22
+ "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
23
23
  end
24
24
 
25
25
  def table_exists?
@@ -86,8 +86,8 @@ module ActiveRecord
86
86
  # # Should return a scope, you can call 'super' here etc.
87
87
  # end
88
88
  # end
89
- def default_scope(scope = nil) # :doc:
90
- scope = Proc.new if block_given?
89
+ def default_scope(scope = nil, &block) # :doc:
90
+ scope = block if block_given?
91
91
 
92
92
  if scope.is_a?(Relation) || !scope.respond_to?(:call)
93
93
  raise ArgumentError,
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  self.default_scopes += [scope]
101
101
  end
102
102
 
103
- def build_default_scope(base_rel = nil)
103
+ def build_default_scope(relation = relation())
104
104
  return if abstract_class?
105
105
 
106
106
  if default_scope_override.nil?
@@ -111,15 +111,14 @@ module ActiveRecord
111
111
  # The user has defined their own default scope method, so call that
112
112
  evaluate_default_scope do
113
113
  if scope = default_scope
114
- (base_rel ||= relation).merge!(scope)
114
+ relation.merge!(scope)
115
115
  end
116
116
  end
117
117
  elsif default_scopes.any?
118
- base_rel ||= relation
119
118
  evaluate_default_scope do
120
- default_scopes.inject(base_rel) do |default_scope, scope|
119
+ default_scopes.inject(relation) do |default_scope, scope|
121
120
  scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
122
- default_scope.merge!(base_rel.instance_exec(&scope))
121
+ default_scope.instance_exec(&scope) || default_scope
123
122
  end
124
123
  end
125
124
  end
@@ -31,7 +31,8 @@ module ActiveRecord
31
31
  ActiveSupport::Deprecation.warn(<<~MSG.squish)
32
32
  Class level methods will no longer inherit scoping from `#{scope._deprecated_scope_source}`
33
33
  in Rails 6.1. To continue using the scoped relation, pass it into the block directly.
34
- To instead access the full set of models, as Rails 6.1 will, use `#{name}.unscoped`.
34
+ To instead access the full set of models, as Rails 6.1 will, use `#{name}.unscoped`,
35
+ or `#{name}.default_scoped` if a model has default scopes.
35
36
  MSG
36
37
  end
37
38
 
@@ -58,7 +59,7 @@ module ActiveRecord
58
59
  end
59
60
 
60
61
  def default_extensions # :nodoc:
61
- if scope = current_scope || build_default_scope
62
+ if scope = scope_for_association || build_default_scope
62
63
  scope.extensions
63
64
  else
64
65
  []
@@ -113,8 +113,8 @@ module ActiveRecord
113
113
  end
114
114
  end
115
115
 
116
- def self.create(connection, block = Proc.new)
117
- relation = block.call Params.new
116
+ def self.create(connection, callable = nil, &block)
117
+ relation = (callable || block).call Params.new
118
118
  query_builder, binds = connection.cacheable_query(self, relation.arel)
119
119
  bind_map = BindMap.new(binds)
120
120
  new(query_builder, bind_map, relation.klass)
@@ -11,6 +11,12 @@ module ActiveRecord
11
11
  # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
12
12
  # already built around just accessing attributes on the model.
13
13
  #
14
+ # Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+) and
15
+ # methods to access the changes made during the last save (+saved_change_to_key?+, +saved_change_to_key+ and
16
+ # +key_before_last_save+).
17
+ #
18
+ # NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead.
19
+ #
14
20
  # Make sure that you declare the database column used for the serialized store as a text, so there's
15
21
  # plenty of room.
16
22
  #
@@ -49,6 +55,12 @@ module ActiveRecord
49
55
  # u.settings[:country] # => 'Denmark'
50
56
  # u.settings['country'] # => 'Denmark'
51
57
  #
58
+ # # Dirty tracking
59
+ # u.color = 'green'
60
+ # u.color_changed? # => true
61
+ # u.color_was # => 'black'
62
+ # u.color_change # => ['black', 'red']
63
+ #
52
64
  # # Add additional accessors to an existing store through store_accessor
53
65
  # class SuperUser < User
54
66
  # store_accessor :settings, :privileges, :servants
@@ -127,6 +139,42 @@ module ActiveRecord
127
139
  define_method(accessor_key) do
128
140
  read_store_attribute(store_attribute, key)
129
141
  end
142
+
143
+ define_method("#{accessor_key}_changed?") do
144
+ return false unless attribute_changed?(store_attribute)
145
+ prev_store, new_store = changes[store_attribute]
146
+ prev_store&.dig(key) != new_store&.dig(key)
147
+ end
148
+
149
+ define_method("#{accessor_key}_change") do
150
+ return unless attribute_changed?(store_attribute)
151
+ prev_store, new_store = changes[store_attribute]
152
+ [prev_store&.dig(key), new_store&.dig(key)]
153
+ end
154
+
155
+ define_method("#{accessor_key}_was") do
156
+ return unless attribute_changed?(store_attribute)
157
+ prev_store, _new_store = changes[store_attribute]
158
+ prev_store&.dig(key)
159
+ end
160
+
161
+ define_method("saved_change_to_#{accessor_key}?") do
162
+ return false unless saved_change_to_attribute?(store_attribute)
163
+ prev_store, new_store = saved_change_to_attribute(store_attribute)
164
+ prev_store&.dig(key) != new_store&.dig(key)
165
+ end
166
+
167
+ define_method("saved_change_to_#{accessor_key}") do
168
+ return unless saved_change_to_attribute?(store_attribute)
169
+ prev_store, new_store = saved_change_to_attribute(store_attribute)
170
+ [prev_store&.dig(key), new_store&.dig(key)]
171
+ end
172
+
173
+ define_method("#{accessor_key}_before_last_save") do
174
+ return unless saved_change_to_attribute?(store_attribute)
175
+ prev_store, _new_store = saved_change_to_attribute(store_attribute)
176
+ prev_store&.dig(key)
177
+ end
130
178
  end
131
179
  end
132
180
 
@@ -4,17 +4,18 @@ module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
5
  delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true
6
6
 
7
- def initialize(klass, arel_table, association = nil)
7
+ def initialize(klass, arel_table, association = nil, types = klass)
8
8
  @klass = klass
9
+ @types = types
9
10
  @arel_table = arel_table
10
11
  @association = association
11
12
  end
12
13
 
13
14
  def resolve_column_aliases(hash)
14
15
  new_hash = hash.dup
15
- hash.each do |key, _|
16
- if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
17
- new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
16
+ hash.each_key do |key|
17
+ if key.is_a?(Symbol) && new_key = klass.attribute_aliases[key.to_s]
18
+ new_hash[new_key] = new_hash.delete(key)
18
19
  end
19
20
  end
20
21
  new_hash
@@ -29,11 +30,7 @@ module ActiveRecord
29
30
  end
30
31
 
31
32
  def type(column_name)
32
- if klass
33
- klass.type_for_attribute(column_name)
34
- else
35
- Type.default_value
36
- end
33
+ types.type_for_attribute(column_name)
37
34
  end
38
35
 
39
36
  def has_column?(column_name)
@@ -52,13 +49,12 @@ module ActiveRecord
52
49
  elsif association && !association.polymorphic?
53
50
  association_klass = association.klass
54
51
  arel_table = association_klass.arel_table.alias(table_name)
52
+ TableMetadata.new(association_klass, arel_table, association)
55
53
  else
56
54
  type_caster = TypeCaster::Connection.new(klass, table_name)
57
- association_klass = nil
58
55
  arel_table = Arel::Table.new(table_name, type_caster: type_caster)
56
+ TableMetadata.new(nil, arel_table, association, type_caster)
59
57
  end
60
-
61
- TableMetadata.new(association_klass, arel_table, association)
62
58
  end
63
59
 
64
60
  def polymorphic_association?
@@ -74,6 +70,6 @@ module ActiveRecord
74
70
  end
75
71
 
76
72
  private
77
- attr_reader :klass, :arel_table, :association
73
+ attr_reader :klass, :types, :arel_table, :association
78
74
  end
79
75
  end
@@ -141,8 +141,21 @@ module ActiveRecord
141
141
  end
142
142
  end
143
143
 
144
- def for_each
145
- databases = Rails.application.config.database_configuration
144
+ def setup_initial_database_yaml
145
+ return {} unless defined?(Rails)
146
+
147
+ begin
148
+ Rails.application.config.load_database_yaml
149
+ rescue
150
+ $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
151
+
152
+ {}
153
+ end
154
+ end
155
+
156
+ def for_each(databases)
157
+ return {} unless defined?(Rails)
158
+
146
159
  database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
147
160
 
148
161
  # if this is a single database application we don't want tasks for each primary database
@@ -153,8 +166,22 @@ module ActiveRecord
153
166
  end
154
167
  end
155
168
 
156
- def create_current(environment = env)
157
- each_current_configuration(environment) { |configuration|
169
+ def raise_for_multi_db(environment = env, command:)
170
+ db_configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
171
+
172
+ if db_configs.count > 1
173
+ dbs_list = []
174
+
175
+ db_configs.each do |db|
176
+ dbs_list << "#{command}:#{db.spec_name}"
177
+ end
178
+
179
+ raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}."
180
+ end
181
+ end
182
+
183
+ def create_current(environment = env, spec_name = nil)
184
+ each_current_configuration(environment, spec_name) { |configuration|
158
185
  create configuration
159
186
  }
160
187
  ActiveRecord::Base.establish_connection(environment.to_sym)
@@ -182,6 +209,26 @@ module ActiveRecord
182
209
  }
183
210
  end
184
211
 
212
+ def truncate_tables(configuration)
213
+ ActiveRecord::Base.connected_to(database: { truncation: configuration }) do
214
+ conn = ActiveRecord::Base.connection
215
+ table_names = conn.tables
216
+ table_names -= [
217
+ conn.schema_migration.table_name,
218
+ InternalMetadata.table_name
219
+ ]
220
+
221
+ ActiveRecord::Base.connection.truncate_tables(*table_names)
222
+ end
223
+ end
224
+ private :truncate_tables
225
+
226
+ def truncate_all(environment = env)
227
+ ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config|
228
+ truncate_tables db_config.config
229
+ end
230
+ end
231
+
185
232
  def migrate
186
233
  check_target_version
187
234
 
@@ -198,7 +245,7 @@ module ActiveRecord
198
245
  end
199
246
 
200
247
  def migrate_status
201
- unless ActiveRecord::SchemaMigration.table_exists?
248
+ unless ActiveRecord::Base.connection.schema_migration.table_exists?
202
249
  Kernel.abort "Schema migrations table does not exist yet."
203
250
  end
204
251
 
@@ -286,10 +333,60 @@ module ActiveRecord
286
333
  end
287
334
  ActiveRecord::InternalMetadata.create_table
288
335
  ActiveRecord::InternalMetadata[:environment] = environment
336
+ ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file)
289
337
  ensure
290
338
  Migration.verbose = verbose_was
291
339
  end
292
340
 
341
+ def schema_up_to_date?(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env, spec_name = "primary")
342
+ file ||= dump_filename(spec_name, format)
343
+
344
+ return true unless File.exist?(file)
345
+
346
+ ActiveRecord::Base.establish_connection(configuration)
347
+ return false unless ActiveRecord::InternalMetadata.table_exists?
348
+ ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file)
349
+ end
350
+
351
+ def reconstruct_from_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env, spec_name = "primary") # :nodoc:
352
+ file ||= dump_filename(spec_name, format)
353
+
354
+ check_schema_file(file)
355
+
356
+ ActiveRecord::Base.establish_connection(configuration)
357
+
358
+ if schema_up_to_date?(configuration, format, file, environment, spec_name)
359
+ truncate_tables(configuration)
360
+ else
361
+ purge(configuration)
362
+ load_schema(configuration, format, file, environment, spec_name)
363
+ end
364
+ rescue ActiveRecord::NoDatabaseError
365
+ create(configuration)
366
+ load_schema(configuration, format, file, environment, spec_name)
367
+ end
368
+
369
+ def dump_schema(configuration, format = ActiveRecord::Base.schema_format, spec_name = "primary") # :nodoc:
370
+ require "active_record/schema_dumper"
371
+ filename = dump_filename(spec_name, format)
372
+ connection = ActiveRecord::Base.connection
373
+
374
+ case format
375
+ when :ruby
376
+ File.open(filename, "w:utf-8") do |file|
377
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
378
+ end
379
+ when :sql
380
+ structure_dump(configuration, filename)
381
+ if connection.schema_migration.table_exists?
382
+ File.open(filename, "a") do |f|
383
+ f.puts connection.dump_schema_information
384
+ f.print "\n"
385
+ end
386
+ end
387
+ end
388
+ end
389
+
293
390
  def schema_file(format = ActiveRecord::Base.schema_format)
294
391
  File.join(db_dir, schema_file_type(format))
295
392
  end
@@ -371,12 +468,14 @@ module ActiveRecord
371
468
  task.is_a?(String) ? task.constantize : task
372
469
  end
373
470
 
374
- def each_current_configuration(environment)
471
+ def each_current_configuration(environment, spec_name = nil)
375
472
  environments = [environment]
376
473
  environments << "test" if environment == "development"
377
474
 
378
475
  environments.each do |env|
379
476
  ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
477
+ next if spec_name && spec_name != db_config.spec_name
478
+
380
479
  yield db_config.config, db_config.spec_name, env
381
480
  end
382
481
  end
@@ -398,6 +497,10 @@ module ActiveRecord
398
497
  def local_database?(configuration)
399
498
  configuration["host"].blank? || LOCAL_HOSTS.include?(configuration["host"])
400
499
  end
500
+
501
+ def schema_sha1(file)
502
+ Digest::SHA1.hexdigest(File.read(file))
503
+ end
401
504
  end
402
505
  end
403
506
  end