activerecord 5.2.8.1 → 6.0.0.beta1

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -816
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +35 -19
  8. data/lib/active_record/associations/association_scope.rb +4 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +14 -50
  12. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  14. data/lib/active_record/associations/collection_association.rb +11 -25
  15. data/lib/active_record/associations/collection_proxy.rb +32 -6
  16. data/lib/active_record/associations/foreign_association.rb +7 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +25 -18
  19. data/lib/active_record/associations/has_one_association.rb +28 -30
  20. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  21. data/lib/active_record/associations/join_dependency/join_association.rb +11 -26
  22. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  23. data/lib/active_record/associations/join_dependency.rb +15 -20
  24. data/lib/active_record/associations/preloader/association.rb +1 -2
  25. data/lib/active_record/associations/preloader.rb +32 -29
  26. data/lib/active_record/associations/singular_association.rb +2 -16
  27. data/lib/active_record/associations.rb +16 -12
  28. data/lib/active_record/attribute_assignment.rb +7 -10
  29. data/lib/active_record/attribute_methods/dirty.rb +64 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +8 -7
  31. data/lib/active_record/attribute_methods/read.rb +16 -48
  32. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  34. data/lib/active_record/attribute_methods/write.rb +15 -16
  35. data/lib/active_record/attribute_methods.rb +34 -56
  36. data/lib/active_record/autosave_association.rb +7 -21
  37. data/lib/active_record/base.rb +2 -2
  38. data/lib/active_record/callbacks.rb +3 -17
  39. data/lib/active_record/coders/yaml_column.rb +1 -13
  40. data/lib/active_record/collection_cache_key.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +13 -36
  42. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +25 -84
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -14
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +5 -11
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -11
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +0 -2
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -27
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +81 -52
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +95 -31
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -90
  53. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  54. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +5 -9
  55. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -7
  56. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  57. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +65 -10
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -4
  60. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +16 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  64. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  65. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  67. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  69. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +11 -36
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +9 -2
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +38 -20
  73. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -1
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -56
  75. data/lib/active_record/connection_adapters/schema_cache.rb +5 -0
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -5
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -9
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +95 -62
  79. data/lib/active_record/connection_handling.rb +132 -26
  80. data/lib/active_record/core.rb +75 -52
  81. data/lib/active_record/counter_cache.rb +4 -29
  82. data/lib/active_record/database_configurations/database_config.rb +37 -0
  83. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  84. data/lib/active_record/database_configurations/url_config.rb +74 -0
  85. data/lib/active_record/database_configurations.rb +184 -0
  86. data/lib/active_record/enum.rb +22 -7
  87. data/lib/active_record/errors.rb +24 -21
  88. data/lib/active_record/explain.rb +1 -1
  89. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  90. data/lib/active_record/fixture_set/render_context.rb +17 -0
  91. data/lib/active_record/fixture_set/table_row.rb +153 -0
  92. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  93. data/lib/active_record/fixtures.rb +140 -472
  94. data/lib/active_record/gem_version.rb +4 -4
  95. data/lib/active_record/inheritance.rb +12 -2
  96. data/lib/active_record/integration.rb +56 -16
  97. data/lib/active_record/internal_metadata.rb +5 -1
  98. data/lib/active_record/locking/optimistic.rb +2 -2
  99. data/lib/active_record/locking/pessimistic.rb +3 -3
  100. data/lib/active_record/log_subscriber.rb +7 -26
  101. data/lib/active_record/migration/command_recorder.rb +35 -5
  102. data/lib/active_record/migration/compatibility.rb +34 -16
  103. data/lib/active_record/migration.rb +38 -37
  104. data/lib/active_record/model_schema.rb +30 -9
  105. data/lib/active_record/nested_attributes.rb +2 -2
  106. data/lib/active_record/no_touching.rb +7 -0
  107. data/lib/active_record/persistence.rb +18 -7
  108. data/lib/active_record/query_cache.rb +11 -4
  109. data/lib/active_record/querying.rb +19 -11
  110. data/lib/active_record/railtie.rb +71 -60
  111. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  112. data/lib/active_record/railties/controller_runtime.rb +30 -35
  113. data/lib/active_record/railties/databases.rake +94 -43
  114. data/lib/active_record/reflection.rb +60 -44
  115. data/lib/active_record/relation/batches.rb +13 -10
  116. data/lib/active_record/relation/calculations.rb +38 -28
  117. data/lib/active_record/relation/delegation.rb +4 -13
  118. data/lib/active_record/relation/finder_methods.rb +12 -25
  119. data/lib/active_record/relation/merger.rb +2 -6
  120. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  121. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  122. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  123. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  124. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  125. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  126. data/lib/active_record/relation/predicate_builder.rb +4 -6
  127. data/lib/active_record/relation/query_attribute.rb +15 -12
  128. data/lib/active_record/relation/query_methods.rb +29 -52
  129. data/lib/active_record/relation/where_clause.rb +4 -0
  130. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  131. data/lib/active_record/relation.rb +150 -69
  132. data/lib/active_record/result.rb +30 -11
  133. data/lib/active_record/sanitization.rb +2 -39
  134. data/lib/active_record/schema.rb +1 -10
  135. data/lib/active_record/schema_dumper.rb +12 -6
  136. data/lib/active_record/schema_migration.rb +4 -0
  137. data/lib/active_record/scoping/default.rb +10 -3
  138. data/lib/active_record/scoping/named.rb +10 -14
  139. data/lib/active_record/scoping.rb +9 -8
  140. data/lib/active_record/statement_cache.rb +32 -5
  141. data/lib/active_record/store.rb +39 -8
  142. data/lib/active_record/table_metadata.rb +1 -4
  143. data/lib/active_record/tasks/database_tasks.rb +89 -23
  144. data/lib/active_record/tasks/mysql_database_tasks.rb +2 -4
  145. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  146. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  147. data/lib/active_record/test_databases.rb +38 -0
  148. data/lib/active_record/test_fixtures.rb +224 -0
  149. data/lib/active_record/timestamp.rb +4 -6
  150. data/lib/active_record/transactions.rb +3 -22
  151. data/lib/active_record/translation.rb +1 -1
  152. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  153. data/lib/active_record/type.rb +3 -4
  154. data/lib/active_record/type_caster/connection.rb +1 -6
  155. data/lib/active_record/type_caster/map.rb +1 -4
  156. data/lib/active_record/validations/uniqueness.rb +13 -25
  157. data/lib/active_record.rb +2 -1
  158. data/lib/arel/alias_predication.rb +9 -0
  159. data/lib/arel/attributes/attribute.rb +37 -0
  160. data/lib/arel/attributes.rb +22 -0
  161. data/lib/arel/collectors/bind.rb +24 -0
  162. data/lib/arel/collectors/composite.rb +31 -0
  163. data/lib/arel/collectors/plain_string.rb +20 -0
  164. data/lib/arel/collectors/sql_string.rb +20 -0
  165. data/lib/arel/collectors/substitute_binds.rb +28 -0
  166. data/lib/arel/crud.rb +42 -0
  167. data/lib/arel/delete_manager.rb +18 -0
  168. data/lib/arel/errors.rb +9 -0
  169. data/lib/arel/expressions.rb +29 -0
  170. data/lib/arel/factory_methods.rb +49 -0
  171. data/lib/arel/insert_manager.rb +49 -0
  172. data/lib/arel/math.rb +45 -0
  173. data/lib/arel/nodes/and.rb +32 -0
  174. data/lib/arel/nodes/ascending.rb +23 -0
  175. data/lib/arel/nodes/binary.rb +52 -0
  176. data/lib/arel/nodes/bind_param.rb +36 -0
  177. data/lib/arel/nodes/case.rb +55 -0
  178. data/lib/arel/nodes/casted.rb +50 -0
  179. data/lib/arel/nodes/count.rb +12 -0
  180. data/lib/arel/nodes/delete_statement.rb +45 -0
  181. data/lib/arel/nodes/descending.rb +23 -0
  182. data/lib/arel/nodes/equality.rb +18 -0
  183. data/lib/arel/nodes/extract.rb +24 -0
  184. data/lib/arel/nodes/false.rb +16 -0
  185. data/lib/arel/nodes/full_outer_join.rb +8 -0
  186. data/lib/arel/nodes/function.rb +44 -0
  187. data/lib/arel/nodes/grouping.rb +8 -0
  188. data/lib/arel/nodes/in.rb +8 -0
  189. data/lib/arel/nodes/infix_operation.rb +80 -0
  190. data/lib/arel/nodes/inner_join.rb +8 -0
  191. data/lib/arel/nodes/insert_statement.rb +37 -0
  192. data/lib/arel/nodes/join_source.rb +20 -0
  193. data/lib/arel/nodes/matches.rb +18 -0
  194. data/lib/arel/nodes/named_function.rb +23 -0
  195. data/lib/arel/nodes/node.rb +50 -0
  196. data/lib/arel/nodes/node_expression.rb +13 -0
  197. data/lib/arel/nodes/outer_join.rb +8 -0
  198. data/lib/arel/nodes/over.rb +15 -0
  199. data/lib/arel/nodes/regexp.rb +16 -0
  200. data/lib/arel/nodes/right_outer_join.rb +8 -0
  201. data/lib/arel/nodes/select_core.rb +63 -0
  202. data/lib/arel/nodes/select_statement.rb +41 -0
  203. data/lib/arel/nodes/sql_literal.rb +16 -0
  204. data/lib/arel/nodes/string_join.rb +11 -0
  205. data/lib/arel/nodes/table_alias.rb +27 -0
  206. data/lib/arel/nodes/terminal.rb +16 -0
  207. data/lib/arel/nodes/true.rb +16 -0
  208. data/lib/arel/nodes/unary.rb +44 -0
  209. data/lib/arel/nodes/unary_operation.rb +20 -0
  210. data/lib/arel/nodes/unqualified_column.rb +22 -0
  211. data/lib/arel/nodes/update_statement.rb +41 -0
  212. data/lib/arel/nodes/values.rb +16 -0
  213. data/lib/arel/nodes/values_list.rb +24 -0
  214. data/lib/arel/nodes/window.rb +126 -0
  215. data/lib/arel/nodes/with.rb +11 -0
  216. data/lib/arel/nodes.rb +67 -0
  217. data/lib/arel/order_predications.rb +13 -0
  218. data/lib/arel/predications.rb +257 -0
  219. data/lib/arel/select_manager.rb +271 -0
  220. data/lib/arel/table.rb +110 -0
  221. data/lib/arel/tree_manager.rb +72 -0
  222. data/lib/arel/update_manager.rb +34 -0
  223. data/lib/arel/visitors/depth_first.rb +199 -0
  224. data/lib/arel/visitors/dot.rb +292 -0
  225. data/lib/arel/visitors/ibm_db.rb +21 -0
  226. data/lib/arel/visitors/informix.rb +56 -0
  227. data/lib/arel/visitors/mssql.rb +143 -0
  228. data/lib/arel/visitors/mysql.rb +83 -0
  229. data/lib/arel/visitors/oracle.rb +159 -0
  230. data/lib/arel/visitors/oracle12.rb +67 -0
  231. data/lib/arel/visitors/postgresql.rb +116 -0
  232. data/lib/arel/visitors/sqlite.rb +39 -0
  233. data/lib/arel/visitors/to_sql.rb +913 -0
  234. data/lib/arel/visitors/visitor.rb +42 -0
  235. data/lib/arel/visitors/where_sql.rb +23 -0
  236. data/lib/arel/visitors.rb +20 -0
  237. data/lib/arel/window_predications.rb +9 -0
  238. data/lib/arel.rb +44 -0
  239. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  240. data/lib/rails/generators/active_record/migration.rb +14 -1
  241. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  242. metadata +107 -29
