activerecord 5.2.2.1 → 6.0.2

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 (269) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +734 -508
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +9 -2
  7. data/lib/active_record/aggregations.rb +4 -2
  8. data/lib/active_record/association_relation.rb +15 -6
  9. data/lib/active_record/associations.rb +20 -15
  10. data/lib/active_record/associations/association.rb +61 -20
  11. data/lib/active_record/associations/association_scope.rb +4 -6
  12. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  14. data/lib/active_record/associations/builder/association.rb +14 -18
  15. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +35 -1
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +16 -28
  22. data/lib/active_record/associations/collection_proxy.rb +19 -48
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +3 -10
  25. data/lib/active_record/associations/has_many_through_association.rb +20 -25
  26. data/lib/active_record/associations/has_one_association.rb +28 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  28. data/lib/active_record/associations/join_dependency.rb +28 -28
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -7
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +40 -32
  32. data/lib/active_record/associations/preloader/association.rb +38 -36
  33. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  34. data/lib/active_record/associations/singular_association.rb +2 -16
  35. data/lib/active_record/attribute_assignment.rb +7 -10
  36. data/lib/active_record/attribute_methods.rb +28 -100
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  38. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  40. data/lib/active_record/attribute_methods/query.rb +2 -3
  41. data/lib/active_record/attribute_methods/read.rb +15 -53
  42. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  44. data/lib/active_record/attribute_methods/write.rb +17 -24
  45. data/lib/active_record/attributes.rb +13 -0
  46. data/lib/active_record/autosave_association.rb +22 -8
  47. data/lib/active_record/base.rb +2 -3
  48. data/lib/active_record/callbacks.rb +5 -19
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +137 -26
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +114 -130
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +26 -11
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +135 -56
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +189 -43
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +151 -198
  61. data/lib/active_record/connection_adapters/column.rb +17 -13
  62. data/lib/active_record/connection_adapters/connection_specification.rb +53 -43
  63. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +7 -11
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +75 -13
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  66. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  67. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  68. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  69. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  70. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  71. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +22 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  81. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  82. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  83. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +47 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  86. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -77
  87. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  88. data/lib/active_record/connection_adapters/postgresql_adapter.rb +164 -74
  89. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  90. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -5
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +131 -143
  95. data/lib/active_record/connection_handling.rb +155 -26
  96. data/lib/active_record/core.rb +104 -59
  97. data/lib/active_record/counter_cache.rb +4 -29
  98. data/lib/active_record/database_configurations.rb +233 -0
  99. data/lib/active_record/database_configurations/database_config.rb +37 -0
  100. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  101. data/lib/active_record/database_configurations/url_config.rb +79 -0
  102. data/lib/active_record/dynamic_matchers.rb +1 -1
  103. data/lib/active_record/enum.rb +37 -7
  104. data/lib/active_record/errors.rb +30 -16
  105. data/lib/active_record/explain.rb +1 -1
  106. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  107. data/lib/active_record/fixture_set/render_context.rb +17 -0
  108. data/lib/active_record/fixture_set/table_row.rb +153 -0
  109. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  110. data/lib/active_record/fixtures.rb +145 -472
  111. data/lib/active_record/gem_version.rb +3 -3
  112. data/lib/active_record/inheritance.rb +13 -3
  113. data/lib/active_record/insert_all.rb +179 -0
  114. data/lib/active_record/integration.rb +68 -16
  115. data/lib/active_record/internal_metadata.rb +10 -2
  116. data/lib/active_record/locking/optimistic.rb +5 -6
  117. data/lib/active_record/locking/pessimistic.rb +3 -3
  118. data/lib/active_record/log_subscriber.rb +7 -26
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  121. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/migration/command_recorder.rb +50 -6
  124. data/lib/active_record/migration/compatibility.rb +91 -64
  125. data/lib/active_record/model_schema.rb +33 -9
  126. data/lib/active_record/nested_attributes.rb +2 -2
  127. data/lib/active_record/no_touching.rb +7 -0
  128. data/lib/active_record/persistence.rb +231 -25
  129. data/lib/active_record/query_cache.rb +11 -4
  130. data/lib/active_record/querying.rb +33 -22
  131. data/lib/active_record/railtie.rb +80 -43
  132. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  133. data/lib/active_record/railties/controller_runtime.rb +30 -35
  134. data/lib/active_record/railties/databases.rake +199 -46
  135. data/lib/active_record/reflection.rb +42 -44
  136. data/lib/active_record/relation.rb +311 -80
  137. data/lib/active_record/relation/batches.rb +13 -10
  138. data/lib/active_record/relation/calculations.rb +67 -57
  139. data/lib/active_record/relation/delegation.rb +26 -43
  140. data/lib/active_record/relation/finder_methods.rb +28 -28
  141. data/lib/active_record/relation/merger.rb +17 -23
  142. data/lib/active_record/relation/predicate_builder.rb +18 -15
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  145. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  146. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  147. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  148. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  149. data/lib/active_record/relation/query_attribute.rb +17 -10
  150. data/lib/active_record/relation/query_methods.rb +247 -73
  151. data/lib/active_record/relation/spawn_methods.rb +1 -1
  152. data/lib/active_record/relation/where_clause.rb +14 -10
  153. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  154. data/lib/active_record/result.rb +30 -11
  155. data/lib/active_record/sanitization.rb +32 -40
  156. data/lib/active_record/schema.rb +2 -11
  157. data/lib/active_record/schema_dumper.rb +22 -7
  158. data/lib/active_record/schema_migration.rb +5 -1
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/scoping/default.rb +6 -7
  161. data/lib/active_record/scoping/named.rb +20 -15
  162. data/lib/active_record/statement_cache.rb +32 -5
  163. data/lib/active_record/store.rb +87 -8
  164. data/lib/active_record/table_metadata.rb +10 -17
  165. data/lib/active_record/tasks/database_tasks.rb +194 -25
  166. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  167. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  168. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  169. data/lib/active_record/test_databases.rb +23 -0
  170. data/lib/active_record/test_fixtures.rb +225 -0
  171. data/lib/active_record/timestamp.rb +39 -25
  172. data/lib/active_record/touch_later.rb +4 -2
  173. data/lib/active_record/transactions.rb +57 -66
  174. data/lib/active_record/translation.rb +1 -1
  175. data/lib/active_record/type.rb +3 -4
  176. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  177. data/lib/active_record/type_caster/connection.rb +15 -14
  178. data/lib/active_record/type_caster/map.rb +1 -4
  179. data/lib/active_record/validations.rb +1 -0
  180. data/lib/active_record/validations/uniqueness.rb +15 -27
  181. data/lib/arel.rb +58 -0
  182. data/lib/arel/alias_predication.rb +9 -0
  183. data/lib/arel/attributes.rb +22 -0
  184. data/lib/arel/attributes/attribute.rb +37 -0
  185. data/lib/arel/collectors/bind.rb +24 -0
  186. data/lib/arel/collectors/composite.rb +31 -0
  187. data/lib/arel/collectors/plain_string.rb +20 -0
  188. data/lib/arel/collectors/sql_string.rb +20 -0
  189. data/lib/arel/collectors/substitute_binds.rb +28 -0
  190. data/lib/arel/crud.rb +42 -0
  191. data/lib/arel/delete_manager.rb +18 -0
  192. data/lib/arel/errors.rb +9 -0
  193. data/lib/arel/expressions.rb +29 -0
  194. data/lib/arel/factory_methods.rb +49 -0
  195. data/lib/arel/insert_manager.rb +49 -0
  196. data/lib/arel/math.rb +45 -0
  197. data/lib/arel/nodes.rb +68 -0
  198. data/lib/arel/nodes/and.rb +32 -0
  199. data/lib/arel/nodes/ascending.rb +23 -0
  200. data/lib/arel/nodes/binary.rb +52 -0
  201. data/lib/arel/nodes/bind_param.rb +36 -0
  202. data/lib/arel/nodes/case.rb +55 -0
  203. data/lib/arel/nodes/casted.rb +50 -0
  204. data/lib/arel/nodes/comment.rb +29 -0
  205. data/lib/arel/nodes/count.rb +12 -0
  206. data/lib/arel/nodes/delete_statement.rb +45 -0
  207. data/lib/arel/nodes/descending.rb +23 -0
  208. data/lib/arel/nodes/equality.rb +18 -0
  209. data/lib/arel/nodes/extract.rb +24 -0
  210. data/lib/arel/nodes/false.rb +16 -0
  211. data/lib/arel/nodes/full_outer_join.rb +8 -0
  212. data/lib/arel/nodes/function.rb +44 -0
  213. data/lib/arel/nodes/grouping.rb +8 -0
  214. data/lib/arel/nodes/in.rb +8 -0
  215. data/lib/arel/nodes/infix_operation.rb +80 -0
  216. data/lib/arel/nodes/inner_join.rb +8 -0
  217. data/lib/arel/nodes/insert_statement.rb +37 -0
  218. data/lib/arel/nodes/join_source.rb +20 -0
  219. data/lib/arel/nodes/matches.rb +18 -0
  220. data/lib/arel/nodes/named_function.rb +23 -0
  221. data/lib/arel/nodes/node.rb +50 -0
  222. data/lib/arel/nodes/node_expression.rb +13 -0
  223. data/lib/arel/nodes/outer_join.rb +8 -0
  224. data/lib/arel/nodes/over.rb +15 -0
  225. data/lib/arel/nodes/regexp.rb +16 -0
  226. data/lib/arel/nodes/right_outer_join.rb +8 -0
  227. data/lib/arel/nodes/select_core.rb +67 -0
  228. data/lib/arel/nodes/select_statement.rb +41 -0
  229. data/lib/arel/nodes/sql_literal.rb +16 -0
  230. data/lib/arel/nodes/string_join.rb +11 -0
  231. data/lib/arel/nodes/table_alias.rb +27 -0
  232. data/lib/arel/nodes/terminal.rb +16 -0
  233. data/lib/arel/nodes/true.rb +16 -0
  234. data/lib/arel/nodes/unary.rb +45 -0
  235. data/lib/arel/nodes/unary_operation.rb +20 -0
  236. data/lib/arel/nodes/unqualified_column.rb +22 -0
  237. data/lib/arel/nodes/update_statement.rb +41 -0
  238. data/lib/arel/nodes/values_list.rb +9 -0
  239. data/lib/arel/nodes/window.rb +126 -0
  240. data/lib/arel/nodes/with.rb +11 -0
  241. data/lib/arel/order_predications.rb +13 -0
  242. data/lib/arel/predications.rb +257 -0
  243. data/lib/arel/select_manager.rb +271 -0
  244. data/lib/arel/table.rb +110 -0
  245. data/lib/arel/tree_manager.rb +72 -0
  246. data/lib/arel/update_manager.rb +34 -0
  247. data/lib/arel/visitors.rb +20 -0
  248. data/lib/arel/visitors/depth_first.rb +204 -0
  249. data/lib/arel/visitors/dot.rb +297 -0
  250. data/lib/arel/visitors/ibm_db.rb +34 -0
  251. data/lib/arel/visitors/informix.rb +62 -0
  252. data/lib/arel/visitors/mssql.rb +157 -0
  253. data/lib/arel/visitors/mysql.rb +83 -0
  254. data/lib/arel/visitors/oracle.rb +159 -0
  255. data/lib/arel/visitors/oracle12.rb +66 -0
  256. data/lib/arel/visitors/postgresql.rb +110 -0
  257. data/lib/arel/visitors/sqlite.rb +39 -0
  258. data/lib/arel/visitors/to_sql.rb +889 -0
  259. data/lib/arel/visitors/visitor.rb +46 -0
  260. data/lib/arel/visitors/where_sql.rb +23 -0
  261. data/lib/arel/window_predications.rb +9 -0
  262. data/lib/rails/generators/active_record/migration.rb +14 -1
  263. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  264. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  265. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  266. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  267. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  268. metadata +111 -26
  269. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -53,7 +53,7 @@ module ActiveRecord
