activerecord 5.0.0.beta3 → 5.0.0.beta4

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +225 -8
  3. data/examples/performance.rb +0 -1
  4. data/examples/simple.rb +0 -1
  5. data/lib/active_record.rb +0 -1
  6. data/lib/active_record/associations.rb +10 -6
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  9. data/lib/active_record/associations/join_dependency.rb +1 -1
  10. data/lib/active_record/associations/preloader.rb +1 -0
  11. data/lib/active_record/associations/preloader/through_association.rb +15 -8
  12. data/lib/active_record/attribute/user_provided_default.rb +10 -5
  13. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  14. data/lib/active_record/attributes.rb +3 -3
  15. data/lib/active_record/autosave_association.rb +1 -1
  16. data/lib/active_record/base.rb +2 -1
  17. data/lib/active_record/collection_cache_key.rb +1 -1
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +0 -19
  19. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -8
  20. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -1
  22. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  23. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +16 -2
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -4
  25. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +20 -10
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -20
  27. data/lib/active_record/connection_adapters/abstract/transaction.rb +13 -1
  28. data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -16
  29. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +119 -108
  30. data/lib/active_record/connection_adapters/column.rb +5 -6
  31. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  32. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  33. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +27 -6
  34. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -13
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -55
  36. data/lib/active_record/connection_adapters/postgresql/column.rb +0 -1
  37. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -0
  38. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  39. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +7 -10
  40. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +65 -25
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +59 -30
  42. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  43. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +7 -0
  44. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -56
  45. data/lib/active_record/connection_adapters/statement_pool.rb +6 -4
  46. data/lib/active_record/core.rb +13 -4
  47. data/lib/active_record/enum.rb +1 -1
  48. data/lib/active_record/errors.rb +9 -0
  49. data/lib/active_record/fixture_set/file.rb +7 -1
  50. data/lib/active_record/gem_version.rb +1 -1
  51. data/lib/active_record/locking/optimistic.rb +4 -0
  52. data/lib/active_record/log_subscriber.rb +1 -1
  53. data/lib/active_record/migration.rb +4 -4
  54. data/lib/active_record/migration/compatibility.rb +1 -1
  55. data/lib/active_record/model_schema.rb +12 -0
  56. data/lib/active_record/nested_attributes.rb +1 -1
  57. data/lib/active_record/persistence.rb +1 -1
  58. data/lib/active_record/query_cache.rb +13 -16
  59. data/lib/active_record/querying.rb +1 -1
  60. data/lib/active_record/railtie.rb +8 -11
  61. data/lib/active_record/railties/databases.rake +5 -5
  62. data/lib/active_record/reflection.rb +21 -4
  63. data/lib/active_record/relation.rb +10 -10
  64. data/lib/active_record/relation/batches.rb +29 -9
  65. data/lib/active_record/relation/delegation.rb +0 -1
  66. data/lib/active_record/relation/finder_methods.rb +27 -8
  67. data/lib/active_record/relation/predicate_builder.rb +4 -2
  68. data/lib/active_record/relation/where_clause.rb +2 -1
  69. data/lib/active_record/schema_dumper.rb +39 -24
  70. data/lib/active_record/scoping/default.rb +2 -1
  71. data/lib/active_record/suppressor.rb +5 -1
  72. data/lib/active_record/tasks/database_tasks.rb +6 -4
  73. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  74. data/lib/active_record/type.rb +1 -1
  75. data/lib/active_record/type/internal/abstract_json.rb +1 -5
  76. data/lib/active_record/type/time.rb +12 -0
  77. data/lib/active_record/validations/uniqueness.rb +1 -4
  78. data/lib/rails/generators/active_record/model/model_generator.rb +15 -11
  79. data/lib/rails/generators/active_record/model/templates/application_record.rb +2 -0
  80. metadata +11 -8
@@ -22,7 +22,7 @@ db_namespace = namespace :db do
22
22
  end
23
23
  end
24
24
 
25
- desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV, it defaults to creating the development and test databases.'
25
+ desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to creating the development and test databases.'
26
26
  task :create => [:load_config] do
27
27
  ActiveRecord::Tasks::DatabaseTasks.create_current
28
28
  end
@@ -33,7 +33,7 @@ db_namespace = namespace :db do
33
33
  end
34
34
  end
35
35
 
36
- desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.'
36
+ desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to dropping the development and test databases.'
37
37
  task :drop => [:load_config, :check_protected_environments] do
38
38
  db_namespace["drop:_unsafe"].invoke
39
39
  end
