activerecord 5.2.1 → 5.2.5

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +228 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations/association.rb +8 -0
  5. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  6. data/lib/active_record/associations/collection_association.rb +9 -8
  7. data/lib/active_record/associations/collection_proxy.rb +8 -34
  8. data/lib/active_record/associations/has_many_association.rb +9 -0
  9. data/lib/active_record/associations/has_many_through_association.rb +28 -11
  10. data/lib/active_record/associations/join_dependency/join_association.rb +28 -7
  11. data/lib/active_record/associations/preloader.rb +1 -1
  12. data/lib/active_record/attribute_methods/dirty.rb +13 -8
  13. data/lib/active_record/autosave_association.rb +25 -11
  14. data/lib/active_record/callbacks.rb +1 -1
  15. data/lib/active_record/collection_cache_key.rb +2 -2
  16. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +36 -11
  17. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  18. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  19. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
  21. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  22. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -8
  23. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  24. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  25. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +7 -1
  26. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  27. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  28. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +10 -24
  29. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  30. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  31. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
  32. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
  33. data/lib/active_record/core.rb +2 -1
  34. data/lib/active_record/enum.rb +1 -0
  35. data/lib/active_record/errors.rb +18 -12
  36. data/lib/active_record/gem_version.rb +1 -1
  37. data/lib/active_record/migration.rb +1 -1
  38. data/lib/active_record/migration/compatibility.rb +15 -15
  39. data/lib/active_record/model_schema.rb +1 -1
  40. data/lib/active_record/persistence.rb +5 -4
  41. data/lib/active_record/querying.rb +1 -1
  42. data/lib/active_record/railtie.rb +1 -3
  43. data/lib/active_record/reflection.rb +10 -14
  44. data/lib/active_record/relation.rb +26 -7
  45. data/lib/active_record/relation/calculations.rb +16 -12
  46. data/lib/active_record/relation/delegation.rb +30 -0
  47. data/lib/active_record/relation/finder_methods.rb +8 -4
  48. data/lib/active_record/relation/merger.rb +8 -5
  49. data/lib/active_record/relation/predicate_builder.rb +14 -9
  50. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  51. data/lib/active_record/relation/query_attribute.rb +5 -3
  52. data/lib/active_record/relation/query_methods.rb +35 -10
  53. data/lib/active_record/relation/spawn_methods.rb +1 -1
  54. data/lib/active_record/scoping/default.rb +2 -2
  55. data/lib/active_record/scoping/named.rb +2 -0
  56. data/lib/active_record/statement_cache.rb +2 -2
  57. data/lib/active_record/tasks/database_tasks.rb +1 -1
  58. data/lib/active_record/transactions.rb +1 -1
  59. metadata +9 -10
@@ -375,7 +375,7 @@ module ActiveRecord
375
375
  # default values when instantiating the Active Record object for this table.
376
376
  def column_defaults
377
377
  load_schema
378
- @column_defaults ||= _default_attributes.to_hash
378
+ @column_defaults ||= _default_attributes.deep_dup.to_hash
379
379
  end
380
380
 
381
381
  def _default_attributes # :nodoc:
@@ -473,15 +473,16 @@ module ActiveRecord
473
473
  verify_readonly_attribute(key.to_s)
474
474
  end
475
475
 
476
+ id_in_database = self.id_in_database
477
+ attributes.each do |k, v|
478
+ write_attribute_without_type_cast(k, v)
479
+ end
480
+
476
481
  affected_rows = self.class._update_record(
477
482
  attributes,
478
483
  self.class.primary_key => id_in_database
479
484
  )
480
485
 
481
- attributes.each do |k, v|
482
- write_attribute_without_type_cast(k, v)
483
- end
484
-
485
486
  affected_rows == 1
486
487
  end
487
488
 
@@ -40,7 +40,7 @@ module ActiveRecord
40
40
  def find_by_sql(sql, binds = [], preparable: nil, &block)
41
41
  result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
42
42
  column_types = result_set.column_types.dup
43
- columns_hash.each_key { |k| column_types.delete k }
43
+ attribute_types.each_key { |k| column_types.delete k }
44
44
  message_bus = ActiveSupport::Notifications.instrumenter
45
45
 
