activerecord 5.2.4.2 → 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 +714 -570
  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 +3 -13
  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 +12 -23
  22. data/lib/active_record/associations/collection_proxy.rb +12 -15
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +2 -10
  25. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  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 +9 -10
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +39 -31
  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 +2 -2
  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 +104 -16
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +99 -123
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -8
  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 +132 -53
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +187 -43
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +138 -195
  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 +6 -10
  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 +12 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  86. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  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 +42 -6
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +129 -141
  95. data/lib/active_record/connection_handling.rb +155 -26
  96. data/lib/active_record/core.rb +103 -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 +15 -7
  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 +4 -4
  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 +76 -49
  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 +228 -24
  129. data/lib/active_record/query_cache.rb +11 -4
  130. data/lib/active_record/querying.rb +32 -20
  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 +32 -30
  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 +53 -47
  139. data/lib/active_record/relation/delegation.rb +26 -43
  140. data/lib/active_record/relation/finder_methods.rb +23 -27
  141. data/lib/active_record/relation/merger.rb +11 -20
  142. data/lib/active_record/relation/predicate_builder.rb +4 -6
  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 +13 -8
  150. data/lib/active_record/relation/query_methods.rb +213 -64
  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 +4 -5
  161. data/lib/active_record/scoping/named.rb +20 -15
  162. data/lib/active_record/statement_cache.rb +30 -3
  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 +56 -65
  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 +110 -25
  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,14 +119,14 @@ 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
129
  def initialize(
121
- adapter = nil,
122
130
  message: nil,
123
131
  sql: nil,
124
132
  binds: nil,
@@ -130,14 +138,14 @@ module ActiveRecord
130
138
  )
131
139
  if table
132
140
  type = primary_key_column.bigint? ? :bigint : primary_key_column.type
133
- msg = <<-EOM.squish
141
+ msg = <<~EOM.squish
134
142
  Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
135
143
  which has type `#{primary_key_column.sql_type}`.
136
144
  To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
137
145
  (For example `t.#{type} :#{foreign_key}`).
138
146
  EOM
139
147
  else
140
- msg = <<-EOM.squish
148
+ msg = <<~EOM.squish
141
149
  There is a mismatch between the foreign key and primary key column types.
142
150
  Verify that the foreign key column type and the primary key of the associated table match types.
143
151
  EOM
@@ -145,7 +153,7 @@ module ActiveRecord
145
153
  if message
146
154
  msg << "\nOriginal message: #{message}"
147
155
  end
148
- super(msg)
156
+ super(msg, sql: sql, binds: binds)
149
157
  end
150
158
  end
151
159
 
@@ -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
@@ -7,6 +7,9 @@ require "set"
7
7
  require "active_support/dependencies"
8
8
  require "active_support/core_ext/digest/uuid"
9
9
  require "active_record/fixture_set/file"
10
+ require "active_record/fixture_set/render_context"
11
+ require "active_record/fixture_set/table_rows"
12
+ require "active_record/test_fixtures"
10
13
  require "active_record/errors"
11
14
 
12
15
  module ActiveRecord
@@ -179,8 +182,8 @@ module ActiveRecord
179
182
  # end
180
183
  # end
181
184
  #
182
- # If you preload your test database with all fixture data (probably in the rake task) and use
183
- # transactional tests, then you may omit all fixtures declarations in your test cases since
185
+ # If you preload your test database with all fixture data (probably by running `rails db:fixtures:load`)
186
+ # and use transactional tests, then you may omit all fixtures declarations in your test cases since
184
187
  # all the data's already there and every case rolls back its changes.
185
188
  #
186
189
  # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
@@ -440,60 +443,6 @@ module ActiveRecord
440
443
 
441
444
  @@all_cached_fixtures = Hash.new { |h, k| h[k] = {} }
442
445
 