53
53
  @model = model
54
54
  @name = name.to_s
55
55
  @attribute_names = @name.match(self.class.pattern)[1].split("_and_")
56
- @attribute_names.map! { |n| @model.attribute_aliases[n] || n }
56
+ @attribute_names.map! { |name| @model.attribute_aliases[name] || name }
57
57
  end
58
58
 
59
59
  def valid?
@@ -31,7 +31,9 @@ module ActiveRecord
31
31
  # as well. With the above example:
32
32
  #
33
33
  # Conversation.active
34
+ # Conversation.not_active
34
35
  # Conversation.archived
36
+ # Conversation.not_archived
35
37
  #
36
38
  # Of course, you can also query them directly if the scopes don't fit your
37
39
  # needs:
@@ -141,10 +143,7 @@ module ActiveRecord
141
143
  end
142
144
  end
143
145
 
144
- # TODO Change this to private once we've dropped Ruby 2.2 support.
145
- # Workaround for Ruby 2.2 "private attribute?" warning.
146
- protected
147
-
146
+ private
148
147
  attr_reader :name, :mapping, :subtype
149
148
  end
150
149
 
@@ -152,14 +151,16 @@ module ActiveRecord
152
151
  klass = self
153
152
  enum_prefix = definitions.delete(:_prefix)
