activerecord 6.0.0.beta3 → 6.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +286 -6
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/associations.rb +3 -2
  6. data/lib/active_record/associations/association.rb +1 -1
  7. data/lib/active_record/associations/builder/association.rb +14 -18
  8. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  9. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  10. data/lib/active_record/associations/builder/has_many.rb +2 -0
  11. data/lib/active_record/associations/builder/has_one.rb +35 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  13. data/lib/active_record/associations/collection_proxy.rb +1 -1
  14. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  15. data/lib/active_record/associations/preloader.rb +11 -6
  16. data/lib/active_record/associations/preloader/association.rb +32 -30
  17. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  18. data/lib/active_record/attribute_methods.rb +4 -3
  19. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  20. data/lib/active_record/attribute_methods/dirty.rb +42 -14
  21. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  22. data/lib/active_record/attribute_methods/query.rb +2 -3
  23. data/lib/active_record/attribute_methods/read.rb +3 -9
  24. data/lib/active_record/attribute_methods/write.rb +6 -12
  25. data/lib/active_record/attributes.rb +13 -0
  26. data/lib/active_record/autosave_association.rb +13 -3
  27. data/lib/active_record/base.rb +0 -1
  28. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -0
  29. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  30. data/lib/active_record/connection_adapters/abstract/database_statements.rb +84 -61
  31. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  32. data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -6
  33. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +70 -14
  35. data/lib/active_record/connection_adapters/abstract_adapter.rb +56 -11
  36. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -69
  37. data/lib/active_record/connection_adapters/column.rb +17 -13
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  39. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
  41. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  42. data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -2
  43. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  44. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  45. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  46. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  47. data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -27
  48. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  49. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  50. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +50 -112
  53. data/lib/active_record/connection_handling.rb +17 -10
  54. data/lib/active_record/core.rb +15 -20
  55. data/lib/active_record/database_configurations.rb +14 -14
  56. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  57. data/lib/active_record/database_configurations/url_config.rb +12 -12
  58. data/lib/active_record/dynamic_matchers.rb +1 -1
  59. data/lib/active_record/enum.rb +6 -0
  60. data/lib/active_record/errors.rb +1 -1
  61. data/lib/active_record/gem_version.rb +1 -1
  62. data/lib/active_record/insert_all.rb +180 -0
  63. data/lib/active_record/integration.rb +13 -1
  64. data/lib/active_record/internal_metadata.rb +5 -1
  65. data/lib/active_record/locking/optimistic.rb +3 -4
  66. data/lib/active_record/log_subscriber.rb +1 -1
  67. data/lib/active_record/migration.rb +25 -18
  68. data/lib/active_record/migration/command_recorder.rb +28 -14
  69. data/lib/active_record/migration/compatibility.rb +10 -0
  70. data/lib/active_record/persistence.rb +206 -13
  71. data/lib/active_record/querying.rb +17 -12
  72. data/lib/active_record/railties/databases.rake +68 -6
  73. data/lib/active_record/reflection.rb +2 -2
  74. data/lib/active_record/relation.rb +98 -20
  75. data/lib/active_record/relation/calculations.rb +39 -39
  76. data/lib/active_record/relation/delegation.rb +22 -30
  77. data/lib/active_record/relation/finder_methods.rb +3 -9
  78. data/lib/active_record/relation/merger.rb +7 -16
  79. data/lib/active_record/relation/query_methods.rb +153 -38
  80. data/lib/active_record/relation/where_clause.rb +9 -5
  81. data/lib/active_record/sanitization.rb +3 -2
  82. data/lib/active_record/schema_dumper.rb +5 -0
  83. data/lib/active_record/schema_migration.rb +1 -1
  84. data/lib/active_record/scoping/default.rb +6 -7
  85. data/lib/active_record/scoping/named.rb +1 -1
  86. data/lib/active_record/statement_cache.rb +2 -2
  87. data/lib/active_record/store.rb +48 -0
  88. data/lib/active_record/table_metadata.rb +3 -3
  89. data/lib/active_record/tasks/database_tasks.rb +36 -1
  90. data/lib/active_record/touch_later.rb +2 -2
  91. data/lib/active_record/transactions.rb +52 -41
  92. data/lib/active_record/validations/uniqueness.rb +3 -5
  93. data/lib/arel/insert_manager.rb +3 -3
  94. data/lib/arel/nodes.rb +2 -1
  95. data/lib/arel/nodes/comment.rb +29 -0
  96. data/lib/arel/nodes/select_core.rb +16 -12
  97. data/lib/arel/nodes/unary.rb +1 -0
  98. data/lib/arel/nodes/values_list.rb +2 -17
  99. data/lib/arel/select_manager.rb +10 -10
  100. data/lib/arel/visitors/depth_first.rb +6 -1
  101. data/lib/arel/visitors/dot.rb +7 -2
  102. data/lib/arel/visitors/ibm_db.rb +13 -0
  103. data/lib/arel/visitors/informix.rb +6 -0
  104. data/lib/arel/visitors/mssql.rb +15 -1
  105. data/lib/arel/visitors/oracle12.rb +4 -5
  106. data/lib/arel/visitors/postgresql.rb +4 -10
  107. data/lib/arel/visitors/to_sql.rb +87 -108
  108. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  109. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  110. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  111. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  112. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  113. metadata +12 -11
  114. data/lib/active_record/collection_cache_key.rb +0 -53
  115. data/lib/arel/nodes/values.rb +0 -16
