activerecord 6.0.0.beta1 → 6.0.0.beta2

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -2
  3. data/lib/active_record.rb +7 -0
  4. data/lib/active_record/associations/association.rb +17 -0
  5. data/lib/active_record/associations/collection_association.rb +5 -6
  6. data/lib/active_record/associations/collection_proxy.rb +12 -41
  7. data/lib/active_record/associations/has_many_association.rb +1 -9
  8. data/lib/active_record/associations/join_dependency/join_association.rb +11 -6
  9. data/lib/active_record/associations/preloader/association.rb +3 -4
  10. data/lib/active_record/associations/preloader/through_association.rb +9 -20
  11. data/lib/active_record/callbacks.rb +3 -3
  12. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +25 -12
  13. data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
  14. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -1
  15. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  16. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +47 -33
  17. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +16 -8
  18. data/lib/active_record/connection_adapters/abstract/transaction.rb +5 -2
  19. data/lib/active_record/connection_adapters/abstract_adapter.rb +6 -4
  20. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -65
  21. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
  22. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  23. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  24. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +59 -1
  25. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  26. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  27. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  28. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  29. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  30. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -27
  31. data/lib/active_record/connection_adapters/postgresql_adapter.rb +30 -0
  32. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +27 -1
  33. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +8 -5
  34. data/lib/active_record/connection_handling.rb +9 -4
  35. data/lib/active_record/core.rb +13 -1
  36. data/lib/active_record/database_configurations.rb +30 -10
  37. data/lib/active_record/database_configurations/hash_config.rb +1 -1
  38. data/lib/active_record/database_configurations/url_config.rb +9 -4
  39. data/lib/active_record/errors.rb +17 -12
  40. data/lib/active_record/gem_version.rb +1 -1
  41. data/lib/active_record/inheritance.rb +1 -1
  42. data/lib/active_record/middleware/database_selector.rb +75 -0
  43. data/lib/active_record/middleware/database_selector/resolver.rb +90 -0
  44. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  45. data/lib/active_record/migration.rb +1 -1
  46. data/lib/active_record/migration/compatibility.rb +62 -63
  47. data/lib/active_record/persistence.rb +6 -6
  48. data/lib/active_record/querying.rb +2 -3
  49. data/lib/active_record/railtie.rb +9 -0
  50. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  51. data/lib/active_record/reflection.rb +15 -29
  52. data/lib/active_record/relation.rb +86 -15
  53. data/lib/active_record/relation/calculations.rb +2 -4
  54. data/lib/active_record/relation/delegation.rb +1 -1
  55. data/lib/active_record/relation/finder_methods.rb +8 -4
  56. data/lib/active_record/relation/query_attribute.rb +5 -3
  57. data/lib/active_record/relation/query_methods.rb +28 -8
  58. data/lib/active_record/relation/spawn_methods.rb +1 -1
  59. data/lib/active_record/relation/where_clause.rb +1 -5
  60. data/lib/active_record/scoping.rb +6 -7
  61. data/lib/active_record/scoping/default.rb +1 -8
  62. data/lib/active_record/scoping/named.rb +9 -1
  63. data/lib/active_record/test_fixtures.rb +2 -2
  64. data/lib/active_record/timestamp.rb +9 -3
  65. data/lib/active_record/validations/uniqueness.rb +3 -1
  66. data/lib/arel.rb +7 -0
  67. data/lib/arel/nodes/and.rb +1 -1
  68. data/lib/arel/nodes/case.rb +1 -1
  69. metadata +11 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c6ced19e10b6f6d4eb33ac9745ce89e3b4ee124c02080e582abe07b9e5ac9a6
4
- data.tar.gz: d0267bc9be2b86eea525eebd296a3eba6dd098ae154c6ec6b91978164e91a898
3
+ metadata.gz: 65ea4dc67676807bb1cec848b592808e9ae0c17428347bc1c54b9d8c7fe7540d
4
+ data.tar.gz: e0c6e2c537026df44775de8c26e8999ce304bfb77fddd9b8f854758c655540b7
5
5
  SHA512:
6
- metadata.gz: 0fa88cf19b4df3cafcb246c31b104bc0df9a231f131d8c046fa20adb20f06285da1379a55ee82b3ef18e313212b4ae88f8f35752ae99bffcd0e669ca6f255b1d
7
- data.tar.gz: 37f5229326765cb0c16f1501c4f3ade8594166a7999402f1016e23dfc9ec01ca009fa6abaf6e0751e288d533393592fe1db64fd0301c1ae1e1bfe5ff4c08d010
6
+ metadata.gz: 3f5d1751f4b0be87fe91d8aac2216dd3184374b59e84b02d02f5578729bf22a51e73a9b21fc2bbc86a869cdbdd2386f6e25c0aba3424be842de17d2891844f4d
7
+ data.tar.gz: 1826fca9a8b097f7e4dd896fc08012bf10ce40159d75676838e16cb2dd72dce252eef5f3a4ab2a0a7eef1438c47584b0521d7fd9f573fa5bfb61cbfe11d98d52
@@ -1,3 +1,100 @@
1
+ ## Rails 6.0.0.beta2 (February 25, 2019) ##
2
+
3
+ * Fix prepared statements caching to be enabled even when query caching is enabled.
4
+
5
+ *Ryuta Kamizono*
6
+
7
+ * Ensure `update_all` series cares about optimistic locking.
8
+
9
+ *Ryuta Kamizono*
10
+
11
+ * Don't allow `where` with non numeric string matches to 0 values.
12
+
13
+ *Ryuta Kamizono*
14
+
15
+ * Introduce `ActiveRecord::Relation#destroy_by` and `ActiveRecord::Relation#delete_by`.
16
+
17
+ `destroy_by` allows relation to find all the records matching the condition and perform
18
+ `destroy_all` on the matched records.
19
+
20
+ Example:
21
+
22
+ Person.destroy_by(name: 'David')
23
+ Person.destroy_by(name: 'David', rating: 4)
24
+
25
+ david = Person.find_by(name: 'David')
26
+ david.posts.destroy_by(id: [1, 2, 3])
27
+
28
+ `delete_by` allows relation to find all the records matching the condition and perform
29
+ `delete_all` on the matched records.
30
+
31
+ Example:
32
+
33
+ Person.delete_by(name: 'David')
34
+ Person.delete_by(name: 'David', rating: 4)
35
+
36
+ david = Person.find_by(name: 'David')
37
+ david.posts.delete_by(id: [1, 2, 3])
38
+
39
+ *Abhay Nikam*
40
+
41
+ * Don't allow `where` with invalid value matches to nil values.
42
+
43
+ Fixes #33624.
44
+
45
+ *Ryuta Kamizono*
46
+
47
+ * SQLite3: Implement `add_foreign_key` and `remove_foreign_key`.
48
+
49
+ *Ryuta Kamizono*
50
+
51
+ * Deprecate using class level querying methods if the receiver scope
52
+ regarded as leaked. Use `klass.unscoped` to avoid the leaking scope.
53
+
54
+ *Ryuta Kamizono*
55
+
56
+ * Allow applications to automatically switch connections.
57
+
58
+ Adds a middleware and configuration options that can be used in your
59
+ application to automatically switch between the writing and reading
60
+ database connections.
61
+
62
+ `GET` and `HEAD` requests will read from the replica unless there was
63
+ a write in the last 2 seconds, otherwise they will read from the primary.
64
+ Non-get requests will always write to the primary. The middleware accepts
65
+ an argument for a Resolver class and a Operations class where you are able
66
+ to change how the auto-switcher works to be most beneficial for your
67
+ application.
68
+
69
+ To use the middleware in your application you can use the following
70
+ configuration options:
71
+
72
+ ```
73
+ config.active_record.database_selector = { delay: 2.seconds }
74
+ config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
75
+ config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
76
+ ```
77
+
78
+ To change the database selection strategy, pass a custom class to the
79
+ configuration options:
80
+
81
+ ```
82
+ config.active_record.database_selector = { delay: 10.seconds }
83
+ config.active_record.database_resolver = MyResolver
84
+ config.active_record.database_resolver_context = MyResolver::MyCookies
85
+ ```
86
+
87
+ *Eileen M. Uchitelle*
88
+
89
+ * MySQL: Support `:size` option to change text and blob size.
90
+
91
+ *Ryuta Kamizono*
92
+
93
+ * Make `t.timestamps` with precision by default.
94
+
95
+ *Ryuta Kamizono*
96
+
97
+
1
98
  ## Rails 6.0.0.beta1 (January 18, 2019) ##