443
- def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
444
- config.pluralize_table_names ?
445
- fixture_set_name.singularize.camelize :
446
- fixture_set_name.camelize
447
- end
448
-
449
- def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
450
- "#{ config.table_name_prefix }"\
451
- "#{ fixture_set_name.tr('/', '_') }"\
452
- "#{ config.table_name_suffix }".to_sym
453
- end
454
-
455
- def self.reset_cache
456
- @@all_cached_fixtures.clear
457
- end
458
-
459
- def self.cache_for_connection(connection)
460
- @@all_cached_fixtures[connection]
461
- end
462
-
463
- def self.fixture_is_cached?(connection, table_name)
464
- cache_for_connection(connection)[table_name]
465
- end
466
-
467
- def self.cached_fixtures(connection, keys_to_fetch = nil)
468
- if keys_to_fetch
469
- cache_for_connection(connection).values_at(*keys_to_fetch)
470
- else
471
- cache_for_connection(connection).values
472
- end
473
- end
474
-
475
- def self.cache_fixtures(connection, fixtures_map)
476
- cache_for_connection(connection).update(fixtures_map)
477
- end
478
-
479
- def self.instantiate_fixtures(object, fixture_set, load_instances = true)
480
- if load_instances
481
- fixture_set.each do |fixture_name, fixture|
482
- begin
483
- object.instance_variable_set "@#{fixture_name}", fixture.find
484
- rescue FixtureClassNotFound
485
- nil
486
- end
487
- end
488
- end
489
- end
490
-
491
- def self.instantiate_all_loaded_fixtures(object, load_instances = true)
492
- all_loaded_fixtures.each_value do |fixture_set|
493
- instantiate_fixtures(object, fixture_set, load_instances)
494
- end
495
- end
496
-
497
446
  cattr_accessor :all_loaded_fixtures, default: {}
498
447
 
499
448
  class ClassCache
@@ -502,14 +451,16 @@ module ActiveRecord
502
451
  @config = config
503
452
 
504
453
  # Remove string values that aren't constants or subclasses of AR
505
- @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) }
454
+ @class_names.delete_if do |klass_name, klass|
455
+ !insert_class(@class_names, klass_name, klass)
456
+ end
506
457
  end
507
458
 
508
459
  def [](fs_name)
509
- @class_names.fetch(fs_name) {
460
+ @class_names.fetch(fs_name) do
510
461
  klass = default_fixture_model(fs_name, @config).safe_constantize
511
462
  insert_class(@class_names, fs_name, klass)
512
- }
463
+ end
513
464
  end
514
465
 
515
466
  private
@@ -528,76 +479,151 @@ module ActiveRecord
528
479
  end
529
480
  end
530
481
 
531
- def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
532
- fixture_set_names = Array(fixture_set_names).map(&:to_s)
533
- class_names = ClassCache.new class_names, config
482
+ class << self
483
+ def default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
484
+ config.pluralize_table_names ?
485
+ fixture_set_name.singularize.camelize :
486
+ fixture_set_name.camelize
487
+ end
534
488
 
535
- # FIXME: Apparently JK uses this.
536
- connection = block_given? ? yield : ActiveRecord::Base.connection
489
+ def default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
490
+ "#{ config.table_name_prefix }"\
491
+ "#{ fixture_set_name.tr('/', '_') }"\
492
+ "#{ config.table_name_suffix }".to_sym
493
+ end
537
494
 
538
- files_to_read = fixture_set_names.reject { |fs_name|
539
- fixture_is_cached?(connection, fs_name)
540
- }
495
+ def reset_cache
496
+ @@all_cached_fixtures.clear
497
+ end
541
498
 
542
- unless files_to_read.empty?
543
- fixtures_map = {}
499
+ def cache_for_connection(connection)
500
+ @@all_cached_fixtures[connection]
501
+ end
544
502
 
545
- fixture_sets = files_to_read.map do |fs_name|
546
- klass = class_names[fs_name]
547
- conn = klass ? klass.connection : connection
548
- fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
549
- conn,
550
- fs_name,
551
- klass,
552
- ::File.join(fixtures_directory, fs_name))
503
+ def fixture_is_cached?(connection, table_name)
504
+ cache_for_connection(connection)[table_name]
505
+ end
506
+
507
+ def cached_fixtures(connection, keys_to_fetch = nil)
508
+ if keys_to_fetch
509
+ cache_for_connection(connection).values_at(*keys_to_fetch)
510
+ else
511
+ cache_for_connection(connection).values
553
512
  end
513
+ end
554
514
 
555
- update_all_loaded_fixtures fixtures_map
556
- fixture_sets_by_connection = fixture_sets.group_by { |fs| fs.model_class ? fs.model_class.connection : connection }
515
+ def cache_fixtures(connection, fixtures_map)
516
+ cache_for_connection(connection).update(fixtures_map)
517
+ end
557
518
 