@@ -29,9 +29,7 @@ module ActiveRecord
29
29
  # <tt>reload</tt> the record and clears changed attributes.
30
30
  def reload(*)
31
31
  super.tap do
32
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
33
32
  @mutations_before_last_save = nil
34
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
35
33
  @mutations_from_database = nil
36
34
  end
37
35
  end
@@ -51,7 +49,7 @@ module ActiveRecord
51
49
  # +to+ When passed, this method will return false unless the value was
52
50
  # changed to the given value
53
51
  def saved_change_to_attribute?(attr_name, **options)
54
- mutations_before_last_save.changed?(attr_name, **options)
52
+ mutations_before_last_save.changed?(attr_name.to_s, options)
55
53
  end
56
54
 
57
55
  # Returns the change to an attribute during the last save. If the
@@ -63,7 +61,7 @@ module ActiveRecord
63
61
  # invoked as +saved_change_to_name+ instead of
64
62
  # <tt>saved_change_to_attribute("name")</tt>.
65
63
  def saved_change_to_attribute(attr_name)
66
- mutations_before_last_save.change_to_attribute(attr_name)
64
+ mutations_before_last_save.change_to_attribute(attr_name.to_s)
67
65
  end
68
66
 
69
67
  # Returns the original value of an attribute before the last save.
@@ -73,7 +71,7 @@ module ActiveRecord
73
71
  # invoked as +name_before_last_save+ instead of
74
72
  # <tt>attribute_before_last_save("name")</tt>.
75
73
  def attribute_before_last_save(attr_name)
76
- mutations_before_last_save.original_value(attr_name)
74
+ mutations_before_last_save.original_value(attr_name.to_s)
77
75
  end
78
76
 
79
77
  # Did the last call to +save+ have any changes to change?
@@ -101,7 +99,7 @@ module ActiveRecord
101
99
  # +to+ When passed, this method will return false unless the value will be
102
100
  # changed to the given value
103
101
  def will_save_change_to_attribute?(attr_name, **options)
104
- mutations_from_database.changed?(attr_name, **options)
102
+ mutations_from_database.changed?(attr_name.to_s, options)
105
103
  end
106
104
 
107
105
  # Returns the change to an attribute that will be persisted during the
@@ -115,7 +113,7 @@ module ActiveRecord
115
113
  # If the attribute will change, the result will be an array containing the
116
114
  # original value and the new value about to be saved.
117
115
  def attribute_change_to_be_saved(attr_name)
118
- mutations_from_database.change_to_attribute(attr_name)
116
+ mutations_from_database.change_to_attribute(attr_name.to_s)
119
117
  end
120
118
 
121
119
  # Returns the value of an attribute in the database, as opposed to the
@@ -127,7 +125,7 @@ module ActiveRecord
127
125
  # saved. It can be invoked as +name_in_database+ instead of
128
126
  # <tt>attribute_in_database("name")</tt>.
129
127
  def attribute_in_database(attr_name)
