activerecord 5.2.0 → 5.2.3

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +214 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations.rb +9 -9
  5. data/lib/active_record/associations/alias_tracker.rb +1 -1
  6. data/lib/active_record/associations/association.rb +25 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  8. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  9. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  10. data/lib/active_record/associations/collection_association.rb +19 -15
  11. data/lib/active_record/associations/collection_proxy.rb +8 -34
  12. data/lib/active_record/associations/has_many_association.rb +9 -0
  13. data/lib/active_record/associations/has_many_through_association.rb +25 -1
  14. data/lib/active_record/associations/has_one_association.rb +8 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  16. data/lib/active_record/associations/join_dependency.rb +39 -64
  17. data/lib/active_record/associations/join_dependency/join_association.rb +12 -18
  18. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  19. data/lib/active_record/associations/singular_association.rb +4 -10
  20. data/lib/active_record/associations/through_association.rb +1 -1
  21. data/lib/active_record/attribute_methods/dirty.rb +15 -10
  22. data/lib/active_record/attribute_methods/read.rb +1 -1
  23. data/lib/active_record/autosave_association.rb +7 -2
  24. data/lib/active_record/callbacks.rb +4 -0
  25. data/lib/active_record/collection_cache_key.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +14 -8
  27. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -14
  33. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  34. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  35. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
  36. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  37. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  39. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  40. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
  41. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  42. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  43. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
  44. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  46. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
  47. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  48. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -4
  49. data/lib/active_record/core.rb +2 -1
  50. data/lib/active_record/counter_cache.rb +17 -13
  51. data/lib/active_record/enum.rb +1 -0
  52. data/lib/active_record/errors.rb +18 -12
  53. data/lib/active_record/gem_version.rb +1 -1
  54. data/lib/active_record/log_subscriber.rb +1 -1
  55. data/lib/active_record/migration.rb +1 -1
  56. data/lib/active_record/migration/compatibility.rb +15 -15
  57. data/lib/active_record/model_schema.rb +1 -1
  58. data/lib/active_record/persistence.rb +6 -5
  59. data/lib/active_record/query_cache.rb +4 -11
  60. data/lib/active_record/querying.rb +1 -1
  61. data/lib/active_record/railtie.rb +1 -3
  62. data/lib/active_record/relation.rb +39 -20
  63. data/lib/active_record/relation/calculations.rb +11 -8
  64. data/lib/active_record/relation/delegation.rb +30 -0
  65. data/lib/active_record/relation/finder_methods.rb +10 -8
  66. data/lib/active_record/relation/merger.rb +10 -11
  67. data/lib/active_record/relation/predicate_builder.rb +20 -14
  68. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  69. data/lib/active_record/relation/query_attribute.rb +5 -3
  70. data/lib/active_record/relation/query_methods.rb +45 -19
  71. data/lib/active_record/relation/spawn_methods.rb +1 -1
  72. data/lib/active_record/scoping/named.rb +2 -0
  73. data/lib/active_record/tasks/database_tasks.rb +1 -1
  74. data/lib/active_record/timestamp.rb +8 -1
  75. data/lib/active_record/transactions.rb +23 -20
  76. data/lib/active_record/type/serialized.rb +4 -0
  77. metadata +9 -10
@@ -6,17 +6,14 @@ module ActiveRecord
6
6
  module Associations
7
7
  class JoinDependency # :nodoc:
8
8
  class JoinAssociation < JoinPart # :nodoc:
9
- # The reflection of the association represented
10
- attr_reader :reflection
9
+ attr_reader :reflection, :tables
10
+ attr_accessor :table
11
11
 
12
- attr_accessor :tables
13
-
14
- def initialize(reflection, children, alias_tracker)
12
+ def initialize(reflection, children)
15
13
  super(reflection.klass, children)
16
14
 
17
- @alias_tracker = alias_tracker
18
- @reflection = reflection
19
- @tables = nil
15
+ @reflection = reflection
16
+ @tables = nil
20
17
  end
21
18
 