154
153
  enum_suffix = definitions.delete(:_suffix)
154
+ enum_scopes = definitions.delete(:_scopes)
155
155
  definitions.each do |name, values|
156
+ assert_valid_enum_definition_values(values)
156
157
  # statuses = { }
157
158
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
158
159
  name = name.to_s
159
160
 
160
161
  # def self.statuses() statuses end
161
162
  detect_enum_conflict!(name, name.pluralize, true)
162
- singleton_class.send(:define_method, name.pluralize) { enum_values }
163
+ singleton_class.define_method(name.pluralize) { enum_values }
163
164
  defined_enums[name] = enum_values
164
165
 
165
166
  detect_enum_conflict!(name, name)
@@ -197,8 +198,16 @@ module ActiveRecord
197
198
  define_method("#{value_method_name}!") { update!(attr => value) }
198
199
 
199
200
  # scope :active, -> { where(status: 0) }
200
- klass.send(:detect_enum_conflict!, name, value_method_name, true)
201
- klass.scope value_method_name, -> { where(attr => value) }
201
+ # scope :not_active, -> { where.not(status: 0) }
202
+ if enum_scopes != false
203
+ klass.send(:detect_negative_condition!, value_method_name)
204
+
205
+ klass.send(:detect_enum_conflict!, name, value_method_name, true)
206
+ klass.scope value_method_name, -> { where(attr => value) }
207
+
208
+ klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
209
+ klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
210
+ end
202
211
  end