130
- mutations_from_database.original_value(attr_name)
128
+ mutations_from_database.original_value(attr_name.to_s)
131
129
  end
132
130
 
133
131
  # Will the next call to +save+ have any changes to persist?
@@ -158,16 +156,46 @@ module ActiveRecord
158
156
  end
159
157
 
160
158
  private
159
+ def mutations_from_database
160
+ sync_with_transaction_state if @transaction_state&.finalized?
161
+ super
162
+ end
163
+
164
+ def mutations_before_last_save
165
+ sync_with_transaction_state if @transaction_state&.finalized?
166
+ super
167
+ end
168
+
161
169
  def write_attribute_without_type_cast(attr_name, value)
162
- name = attr_name.to_s
163
- if self.class.attribute_alias?(name)
164
- name = self.class.attribute_alias(name)
165
- end
166
- result = super(name, value)
167
- clear_attribute_change(name)
170
+ result = super
171
+ clear_attribute_change(attr_name)
168
172
  result
169
173
  end
170
174
 
175
+ def _touch_row(attribute_names, time)
176
+ @_touch_attr_names = Set.new(attribute_names)
177
+
178
+ affected_rows = super
179
+
180
+ changes = {}
181
+ @attributes.keys.each do |attr_name|
182
+ next if @_touch_attr_names.include?(attr_name)
183
+
184
+ if attribute_changed?(attr_name)
185
+ changes[attr_name] = _read_attribute(attr_name)
186
+ _write_attribute(attr_name, attribute_was(attr_name))
187
+ clear_attribute_change(attr_name)
188
+ end
189
+ end
190
+
191
+ changes_applied
192
+ changes.each { |attr_name, value| _write_attribute(attr_name, value) }
193
+
194
+ affected_rows
195
+ ensure
196
+ @_touch_attr_names = nil
197
+ end
198
+
171
199
  def _update_record(attribute_names = attribute_names_for_partial_writes)
172
200
  affected_rows = super
173
201
  changes_applied
@@ -16,40 +16,32 @@ module ActiveRecord
16
16
 
17
17
  # Returns the primary key column's value.
18
18
  def id
19
- sync_with_transaction_state
20
- primary_key = self.class.primary_key
21
- _read_attribute(primary_key) if primary_key
19
+ _read_attribute(@primary_key)
22
20
  end
23
21
 
24
22
  # Sets the primary key column's value.
25
23
  def id=(value)
26
- sync_with_transaction_state
27
- primary_key = self.class.primary_key
28
- _write_attribute(primary_key, value) if primary_key
24
+ _write_attribute(@primary_key, value)
29
25
  end
30
26
 
31
27
  # Queries the primary key column's value.
32
28
  def id?
33
- sync_with_transaction_state
34
- query_attribute(self.class.primary_key)
29
+ query_attribute(@primary_key)
35
30
  end
36
31
 
37
32
  # Returns the primary key column's value before type cast.
38
33
  def id_before_type_cast
39
- sync_with_transaction_state
40
- read_attribute_before_type_cast(self.class.primary_key)
34
+ read_attribute_before_type_cast(@primary_key)
41
35
  end
42
36
 
43
37
  # Returns the primary key column's previous value.
44
38
  def id_was
45
- sync_with_transaction_state
46
- attribute_was(self.class.primary_key)
39
+ attribute_was(@primary_key)
47
40
  end
48
41
 
49
42
  # Returns the primary key column's value from the database.
50
43
  def id_in_database
51
- sync_with_transaction_state
52
- attribute_in_database(self.class.primary_key)
44
+ attribute_in_database(@primary_key)
53
45
  end
54
46
 
55
47
  private
@@ -122,7 +114,7 @@ module ActiveRecord
122
114
  #
123
115
  # Project.primary_key # => "foo_id"
124
116
  def primary_key=(value)
125
- @primary_key = value && value.to_s
117
+ @primary_key = value && -value.to_s
126
118
  @quoted_primary_key = nil
127
119
  @attributes_builder = nil
128
120
  end
@@ -16,8 +16,7 @@ module ActiveRecord
16
16
  when true then true
17
17
  when false, nil then false
18
18
  else