22
19
  def match?(other)
@@ -24,14 +21,13 @@ module ActiveRecord
24
21
  super && reflection == other.reflection
25
22
  end
26
23
 
27
- def join_constraints(foreign_table, foreign_klass, join_type, tables, chain)
28
- joins = []
29
- tables = tables.reverse
24
+ def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
25
+ joins = []
30
26
 
31
27
  # The chain starts with the target table, but we want to end with it here (makes
32
28
  # more sense in this context), so we reverse
33
- chain.reverse_each do |reflection|
34
- table = tables.shift
29
+ reflection.chain.reverse_each.with_index(1) do |reflection, i|
30
+ table = tables[-i]
35
31
  klass = reflection.klass
36
32
 
37
33
  constraint = reflection.build_join_constraint(table, foreign_table)
@@ -54,12 +50,10 @@ module ActiveRecord
54
50
  joins
55
51
  end
56
52
 
57
- def table
58
- tables.first
53
+ def tables=(tables)
54
+ @tables = tables
55
+ @table = tables.first
59
56
  end
60
-
61
- protected
62
- attr_reader :alias_tracker
63
57
  end
64
58
  end
65
59
  end
@@ -33,6 +33,13 @@ module ActiveRecord
33
33
  children.each { |child| child.each(&block) }
34
34
  end
35
35
 
36
+ def each_children(&block)
37
+ children.each do |child|
38
+ yield self, child
39
+ child.each_children(&block)
40
+ end
41
+ end
42
+
36
43
  # An Arel::Table for the active_record
37
44
  def table
38
45
  raise NotImplementedError
@@ -17,9 +17,8 @@ module ActiveRecord
17
17
  replace(record)
18
18
  end
19
19
 
20
- def build(attributes = {})
21
- record = build_record(attributes)
22
- yield(record) if block_given?
20
+ def build(attributes = {}, &block)
21
+ record = build_record(attributes, &block)
23
22
  set_new_record(record)
24
23
  record
25
24
  end
@@ -62,13 +61,8 @@ module ActiveRecord
62
61
  replace(record)
63
62
  end
64
63
 
65
- def _create_record(attributes, raise_error = false)
66
- unless owner.persisted?
67
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
68
- end
69
-
70
- record = build_record(attributes)
71
- yield(record) if block_given?
64
+ def _create_record(attributes, raise_error = false, &block)
65
+ record = build_record(attributes, &block)
72
66
  saved = record.save
73
67
  set_new_record(record)
74
68
  raise RecordInvalid.new(record) if !saved && raise_error
@@ -114,7 +114,7 @@ module ActiveRecord
114
114
  attributes[inverse.foreign_key] = target.id
115
115
  end
116
116
 
117
- super(attributes)
117
+ super
118
118
  end
119
119
  end
120
120
  end
@@ -16,9 +16,6 @@ module ActiveRecord
16
16
 
17
17
  class_attribute :partial_writes, instance_writer: false, default: true
18
18
 
19
- after_create { changes_applied }
20
- after_update { changes_applied }
21
-
22
19
  # Attribute methods for "changed in last call to save?"
23
20
  attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
24
21
  attribute_method_prefix("saved_change_to_")
@@ -114,27 +111,35 @@ module ActiveRecord
114
111
 
115
112
  # Alias for +changed+
116
113
  def changed_attribute_names_to_save
117
- changes_to_save.keys
114
+ mutations_from_database.changed_attribute_names
118
115
  end
119
116
 
120
117
  # Alias for +changed_attributes+
121
118
  def attributes_in_database
122
- changes_to_save.transform_values(&:first)
119
+ mutations_from_database.changed_values
123
120
  end
124
121
 
125
122
  private
126
- def write_attribute_without_type_cast(attr_name, _)
127
- result = super
128
- clear_attribute_change(attr_name)
123
+ def write_attribute_without_type_cast(attr_name, value)
124
+ name = attr_name.to_s
125
+ if self.class.attribute_alias?(name)
126
+ name = self.class.attribute_alias(name)
127
+ end
128
+ result = super(name, value)
129
+ clear_attribute_change(name)
129
130
  result
