activerecord 5.0.0.1 → 5.0.1.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +196 -2
  3. data/README.rdoc +1 -1
  4. data/lib/active_record.rb +1 -1
  5. data/lib/active_record/aggregations.rb +4 -2
  6. data/lib/active_record/association_relation.rb +4 -1
  7. data/lib/active_record/associations.rb +5 -0
  8. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +4 -2
  9. data/lib/active_record/associations/builder/singular_association.rb +10 -1
  10. data/lib/active_record/associations/collection_association.rb +22 -17
  11. data/lib/active_record/associations/collection_proxy.rb +20 -7
  12. data/lib/active_record/associations/has_many_through_association.rb +4 -0
  13. data/lib/active_record/associations/join_dependency.rb +10 -4
  14. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  15. data/lib/active_record/associations/preloader/association.rb +18 -4
  16. data/lib/active_record/associations/preloader/collection_association.rb +0 -1
  17. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  18. data/lib/active_record/associations/singular_association.rb +8 -2
  19. data/lib/active_record/attribute.rb +3 -3
  20. data/lib/active_record/attribute_methods.rb +3 -7
  21. data/lib/active_record/attribute_methods/primary_key.rb +14 -1
  22. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  23. data/lib/active_record/attribute_set.rb +2 -0
  24. data/lib/active_record/attribute_set/builder.rb +29 -7
  25. data/lib/active_record/attributes.rb +3 -3
  26. data/lib/active_record/autosave_association.rb +15 -11
  27. data/lib/active_record/base.rb +1 -1
  28. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +40 -32
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  30. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -0
  31. data/lib/active_record/connection_adapters/abstract/quoting.rb +4 -4
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -7
  33. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -33
  34. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -5
  35. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +42 -45
  36. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  37. data/lib/active_record/connection_adapters/mysql/database_statements.rb +6 -23
  38. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  39. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -5
  40. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -0
  41. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +12 -1
  42. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  43. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -1
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +6 -2
  46. data/lib/active_record/core.rb +3 -1
  47. data/lib/active_record/enum.rb +6 -5
  48. data/lib/active_record/gem_version.rb +2 -2
  49. data/lib/active_record/integration.rb +13 -10
  50. data/lib/active_record/migration.rb +6 -5
  51. data/lib/active_record/model_schema.rb +134 -47
  52. data/lib/active_record/no_touching.rb +4 -0
  53. data/lib/active_record/persistence.rb +10 -4
  54. data/lib/active_record/query_cache.rb +13 -15
  55. data/lib/active_record/querying.rb +3 -3
  56. data/lib/active_record/railties/controller_runtime.rb +1 -1
  57. data/lib/active_record/reflection.rb +8 -0
  58. data/lib/active_record/relation.rb +7 -4
  59. data/lib/active_record/relation/calculations.rb +11 -11
  60. data/lib/active_record/relation/delegation.rb +1 -1
  61. data/lib/active_record/relation/finder_methods.rb +11 -9
  62. data/lib/active_record/relation/query_methods.rb +3 -3
  63. data/lib/active_record/result.rb +7 -1
  64. data/lib/active_record/sanitization.rb +11 -1
  65. data/lib/active_record/schema_dumper.rb +10 -17
  66. data/lib/active_record/scoping/named.rb +1 -1
  67. data/lib/active_record/statement_cache.rb +2 -2
  68. data/lib/active_record/table_metadata.rb +4 -3
  69. data/lib/active_record/touch_later.rb +6 -1
  70. data/lib/active_record/type/internal/abstract_json.rb +5 -1
  71. data/lib/active_record/validations/uniqueness.rb +3 -4
  72. metadata +9 -10
@@ -45,6 +45,10 @@ module ActiveRecord
45
45
  NoTouching.applied_to?(self.class)
46
46
  end
47
47
 
48
+ def touch_later(*) # :nodoc:
49
+ super unless no_touching?
50
+ end
51
+
48
52
  def touch(*) # :nodoc:
49
53
  super unless no_touching?