19
- column = self.class.columns_hash[attr_name]
20
- if column.nil?
19
+ if !type_for_attribute(attr_name) { false }
21
20
  if Numeric === value || value !~ /[^0-9]/
22
21
  !value.to_i.zero?
23
22
  else
@@ -33,7 +32,7 @@ module ActiveRecord
33
32
  end
34
33
 
35
34
  private
36
- # Handle *? for method_missing.
35
+ # Dispatch target for <tt>*?</tt> attribute methods.
37
36
  def attribute?(attribute_name)
38
37
  query_attribute(attribute_name)
39
38
  end
@@ -9,14 +9,11 @@ module ActiveRecord
9
9
  private
10
10
 
11
11
  def define_method_attribute(name)
12
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
13
-
14
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
15
13
  generated_attribute_methods, name
16
14
  ) do |temp_method_name, attr_name_expr|
17
15
  generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
18
16
  def #{temp_method_name}
19
- #{sync_with_transaction_state}
20
17
  name = #{attr_name_expr}
21
18
  _read_attribute(name) { |n| missing_attribute(n, caller) }
22
19
  end
@@ -30,19 +27,16 @@ module ActiveRecord
30
27
  # to a date object, like Date.new(2004, 12, 12)).
31
28
  def read_attribute(attr_name, &block)
32
29
  name = attr_name.to_s
33
- if self.class.attribute_alias?(name)
34
- name = self.class.attribute_alias(name)
35
- end
30
+ name = self.class.attribute_aliases[name] || name
36
31
 
37
- primary_key = self.class.primary_key
38
- name = primary_key if name == "id" && primary_key
39
- sync_with_transaction_state if name == primary_key
32
+ name = @primary_key if name == "id" && @primary_key
40
33
  _read_attribute(name, &block)
41
34
  end
42
35
 
43
36
  # This method exists to avoid the expensive primary_key check internally, without
44
37
  # breaking compatibility with the read_attribute API
45
38
  def _read_attribute(attr_name, &block) # :nodoc
39
+ sync_with_transaction_state if @transaction_state&.finalized?
46
40
  @attributes.fetch_value(attr_name.to_s, &block)
47
41
  end
48
42
 
@@ -13,15 +13,12 @@ module ActiveRecord
13
13
  private
14
14
 
15
15
  def define_method_attribute=(name)
16
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
17
-
18
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
19
17
  generated_attribute_methods, name, writer: true,
20
18
  ) do |temp_method_name, attr_name_expr|
21
19
  generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
22
20
  def #{temp_method_name}(value)
23
21
  name = #{attr_name_expr}
24
- #{sync_with_transaction_state}
25
22
  _write_attribute(name, value)
26
23
  end
27
24
  RUBY
@@ -34,31 +31,28 @@ module ActiveRecord
34
31
  # turned into +nil+.
35
32
  def write_attribute(attr_name, value)
36
33
  name = attr_name.to_s
37
- if self.class.attribute_alias?(name)
38
- name = self.class.attribute_alias(name)
39
- end
34
+ name = self.class.attribute_aliases[name] || name
40
35
 
41
- primary_key = self.class.primary_key
42
- name = primary_key if name == "id" && primary_key
43
- sync_with_transaction_state if name == primary_key
36
+ name = @primary_key if name == "id" && @primary_key
44
37
  _write_attribute(name, value)
45
38
  end
46
39
 
47
40
  # This method exists to avoid the expensive primary_key check internally, without
48
41
  # breaking compatibility with the write_attribute API
49
42
  def _write_attribute(attr_name, value) # :nodoc:
43
+ sync_with_transaction_state if @transaction_state&.finalized?
50
44
  @attributes.write_from_user(attr_name.to_s, value)
51
45
  value
52
46
  end
53
47
 
54
48
  private
55
49
  def write_attribute_without_type_cast(attr_name, value)
56
- name = attr_name.to_s
57
- @attributes.write_cast_value(name, value)
50
+ sync_with_transaction_state if @transaction_state&.finalized?
51
+ @attributes.write_cast_value(attr_name.to_s, value)
58
52
  value
59
53
  end
60
54
 
61
- # Handle *= for method_missing.
55
+ # Dispatch target for <tt>*=</tt> attribute methods.
62
56
  def attribute=(attribute_name, value)
63
57
  _write_attribute(attribute_name, value)