@@ -256,7 +256,7 @@ db_namespace = namespace :db do
256
256
  end
257
257
 
258
258
  desc 'Loads a schema.rb file into the database'
259
- task :load => [:environment, :load_config] do
259
+ task :load => [:environment, :load_config, :check_protected_environments] do
260
260
  ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
261
261
  end
262
262
 
@@ -278,7 +278,7 @@ db_namespace = namespace :db do
278
278
  desc 'Clears a db/schema_cache.dump file.'
279
279
  task :clear => [:environment, :load_config] do
280
280
  filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
281
- FileUtils.rm(filename) if File.exist?(filename)
281
+ rm_f filename, verbose: false
282
282
  end
283
283
  end
284
284
 
@@ -302,7 +302,7 @@ db_namespace = namespace :db do
302
302
  end
303
303
 
304
304
  desc "Recreates the databases from the structure.sql file"
305
- task :load => [:environment, :load_config] do
305
+ task :load => [:environment, :load_config, :check_protected_environments] do
306
306
  ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
307
307
  end
308
308
 
@@ -138,6 +138,10 @@ module ActiveRecord
138
138
  # PolymorphicReflection
139
139
  # RuntimeReflection
140
140
  class AbstractReflection # :nodoc:
141
+ def through_reflection?
142
+ false
143
+ end
144
+
141
145
  def table_name
142
146
  klass.table_name
143
147
  end
@@ -445,6 +449,10 @@ module ActiveRecord
445
449
  scope ? [[scope]] : [[]]
446
450
  end
447
451
 
452
+ def has_scope?
453
+ scope
454
+ end
455
+
448
456
  def has_inverse?
449
457
  inverse_name
450
458
  end
@@ -700,6 +708,10 @@ module ActiveRecord
700
708
  @source_reflection_name = delegate_reflection.options[:source]
701
709
  end
702
710
 
711
+ def through_reflection?
712
+ true
713
+ end
714
+
703
715
  def klass
704
716
  @klass ||= delegate_reflection.compute_class(class_name)
705
717
  end
@@ -765,7 +777,6 @@ module ActiveRecord
765
777
  # This is for clearing cache on the reflection. Useful for tests that need to compare
766
778
  # SQL queries on associations.
767
779
  def clear_association_scope_cache # :nodoc:
768
- @chain = nil
769
780
  delegate_reflection.clear_association_scope_cache
770
781
  source_reflection.clear_association_scope_cache
771
782
  through_reflection.clear_association_scope_cache
@@ -812,13 +823,19 @@ module ActiveRecord
812
823
  end
813
824
  end
814
825
 
826
+ def has_scope?
827
+ scope || options[:source_type] ||
828
+ source_reflection.has_scope? ||
829
+ through_reflection.has_scope?
830
+ end
831
+
815
832
  def join_keys(association_klass)
816
833
  source_reflection.join_keys(association_klass)
817
834
  end
818
835
 
819
836
  # A through association is nested if there would be more than one join table
820
837
  def nested?
821
- chain.length > 2
838
+ source_reflection.through_reflection? || through_reflection.through_reflection?
822
839
  end
823
840
 
824
841
  # We want to use the klass from this reflection, rather than just delegate straight to
@@ -857,7 +874,7 @@ module ActiveRecord
857
874
  example_options = options.dup
858
875
  example_options[:source] = source_reflection_names.first
859
876
  ActiveSupport::Deprecation.warn \
860
- "Ambiguous source reflection for through association. Please " \
877
+ "Ambiguous source reflection for through association. Please " \
861
878
  "specify a :source directive on your declaration like:\n" \
862
879
  "\n" \
863
880
  " class #{active_record.name} < ActiveRecord::Base\n" \
@@ -995,7 +1012,7 @@ module ActiveRecord
995
1012
  end
996
1013
 
997
1014
  def constraints
998
- [source_type_info]
1015
+ @reflection.constraints + [source_type_info]
999
1016
  end
1000
1017
 
1001
1018
  def source_type_info
@@ -45,8 +45,8 @@ module ActiveRecord
45
45
  k.name == primary_key
46
46
  }]
47
47
 
48
- if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
49
- primary_key_value = connection.next_sequence_value(klass.sequence_name)
48
+ if !primary_key_value && klass.prefetch_primary_key?
49
+ primary_key_value = klass.next_sequence_value
50
50
  values[arel_attribute(klass.primary_key)] = primary_key_value
51
51
  end
52
52
  end
@@ -94,12 +94,12 @@ module ActiveRecord
94
94
  end
95
95
 
96
96
  def substitute_values(values) # :nodoc:
97
- binds = values.map do |arel_attr, value|
98
- QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
99
- end
97
+ binds = []
98
+ substitutes = []
100
99
 
101
- substitutes = values.map do |(arel_attr, _)|
102
- [arel_attr, Arel::Nodes::BindParam.new]
100
+ values.each do |arel_attr, value|
101
+ binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
102
+ substitutes.push [arel_attr, Arel::Nodes::BindParam.new]
103
103
  end
104
104
 
105
105
  [substitutes, binds]
@@ -428,7 +428,7 @@ module ActiveRecord
428
428
  id = id.id
429
429
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
430
430
  You are passing an instance of ActiveRecord::Base to `update`.
431
- Please pass the id of the object by calling `.id`
431
+ Please pass the id of the object by calling `.id`.
432
432
  MSG
433
433
  end
434
434
  object = find(id)
@@ -457,7 +457,7 @@ module ActiveRecord
457
457
  if conditions
458
458
  ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
459
459
  Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
460
- To achieve the same use where(conditions).destroy_all
460
+ To achieve the same use where(conditions).destroy_all.
461
461
  MESSAGE
462
462
  where(conditions).destroy_all
463
463
  else
@@ -527,7 +527,7 @@ module ActiveRecord
527
527
  if conditions
528
528
  ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
529
529
  Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
530
- To achieve the same use where(conditions).delete_all
530
+ To achieve the same use where(conditions).delete_all.
531
531
  MESSAGE
532
532
  where(conditions).delete_all
533
533
  else
@@ -2,6 +2,8 @@ require "active_record/relation/batches/batch_enumerator"
2
2
 
3
3
  module ActiveRecord
4
4
  module Batches
5
+ ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
6
+
5
7
  # Looping through a collection of records from the database
6
8
  # (using the Scoping::Named::ClassMethods.all method, for example)
7
9
  # is very inefficient since it will try to instantiate all the objects at once.
@@ -31,6 +33,9 @@ module ActiveRecord
31
33
  # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
32
34
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
33
35
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
36
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
37
+ # the order and limit have to be ignored due to batching.
38
+ #
34
39
  # This is especially useful if you want multiple workers dealing with
35
40
  # the same processing queue. You can make worker 1 handle all the records
36
41
  # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -48,13 +53,13 @@ module ActiveRecord
48
53
  #
49
54
  # NOTE: You can't set the limit either, that's used to control
50
55
  # the batch sizes.
51
- def find_each(start: nil, finish: nil, batch_size: 1000)
56
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
52
57
  if block_given?
53
- find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records|
58
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
54
59
  records.each { |record| yield record }
55
60
  end
56
61
  else
57
- enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do
62
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
58
63
  relation = self
59
64
  apply_limits(relation, start, finish).size
60
65
  end
@@ -83,6 +88,9 @@ module ActiveRecord
83
88
  # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
84
89
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
85
90
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
91
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
92
+ # the order and limit have to be ignored due to batching.
93
+ #
86
94
  # This is especially useful if you want multiple workers dealing with
87
95
  # the same processing queue. You can make worker 1 handle all the records
88
96
  # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -100,16 +108,16 @@ module ActiveRecord
100
108
  #
101
109
  # NOTE: You can't set the limit either, that's used to control
102
110
  # the batch sizes.
103
- def find_in_batches(start: nil, finish: nil, batch_size: 1000)
111
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
104
112
  relation = self
105
113
  unless block_given?
106
- return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do
114
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
107
115
  total = apply_limits(relation, start, finish).size
108
116
  (total - 1).div(batch_size) + 1
109
117
  end
110
118
  end
111
119
 
112
- in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch|
120
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
113
121
  yield batch.to_a
114
122
  end
115
123
  end
@@ -140,6 +148,8 @@ module ActiveRecord
140
148
  # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
141
149
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
142
150
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
151
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
152
+ # the order and limit have to be ignored due to batching.
143
153
  #
144
154
  # This is especially useful if you want to work with the
145
155
  # ActiveRecord::Relation object instead of the array of records, or if
@@ -171,14 +181,14 @@ module ActiveRecord
171
181
  #
172
182
  # NOTE: You can't set the limit either, that's used to control the batch
173
183
  # sizes.
174
- def in_batches(of: 1000, start: nil, finish: nil, load: false)
184
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
175
185
  relation = self
176
186
  unless block_given?
177
187
  return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
178
188
  end
179
189
 
180
- if logger && (arel.orders.present? || arel.taken.present?)
181
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
190
+ if arel.orders.present? || arel.taken.present?
191
+ act_on_order_or_limit_ignored(error_on_ignore)
182
192
  end