2
99
 
3
100
  * Remove deprecated `#set_state` from the transaction object.
@@ -454,8 +551,8 @@
454
551
 
455
552
  Iterating over the database configurations has also changed. Instead of
456
553
  calling hash methods on the `configurations` hash directly, a new method `configs_for` has
457
- been provided that allows you to select the correct configuration. `env_name`, and
458
- `spec_name` arguments are optional. For example these return an array of
554
+ been provided that allows you to select the correct configuration. `env_name` and
555
+ `spec_name` arguments are optional. For example, these return an array of
459
556
  database config objects for the requested environment and a single database config object
460
557
  will be returned for the requested environment and specification name respectively.
461
558
 
@@ -74,6 +74,7 @@ module ActiveRecord
74
74
  autoload :Translation
75
75
  autoload :Validations
76
76
  autoload :SecureToken
77
+ autoload :DatabaseSelector, "active_record/middleware/database_selector"
77
78
 
78
79
  eager_autoload do
79
80
  autoload :ActiveRecordError, "active_record/errors"
@@ -153,6 +154,12 @@ module ActiveRecord
153
154
  end
154
155
  end
155
156
 
157
+ module Middleware
158
+ extend ActiveSupport::Autoload
159
+
160
+ autoload :DatabaseSelector, "active_record/middleware/database_selector"
161
+ end
162
+
156
163
  module Tasks
157
164
  extend ActiveSupport::Autoload
158
165
 
@@ -17,6 +17,23 @@ module ActiveRecord
17
17
  # CollectionAssociation
18
18
  # HasManyAssociation + ForeignAssociation
19
19
  # HasManyThroughAssociation + ThroughAssociation
20
+ #
21
+ # Associations in Active Record are middlemen between the object that
22
+ # holds the association, known as the <tt>owner</tt>, and the associated
23
+ # result set, known as the <tt>target</tt>. Association metadata is available in
24
+ # <tt>reflection</tt>, which is an instance of <tt>ActiveRecord::Reflection::AssociationReflection</tt>.
25
+ #
26
+ # For example, given
27
+ #
28
+ # class Blog < ActiveRecord::Base
29
+ # has_many :posts
30
+ # end
31
+ #
32
+ # blog = Blog.first
33
+ #
34
+ # The association of <tt>blog.posts</tt> has the object +blog+ as its
35
+ # <tt>owner</tt>, the collection of its posts as <tt>target</tt>, and
36
+ # the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
20
37
  class Association #:nodoc:
21
38
  attr_reader :owner, :target, :reflection
22
39
 
@@ -109,9 +109,8 @@ module ActiveRecord
109
109
  end
110
110
  end
111
111
 
112
- # Add +records+ to this association. Returns +self+ so method calls may
113
- # be chained. Since << flattens its argument list and inserts each record,
114
- # +push+ and +concat+ behave identically.
112
+ # Add +records+ to this association. Since +<<+ flattens its argument list
113
+ # and inserts each record, +push+ and +concat+ behave identically.
115
114
  def concat(*records)
116
115
  records = records.flatten
117
116
  if owner.new_record?
@@ -233,7 +232,7 @@ module ActiveRecord
233
232
  # loaded and you are going to fetch the records anyway it is better to
234
233
  # check <tt>collection.length.zero?</tt>.
235
234
  def empty?
236
- if loaded? || @association_ids
235
+ if loaded? || @association_ids || reflection.has_cached_counter?
237
236
  size.zero?
238
237
  else
239
238
  target.empty? && !scope.exists?
@@ -347,7 +346,6 @@ module ActiveRecord
347
346
  add_to_target(record) do
348
347
  result = insert_record(record, true, raise) {
349
348
  @_was_loaded = loaded?
350
- @association_ids = nil
351
349
  }
352
350
  end
353
351
  raise ActiveRecord::Rollback unless result
@@ -384,6 +382,7 @@ module ActiveRecord
384
382
 
385
383
  delete_records(existing_records, method) if existing_records.any?
386
384
  @target -= records
385
+ @association_ids = nil
387
386
 
388
387
  records.each { |record| callback(:after_remove, record) }
389
388
  end
@@ -424,7 +423,6 @@ module ActiveRecord
424
423
  unless owner.new_record?