64
58
  end
@@ -41,6 +41,9 @@ module ActiveRecord
41
41
  # +range+ (PostgreSQL only) specifies that the type should be a range (see the
42
42
  # examples below).
43
43
  #
44
+ # When using a symbol for +cast_type+, extra options are forwarded to the
45
+ # constructor of the type object.
46
+ #
44
47
  # ==== Examples
45
48
  #
46
49
  # The type detected by Active Record can be overridden.
@@ -112,6 +115,16 @@ module ActiveRecord
112
115
  # my_float_range: 1.0..3.5
113
116
  # }
114
117
  #
118
+ # Passing options to the type constructor
119
+ #
120
+ # # app/models/my_model.rb
121
+ # class MyModel < ActiveRecord::Base
122
+ # attribute :small_int, :integer, limit: 2
123
+ # end
124
+ #
125
+ # MyModel.create(small_int: 65537)
126
+ # # => Error: 65537 is out of range for the limit of two bytes
127
+ #
115
128
  # ==== Creating Custom Types
116
129
  #
117
130
  # Users may also define their own custom types, as long as they respond
@@ -382,10 +382,14 @@ module ActiveRecord
382
382
  if association = association_instance_get(reflection.name)
383
383
  autosave = reflection.options[:autosave]
384
384
 
385
+ # By saving the instance variable in a local variable,
386
+ # we make the whole callback re-entrant.
387
+ new_record_before_save = @new_record_before_save
388
+
385
389
  # reconstruct the scope now that we know the owner's id
386
390
  association.reset_scope
387
391
 
388
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
392
+ if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
389
393
  if autosave
390
394
  records_to_destroy = records.select(&:marked_for_destruction?)
391
395
  records_to_destroy.each { |record| association.destroy(record) }
@@ -397,7 +401,7 @@ module ActiveRecord
397
401
 
398
402
  saved = true
399
403
 
400
- if autosave != false && (@new_record_before_save || record.new_record?)
404
+ if autosave != false && (new_record_before_save || record.new_record?)
401
405
  if autosave
402
406
  saved = association.insert_record(record, false)
403
407
  elsif !reflection.nested?
@@ -457,10 +461,16 @@ module ActiveRecord
457
461
  # If the record is new or it has changed, returns true.
458
462
  def record_changed?(reflection, record, key)
459
463
  record.new_record? ||