183
193
 
184
194
  relation = relation.reorder(batch_order).limit(of)
@@ -219,5 +229,15 @@ module ActiveRecord
219
229
  def batch_order
220
230
  "#{quoted_table_name}.#{quoted_primary_key} ASC"
221
231
  end
232
+
233
+ def act_on_order_or_limit_ignored(error_on_ignore)
234
+ raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
235
+
236
+ if raise_error
237
+ raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
238
+ elsif logger
239
+ logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
240
+ end
241
+ end
222
242
  end
223
243
  end
@@ -1,4 +1,3 @@
1
- require 'set'
2
1
  require 'active_support/concern'
3
2
 
4
3
  module ActiveRecord
@@ -42,10 +42,10 @@ module ActiveRecord
42
42
  # Person.find_by(name: 'Spartacus', rating: 4)
43
43
  # # returns the first item or nil.
44
44
  #
45
- # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
45
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
46
46
  # # returns the first item or returns a new instance (requires you call .save to persist against the database).
47
47
  #
48
- # Person.where(name: 'Spartacus', rating: 4).first_or_create
48
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
49
49
  # # returns the first item or creates it and returns it.
50
50
  #
51
51
  # ==== Alternatives for #find
@@ -255,13 +255,13 @@ module ActiveRecord
255
255
  # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
256
256
  # Person.where(["user_name = :u", { u: user_name }]).third_to_last
257
257
  def third_to_last
258
- find_nth(-3)
258
+ find_nth_from_last 3
259
259
  end
260
260
 
261
261
  # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
262
262
  # is found.
263
263
  def third_to_last!
264
- find_nth!(-3)
264
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
265
265
  end
266
266
 
267
267
  # Find the second-to-last record.
@@ -271,13 +271,13 @@ module ActiveRecord
271
271
  # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
272
272
  # Person.where(["user_name = :u", { u: user_name }]).second_to_last
273
273
  def second_to_last
274
- find_nth(-2)
274
+ find_nth_from_last 2
275
275
  end
276
276
 
277
277
  # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
278
278
  # is found.
279
279
  def second_to_last!
280
- find_nth!(-2)
280
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
281
281
  end
282
282
 
283
283
  # Returns true if a record exists in the table that matches the +id+ or
@@ -312,7 +312,7 @@ module ActiveRecord
312
312
  conditions = conditions.id
313
313
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
314
314
  You are passing an instance of ActiveRecord::Base to `exists?`.
315
- Please pass the id of the object by calling `.id`
315
+ Please pass the id of the object by calling `.id`.
316
316
  MSG
317
317
  end
318
318
 
@@ -467,7 +467,7 @@ module ActiveRecord
467
467
  id = id.id
468
468
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
469
469
  You are passing an instance of ActiveRecord::Base to `find`.
470
- Please pass the id of the object by calling `.id`
470
+ Please pass the id of the object by calling `.id`.
471
471
  MSG
472
472
  end
473
473
 
@@ -561,6 +561,25 @@ module ActiveRecord
561
561
  relation.limit(limit).to_a
562
562
  end
563
563
 
564
+ def find_nth_from_last(index)
565
+ if loaded?
566
+ @records[-index]
567
+ else
568
+ relation = if order_values.empty? && primary_key
569
+ order(arel_attribute(primary_key).asc)
570
+ else
571
+ self
572
+ end
573
+
574
+ relation.to_a[-index]
575
+ # TODO: can be made more performant on large result sets by
576
+ # for instance, last(index)[-index] (which would require
577
+ # refactoring the last(n) finder method to make test suite pass),
578
+ # or by using a combination of reverse_order, limit, and offset,
579
+ # e.g., reverse_order.offset(index-1).first
580
+ end
581
+ end
582
+
564
583
  private
565
584
 
566
585
  def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
@@ -136,9 +136,11 @@ module ActiveRecord
136
136
  end
137
137
 
138
138
  def convert_dot_notation_to_hash(attributes)
139
- dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
139
+ dot_notation = attributes.select do |k, v|
140
+ k.include?(".".freeze) && !v.is_a?(Hash)
141
+ end
140
142
 
141
- dot_notation.each do |key|
143
+ dot_notation.each_key do |key|
142
144
  table_name, column_name = key.split(".".freeze)
143
145
  value = attributes.delete(key)
144
146
  attributes[table_name] ||= {}
@@ -158,8 +158,9 @@ module ActiveRecord
158
158
  end
159
159
  end