50
54
  end
@@ -63,10 +63,10 @@ module ActiveRecord
63
63
  #
64
64
  # See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
65
65
  # how this "single-table" inheritance mapping is implemented.
66
- def instantiate(attributes, column_types = {})
66
+ def instantiate(attributes, column_types = {}, &block)
67
67
  klass = discriminate_class_for_record(attributes)
68
68
  attributes = klass.attributes_builder.build_from_database(attributes, column_types)
69
- klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
69
+ klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block)
70
70
  end
71
71
 
72
72
  private
@@ -252,7 +252,8 @@ module ActiveRecord
252
252
  name = name.to_s
253
253
  verify_readonly_attribute(name)
254
254
  public_send("#{name}=", value)
255
- save(validate: false) if changed?
255
+
256
+ changed? ? save(validate: false) : true
256
257
  end
257
258
 
258
259
  # Updates the attributes of the model from the passed-in hash and saves the
@@ -479,7 +480,12 @@ module ActiveRecord
479
480
  # ball.touch(:updated_at) # => raises ActiveRecordError
480
481
  #
481
482
  def touch(*names, time: nil)
482
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
483
+ unless persisted?
484
+ raise ActiveRecordError, <<-MSG.squish
485
+ cannot touch on a new or destroyed record object. Consider using
486
+ persisted?, new_record?, or destroyed? before touching
487
+ MSG
488
+ end
483
489
 
484
490
  time ||= current_time_from_proper_timezone
485
491
  attributes = timestamp_attributes_for_update_in_model
@@ -24,30 +24,28 @@ module ActiveRecord
24
24
  end
25
25
 
26
26
  def self.run
27
- connection = ActiveRecord::Base.connection
28
- enabled = connection.query_cache_enabled
29
27
  connection_id = ActiveRecord::Base.connection_id
30
- connection.enable_query_cache!
31
28
 
32
- [enabled, connection_id]
33
- end
29
+ caching_pool = ActiveRecord::Base.connection_pool
30
+ caching_was_enabled = caching_pool.query_cache_enabled
31
+
32
+ caching_pool.enable_query_cache!
34
33
 
35
- def self.complete(state)
36
- enabled, connection_id = state
34
+ [caching_pool, caching_was_enabled, connection_id]
35
+ end
37
36
 
37
+ def self.complete((caching_pool, caching_was_enabled, connection_id))
38
38
  ActiveRecord::Base.connection_id = connection_id
39
- ActiveRecord::Base.connection.clear_query_cache
40
- ActiveRecord::Base.connection.disable_query_cache! unless enabled
39
+
40
+ caching_pool.disable_query_cache! unless caching_was_enabled
41
+
42
+ ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
43
+ pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
44
+ end
41
45
  end
42
46
 
43
47
  def self.install_executor_hooks(executor = ActiveSupport::Executor)
44
48
  executor.register_hook(self)
45
-
46
- executor.to_complete do
47
- unless ActiveRecord::Base.connection.transaction_open?
48
- ActiveRecord::Base.clear_active_connections!
49
- end
50
- end
51
49
  end
52
50
  end
53
51
  end
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  #
36
36
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
37
37
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
38
- def find_by_sql(sql, binds = [], preparable: nil)
38
+ def find_by_sql(sql, binds = [], preparable: nil, &block)
39
39
  result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
40
40
  column_types = result_set.column_types.dup
41
41
  columns_hash.each_key { |k| column_types.delete k }
@@ -46,8 +46,8 @@ module ActiveRecord
46
46
  class_name: name
47
47
  }
48
48
 
49
- message_bus.instrument('instantiation.active_record', payload) do
50
- result_set.map { |record| instantiate(record, column_types) }
49
+ message_bus.instrument("instantiation.active_record", payload) do
50
+ result_set.map { |record| instantiate(record, column_types, &block) }
51
51
  end
52
52
  end
53
53
 
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def cleanup_view_runtime
22
- if logger.info? && ActiveRecord::Base.connected?
22
+ if logger && logger.info? && ActiveRecord::Base.connected?
23
23
  db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