46
46
  payload = {
@@ -169,9 +169,7 @@ end_warning
169
169
  end
170
170
 
171
171
  initializer "active_record.set_executor_hooks" do
172
- ActiveSupport.on_load(:active_record) do
173
- ActiveRecord::QueryCache.install_executor_hooks
174
- end
172
+ ActiveRecord::QueryCache.install_executor_hooks
175
173
  end
176
174
 
177
175
  initializer "active_record.add_watchable_files" do |app|
@@ -174,28 +174,24 @@ module ActiveRecord
174
174
  scope ? [scope] : []
175
175
  end
176
176
 
177
- def build_join_constraint(table, foreign_table)
178
- key = join_keys.key
179
- foreign_key = join_keys.foreign_key
180
-
181
- constraint = table[key].eq(foreign_table[foreign_key])
182
-
183
- if klass.finder_needs_type_condition?
184
- table.create_and([constraint, klass.send(:type_condition, table)])
185
- else
186
- constraint
187
- end
188
- end
189
-
190
- def join_scope(table, foreign_klass)
177
+ def join_scope(table, foreign_table, foreign_klass)
191
178
  predicate_builder = predicate_builder(table)
192
179
  scope_chain_items = join_scopes(table, predicate_builder)
193
180
  klass_scope = klass_join_scope(table, predicate_builder)
194
181
 
182
+ key = join_keys.key
183
+ foreign_key = join_keys.foreign_key
184
+
185
+ klass_scope.where!(table[key].eq(foreign_table[foreign_key]))
186
+
195
187
  if type
196
188
  klass_scope.where!(type => foreign_klass.polymorphic_name)
197
189
  end
198
190
 
191
+ if klass.finder_needs_type_condition?
192
+ klass_scope.where!(klass.send(:type_condition, table))
193
+ end
194
+
199
195
  scope_chain_items.inject(klass_scope, &:merge!)
200
196
  end
201
197
 
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  # user = users.new { |user| user.name = 'Oscar' }
55
55
  # user.name # => Oscar
56
56
  def new(attributes = nil, &block)
57
- scoping { klass.new(scope_for_create(attributes), &block) }
57
+ scoping { klass.new(values_for_create(attributes), &block) }
58
58
  end
59
59
 
60
60
  alias build new
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  if attributes.is_a?(Array)
83
83
  attributes.collect { |attr| create(attr, &block) }
84
84
  else
85
- scoping { klass.create(scope_for_create(attributes), &block) }
85
+ scoping { klass.create(values_for_create(attributes), &block) }
86
86
  end
87
87
  end
88
88
 
@@ -96,7 +96,7 @@ module ActiveRecord
96
96
  if attributes.is_a?(Array)
97
97
  attributes.collect { |attr| create!(attr, &block) }
98
98
  else
99
- scoping { klass.create!(scope_for_create(attributes), &block) }
99
+ scoping { klass.create!(values_for_create(attributes), &block) }
100
100
  end
101
101
  end
102
102
 
@@ -337,6 +337,14 @@ module ActiveRecord
337
337
  @klass.connection.update stmt, "#{@klass} Update All"
338
338
  end
339
339
 
340
+ def update(id = :all, attributes) # :nodoc:
341
+ if id == :all
342
+ each { |record| record.update(attributes) }
343
+ else
344
+ klass.update(id, attributes)
345
+ end
346
+ end
347
+
340
348
  # Destroys the records by instantiating each
341
349
  # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
342
350
  # Each object's callbacks are executed (including <tt>:dependent</tt> association options).
@@ -456,10 +464,8 @@ module ActiveRecord
456
464
  where_clause.to_h(relation_table_name)
457
465
  end
458
466
 
459
- def scope_for_create(attributes = nil)
460
- scope = where_values_hash.merge!(create_with_value.stringify_keys)
461
- scope.merge!(attributes) if attributes
462
- scope
467
+ def scope_for_create
468
+ where_values_hash.merge!(create_with_value.stringify_keys)
463
469
  end
464
470
 
465
471
  # Returns true if relation needs eager loading.
@@ -606,5 +612,18 @@ module ActiveRecord
606
612
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
607
613
  string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
608
614
  end
615
+
616
+ def values_for_create(attributes = nil)
617
+ result = attributes ? where_values_hash.merge!(attributes) : where_values_hash
618
+
619
+ # NOTE: if there are same keys in both create_with and result, create_with should be used.
620
+ # This is to make sure nested attributes don't get passed to the klass.new,
621
+ # while keeping the precedence of the duplicate keys in create_with.
622
+ create_with_value.stringify_keys.each do |k, v|
623
+ result[k] = v if result.key?(k)
624
+ end
625
+
626
+ result
627
+ end
609
628
  end
610
629
  end
@@ -133,11 +133,12 @@ module ActiveRecord
133
133
  relation = apply_join_dependency
134
134
 
135
135
  if operation.to_s.downcase == "count"
136
- relation.distinct!
137
- # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
138
- if (column_name == :all || column_name.nil?) && select_values.empty?
139
- relation.order_values = []
136
+ unless distinct_value || distinct_select?(column_name || select_for_count)
137
+ relation.distinct!
138
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
140
139
  end
140
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
141
+ relation.order_values = []
141
142
  end
142
143
 
143
144
  relation.calculate(operation, column_name)
@@ -190,11 +191,9 @@ module ActiveRecord
190
191
  relation = apply_join_dependency
191
192
  relation.pluck(*column_names)
192
193
  else
193
- enforce_raw_sql_whitelist(column_names)
194
+ klass.enforce_raw_sql_whitelist(column_names)
194
195
  relation = spawn
195
- relation.select_values = column_names.map { |cn|
196
- @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
197
- }
196
+ relation.select_values = column_names
198
197
  result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
199
198
  result.cast_values(klass.attribute_types)
200
199
  end
@@ -209,7 +208,6 @@ module ActiveRecord
209
208
  end
210
209
 
211
210
  private
212
-
213
211
  def has_include?(column_name)
214
212
  eager_loading? || (includes_values.present? && column_name && column_name != :all)
215
213
  end
@@ -224,10 +222,12 @@ module ActiveRecord
224
222
  if operation == "count"
225
223
  column_name ||= select_for_count
226
224
  if column_name == :all
227
- if distinct && (group_values.any? || select_values.empty? && order_values.empty?)
225
+ if !distinct
226
+ distinct = distinct_select?(select_for_count) if group_values.empty?
227
+ elsif group_values.any? || select_values.empty? && order_values.empty?
228
228
  column_name = primary_key
229
229
  end
230
- elsif column_name =~ /\s*DISTINCT[\s(]+/i
230
+ elsif distinct_select?(column_name)
231
231
  distinct = nil
232
232
  end
233
233
  end
@@ -239,6 +239,10 @@ module ActiveRecord
239
239
  end
240
240
  end
241
241
 
242
+ def distinct_select?(column_name)
243
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
244
+ end
245
+
242
246
  def aggregate_column(column_name)
243
247
  return column_name if Arel::Expressions === column_name
244
248
 
@@ -383,7 +387,7 @@ module ActiveRecord
383
387
  case operation
384
388
  when "count" then value.to_i
385
389
  when "sum" then type.deserialize(value || 0)
386
- when "average" then value.respond_to?(:to_d) ? value.to_d : value
390
+ when "average" then value && value.respond_to?(:to_d) ? value.to_d : value
387
391
  else type.deserialize(value)
388
392
  end
389
393
  end
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  delegate = Class.new(klass) {
18
18
  include ClassSpecificRelation
19
19
  }
20
+ include_relation_methods(delegate)
20
21
  mangled_name = klass.name.gsub("::".freeze, "_".freeze)
21
22
  const_set mangled_name, delegate
22
23
  private_constant mangled_name
@@ -29,6 +30,35 @@ module ActiveRecord
29
30
  child_class.initialize_relation_delegate_cache
30
31
  super
31
32
  end
33
+
34
+ protected
35
+ def include_relation_methods(delegate)
36
+ superclass.include_relation_methods(delegate) unless base_class == self
37
+ delegate.include generated_relation_methods
38
+ end
39
+
40
+ private
41
+ def generated_relation_methods
42
+ @generated_relation_methods ||= Module.new.tap do |mod|
43
+ mod_name = "GeneratedRelationMethods"
44
+ const_set mod_name, mod
45
+ private_constant mod_name
46
+ end
47
+ end
48
+
49
+ def generate_relation_method(method)
50
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
51
+ generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
52
+ def #{method}(*args, &block)
53
+ scoping { klass.#{method}(*args, &block) }
54
+ end
55
+ RUBY
56
+ else
57
+ generated_relation_methods.send(:define_method, method) do |*args, &block|
58
+ scoping { klass.public_send(method, *args, &block) }
59
+ end
60
+ end
61
+ end
32
62
  end
33
63
 
34
64
  extend ActiveSupport::Concern
@@ -319,7 +319,7 @@ module ActiveRecord
319
319
 
320
320
  relation = construct_relation_for_exists(conditions)
321
321
 
322
- skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
322
+ skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists") } ? true : false
323
323
  rescue ::RangeError
324
324
  false
325
325
  end
@@ -359,11 +359,15 @@ module ActiveRecord
359
359
  end
360
360
 
361
361
  def construct_relation_for_exists(conditions)
362
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
362
+ if distinct_value && offset_value
363
+ relation = except(:order).limit!(1)
364
+ else
365
+ relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
366
+ end
363
367
 
364
368
  case conditions
365
369
  when Array, Hash
366
- relation.where!(conditions)
370
+ relation.where!(conditions) unless conditions.empty?
367
371
  else
368
372
  relation.where!(primary_key => conditions) unless conditions == :none
369
373
  end
@@ -417,7 +421,7 @@ module ActiveRecord
417
421
  raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
418
422
 
419
423
  expects_array = ids.first.kind_of?(Array)
420
- return ids.first if expects_array && ids.first.empty?
424
+ return [] if expects_array && ids.first.empty?
421
425
 
422
426
  ids = ids.flatten.compact.uniq
423
427
 
@@ -156,10 +156,10 @@ module ActiveRecord
156
156
  def merge_multi_values
157
157
  if other.reordering_value
158
158
  # override any order specified in the original relation
159
- relation.reorder! other.order_values
159
+ relation.reorder!(*other.order_values)
160
160
  elsif other.order_values.any?
161
161
  # merge in order_values from relation
162
- relation.order! other.order_values
162
+ relation.order!(*other.order_values)
163
163
  end
164
164
 
165
165
  extensions = other.extensions - relation.extensions
@@ -175,9 +175,7 @@ module ActiveRecord
175
175
  end
176
176
 
177
177
  def merge_clauses
178
- if relation.from_clause.empty? && !other.from_clause.empty?
179
- relation.from_clause = other.from_clause
180
- end
178
+ relation.from_clause = other.from_clause if replace_from_clause?
181
179
 
182
180
  where_clause = relation.where_clause.merge(other.where_clause)
183
181
  relation.where_clause = where_clause unless where_clause.empty?
@@ -185,6 +183,11 @@ module ActiveRecord
185
183
  having_clause = relation.having_clause.merge(other.having_clause)
186
184
  relation.having_clause = having_clause unless having_clause.empty?
187
185
  end
186
+
187
+ def replace_from_clause?
188
+ relation.from_clause.empty? && !other.from_clause.empty? &&
189
+ relation.klass.base_class == other.klass.base_class
190
+ end
188
191
  end
189
192
  end
190
193
  end
@@ -93,16 +93,21 @@ module ActiveRecord
93
93
  queries.reduce(&:or)
94
94
  elsif table.aggregated_with?(key)
95
95
  mapping = table.reflect_on_aggregation(key).mapping
96
- queries = Array.wrap(value).map do |object|
97
- mapping.map do |field_attr, aggregate_attr|
98
- if mapping.size == 1 && !object.respond_to?(aggregate_attr)
99
- build(table.arel_attribute(field_attr), object)
100
- else
101
- build(table.arel_attribute(field_attr), object.send(aggregate_attr))
102
- end
103
- end.reduce(&:and)
96
+ values = value.nil? ? [nil] : Array.wrap(value)
97
+ if mapping.length == 1 || values.empty?
98
+ column_name, aggr_attr = mapping.first
99
+ values = values.map do |object|
100
+ object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
101
+ end
102
+ build(table.arel_attribute(column_name), values)
103
+ else
104
+ queries = values.map do |object|
105
+ mapping.map do |field_attr, aggregate_attr|
106
+ build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
107
+ end.reduce(&:and)
108
+ end
109
+ queries.reduce(&:or)
104
110
  end
105
- queries.reduce(&:or)
106
111
  else
107
112
  build(table.arel_attribute(key), value)
108
113
  end
@@ -19,10 +19,10 @@ module ActiveRecord
19
19
  when 0 then NullPredicate
20
20
  when 1 then predicate_builder.build(attribute, values.first)
21
21
  else
22
- bind_values = values.map do |v|
22
+ values.map! do |v|
23
23
  predicate_builder.build_bind_attribute(attribute.name, v)
24
24
  end
25
- attribute.in(bind_values)
25
+ values.empty? ? NullPredicate : attribute.in(values)
26
26
  end
27
27
 
28
28
  unless nils.empty?
@@ -18,13 +18,15 @@ module ActiveRecord
18
18
  end
19
19
 
20
20
  def nil?
21
- !value_before_type_cast.is_a?(StatementCache::Substitute) &&
22
- (value_before_type_cast.nil? || value_for_database.nil?)
21
+ unless value_before_type_cast.is_a?(StatementCache::Substitute)
22
+ value_before_type_cast.nil? ||
23
+ type.respond_to?(:subtype, true) && value_for_database.nil?
24
+ end
23
25
  end
24
26
 
25
27
  def boundable?
26
28
  return @_boundable if defined?(@_boundable)
27
- nil?
29
+ value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
28
30
  @_boundable = true
29
31
  rescue ::RangeError
30
32
  @_boundable = false
@@ -232,9 +232,6 @@ module ActiveRecord
232
232
 
233
233
  def _select!(*fields) # :nodoc:
234
234
  fields.flatten!
235
- fields.map! do |field|
236
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
237
- end
238
235
  self.select_values += fields
239
236
  self
240
237
  end
@@ -1051,11 +1048,14 @@ module ActiveRecord
1051
1048
 
1052
1049
  def arel_columns(columns)
1053
1050
  columns.flat_map do |field|
1054
- if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value
1055
- arel_attribute(field)
1056
- elsif Symbol === field
1057
- connection.quote_table_name(field.to_s)
1058
- elsif Proc === field
1051
+ case field
1052
+ when Symbol
1053
+ arel_column(field.to_s) do |attr_name|
1054
+ connection.quote_table_name(attr_name)
1055
+ end
1056
+ when String
1057
+ arel_column(field, &:itself)
1058
+ when Proc
1059
1059
  field.call
1060
1060
  else
1061
1061
  field
@@ -1063,6 +1063,21 @@ module ActiveRecord
1063
1063
  end
1064
1064
  end
1065
1065
 
1066
+ def arel_column(field)
1067
+ field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1068
+ from = from_clause.name || from_clause.value
1069
+
1070
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1071
+ arel_attribute(field)
1072
+ else
1073
+ yield field
1074
+ end
1075
+ end
1076
+
1077
+ def table_name_matches?(from)
1078
+ /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1079
+ end
1080
+
1066
1081
  def reverse_sql_order(order_query)
1067
1082
  if order_query.empty?
1068
1083
  return [arel_attribute(primary_key).desc] if primary_key
@@ -1144,14 +1159,14 @@ module ActiveRecord
1144
1159
  order_args.map! do |arg|
1145
1160
  case arg
1146
1161
  when Symbol
1147
- arel_attribute(arg).asc
1162
+ order_column(arg.to_s).asc
1148
1163
  when Hash
1149
1164
  arg.map { |field, dir|
1150
1165
  case field
1151
1166
  when Arel::Nodes::SqlLiteral
1152
1167
  field.send(dir.downcase)
1153
1168
  else
1154
- arel_attribute(field).send(dir.downcase)
1169
+ order_column(field.to_s).send(dir.downcase)
1155
1170
  end
1156
1171
  }
1157
1172
  else
@@ -1160,6 +1175,16 @@ module ActiveRecord
1160
1175
  end.flatten!
1161
1176
  end
1162
1177
 
1178
+ def order_column(field)
1179
+ arel_column(field) do |attr_name|
1180
+ if attr_name == "count" && !group_values.empty?
1181
+ arel_attribute(attr_name)
1182
+ else
1183
+ Arel.sql(connection.quote_table_name(attr_name))
1184
+ end
1185
+ end
1186
+ end
1187
+
1163
1188
  # Checks to make sure that the arguments are not blank. Note that if some
1164
1189
  # blank-like object were initially passed into the query method, then this
1165
1190
  # method will not raise an error.