160
160
 
161
+ ARRAY_WITH_EMPTY_STRING = ['']
161
162
  def non_empty_predicates
162
- predicates - ['']
163
+ predicates - ARRAY_WITH_EMPTY_STRING
163
164
  end
164
165
 
165
166
  def wrap_sql_literal(node)
@@ -104,10 +104,7 @@ HEADER
104
104
  end
105
105
 
106
106
  def table(table, stream)
107
- columns = @connection.columns(table).map do |column|
108
- column.instance_variable_set(:@table_name, table)
109
- column
110
- end
107
+ columns = @connection.columns(table)
111
108
  begin
112
109
  tbl = StringIO.new
113
110
 
@@ -126,7 +123,7 @@ HEADER
126
123
  tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id'
127
124
  pkcol = columns.detect { |c| c.name == pk }
128
125
  pkcolspec = @connection.column_spec_for_primary_key(pkcol)
129
- if pkcolspec
126
+ if pkcolspec.present?
130
127
  pkcolspec.each do |key, value|
131
128
  tbl.print ", #{key}: #{value}"
132
129
  end
@@ -141,6 +138,10 @@ HEADER
141
138
  table_options = @connection.table_options(table)
142
139
  tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
143
140
 
141
+ if comment = @connection.table_comment(table).presence
142
+ tbl.print ", comment: #{comment.inspect}"
143
+ end
144
+
144
145
  tbl.puts " do |t|"
145
146
 
146
147
  # then dump all non-primary key columns
@@ -178,11 +179,11 @@ HEADER
178
179
  tbl.puts
179
180
  end
180
181
 
182
+ indexes_in_create(table, tbl)
183
+
181
184
  tbl.puts " end"
182
185
  tbl.puts
183
186
 
184
- indexes(table, tbl)
185
-
186
187
  tbl.rewind
187
188
  stream.print tbl.read
188
189
  rescue => e
@@ -194,26 +195,12 @@ HEADER
194
195
  stream
195
196
  end
196
197
 
198
+ # Keep it for indexing materialized views
197
199
  def indexes(table, stream)
198
200
  if (indexes = @connection.indexes(table)).any?
199
201
  add_index_statements = indexes.map do |index|
200
- statement_parts = [
201
- "add_index #{remove_prefix_and_suffix(index.table).inspect}",
202
- index.columns.inspect,
203
- "name: #{index.name.inspect}",
204
- ]
205
- statement_parts << 'unique: true' if index.unique
206
-
207
- index_lengths = (index.lengths || []).compact
208
- statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
209
-
210
- index_orders = index.orders || {}
211
- statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
212
- statement_parts << "where: #{index.where.inspect}" if index.where
213
- statement_parts << "using: #{index.using.inspect}" if index.using
214
- statement_parts << "type: #{index.type.inspect}" if index.type
215
-
216
- " #{statement_parts.join(', ')}"
202
+ table_name = remove_prefix_and_suffix(index.table).inspect
203
+ " add_index #{([table_name]+index_parts(index)).join(', ')}"
217
204
  end
218
205
 
219
206
  stream.puts add_index_statements.sort.join("\n")
@@ -221,6 +208,34 @@ HEADER
221
208
  end
222
209
  end
223
210
 
211
+ def indexes_in_create(table, stream)
212
+ if (indexes = @connection.indexes(table)).any?
213
+ index_statements = indexes.map do |index|
214
+ " t.index #{index_parts(index).join(', ')}"
215
+ end
216
+ stream.puts index_statements.sort.join("\n")
217
+ end
218
+ end
219
+
220
+ def index_parts(index)
221
+ index_parts = [
222
+ index.columns.inspect,
223
+ "name: #{index.name.inspect}",
224
+ ]
225
+ index_parts << 'unique: true' if index.unique
226
+
227
+ index_lengths = (index.lengths || []).compact
228
+ index_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
229
+
230
+ index_orders = index.orders || {}
231
+ index_parts << "order: #{index.orders.inspect}" if index_orders.any?
232
+ index_parts << "where: #{index.where.inspect}" if index.where
233
+ index_parts << "using: #{index.using.inspect}" if index.using
234
+ index_parts << "type: #{index.type.inspect}" if index.type
235
+ index_parts << "comment: #{index.comment.inspect}" if index.comment
236
+ index_parts
237
+ end
238
+
224
239
  def foreign_keys(table, stream)
225
240
  if (foreign_keys = @connection.foreign_keys(table)).any?
226
241
  add_foreign_key_statements = foreign_keys.map do |foreign_key|