@@ -141,10 +141,7 @@ module ActiveRecord
141
141
  end
142
142
  end
143
143
 
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
-
144
+ private
148
145
  attr_reader :name, :mapping, :subtype
149
146
  end
150
147
 
@@ -152,14 +149,16 @@ module ActiveRecord
152
149
  klass = self
153
150
  enum_prefix = definitions.delete(:_prefix)
154
151
  enum_suffix = definitions.delete(:_suffix)
152
+ enum_scopes = definitions.delete(:_scopes)
155
153
  definitions.each do |name, values|
154
+ assert_valid_enum_definition_values(values)
156
155
  # statuses = { }
157
156
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
158
157
  name = name.to_s
159
158
 
160
159
  # def self.statuses() statuses end
161
160
  detect_enum_conflict!(name, name.pluralize, true)
162
- singleton_class.send(:define_method, name.pluralize) { enum_values }
161
+ singleton_class.define_method(name.pluralize) { enum_values }
163
162
  defined_enums[name] = enum_values
164
163
 
165
164
  detect_enum_conflict!(name, name)
@@ -197,8 +196,10 @@ module ActiveRecord
197
196
  define_method("#{value_method_name}!") { update!(attr => value) }
198
197
 
199
198
  # 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) }
199
+ if enum_scopes != false
200
+ klass.send(:detect_enum_conflict!, name, value_method_name, true)
201
+ klass.scope value_method_name, -> { where(attr => value) }
202
+ end
202
203
  end