558
- fixture_sets_by_connection.each do |conn, set|
559
- table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
519
+ def instantiate_fixtures(object, fixture_set, load_instances = true)
520
+ return unless load_instances
521
+ fixture_set.each do |fixture_name, fixture|
522
+ object.instance_variable_set "@#{fixture_name}", fixture.find
523
+ rescue FixtureClassNotFound
524
+ nil
525
+ end
526
+ end
560
527
 
561
- set.each do |fs|
562
- fs.table_rows.each do |table, rows|
563
- table_rows_for_connection[table].unshift(*rows)
564
- end
565
- end
566
- conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
528
+ def instantiate_all_loaded_fixtures(object, load_instances = true)
529
+ all_loaded_fixtures.each_value do |fixture_set|
530
+ instantiate_fixtures(object, fixture_set, load_instances)
531
+ end
532
+ end
567
533
 
568
- # Cap primary key sequences to max(pk).
569
- if conn.respond_to?(:reset_pk_sequence!)
570
- set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
571
- end
534
+ def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
535
+ fixture_set_names = Array(fixture_set_names).map(&:to_s)
536
+ class_names = ClassCache.new class_names, config
537
+
538
+ # FIXME: Apparently JK uses this.
539
+ connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
540
+
541
+ fixture_files_to_read = fixture_set_names.reject do |fs_name|
542
+ fixture_is_cached?(connection.call, fs_name)
572
543
  end
573
544
 
574
- cache_fixtures(connection, fixtures_map)
545
+ if fixture_files_to_read.any?
546
+ fixtures_map = read_and_insert(
547
+ fixtures_directory,
548
+ fixture_files_to_read,
549
+ class_names,
550
+ connection,
551
+ )
552
+ cache_fixtures(connection.call, fixtures_map)
553
+ end
554
+ cached_fixtures(connection.call, fixture_set_names)
575
555
  end
576
- cached_fixtures(connection, fixture_set_names)
577
- end
578
556
 
579
- # Returns a consistent, platform-independent identifier for +label+.
580
- # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
581
- def self.identify(label, column_type = :integer)
582
- if column_type == :uuid
583
- Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
584
- else
585
- Zlib.crc32(label.to_s) % MAX_ID
557
+ # Returns a consistent, platform-independent identifier for +label+.
558
+ # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
559
+ def identify(label, column_type = :integer)
560
+ if column_type == :uuid
561
+ Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
562
+ else
563
+ Zlib.crc32(label.to_s) % MAX_ID
564
+ end
586
565
  end
587
- end
588
566
 
589
- # Superclass for the evaluation contexts used by ERB fixtures.
590
- def self.context_class
591
- @context_class ||= Class.new
592
- end
567
+ # Superclass for the evaluation contexts used by ERB fixtures.
568
+ def context_class
569
+ @context_class ||= Class.new
570
+ end
593
571
 
594
- def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
595
- all_loaded_fixtures.update(fixtures_map)
572
+ private
573
+
574
+ def read_and_insert(fixtures_directory, fixture_files, class_names, connection) # :nodoc:
575
+ fixtures_map = {}
576
+ fixture_sets = fixture_files.map do |fixture_set_name|
577
+ klass = class_names[fixture_set_name]
578
+ fixtures_map[fixture_set_name] = new( # ActiveRecord::FixtureSet.new
579
+ nil,
580
+ fixture_set_name,
581
+ klass,
582
+ ::File.join(fixtures_directory, fixture_set_name)
583
+ )
584
+ end
585
+ update_all_loaded_fixtures(fixtures_map)
586
+
587
+ insert(fixture_sets, connection)
588
+
589
+ fixtures_map
590
+ end
591
+
592
+ def insert(fixture_sets, connection) # :nodoc:
593
+ fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
594
+ if fixture_set.model_class
595
+ fixture_set.model_class.connection
596
+ else
597
+ connection.call
598
+ end
599
+ end
600
+
601
+ fixture_sets_by_connection.each do |conn, set|
602
+ table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
603
+
604
+ set.each do |fixture_set|
605
+ fixture_set.table_rows.each do |table, rows|
606
+ table_rows_for_connection[table].unshift(*rows)
607
+ end
608
+ end
609
+
610
+ conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
611
+
612
+ # Cap primary key sequences to max(pk).
613
+ if conn.respond_to?(:reset_pk_sequence!)
614
+ set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
615
+ end
616
+ end
617
+ end
618
+
619
+ def update_all_loaded_fixtures(fixtures_map) # :nodoc:
620
+ all_loaded_fixtures.update(fixtures_map)
621
+ end
596
622
  end