203
212
  end
204
213
  enum_values.freeze
@@ -214,10 +223,24 @@ module ActiveRecord
214
223
  end
215
224
  end
216
225
 
226
+ def assert_valid_enum_definition_values(values)
227
+ unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
228
+ error_message = <<~MSG
229
+ Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
230
+ MSG
231
+ raise ArgumentError, error_message
232
+ end
233
+
234
+ if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
235
+ raise ArgumentError, "Enum label name must not be blank."
236
+ end
237
+ end
238
+
217
239
  ENUM_CONFLICT_MESSAGE = \
218
240
  "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
219
241
  "this will generate a %{type} method \"%{method}\", which is already defined " \
220
242
  "by %{source}."
243
+ private_constant :ENUM_CONFLICT_MESSAGE
221
244
 
222
245
  def detect_enum_conflict!(enum_name, method_name, klass_method = false)
223
246
  if klass_method && dangerous_class_method?(method_name)
@@ -240,5 +263,12 @@ module ActiveRecord
240
263
  source: source
241
264
  }
242
265
  end
266
+
267
+ def detect_negative_condition!(method_name)
268
+ if method_name.start_with?("not_") && logger
269
+ logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
270
+ " This will cause a conflict with auto generated negative scopes."
271
+ end
272
+ end
243
273
  end