24
24
  self.db_runtime = (db_runtime || 0) + db_rt_before_render
25
25
  runtime = super
@@ -311,6 +311,10 @@ module ActiveRecord
311
311
  active_record == other_aggregation.active_record
312
312
  end
313
313
 
314
+ def scope_for(klass)
315
+ scope ? klass.unscoped.instance_exec(nil, &scope) : klass.unscoped
316
+ end
317
+
314
318
  private
315
319
  def derive_class_name
316
320
  name.to_s.camelize
@@ -394,6 +398,10 @@ module ActiveRecord
394
398
  options[:primary_key] || primary_key(klass || self.klass)
395
399
  end
396
400
 
401
+ def association_primary_key_type
402
+ klass.type_for_attribute(association_primary_key)
403
+ end
404
+
397
405
  def active_record_primary_key
398
406
  @active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
399
407
  end
@@ -372,6 +372,9 @@ module ActiveRecord
372
372
  #
373
373
  # # Update all books that match conditions, but limit it to 5 ordered by date
374
374
  # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
375
+ #
376
+ # # Update all invoices and set the number column to its id value.
377
+ # Invoice.update_all('number = id')
375
378
  def update_all(updates)
376
379
  raise ArgumentError, "Empty list of attributes to change" if updates.blank?
377
380
 
@@ -576,8 +579,8 @@ module ActiveRecord
576
579
  # return value is the relation itself, not the records.
577
580
  #
578
581
  # Post.where(published: true).load # => #<ActiveRecord::Relation>
579
- def load
580
- exec_queries unless loaded?
582
+ def load(&block)
583
+ exec_queries(&block) unless loaded?
581
584
 
582
585
  self
583
586
  end
@@ -695,8 +698,8 @@ module ActiveRecord
695
698
 
696
699
  private
697
700
 
698
- def exec_queries
699
- @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze
701
+ def exec_queries(&block)
702
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze
700
703
 
701
704
  preload = preload_values
702
705
  preload += includes_values unless eager_loading?
@@ -113,7 +113,10 @@ module ActiveRecord
113
113
  end
114
114
 
115
115
  if has_include?(column_name)
116
- construct_relation_for_association_calculations.calculate(operation, column_name)
116
+ relation = construct_relation_for_association_calculations
117
+ relation = relation.distinct if operation.to_s.downcase == "count"
118
+
119
+ relation.calculate(operation, column_name)
117
120
  else
118
121
  perform_calculation(operation, column_name)
119
122
  end
@@ -156,7 +159,7 @@ module ActiveRecord
156
159
  #
157
160
  def pluck(*column_names)
158
161
  if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
159
- return @records.pluck(*column_names)
162
+ return records.pluck(*column_names)
160
163
  end
161
164
 
162
165
  if has_include?(column_names.first)
@@ -194,11 +197,6 @@ module ActiveRecord
194
197
 
195
198
  if operation == "count"
196
199
  column_name ||= select_for_count
197
-
198
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
199
- distinct = true
200
- end
201
-
202
200
  column_name = primary_key if column_name == :all && distinct
203
201
  distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
204
202
  end
@@ -308,8 +306,10 @@ module ActiveRecord
308
306
 