597
623
 
598
624
  attr_reader :table_name, :name, :fixtures, :model_class, :config
599
625
 
600
- def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
626
+ def initialize(_, name, class_name, path, config = ActiveRecord::Base)
601
627
  @name = name
602
628
  @path = path
603
629
  @config = config
@@ -606,11 +632,7 @@ module ActiveRecord
606
632
 
607
633
  @fixtures = read_fixture_files(path)
608
634
 
609
- @connection = connection
610
-
611
- @table_name = (model_class.respond_to?(:table_name) ?
612
- model_class.table_name :
613
- self.class.default_fixture_table_name(name, config))
635
+ @table_name = model_class&.table_name || self.class.default_fixture_table_name(name, config)
614
636
  end
615
637
 
616
638
  def [](x)
@@ -632,152 +654,18 @@ module ActiveRecord
632
654
  # Returns a hash of rows to be inserted. The key is the table, the value is
633
655
  # a list of rows to insert to that table.
634
656
  def table_rows
635
- now = config.default_timezone == :utc ? Time.now.utc : Time.now
636
-
637
657
  # allow a standard key to be used for doing defaults in YAML
638
658
  fixtures.delete("DEFAULTS")
639
659
 
640
- # track any join tables we need to insert later
641
- rows = Hash.new { |h, table| h[table] = [] }
642
-
643
- rows[table_name] = fixtures.map do |label, fixture|
644
- row = fixture.to_hash
645
-
646
- if model_class
647
- # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
648
- if model_class.record_timestamps
649
- timestamp_column_names.each do |c_name|
650
- row[c_name] = now unless row.key?(c_name)
651
- end
652
- end
653
-
654
- # interpolate the fixture label
655
- row.each do |key, value|
656
- row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
657
- end
658
-
659
- # generate a primary key if necessary
660
- if has_primary_key_column? && !row.include?(primary_key_name)
661
- row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
662
- end
663
-
664
- # Resolve enums
665
- model_class.defined_enums.each do |name, values|
666
- if row.include?(name)
667
- row[name] = values.fetch(row[name], row[name])
668
- end
669
- end
670
-
671
- # If STI is used, find the correct subclass for association reflection
672
- reflection_class =
673
- if row.include?(inheritance_column_name)
674
- row[inheritance_column_name].constantize rescue model_class
675
- else
676
- model_class
677
- end
678
-
679
- reflection_class._reflections.each_value do |association|
680
- case association.macro
681
- when :belongs_to
682
- # Do not replace association name with association foreign key if they are named the same
683
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
684
-
685
- if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
686
- if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
687
- # support polymorphic belongs_to as "label (Type)"
688
- row[association.foreign_type] = $1
689
- end
690
-
691
- fk_type = reflection_class.type_for_attribute(fk_name).type
692
- row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
693
- end
694
- when :has_many
695
- if association.options[:through]
696
- add_join_records(rows, row, HasManyThroughProxy.new(association))
697
- end
698
- end
699
- end
700
- end
701
-
702
- row
703
- end
704
- rows
705
- end
706
-
707
- class ReflectionProxy # :nodoc:
708
- def initialize(association)
709
- @association = association
710
- end
711
-
712
- def join_table
713
- @association.join_table
714
- end
715
-
716
- def name
717
- @association.name
718
- end
719
-
720
- def primary_key_type
721
- @association.klass.type_for_attribute(@association.klass.primary_key).type
722
- end
723
- end
724
-
725
- class HasManyThroughProxy < ReflectionProxy # :nodoc:
726
- def rhs_key
727
- @association.foreign_key
728
- end
729
-
730
- def lhs_key
731
- @association.through_reflection.foreign_key
732
- end
733
-
734
- def join_table
735
- @association.through_reflection.table_name
736
- end
660
+ TableRows.new(
661
+ table_name,
662
+ model_class: model_class,
663
+ fixtures: fixtures,
664
+ config: config,
665
+ ).to_hash
737
666
  end