130
131
  end
131
132
 
132
133
  def _update_record(*)
133
- partial_writes? ? super(keys_for_partial_write) : super
134
+ affected_rows = partial_writes? ? super(keys_for_partial_write) : super
135
+ changes_applied
136
+ affected_rows
134
137
  end
135
138
 
136
139
  def _create_record(*)
137
- partial_writes? ? super(keys_for_partial_write) : super
140
+ id = partial_writes? ? super(keys_for_partial_write) : super
141
+ changes_applied
142
+ id
138
143
  end
139
144
 
140
145
  def keys_for_partial_write
@@ -69,7 +69,7 @@ module ActiveRecord
69
69
  if defined?(JRUBY_VERSION)
70
70
  # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
71
71
  # https://github.com/jruby/jruby/pull/2562
72
- def _read_attribute(attr_name, &block) # :nodoc
72
+ def _read_attribute(attr_name, &block) # :nodoc:
73
73
  @attributes.fetch_value(attr_name.to_s, &block)
74
74
  end
75
75
  else
@@ -400,8 +400,13 @@ module ActiveRecord
400
400
  if autosave != false && (@new_record_before_save || record.new_record?)
401
401
  if autosave
402
402
  saved = association.insert_record(record, false)
403
- else
404
- association.insert_record(record) unless reflection.nested?
403
+ elsif !reflection.nested?
404
+ association_saved = association.insert_record(record)
405
+
406
+ if reflection.validate?
407
+ errors.add(reflection.name) unless association_saved
408
+ saved = association_saved
409
+ end
405
410
  end
406
411
  elsif autosave
407
412
  saved = record.save(validate: false)
@@ -332,6 +332,10 @@ module ActiveRecord
332
332
  _run_touch_callbacks { super }
333
333
  end
334
334
 
335
+ def increment!(attribute, by = 1, touch: nil) # :nodoc:
336
+ touch ? _run_touch_callbacks { super } : super
337
+ end
338
+
335
339
  private
336
340
 
337
341
  def create_or_update(*)
@@ -20,9 +20,9 @@ module ActiveRecord
20
20
  select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
21
21
 
22
22
  if collection.has_limit_or_offset?
23
- query = collection.select(column)
23
+ query = collection.select("#{column} AS collection_cache_key_timestamp")
24
24
  subquery_alias = "subquery_for_cache_key"
25
- subquery_column = "#{subquery_alias}.#{timestamp_column}"
25
+ subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
26
26
  subquery = query.arel.as(subquery_alias)
27
27
  arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
28
28
  else
@@ -188,7 +188,9 @@ module ActiveRecord
188
188
  t0 = Time.now
189
189
  elapsed = 0
190
190
  loop do
191
- @cond.wait(timeout - elapsed)
191
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
192
+ @cond.wait(timeout - elapsed)
193
+ end
192
194
 
193
195
  return remove if any?
194
196
 
@@ -913,6 +915,16 @@ module ActiveRecord
913
915
  # about the model. The model needs to pass a specification name to the handler,
914
916
  # in order to look up the correct connection pool.
915
917
  class ConnectionHandler
918
+ def self.create_owner_to_pool # :nodoc:
919
+ Concurrent::Map.new(initial_capacity: 2) do |h, k|
920
+ # Discard the parent's connection pools immediately; we have no need
921
+ # of them
922
+ discard_unowned_pools(h)
923
+
924
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
925
+ end
926
+ end
927
+
916
928
  def self.unowned_pool_finalizer(pid_map) # :nodoc:
917
929
  lambda do |_|
918
930
  discard_unowned_pools(pid_map)
@@ -927,13 +939,7 @@ module ActiveRecord
927
939
 
928
940
  def initialize
929
941
  # These caches are keyed by spec.name (ConnectionSpecification#name).