425
424
  result &&= insert_record(record, true, raise) {
426
425
  @_was_loaded = loaded?
427
- @association_ids = nil
428
426
  }
429
427
  end
430
428
  end
@@ -447,6 +445,7 @@ module ActiveRecord
447
445
  if index
448
446
  target[index] = record
449
447
  elsif @_was_loaded || !loaded?
448
+ @association_ids = nil
450
449
  target << record
451
450
  end
452
451
 
@@ -2,11 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
- # Association proxies in Active Record are middlemen between the object that
6
- # holds the association, known as the <tt>@owner</tt>, and the actual associated
7
- # object, known as the <tt>@target</tt>. The kind of association any proxy is
8
- # about is available in <tt>@reflection</tt>. That's an instance of the class
9
- # ActiveRecord::Reflection::AssociationReflection.
5
+ # Collection proxies in Active Record are middlemen between an
6
+ # <tt>association</tt>, and its <tt>target</tt> result set.
10
7
  #
11
8
  # For example, given
12
9
  #
@@ -16,14 +13,14 @@ module ActiveRecord
16
13
  #
17
14
  # blog = Blog.first
18
15
  #
19
- # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
20
- # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
21
- # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
16
+ # The collection proxy returned by <tt>blog.posts</tt> is built from a
17
+ # <tt>:has_many</tt> <tt>association</tt>, and delegates to a collection
18
+ # of posts as the <tt>target</tt>.
22
19
  #
23
- # This class delegates unknown methods to <tt>@target</tt> via
24
- # <tt>method_missing</tt>.
20
+ # This class delegates unknown methods to the <tt>association</tt>'s
21
+ # relation class via a delegate cache.
25
22
  #
26
- # The <tt>@target</tt> object is not \loaded until needed. For example,
23
+ # The <tt>target</tt> result set is not loaded until needed. For example,
27
24
  #
28
25
  # blog.posts.count
29
26
  #
@@ -366,34 +363,6 @@ module ActiveRecord
366
363
  @association.create!(attributes, &block)
367
364
  end
368
365
 
369
- # Add one or more records to the collection by setting their foreign keys
370
- # to the association's primary key. Since #<< flattens its argument list and
371
- # inserts each record, +push+ and #concat behave identically. Returns +self+
372
- # so method calls may be chained.
373
- #
374
- # class Person < ActiveRecord::Base
375
- # has_many :pets
376
- # end
377
- #
378
- # person.pets.size # => 0
379
- # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
380
- # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
381
- # person.pets.size # => 3
382
- #
383
- # person.id # => 1
384
- # person.pets
385
- # # => [
386
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
387
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
388
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
389
- # # ]
390
- #
391
- # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
392
- # person.pets.size # => 5
393
- def concat(*records)
394
- @association.concat(*records)
395
- end
396
-
397
366
  # Replaces this collection with +other_array+. This will perform a diff
398
367
  # and delete/add only records that have changed.
399
368
  #
@@ -1033,8 +1002,9 @@ module ActiveRecord
1033
1002
  end
1034
1003
 
1035
1004
  # Adds one or more +records+ to the collection by setting their foreign keys
1036
- # to the association's primary key. Returns +self+, so several appends may be
1037
- # chained together.
1005
+ # to the association's primary key. Since +<<+ flattens its argument list and
1006
+ # inserts each record, +push+ and +concat+ behave identically. Returns +self+
1007
+ # so several appends may be chained together.
1038
1008
  #
1039
1009
  # class Person < ActiveRecord::Base
1040
1010
  # has_many :pets
@@ -1057,6 +1027,7 @@ module ActiveRecord
1057
1027
  end
1058
1028
  alias_method :push, :<<
1059
1029
  alias_method :append, :<<
1030
+ alias_method :concat, :<<
1060
1031
 
1061
1032
  def prepend(*args)
1062
1033
  raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
@@ -36,14 +36,6 @@ module ActiveRecord
36
36
  super
37
37
  end
38
38
 
39
- def empty?
40
- if reflection.has_cached_counter?
41
- size.zero?
42
- else
43
- super
44
- end
45
- end
46
-
47
39
  private
48
40
 
49
41
  # Returns the number of records in this collection.
@@ -69,7 +61,7 @@ module ActiveRecord
69
61
  # If there's nothing in the database and @target has no new records
70
62
  # we are certain the current target is an empty array. This is a