203
204
  end
204
205
  enum_values.freeze
@@ -214,10 +215,24 @@ module ActiveRecord
214
215
  end
215
216
  end
216
217
 
218
+ def assert_valid_enum_definition_values(values)
219
+ unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
220
+ error_message = <<~MSG
221
+ Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
222
+ MSG
223
+ raise ArgumentError, error_message
224
+ end
225
+
226
+ if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
227
+ raise ArgumentError, "Enum label name must not be blank."
228
+ end
229
+ end
230
+
217
231
  ENUM_CONFLICT_MESSAGE = \
218
232
  "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
219
233
  "this will generate a %{type} method \"%{method}\", which is already defined " \
220
234
  "by %{source}."
235
+ private_constant :ENUM_CONFLICT_MESSAGE
221
236
 
222
237
  def detect_enum_conflict!(enum_name, method_name, klass_method = false)
223
238
  if klass_method && dangerous_class_method?(method_name)
@@ -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
@@ -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,33 +119,23 @@ 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(
121
- adapter = nil,
122
- message: nil,
123
- sql: nil,
124
- binds: nil,
125
- table: nil,
126
- foreign_key: nil,
127
- target_table: nil,
128
- primary_key: nil,
129
- primary_key_column: nil
130
- )
129
+ def initialize(adapter = nil, message: nil, sql: nil, binds: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil)
130
+ @adapter = adapter
131
131
  if table
132
- type = primary_key_column.bigint? ? :bigint : primary_key_column.type
133
- msg = <<-EOM.squish
134
- Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
135
- which has type `#{primary_key_column.sql_type}`.
136
- To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
137
- (For example `t.#{type} :#{foreign_key}`).
132
+ msg = +<<~EOM
133
+ Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`.
134
+ This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`.
135
+ To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`).
138
136
  EOM
139
137
  else
140
- msg = <<-EOM.squish
138
+ msg = +<<~EOM
141
139
  There is a mismatch between the foreign key and primary key column types.
142
140
  Verify that the foreign key column type and the primary key of the associated table match types.
143
141
  EOM
@@ -145,8 +143,13 @@ module ActiveRecord
145
143
  if message
146
144
  msg << "\nOriginal message: #{message}"
147
145
  end
148
- super(msg)
146
+ super(msg, sql: sql, binds: binds)
149
147
  end
148
+
149
+ private
150
+ def column_type(table, column)
151
+ @adapter.columns(table).detect { |c| c.name == column }.sql_type
152
+ end
150
153
  end
151
154
 
152
155
  # 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