930
- @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
931
- # Discard the parent's connection pools immediately; we have no need
932
- # of them
933
- ConnectionHandler.discard_unowned_pools(h)
934
-
935
- h[k] = Concurrent::Map.new(initial_capacity: 2)
936
- end
942
+ @owner_to_pool = ConnectionHandler.create_owner_to_pool
937
943
 
938
944
  # Backup finalizer: if the forked child never needed a pool, the above
939
945
  # early discard has not occurred
@@ -62,6 +62,11 @@ module ActiveRecord
62
62
  def joins_per_query
63
63
  256
64
64
  end
65
+
66
+ private
67
+ def bind_params_length
68
+ 65535
69
+ end
65
70
  end
66
71
  end
67
72
  end
@@ -20,9 +20,22 @@ module ActiveRecord
20
20
  raise "Passing bind parameters with an arel AST is forbidden. " \
21
21
  "The values must be stored on the AST directly"
22
22
  end
23
- sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
24
- [sql.freeze, binds || []]
23
+
24
+ if prepared_statements
25
+ sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
26
+
27
+ if binds.length > bind_params_length
28
+ unprepared_statement do
29
+ sql, binds = to_sql_and_binds(arel_or_sql_string)
30
+ visitor.preparable = false
31
+ end
32
+ end
33
+ else
34
+ sql = visitor.accept(arel_or_sql_string.ast, collector).value
35
+ end
36
+ [sql.freeze, binds]
25
37
  else
38
+ visitor.preparable = false if prepared_statements
26
39
  [arel_or_sql_string.dup.freeze, binds]
27
40
  end
28
41
  end
@@ -46,11 +59,11 @@ module ActiveRecord
46
59
  def select_all(arel, name = nil, binds = [], preparable: nil)
47
60
  arel = arel_from_relation(arel)
48
61
  sql, binds = to_sql_and_binds(arel, binds)
49
- if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
50
- preparable = false
51
- else
52
- preparable = visitor.preparable
62
+
63
+ if preparable.nil?
64
+ preparable = prepared_statements ? visitor.preparable : false
53
65
  end
66
+
54
67
  if prepared_statements && preparable
55
68
  select_prepared(sql, name, binds)
56
69
  else
@@ -96,6 +96,11 @@ module ActiveRecord
96
96
  if @query_cache_enabled && !locked?(arel)
97
97
  arel = arel_from_relation(arel)
98
98
  sql, binds = to_sql_and_binds(arel, binds)
99
+
100
+ if preparable.nil?
101
+ preparable = prepared_statements ? visitor.preparable : false
102
+ end
103
+
99
104
  cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
100
105
  else
101
106
  super
@@ -130,6 +130,7 @@ module ActiveRecord
130
130
  end
131
131
 
132
132
  def quoted_time(value) # :nodoc:
133
+ value = value.change(year: 2000, month: 1, day: 1)
133
134
  quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "")
134
135
  end
135
136
 
@@ -498,6 +498,9 @@ module ActiveRecord
498
498
  # t.date
499
499
  # t.binary
500
500
  # t.boolean
501
+ # t.foreign_key
502
+ # t.json
503
+ # t.virtual
501
504
  # t.remove
502
505
  # t.remove_references
503
506
  # t.remove_belongs_to
@@ -662,19 +665,19 @@ module ActiveRecord
662
665
 
663
666
  # Adds a foreign key.
664
667
  #
665
- # t.foreign_key(:authors)
668
+ # t.foreign_key(:authors)
666
669
  #
667
670
  # See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
668
- def foreign_key(*args) # :nodoc:
671
+ def foreign_key(*args)
669
672
  @base.add_foreign_key(name, *args)
670
673
  end
671
674
 
672
675
  # Checks to see if a foreign key exists.
673
676
  #
674
- # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors)
677
+ # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors)
675
678
  #
676
679
  # See {connection.foreign_key_exists?}[rdoc-ref:SchemaStatements#foreign_key_exists?]
677
- def foreign_key_exists?(*args) # :nodoc:
680
+ def foreign_key_exists?(*args)
678
681
  @base.foreign_key_exists?(name, *args)