460
- (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
464
+ association_foreign_key_changed?(reflection, record, key) ||
461
465
  record.will_save_change_to_attribute?(reflection.foreign_key)
462
466
  end
463
467
 
468
+ def association_foreign_key_changed?(reflection, record, key)
469
+ return false if reflection.through_reflection?
470
+
471
+ record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
472
+ end
473
+
464
474
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
465
475
  #
466
476
  # In addition, it will destroy the association if it was marked for destruction.
@@ -288,7 +288,6 @@ module ActiveRecord #:nodoc:
288
288
  extend Explain
289
289
  extend Enum
290
290
  extend Delegation::DelegateCache
291
- extend CollectionCacheKey
292
291
  extend Aggregations::ClassMethods
293
292
 
294
293
  include Core
@@ -810,6 +810,7 @@ module ActiveRecord
810
810
  def new_connection
811
811
  Base.send(spec.adapter_method, spec.config).tap do |conn|
812
812
  conn.schema_cache = schema_cache.dup if schema_cache
813
+ conn.check_version
813
814
  end
814
815
  end
815
816
 
@@ -5,20 +5,24 @@ require "active_support/deprecation"
5
5
  module ActiveRecord
6
6
  module ConnectionAdapters # :nodoc:
7
7
  module DatabaseLimits
8
+ def max_identifier_length # :nodoc:
9
+ 64
10
+ end
11
+
8
12
  # Returns the maximum length of a table alias.
9
13
  def table_alias_length
10
- 255
14
+ max_identifier_length
11
15
  end
12
16
 
13
17
  # Returns the maximum length of a column name.
14
18
  def column_name_length
15
- 64
19
+ max_identifier_length
16
20
  end
17
21
  deprecate :column_name_length
18
22
 
19
23
  # Returns the maximum length of a table name.
20
24
  def table_name_length
21
- 64
25
+ max_identifier_length
22
26
  end
23
27
  deprecate :table_name_length
24
28
 
@@ -33,7 +37,7 @@ module ActiveRecord
33
37
 
34
38
  # Returns the maximum length of an index name.
35
39
  def index_name_length
36
- 64
40
+ max_identifier_length
37
41
  end
38
42
 
39
43
  # Returns the maximum number of columns per table.
@@ -131,7 +131,7 @@ module ActiveRecord
131
131
  # +binds+ as the bind substitutes. +name+ is logged along with
132
132
  # the executed +sql+ statement.
133
133
  def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
134
- sql, binds = sql_for_insert(sql, pk, sequence_name, binds)
134
+ sql, binds = sql_for_insert(sql, pk, binds)
135
135
  exec_query(sql, name, binds)
136
136
  end
137
137
 
@@ -142,11 +142,6 @@ module ActiveRecord
142
142
  exec_query(sql, name, binds)
143
143
  end
144
144
 
145
- # Executes the truncate statement.
146
- def truncate(table_name, name = nil)
147
- raise NotImplementedError
148
- end
149
-
150
145
  # Executes update +sql+ statement in the context of this connection using
151
146
  # +binds+ as the bind substitutes. +name+ is logged along with
152
147
  # the executed +sql+ statement.
@@ -181,6 +176,23 @@ module ActiveRecord
181
176
  exec_delete(sql, name, binds)
182
177
  end
183
178
 
179
+ # Executes the truncate statement.
180
+ def truncate(table_name, name = nil)
181
+ execute(build_truncate_statements(table_name), name)
182
+ end
183
+
184
+ def truncate_tables(*table_names) # :nodoc:
185
+ return if table_names.empty?
186
+
187
+ with_multi_statements do
188
+ disable_referential_integrity do
189
+ Array(build_truncate_statements(*table_names)).each do |sql|
190
+ execute_batch(sql, "Truncate Tables")
191
+ end
192
+ end
193
+ end
194
+ end
195
+
184
196
  # Runs the given block in a database transaction, and returns the result
185
197
  # of the block.
186
198
  #
@@ -341,46 +353,20 @@ module ActiveRecord
341
353
  # We keep this method to provide fallback
342
354
  # for databases like sqlite that do not support bulk inserts.
343
355
  def insert_fixture(fixture, table_name)
344
- fixture = fixture.stringify_keys
345
-
346
- columns = schema_cache.columns_hash(table_name)
347
- binds = fixture.map do |name, value|
348
- if column = columns[name]
349
- type = lookup_cast_type_from_column(column)
350
- Relation::QueryAttribute.new(name, value, type)
351
- else
352
- raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
353
- end
354
- end
355
-
356
- table = Arel::Table.new(table_name)
357
-
358
- values = binds.map do |bind|
359
- value = with_yaml_fallback(bind.value_for_database)
360
- [table[bind.name], value]
361
- end
362
-
363
- manager = Arel::InsertManager.new
364
- manager.into(table)
365
- manager.insert(values)
366
- execute manager.to_sql, "Fixture Insert"
356
+ execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
367
357
  end
368
358
 
369
359
  def insert_fixtures_set(fixture_set, tables_to_delete = [])
370
- fixture_inserts = fixture_set.map do |table_name, fixtures|
371
- next if fixtures.empty?
372
-
373
- build_fixture_sql(fixtures, table_name)
374
- end.compact
375
-
376
- table_deletes = tables_to_delete.map { |table| +"DELETE FROM #{quote_table_name table}" }
377
- total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
378
-
379
- disable_referential_integrity do
380
- transaction(requires_new: true) do
381
- total_sql.each do |sql|
382
- execute sql, "Fixtures Load"
383
- yield if block_given?
360
+ fixture_inserts = build_fixture_statements(fixture_set)
361
+ table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
362
+ total_sql = Array(combine_multi_statements(table_deletes + fixture_inserts))
363
+
364
+ with_multi_statements do
365
+ disable_referential_integrity do
366
+ transaction(requires_new: true) do
367
+ total_sql.each do |sql|
368
+ execute_batch(sql, "Fixtures Load")
369
+ end
384
370
  end
385
371
  end
386
372
  end
@@ -404,15 +390,33 @@ module ActiveRecord
404
390
  end
405
391
  end
406
392
 
393
+ # Fixture value is quoted by Arel, however scalar values
394
+ # are not quotable. In this case we want to convert
395
+ # the column value to YAML.
396
+ def with_yaml_fallback(value) # :nodoc:
397
+ if value.is_a?(Hash) || value.is_a?(Array)
398
+ YAML.dump(value)
399
+ else
400
+ value
401
+ end
402
+ end
403
+
407
404
  private
405
+ def execute_batch(sql, name = nil)
406
+ execute(sql, name)
407
+ end
408
+
409
+ DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
410
+ private_constant :DEFAULT_INSERT_VALUE
411
+
408
412
  def default_insert_value(column)
409
- Arel.sql("DEFAULT")
413
+ DEFAULT_INSERT_VALUE
410
414
  end
411
415
 
412
416
  def build_fixture_sql(fixtures, table_name)
413
417
  columns = schema_cache.columns_hash(table_name)
414
418
 
415
- values = fixtures.map do |fixture|
419
+ values_list = fixtures.map do |fixture|
416
420
  fixture = fixture.stringify_keys
417
421
 
418
422
  unknown_columns = fixture.keys - columns.keys
@@ -423,8 +427,7 @@ module ActiveRecord
423
427
  columns.map do |name, column|
424
428
  if fixture.key?(name)
425
429
  type = lookup_cast_type_from_column(column)
426
- bind = Relation::QueryAttribute.new(name, fixture[name], type)
427
- with_yaml_fallback(bind.value_for_database)
430
+ with_yaml_fallback(type.serialize(fixture[name]))
428
431
  else
429
432
  default_insert_value(column)
430
433
  end
@@ -434,12 +437,43 @@ module ActiveRecord
434
437
  table = Arel::Table.new(table_name)
435
438
  manager = Arel::InsertManager.new
436
439
  manager.into(table)
437
- columns.each_key { |column| manager.columns << table[column] }
438
- manager.values = manager.create_values_list(values)
439
440
 
441
+ if values_list.size == 1
442
+ values = values_list.shift
443
+ new_values = []
444
+ columns.each_key.with_index { |column, i|
445
+ unless values[i].equal?(DEFAULT_INSERT_VALUE)
446
+ new_values << values[i]
447
+ manager.columns << table[column]
448
+ end
449
+ }
450
+ values_list << new_values
451
+ else
452
+ columns.each_key { |column| manager.columns << table[column] }
453
+ end
454
+
455
+ manager.values = manager.create_values_list(values_list)
440
456
  manager.to_sql
441
457
  end
442
458
 
459
+ def build_fixture_statements(fixture_set)
460
+ fixture_set.map do |table_name, fixtures|
461
+ next if fixtures.empty?
462
+ build_fixture_sql(fixtures, table_name)
463
+ end.compact
464
+ end
465
+
466
+ def build_truncate_statements(*table_names)
467
+ truncate_tables = table_names.map do |table_name|
468
+ "TRUNCATE TABLE #{quote_table_name(table_name)}"
469
+ end
470
+ combine_multi_statements(truncate_tables)
471
+ end
472
+
473
+ def with_multi_statements
474
+ yield
475
+ end
476
+
443
477
  def combine_multi_statements(total_sql)
444
478
  total_sql.join(";\n")
445
479
  end
@@ -453,7 +487,7 @@ module ActiveRecord
453
487
  exec_query(sql, name, binds, prepare: true)
454
488
  end
455
489
 
456
- def sql_for_insert(sql, pk, sequence_name, binds)
490
+ def sql_for_insert(sql, pk, binds)
457
491
  [sql, binds]
458
492
  end
459
493
 
@@ -473,17 +507,6 @@ module ActiveRecord
473
507
  relation
474
508
  end
475
509
  end
476
-
477
- # Fixture value is quoted by Arel, however scalar values
478
- # are not quotable. In this case we want to convert
479
- # the column value to YAML.
480
- def with_yaml_fallback(value)
481
- if value.is_a?(Hash) || value.is_a?(Array)
482
- YAML.dump(value)
483
- else
484
- value
485
- end
486
- end
487
510
  end
488
511
  end
489
512
  end