309
307
  Hash[calculated_data.map do |row|
310
308
  key = group_columns.map { |aliaz, col_name|
311
- column = calculated_data.column_types.fetch(aliaz) do
312
- type_for(col_name)
309
+ column = type_for(col_name) do
310
+ calculated_data.column_types.fetch(aliaz) do
311
+ Type::Value.new
312
+ end
313
313
  end
314
314
  type_cast_calculated_value(row[aliaz], column)
315
315
  }
@@ -342,9 +342,9 @@ module ActiveRecord
342
342
  @klass.connection.table_alias_for(table_name)
343
343
  end
344
344
 
345
- def type_for(field)
345
+ def type_for(field, &block)
346
346
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
347
- @klass.type_for_attribute(field_name)
347
+ @klass.type_for_attribute(field_name, &block)
348
348
  end
349
349
 
350
350
  def type_cast_calculated_value(value, type, operation = nil)
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
 
38
38
  delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
39
39
  :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
40
- :shuffle, :split, to: :records
40
+ :shuffle, :split, :index, to: :records
41
41
 
42
42
  delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
43
43
  :connection, :columns_hash, :to => :klass
@@ -343,18 +343,20 @@ module ActiveRecord
343
343
  # of results obtained should be provided in the +result_size+ argument and
344
344
  # the expected number of results should be provided in the +expected_size+
345
345
  # argument.
346
- def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
346
+ def raise_record_not_found_exception!(ids, result_size, expected_size, key = primary_key) #:nodoc:
347
347
  conditions = arel.where_sql(@klass.arel_engine)
348
348
  conditions = " [#{conditions}]" if conditions
349
+ name = @klass.name
349
350
 
350
351
  if Array(ids).size == 1
351
- error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
352
+ error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
353
+ raise RecordNotFound.new(error, name, key, ids)
352
354
  else
353
- error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
355
+ error = "Couldn't find all #{name.pluralize} with '#{key}': "
354
356
  error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
355
- end
356
357
 
357
- raise RecordNotFound, error
358
+ raise RecordNotFound, error
359
+ end
358
360
  end
359
361
 
360
362
  private
@@ -520,7 +522,7 @@ module ActiveRecord
520
522
 
521
523
  def find_take
522
524
  if loaded?
523
- @records.first
525
+ records.first
524
526
  else
525
527
  @take ||= limit(1).records.first
526
528
  end
@@ -537,7 +539,7 @@ module ActiveRecord
537
539
  MSG
538
540
  end
539
541
  if loaded?
540
- @records[index]
542
+ records[index]
541
543
  else
542
544
  offset ||= offset_index
543
545
  @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
@@ -563,7 +565,7 @@ module ActiveRecord
563
565
 
564
566
  def find_nth_from_last(index)
565
567
  if loaded?
566
- @records[-index]
568
+ records[-index]
567
569
  else
568
570
  relation = if order_values.empty? && primary_key
569
571
  order(arel_attribute(primary_key).asc)
@@ -584,7 +586,7 @@ module ActiveRecord
584
586
 
585
587
  def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
586
588
  if loaded?
587
- @records[index, limit]
589
+ records[index, limit]
588
590
  else
589
591
  index += offset
590
592
  find_nth_with_limit(index, limit)
@@ -1141,9 +1141,9 @@ module ActiveRecord
1141
1141
  end
1142
1142
 
1143
1143
  def does_not_support_reverse?(order)
1144
- #uses sql function with multiple arguments
1145
- order =~ /\([^()]*,[^()]*\)/ ||
1146
- # uses "nulls first" like construction
1144
+ # Uses SQL function with multiple arguments.
1145
+ (order.include?(',') && order.split(',').find { |section| section.count('(') != section.count(')')}) ||
1146
+ # Uses "nulls first" like construction.
1147
1147
  order =~ /nulls (first|last)\Z/i
1148
1148
  end
1149
1149
 
@@ -75,8 +75,14 @@ module ActiveRecord
75
75
  hash_rows[idx]
76
76
  end
77
77
 
78
+ def first
79
+ return nil if @rows.empty?
80
+ Hash[@columns.zip(@rows.first)]
81
+ end
82
+
78
83
  def last
79
- hash_rows.last
84
+ return nil if @rows.empty?
85
+ Hash[@columns.zip(@rows.last)]
80
86
  end
81
87
 
82
88
  def cast_values(type_overrides = {}) # :nodoc:
@@ -116,7 +116,17 @@ module ActiveRecord
116
116
  def sanitize_sql_hash_for_assignment(attrs, table)
117
117
  c = connection
118
118
  attrs.map do |attr, value|
119
- value = type_for_attribute(attr.to_s).serialize(value)
119
+ if value.is_a?(Base)
120
+ require "active_support/core_ext/string/filters"
121
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
122
+ Passing `ActiveRecord::Base` objects to
123
+ `sanitize_sql_hash_for_assignment` (or methods which call it,
124
+ such as `update_all`) is deprecated. Please pass the id directly,
125
+ instead.
126
+ WARNING
127
+ else
128
+ value = type_for_attribute(attr.to_s).serialize(value)
129
+ end
120
130
  "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
121
131
  end.join(', ')
122
132
  end
@@ -105,12 +105,7 @@ HEADER
105
105
  tbl = StringIO.new
106
106
 
107
107
  # first dump primary key column
108
- if @connection.respond_to?(:primary_keys)
109
- pk = @connection.primary_keys(table)
110
- pk = pk.first unless pk.size > 1
111
- else
112
- pk = @connection.primary_key(table)
113
- end
108
+ pk = @connection.primary_key(table)
114
109
 
115
110
  tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
116
111
 
@@ -132,10 +127,8 @@ HEADER
132
127
  tbl.print ", force: :cascade"
133
128
 
134
129
  table_options = @connection.table_options(table)
135
- tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
136
-
137
- if comment = @connection.table_comment(table).presence
138
- tbl.print ", comment: #{comment.inspect}"
130
+ if table_options.present?
131
+ tbl.print ", #{format_options(table_options)}"
139
132
  end
140
133
 
141
134
  tbl.puts " do |t|"
@@ -218,13 +211,9 @@ HEADER
218
211
  index.columns.inspect,
219
212
  "name: #{index.name.inspect}",
220
213
  ]