244
274
  end
@@ -49,6 +49,10 @@ module ActiveRecord
49
49
  class ConnectionNotEstablished < ActiveRecordError
50
50
  end
51
51
 
52
+ # Raised when a write to the database is attempted on a read only connection.
53
+ class ReadOnlyError < ActiveRecordError
54
+ end
55
+
52
56
  # Raised when Active Record cannot find a record by given id or set of ids.
53
57
  class RecordNotFound < ActiveRecordError
54
58
  attr_reader :model, :primary_key, :id
@@ -64,7 +68,7 @@ module ActiveRecord
64
68
 
65
69
  # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
66
70
  # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
67
- # methods when a record is invalid and can not be saved.
71
+ # methods when a record is invalid and cannot be saved.
68
72
  class RecordNotSaved < ActiveRecordError
69
73
  attr_reader :record
70
74
 
@@ -97,9 +101,13 @@ module ActiveRecord
97
101
  #
98
102
  # Wraps the underlying database error as +cause+.
99
103
  class StatementInvalid < ActiveRecordError
100
- def initialize(message = nil)
104
+ def initialize(message = nil, sql: nil, binds: nil)
101
105
  super(message || $!.try(:message))
106
+ @sql = sql
107
+ @binds = binds
102
108
  end
109
+
110
+ attr_reader :sql, :binds
103
111
  end
104
112
 
105
113
  # Defunct wrapper class kept for compatibility.
@@ -111,22 +119,33 @@ module ActiveRecord
111
119
  class RecordNotUnique < WrappedDatabaseException
112
120
  end
113
121
 
114
- # Raised when a record cannot be inserted or updated because it references a non-existent record.
122
+ # Raised when a record cannot be inserted or updated because it references a non-existent record,
123
+ # or when a record cannot be deleted because a parent record references it.
115
124
  class InvalidForeignKey < WrappedDatabaseException
116
125
  end
117
126
 
118
127
  # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
119
128
  class MismatchedForeignKey < StatementInvalid
120
- def initialize(adapter = nil, message: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil)
121
- @adapter = adapter
129
+ def initialize(
130
+ message: nil,
131
+ sql: nil,
132
+ binds: nil,
133
+ table: nil,
134
+ foreign_key: nil,
135
+ target_table: nil,
136
+ primary_key: nil,
137
+ primary_key_column: nil
138
+ )
122
139
  if table
123
- msg = <<-EOM.strip_heredoc
124
- Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`.
125
- This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`.
126
- To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`).
140
+ type = primary_key_column.bigint? ? :bigint : primary_key_column.type
141
+ msg = <<~EOM.squish
142
+ Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
143
+ which has type `#{primary_key_column.sql_type}`.
144
+ To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
145
+ (For example `t.#{type} :#{foreign_key}`).
127
146
  EOM
128
147
  else
129
- msg = <<-EOM.strip_heredoc
148
+ msg = <<~EOM.squish
130
149
  There is a mismatch between the foreign key and primary key column types.
131
150
  Verify that the foreign key column type and the primary key of the associated table match types.
132
151
  EOM
@@ -134,13 +153,8 @@ module ActiveRecord
134
153
  if message
135
154
  msg << "\nOriginal message: #{message}"
136
155
  end
137
- super(msg)
156
+ super(msg, sql: sql, binds: binds)
138
157
  end