71
63
  # documented side-effect of the method that may avoid an extra SELECT.
72
- (@target ||= []) && loaded! if count == 0
64
+ loaded! if count == 0
73
65
 
74
66
  [association_scope.limit_value, count].compact.min
75
67
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record/associations/join_dependency/join_part"
4
+ require "active_support/core_ext/array/extract"
4
5
 
5
6
  module ActiveRecord
6
7
  module Associations
@@ -30,17 +31,21 @@ module ActiveRecord
30
31
  table = tables[-i]
31
32
  klass = reflection.klass
32
33
 
33
- constraint = reflection.build_join_constraint(table, foreign_table)
34
+ join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
34
35
 
35
- joins << table.create_join(table, table.create_on(constraint), join_type)
36
-
37
- join_scope = reflection.join_scope(table, foreign_klass)
38
36
  arel = join_scope.arel(alias_tracker.aliases)
37
+ nodes = arel.constraints.first
38
+
39
+ others = nodes.children.extract! do |node|
40
+ Arel.fetch_attribute(node) { |attr| attr.relation.name != table.name }
41
+ end
42
+
43
+ joins << table.create_join(table, table.create_on(nodes), join_type)
39
44
 
40
- if arel.constraints.any?
45
+ unless others.empty?
41
46
  joins.concat arel.join_sources
42
47
  right = joins.last.right
43
- right.expr = right.expr.and(arel.constraints)
48
+ right.expr.children.concat(others)
44
49
  end
45
50
 
46
51
  # The current table in this iteration becomes the foreign table in the next
@@ -42,11 +42,10 @@ module ActiveRecord
42
42
 
43
43
  def associate_records_to_owner(owner, records)
44
44
  association = owner.association(reflection.name)
45
- association.loaded!
46
45
  if reflection.collection?
47
- association.target.concat(records)
46
+ association.target = records
48
47
  else
49
- association.target = records.first unless records.empty?
48
+ association.target = records.first
50
49
  end
51
50
  end
52
51
 
@@ -116,7 +115,7 @@ module ActiveRecord
116
115
  def build_scope
117
116
  scope = klass.scope_for_association
118
117
 
119
- if reflection.type
118
+ if reflection.type && !reflection.through_reflection?
120
119
  scope.where!(reflection.type => model.polymorphic_name)
121
120
  end
122
121
 
@@ -7,10 +7,9 @@ module ActiveRecord
7
7
  def run(preloader)
8
8
  already_loaded = owners.first.association(through_reflection.name).loaded?
9
9
  through_scope = through_scope()
10
- reflection_scope = target_reflection_scope
11
10
  through_preloaders = preloader.preload(owners, through_reflection.name, through_scope)
12
11
  middle_records = through_preloaders.flat_map(&:preloaded_records)
13
- preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope)
12
+ preloaders = preloader.preload(middle_records, source_reflection.name, scope)
14
13
  @preloaded_records = preloaders.flat_map(&:preloaded_records)
15
14
 
16
15
  owners.each do |owner|
@@ -25,18 +24,18 @@ module ActiveRecord
25
24
  owner.association(through_reflection.name).reset if through_scope
26
25
  end
27
26
  result = through_records.flat_map do |record|
28
- association = record.association(source_reflection.name)
29
- target = association.target
30
- association.reset if preload_scope
31
- target
27
+ record.association(source_reflection.name).target
32
28
  end
33
29
  result.compact!
34
- if reflection_scope
35
- result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any?
36
- result.uniq! if reflection_scope.distinct_value
37
- end
30
+ result.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
31
+ result.uniq! if scope.distinct_value
38
32
  associate_records_to_owner(owner, result)
39
33
  end
34
+ unless scope.empty_scope?
35
+ middle_records.each do |owner|
36
+ owner.association(source_reflection.name).reset
37
+ end
38
+ end
40
39
  end
41
40
 
42
41
  private
@@ -91,16 +90,6 @@ module ActiveRecord
91
90
 
92
91
  scope unless scope.empty_scope?
93
92
  end
94
-
95
- def target_reflection_scope
96
- if preload_scope
97
- reflection_scope.merge(preload_scope)
98
- elsif reflection.scope
99
- reflection_scope
100
- else
101
- nil
102
- end
103
- end
104
93
  end
105
94
  end
106
95
  end