221
- index_parts << 'unique: true' if index.unique
222
-
223
- index_lengths = (index.lengths || []).compact
224
- index_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
225
-
226
- index_orders = index.orders || {}
227
- index_parts << "order: #{index.orders.inspect}" if index_orders.any?
214
+ index_parts << "unique: true" if index.unique
215
+ index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
216
+ index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
228
217
  index_parts << "where: #{index.where.inspect}" if index.where
229
218
  index_parts << "using: #{index.using.inspect}" if index.using
230
219
  index_parts << "type: #{index.type.inspect}" if index.type
@@ -262,6 +251,10 @@ HEADER
262
251
  end
263
252
  end
264
253
 
254
+ def format_options(options)
255
+ options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
256
+ end
257
+
265
258
  def remove_prefix_and_suffix(table)
266
259
  table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
267
260
  end
@@ -174,7 +174,7 @@ module ActiveRecord
174
174
  protected
175
175
 
176
176
  def valid_scope_name?(name)
177
- if respond_to?(name, true)
177
+ if respond_to?(name, true) && logger
178
178
  logger.warn "Creating scope :#{name}. " \
179
179
  "Overwriting existing method #{self.name}.#{name}."
180
180
  end
@@ -101,12 +101,12 @@ module ActiveRecord
101
101
  @bind_map = bind_map
102
102
  end
103
103
 
104
- def execute(params, klass, connection)
104
+ def execute(params, klass, connection, &block)
105
105
  bind_values = bind_map.bind params
106
106
 
107
107
  sql = query_builder.sql_for bind_values, connection
108
108
 
109
- klass.find_by_sql(sql, bind_values, preparable: true)
109
+ klass.find_by_sql(sql, bind_values, preparable: true, &block)
110
110
  end
111
111
  alias :call :execute
112
112
  end
@@ -42,10 +42,11 @@ module ActiveRecord
42
42
  end
43
43
 
44
44
  def associated_table(table_name)
45
- return self if table_name == arel_table.name
45
+ association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
46
46
 
47
- association = klass._reflect_on_association(table_name)
48
- if association && !association.polymorphic?
47
+ if !association && table_name == arel_table.name
48
+ return self
49
+ elsif association && !association.polymorphic?
49
50
  association_klass = association.klass
50
51
  arel_table = association_klass.arel_table.alias(table_name)
51
52
  else