139
-
140
- private
141
- def column_type(table, column)
142
- @adapter.columns(table).detect { |c| c.name == column }.sql_type
143
- end
144
158
  end
145
159
 
146
160
  # Raised when a record cannot be inserted or updated because it would violate a not null constraint.
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  # Returns a formatted string ready to be logged.
19
19
  def exec_explain(queries) # :nodoc:
20
20
  str = queries.map do |sql, binds|
21
- msg = "EXPLAIN for: #{sql}".dup
21
+ msg = +"EXPLAIN for: #{sql}"
22
22
  unless binds.empty?
23
23
  msg << " "
24
24
  msg << binds.map { |attr| render_bind(attr) }.inspect
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class FixtureSet
5
+ class ModelMetadata # :nodoc:
6
+ def initialize(model_class)
7
+ @model_class = model_class
8
+ end
9
+
10
+ def primary_key_name
11
+ @primary_key_name ||= @model_class && @model_class.primary_key
12
+ end
13
+
14
+ def primary_key_type
15
+ @primary_key_type ||= @model_class && @model_class.type_for_attribute(@model_class.primary_key).type
16
+ end
17
+
18
+ def has_primary_key_column?
19
+ @has_primary_key_column ||= primary_key_name &&
20
+ @model_class.columns.any? { |col| col.name == primary_key_name }
21
+ end
22
+
23
+ def timestamp_column_names
24
+ @timestamp_column_names ||=
25
+ %w(created_at created_on updated_at updated_on) & @model_class.column_names
26
+ end
27
+
28
+ def inheritance_column_name
29
+ @inheritance_column_name ||= @model_class && @model_class.inheritance_column
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: This class has to be defined in compact style in
4
+ # order for rendering context subclassing to work correctly.
5
+ class ActiveRecord::FixtureSet::RenderContext # :nodoc:
6
+ def self.create_subclass
7
+ Class.new(ActiveRecord::FixtureSet.context_class) do
8
+ def get_binding
9
+ binding()
10
+ end
11
+
12
+ def binary(path)
13
+ %(!!binary "#{Base64.strict_encode64(File.read(path))}")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class FixtureSet
5
+ class TableRow # :nodoc:
6
+ class ReflectionProxy # :nodoc:
7
+ def initialize(association)
8
+ @association = association
9
+ end
10
+
11
+ def join_table
12
+ @association.join_table
13
+ end
14
+
15
+ def name
16
+ @association.name
17
+ end
18
+
19
+ def primary_key_type
20
+ @association.klass.type_for_attribute(@association.klass.primary_key).type
21
+ end
22
+ end
23
+
24
+ class HasManyThroughProxy < ReflectionProxy # :nodoc:
25
+ def rhs_key
26
+ @association.foreign_key
27
+ end
28
+
29
+ def lhs_key
30
+ @association.through_reflection.foreign_key
31
+ end
32
+
33
+ def join_table
34
+ @association.through_reflection.table_name
35
+ end
36
+ end
37
+
38
+ def initialize(fixture, table_rows:, label:, now:)
39
+ @table_rows = table_rows
40
+ @label = label
41
+ @now = now
42
+ @row = fixture.to_hash
43
+ fill_row_model_attributes
44
+ end
45
+
46
+ def to_hash
47
+ @row
48
+ end
49
+
50
+ private
51
+
52
+ def model_metadata
53
+ @table_rows.model_metadata
54
+ end
55
+
56
+ def model_class
57
+ @table_rows.model_class
58
+ end
59
+
60
+ def fill_row_model_attributes
61
+ return unless model_class
62
+ fill_timestamps
63
+ interpolate_label
64
+ generate_primary_key
65
+ resolve_enums
66
+ resolve_sti_reflections
67
+ end
68
+
69
+ def reflection_class
70
+ @reflection_class ||= if @row.include?(model_metadata.inheritance_column_name)
71
+ @row[model_metadata.inheritance_column_name].constantize rescue model_class
72
+ else
73
+ model_class
74
+ end
75
+ end
76
+
77
+ def fill_timestamps
78
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
79
+ if model_class.record_timestamps
80
+ model_metadata.timestamp_column_names.each do |c_name|
81
+ @row[c_name] = @now unless @row.key?(c_name)
82
+ end
83
+ end
84
+ end
85
+
86
+ def interpolate_label
87
+ # interpolate the fixture label
88
+ @row.each do |key, value|
89
+ @row[key] = value.gsub("$LABEL", @label.to_s) if value.is_a?(String)
90
+ end
91
+ end
92
+
93
+ def generate_primary_key
94
+ # generate a primary key if necessary
95
+ if model_metadata.has_primary_key_column? && !@row.include?(model_metadata.primary_key_name)
96
+ @row[model_metadata.primary_key_name] = ActiveRecord::FixtureSet.identify(
97
+ @label, model_metadata.primary_key_type
98
+ )
99
+ end
100
+ end
101
+
102
+ def resolve_enums
103
+ model_class.defined_enums.each do |name, values|
104
+ if @row.include?(name)
105
+ @row[name] = values.fetch(@row[name], @row[name])
106
+ end
107
+ end
108
+ end
109
+
110
+ def resolve_sti_reflections
111
+ # If STI is used, find the correct subclass for association reflection
112
+ reflection_class._reflections.each_value do |association|
113
+ case association.macro
114
+ when :belongs_to
115
+ # Do not replace association name with association foreign key if they are named the same
116
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
117
+
118
+ if association.name.to_s != fk_name && value = @row.delete(association.name.to_s)
119
+ if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
120
+ # support polymorphic belongs_to as "label (Type)"
121
+ @row[association.foreign_type] = $1
122
+ end
123
+
124
+ fk_type = reflection_class.type_for_attribute(fk_name).type
125
+ @row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
126
+ end
127
+ when :has_many
128
+ if association.options[:through]
129
+ add_join_records(HasManyThroughProxy.new(association))
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ def add_join_records(association)
136
+ # This is the case when the join table has no fixtures file
137
+ if (targets = @row.delete(association.name.to_s))
138
+ table_name = association.join_table
139
+ column_type = association.primary_key_type
140
+ lhs_key = association.lhs_key
141
+ rhs_key = association.rhs_key
142
+
143
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
144
+ joins = targets.map do |target|
145
+ { lhs_key => @row[model_metadata.primary_key_name],
146
+ rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
147
+ end
148
+ @table_rows.tables[table_name].concat(joins)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/fixture_set/table_row"
4
+ require "active_record/fixture_set/model_metadata"
5
+
6
+ module ActiveRecord
7
+ class FixtureSet
8
+ class TableRows # :nodoc:
9
+ def initialize(table_name, model_class:, fixtures:, config:)
10
+ @model_class = model_class
11
+
12
+ # track any join tables we need to insert later
13
+ @tables = Hash.new { |h, table| h[table] = [] }
14
+
15
+ # ensure this table is loaded before any HABTM associations
16
+ @tables[table_name] = nil
17
+
18
+ build_table_rows_from(table_name, fixtures, config)
19
+ end
20
+
21
+ attr_reader :tables, :model_class
22
+
23
+ def to_hash
24
+ @tables.transform_values { |rows| rows.map(&:to_hash) }
25
+ end
26
+
27
+ def model_metadata
28
+ @model_metadata ||= ModelMetadata.new(model_class)
29
+ end
30
+
31
+ private
32
+
33
+ def build_table_rows_from(table_name, fixtures, config)
34
+ now = config.default_timezone == :utc ? Time.now.utc : Time.now
35
+
36
+ @tables[table_name] = fixtures.map do |label, fixture|
37
+ TableRow.new(
38
+ fixture,
39
+ table_rows: self,
40
+ label: label,
41
+ now: now,
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end