679
682
  end
680
683
  end
@@ -305,7 +305,7 @@ module ActiveRecord
305
305
  yield td if block_given?
306
306
 
307
307
  if options[:force]
308
- drop_table(table_name, **options, if_exists: true)
308
+ drop_table(table_name, options.merge(if_exists: true))
309
309
  end
310
310
 
311
311
  result = execute schema_creation.accept td
@@ -741,22 +741,13 @@ module ActiveRecord
741
741
  # ====== Creating an index with a specific operator class
742
742
  #
743
743
  # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
744
- #
745
- # generates:
746
- #
747
- # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
744
+ # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
748
745
  #
749
746
  # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
750
- #
751
- # generates:
752
- #
753
- # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
747
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
754
748
  #
755
749
  # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
756
- #
757
- # generates:
758
- #
759
- # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
750
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
760
751
  #
761
752
  # Note: only supported by PostgreSQL
762
753
  #
@@ -908,7 +899,7 @@ module ActiveRecord
908
899
  foreign_key_options = { to_table: reference_name }
909
900
  end
910
901
  foreign_key_options[:column] ||= "#{ref_name}_id"
911
- remove_foreign_key(table_name, **foreign_key_options)
902
+ remove_foreign_key(table_name, foreign_key_options)
912
903
  end
913
904
 
914
905
  remove_column(table_name, "#{ref_name}_id")
@@ -17,11 +17,19 @@ module ActiveRecord
17
17
  end
18
18
 
19
19
  def committed?
20
- @state == :committed
20
+ @state == :committed || @state == :fully_committed
21
+ end
22
+
23
+ def fully_committed?
24
+ @state == :fully_committed
21
25
  end
22
26
 
23
27
  def rolledback?
24
- @state == :rolledback
28
+ @state == :rolledback || @state == :fully_rolledback
29
+ end
30
+
31
+ def fully_rolledback?
32
+ @state == :fully_rolledback
25
33
  end
26
34
 
27
35
  def fully_completed?
@@ -55,10 +63,19 @@ module ActiveRecord
55
63
  @state = :rolledback
56
64
  end
57
65
 
66
+ def full_rollback!
67
+ @children.each { |c| c.rollback! }
68
+ @state = :fully_rolledback
69
+ end
70
+
58
71
  def commit!
59
72
  @state = :committed
60
73
  end
61
74
 
75
+ def full_commit!
76
+ @state = :fully_committed
77
+ end
78
+
62
79
  def nullify!
63
80
  @state = nil
64
81
  end
@@ -89,10 +106,6 @@ module ActiveRecord
89
106
  records << record
90
107
  end
91
108
 
92
- def rollback
93
- @state.rollback!
94
- end
95
-
96
109
  def rollback_records
97
110
  ite = records.uniq
98
111
  while record = ite.shift
@@ -104,10 +117,6 @@ module ActiveRecord
104
117
  end
105
118
  end
106
119
 
107
- def commit
108
- @state.commit!
109
- end
110
-
111
120
  def before_commit_records
112
121
  records.uniq.each(&:before_committed!) if @run_commit_callbacks
113
122
  end
@@ -146,12 +155,12 @@ module ActiveRecord
146
155
 
147
156
  def rollback
148
157
  connection.rollback_to_savepoint(savepoint_name)
149
- super
158
+ @state.rollback!
150
159
  end
151
160
 
152
161
  def commit
153
162
  connection.release_savepoint(savepoint_name)
154
- super
163
+ @state.commit!
155
164
  end
156
165
 
157
166
  def full_rollback?; false; end
@@ -169,12 +178,12 @@ module ActiveRecord
169
178
 
170
179
  def rollback
171
180
  connection.rollback_db_transaction
172
- super
181
+ @state.full_rollback!
173
182
  end
174
183
 
175
184
  def commit
176
185
  connection.commit_db_transaction
177
- super
186
+ @state.full_commit!
178
187
  end
179
188
  end
180
189