738
667
 
739
668
  private
740
- def primary_key_name
741
- @primary_key_name ||= model_class && model_class.primary_key
742
- end
743
-
744
- def primary_key_type
745
- @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type
746
- end
747
-
748
- def add_join_records(rows, row, association)
749
- # This is the case when the join table has no fixtures file
750
- if (targets = row.delete(association.name.to_s))
751
- table_name = association.join_table
752
- column_type = association.primary_key_type
753
- lhs_key = association.lhs_key
754
- rhs_key = association.rhs_key
755
-
756
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
757
- rows[table_name].concat targets.map { |target|
758
- { lhs_key => row[primary_key_name],
759
- rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
760
- }
761
- end
762
- end
763
-
764
- def has_primary_key_column?
765
- @has_primary_key_column ||= primary_key_name &&
766
- model_class.columns.any? { |c| c.name == primary_key_name }
767
- end
768
-
769
- def timestamp_column_names
770
- @timestamp_column_names ||=
771
- %w(created_at created_on updated_at updated_on) & column_names
772
- end
773
-
774
- def inheritance_column_name
775
- @inheritance_column_name ||= model_class && model_class.inheritance_column
776
- end
777
-
778
- def column_names
779
- @column_names ||= @connection.columns(@table_name).collect(&:name)
780
- end
781
669
 
782
670
  def model_class=(class_name)
783
671
  if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@@ -841,224 +729,9 @@ module ActiveRecord
841
729
  alias :to_hash :fixture
842
730
 
843
731
  def find
844
- if model_class
845
- model_class.unscoped do
846
- model_class.find(fixture[model_class.primary_key])
847
- end
848
- else
849
- raise FixtureClassNotFound, "No class attached to find."
850
- end
851
- end
852
- end
853
- end
854
-
855
- module ActiveRecord
856
- module TestFixtures
857
- extend ActiveSupport::Concern
858
-
859
- def before_setup # :nodoc:
860
- setup_fixtures
861
- super
862
- end
863
-
864
- def after_teardown # :nodoc:
865
- super
866
- teardown_fixtures
867
- end
868
-
869
- included do
870
- class_attribute :fixture_path, instance_writer: false
871
- class_attribute :fixture_table_names, default: []
872
- class_attribute :fixture_class_names, default: {}
873
- class_attribute :use_transactional_tests, default: true
874
- class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
875
- class_attribute :pre_loaded_fixtures, default: false
876
- class_attribute :config, default: ActiveRecord::Base
877
- end
878
-
879
- module ClassMethods
880
- # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
881
- #
882
- # Examples:
883
- #
884
- # set_fixture_class some_fixture: SomeModel,
885
- # 'namespaced/fixture' => Another::Model
886
- #
887
- # The keys must be the fixture names, that coincide with the short paths to the fixture files.
888
- def set_fixture_class(class_names = {})
889
- self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
890
- end
891
-
892
- def fixtures(*fixture_set_names)
893
- if fixture_set_names.first == :all
894
- fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
895
- fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
896
- else
897
- fixture_set_names = fixture_set_names.flatten.map(&:to_s)
898
- end
899
-
900
- self.fixture_table_names |= fixture_set_names
901
- setup_fixture_accessors(fixture_set_names)
902
- end
903
-
904
- def setup_fixture_accessors(fixture_set_names = nil)
905
- fixture_set_names = Array(fixture_set_names || fixture_table_names)
906
- methods = Module.new do
907
- fixture_set_names.each do |fs_name|
908
- fs_name = fs_name.to_s
909
- accessor_name = fs_name.tr("/", "_").to_sym
910
-
911
- define_method(accessor_name) do |*fixture_names|
912
- force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
913
- return_single_record = fixture_names.size == 1
914
- fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
915
-
916
- @fixture_cache[fs_name] ||= {}
917
-
918
- instances = fixture_names.map do |f_name|
919
- f_name = f_name.to_s if f_name.is_a?(Symbol)
920
- @fixture_cache[fs_name].delete(f_name) if force_reload
921
-
922
- if @loaded_fixtures[fs_name][f_name]
923
- @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
924
- else
925
- raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
926
- end
927
- end
928
-
929
- return_single_record ? instances.first : instances
930
- end
931
- private accessor_name
932
- end
933
- end
934
- include methods
935
- end
936
-
937
- def uses_transaction(*methods)
938
- @uses_transaction = [] unless defined?(@uses_transaction)
939
- @uses_transaction.concat methods.map(&:to_s)
940
- end
941
-
942
- def uses_transaction?(method)
943
- @uses_transaction = [] unless defined?(@uses_transaction)
944
- @uses_transaction.include?(method.to_s)
945
- end
946
- end
947
-
948
- def run_in_transaction?
949
- use_transactional_tests &&
950
- !self.class.uses_transaction?(method_name)
951
- end
952
-
953
- def setup_fixtures(config = ActiveRecord::Base)
954
- if pre_loaded_fixtures && !use_transactional_tests
955
- raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
956
- end
957
-
958
- @fixture_cache = {}
959
- @fixture_connections = []
960
- @@already_loaded_fixtures ||= {}
961
- @connection_subscriber = nil
962
-
963
- # Load fixtures once and begin transaction.
964
- if run_in_transaction?
965
- if @@already_loaded_fixtures[self.class]
966
- @loaded_fixtures = @@already_loaded_fixtures[self.class]
967
- else
968
- @loaded_fixtures = load_fixtures(config)
969
- @@already_loaded_fixtures[self.class] = @loaded_fixtures
970
- end
971
-
972
- # Begin transactions for connections already established
973
- @fixture_connections = enlist_fixture_connections
974
- @fixture_connections.each do |connection|
975
- connection.begin_transaction joinable: false
976
- connection.pool.lock_thread = true
977
- end
978
-
979
- # When connections are established in the future, begin a transaction too
980
- @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
981
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
982
-
983
- if spec_name
984
- begin
985
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
986
- rescue ConnectionNotEstablished
987
- connection = nil
988
- end
989
-
990
- if connection && !@fixture_connections.include?(connection)
991
- connection.begin_transaction joinable: false
992
- connection.pool.lock_thread = true
993
- @fixture_connections << connection
994
- end
995
- end
996
- end
997
-
998
- # Load fixtures for every test.
999
- else
1000
- ActiveRecord::FixtureSet.reset_cache
1001
- @@already_loaded_fixtures[self.class] = nil
1002
- @loaded_fixtures = load_fixtures(config)
1003
- end
1004
-
1005
- # Instantiate fixtures for every test if requested.
1006
- instantiate_fixtures if use_instantiated_fixtures
1007
- end
1008
-
1009
- def teardown_fixtures
1010
- # Rollback changes if a transaction is active.
1011
- if run_in_transaction?
1012
- ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
1013
- @fixture_connections.each do |connection|
1014
- connection.rollback_transaction if connection.transaction_open?
1015
- connection.pool.lock_thread = false
1016
- end
1017
- @fixture_connections.clear
1018
- else
1019
- ActiveRecord::FixtureSet.reset_cache
1020
- end
1021
-
1022
- ActiveRecord::Base.clear_active_connections!
1023
- end
1024
-
1025
- def enlist_fixture_connections
1026
- ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
1027
- end
1028
-
1029
- private
1030
- def load_fixtures(config)
1031
- fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
1032
- Hash[fixtures.map { |f| [f.name, f] }]
1033
- end
1034
-
1035
- def instantiate_fixtures
1036
- if pre_loaded_fixtures
1037
- raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
1038
- ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
1039
- else
1040
- raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil?
1041
- @loaded_fixtures.each_value do |fixture_set|
1042
- ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
1043
- end
1044
- end
1045
- end
1046
-
1047
- def load_instances?
1048
- use_instantiated_fixtures != :no_instances
1049
- end
1050
- end
1051
- end
1052
-
1053
- class ActiveRecord::FixtureSet::RenderContext # :nodoc:
1054
- def self.create_subclass
1055
- Class.new ActiveRecord::FixtureSet.context_class do
1056
- def get_binding
1057
- binding()
1058
- end
1059
-
1060
- def binary(path)
1061
- %(!!binary "#{Base64.strict_encode64(File.read(path))}")
732
+ raise FixtureClassNotFound, "No class attached to find." unless model_class
733
+ model_class.unscoped do
734
+ model_class.find(fixture[model_class.primary_key])
1062
735
  end
1063
736
  end
1064
737
  end