activerecord 6.0.0.beta1 → 6.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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +529 -10
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +7 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +27 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +5 -6
  16. data/lib/active_record/associations/collection_proxy.rb +13 -42
  17. data/lib/active_record/associations/has_many_association.rb +1 -9
  18. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  19. data/lib/active_record/associations/join_dependency.rb +14 -9
  20. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  21. data/lib/active_record/associations/preloader.rb +12 -7
  22. data/lib/active_record/associations/preloader/association.rb +37 -34
  23. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  24. data/lib/active_record/attribute_methods.rb +3 -53
  25. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  26. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  27. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  28. data/lib/active_record/attribute_methods/query.rb +2 -3
  29. data/lib/active_record/attribute_methods/read.rb +3 -9
  30. data/lib/active_record/attribute_methods/write.rb +6 -12
  31. data/lib/active_record/attributes.rb +13 -0
  32. data/lib/active_record/autosave_association.rb +21 -7
  33. data/lib/active_record/base.rb +0 -1
  34. data/lib/active_record/callbacks.rb +3 -3
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
  36. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  37. data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
  38. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
  39. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  40. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  42. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  44. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  45. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
  46. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
  47. data/lib/active_record/connection_adapters/column.rb +17 -13
  48. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  49. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  52. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  53. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  54. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  55. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  56. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  59. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  60. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  62. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  63. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  66. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
  68. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  69. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  70. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  71. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
  74. data/lib/active_record/connection_handling.rb +40 -17
  75. data/lib/active_record/core.rb +35 -24
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  78. data/lib/active_record/database_configurations/url_config.rb +21 -16
  79. data/lib/active_record/dynamic_matchers.rb +1 -1
  80. data/lib/active_record/enum.rb +15 -0
  81. data/lib/active_record/errors.rb +18 -13
  82. data/lib/active_record/fixtures.rb +11 -6
  83. data/lib/active_record/gem_version.rb +2 -2
  84. data/lib/active_record/inheritance.rb +1 -1
  85. data/lib/active_record/insert_all.rb +179 -0
  86. data/lib/active_record/integration.rb +13 -1
  87. data/lib/active_record/internal_metadata.rb +5 -1
  88. data/lib/active_record/locking/optimistic.rb +3 -4
  89. data/lib/active_record/log_subscriber.rb +1 -1
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  92. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/migration/command_recorder.rb +28 -14
  95. data/lib/active_record/migration/compatibility.rb +72 -63
  96. data/lib/active_record/model_schema.rb +3 -0
  97. data/lib/active_record/persistence.rb +212 -19
  98. data/lib/active_record/querying.rb +18 -14
  99. data/lib/active_record/railtie.rb +9 -1
  100. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  101. data/lib/active_record/railties/databases.rake +124 -25
  102. data/lib/active_record/reflection.rb +18 -32
  103. data/lib/active_record/relation.rb +185 -35
  104. data/lib/active_record/relation/calculations.rb +40 -44
  105. data/lib/active_record/relation/delegation.rb +23 -31
  106. data/lib/active_record/relation/finder_methods.rb +23 -14
  107. data/lib/active_record/relation/merger.rb +11 -16
  108. data/lib/active_record/relation/query_attribute.rb +5 -3
  109. data/lib/active_record/relation/query_methods.rb +230 -69
  110. data/lib/active_record/relation/spawn_methods.rb +1 -1
  111. data/lib/active_record/relation/where_clause.rb +10 -10
  112. data/lib/active_record/sanitization.rb +33 -4
  113. data/lib/active_record/schema.rb +1 -1
  114. data/lib/active_record/schema_dumper.rb +10 -1
  115. data/lib/active_record/schema_migration.rb +1 -1
  116. data/lib/active_record/scoping.rb +6 -7
  117. data/lib/active_record/scoping/default.rb +7 -15
  118. data/lib/active_record/scoping/named.rb +10 -2
  119. data/lib/active_record/statement_cache.rb +2 -2
  120. data/lib/active_record/store.rb +48 -0
  121. data/lib/active_record/table_metadata.rb +9 -13
  122. data/lib/active_record/tasks/database_tasks.rb +109 -6
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  124. data/lib/active_record/test_databases.rb +1 -16
  125. data/lib/active_record/test_fixtures.rb +2 -2
  126. data/lib/active_record/timestamp.rb +35 -19
  127. data/lib/active_record/touch_later.rb +4 -2
  128. data/lib/active_record/transactions.rb +56 -46
  129. data/lib/active_record/type_caster/connection.rb +16 -10
  130. data/lib/active_record/validations.rb +1 -0
  131. data/lib/active_record/validations/uniqueness.rb +4 -4
  132. data/lib/arel.rb +18 -4
  133. data/lib/arel/insert_manager.rb +3 -3
  134. data/lib/arel/nodes.rb +2 -1
  135. data/lib/arel/nodes/and.rb +1 -1
  136. data/lib/arel/nodes/case.rb +1 -1
  137. data/lib/arel/nodes/comment.rb +29 -0
  138. data/lib/arel/nodes/select_core.rb +16 -12
  139. data/lib/arel/nodes/unary.rb +1 -0
  140. data/lib/arel/nodes/values_list.rb +2 -17
  141. data/lib/arel/select_manager.rb +10 -10
  142. data/lib/arel/visitors/depth_first.rb +7 -2
  143. data/lib/arel/visitors/dot.rb +7 -2
  144. data/lib/arel/visitors/ibm_db.rb +13 -0
  145. data/lib/arel/visitors/informix.rb +6 -0
  146. data/lib/arel/visitors/mssql.rb +15 -1
  147. data/lib/arel/visitors/oracle12.rb +4 -5
  148. data/lib/arel/visitors/postgresql.rb +4 -10
  149. data/lib/arel/visitors/to_sql.rb +107 -131
  150. data/lib/arel/visitors/visitor.rb +9 -5
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  152. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  154. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  155. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  156. metadata +19 -12
  157. data/lib/active_record/collection_cache_key.rb +0 -53
  158. data/lib/arel/nodes/values.rb +0 -16
@@ -13,6 +13,8 @@ columns. Although these mappings can be defined explicitly, it's recommended
13
13
  to follow naming conventions, especially when getting started with the
14
14
  library.
15
15
 
16
+ You can read more about Active Record in the {Active Record Basics}[https://edgeguides.rubyonrails.org/active_record_basics.html] guide.
17
+
16
18
  A short rundown of some of the major features:
17
19
 
18
20
  * Automated mapping between classes and tables, attributes and columns.
@@ -206,7 +208,7 @@ Active Record is released under the MIT license:
206
208
 
207
209
  API documentation is at:
208
210
 
209
- * http://api.rubyonrails.org
211
+ * https://api.rubyonrails.org
210
212
 
211
213
  Bug reports for the Ruby on Rails project can be filed here:
212
214
 
@@ -55,7 +55,6 @@ module ActiveRecord
55
55
  autoload :Persistence
56
56
  autoload :QueryCache
57
57
  autoload :Querying
58
- autoload :CollectionCacheKey
59
58
  autoload :ReadonlyAttributes
60
59
  autoload :RecordInvalid, "active_record/validations"
61
60
  autoload :Reflection
@@ -74,6 +73,7 @@ module ActiveRecord
74
73
  autoload :Translation
75
74
  autoload :Validations
76
75
  autoload :SecureToken
76
+ autoload :DatabaseSelector, "active_record/middleware/database_selector"
77
77
 
78
78
  eager_autoload do
79
79
  autoload :ActiveRecordError, "active_record/errors"
@@ -153,6 +153,12 @@ module ActiveRecord
153
153
  end
154
154
  end
155
155
 
156
+ module Middleware
157
+ extend ActiveSupport::Autoload
158
+
159
+ autoload :DatabaseSelector, "active_record/middleware/database_selector"
160
+ end
161
+
156
162
  module Tasks
157
163
  extend ActiveSupport::Autoload
158
164
 
@@ -15,17 +15,26 @@ module ActiveRecord
15
15
  other == records
16
16
  end
17
17
 
18
- def build(*args, &block)
19
- scoping { @association.build(*args, &block) }
18
+ def build(attributes = nil, &block)
19
+ block = _deprecated_scope_block("new", &block)
20
+ @association.scoping(self) do
21
+ @association.build(attributes, &block)
22
+ end
20
23
  end
21
24
  alias new build
22
25
 
23
- def create(*args, &block)
24
- scoping { @association.create(*args, &block) }
26
+ def create(attributes = nil, &block)
27
+ block = _deprecated_scope_block("create", &block)
28
+ @association.scoping(self) do
29
+ @association.create(attributes, &block)
30
+ end
25
31
  end
26
32
 
27
- def create!(*args, &block)
28
- scoping { @association.create!(*args, &block) }
33
+ def create!(attributes = nil, &block)
34
+ block = _deprecated_scope_block("create!", &block)
35
+ @association.scoping(self) do
36
+ @association.create!(attributes, &block)
37
+ end
29
38
  end
30
39
 
31
40
  private
@@ -92,7 +92,7 @@ module ActiveRecord
92
92
  through_reflection = reflection.through_reflection
93
93
  source_reflection_names = reflection.source_reflection_names
94
94
  source_associations = reflection.through_reflection.klass._reflections.keys
95
- super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ', locale: :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ', locale: :en)}?")
95
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}?")
96
96
  else
97
97
  super("Could not find the source association(s).")
98
98
  end
@@ -703,8 +703,9 @@ module ActiveRecord
703
703
  # #belongs_to associations.
704
704
  #
705
705
  # Extra options on the associations, as defined in the
706
- # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
707
- # also prevent the association's inverse from being found automatically.
706
+ # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt>
707
+ # constant, or a custom scope, will also prevent the association's inverse
708
+ # from being found automatically.
708
709
  #
709
710
  # The automatic guessing of the inverse association uses a heuristic based
710
711
  # on the name of the class, so it may not work for all associations,
@@ -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
 
@@ -26,6 +43,7 @@ module ActiveRecord
26
43
  reflection.check_validity!
27
44
 
28
45
  @owner, @reflection = owner, reflection
46
+ @_scope = nil
29
47
 
30
48
  reset
31
49
  reset_scope
@@ -78,7 +96,7 @@ module ActiveRecord
78
96
  end
79
97
 
80
98
  def scope
81
- target_scope.merge!(association_scope)
99
+ @_scope&.spawn || target_scope.merge!(association_scope)
82
100
  end
83
101
 
84
102
  def reset_scope
@@ -178,6 +196,13 @@ module ActiveRecord
178
196
  _create_record(attributes, true, &block)
179
197
  end
180
198
 
199
+ def scoping(relation, &block)
200
+ @_scope = relation
201
+ relation.scoping(&block)
202
+ ensure
203
+ @_scope = nil
204
+ end
205
+
181
206
  private
182
207
  def find_target
183
208
  scope = self.scope
@@ -208,7 +233,7 @@ module ActiveRecord
208
233
  # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
209
234
  # through association's scope)
210
235
  def target_scope
211
- AssociationRelation.create(klass, self).merge!(klass.all)
236
+ AssociationRelation.create(klass, self).merge!(klass.scope_for_association)
212
237
  end
213
238
 
214
239
  def scope_for_create
@@ -27,40 +27,32 @@ module ActiveRecord::Associations::Builder # :nodoc:
27
27
  "Please choose a different association name."
28
28
  end
29
29
 
30
- extension = define_extensions model, name, &block
31
- reflection = create_reflection model, name, scope, options, extension
30
+ reflection = create_reflection(model, name, scope, options, &block)
32
31
  define_accessors model, reflection
33
32
  define_callbacks model, reflection
34
33
  define_validations model, reflection
35
34
  reflection
36
35
  end
37
36
 
38
- def self.create_reflection(model, name, scope, options, extension = nil)
37
+ def self.create_reflection(model, name, scope, options, &block)
39
38
  raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
40
39
 
41
40
  validate_options(options)
42
41
 
43
- scope = build_scope(scope, extension)
42
+ extension = define_extensions(model, name, &block)
43
+ options[:extend] = [*options[:extend], extension] if extension
44
+
45
+ scope = build_scope(scope)
44
46
 
45
47
  ActiveRecord::Reflection.create(macro, name, scope, options, model)
46
48
  end
47
49
 
48
- def self.build_scope(scope, extension)
49
- new_scope = scope
50
-
50
+ def self.build_scope(scope)
51
51
  if scope && scope.arity == 0
52
- new_scope = proc { instance_exec(&scope) }
53
- end
54
-
55
- if extension
56
- new_scope = wrap_scope new_scope, extension
52
+ proc { instance_exec(&scope) }
53
+ else
54
+ scope
57
55
  end
58
-
59
- new_scope
60
- end
61
-
62
- def self.wrap_scope(scope, extension)
63
- scope
64
56
  end
65
57
 
66
58
  def self.macro
@@ -136,5 +128,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
136
128
  name = reflection.name
137
129
  model.before_destroy lambda { |o| o.association(name).handle_dependency }
138
130
  end
131
+
132
+ private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
133
+ :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
134
+ :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
139
135
  end
140
136
  end
@@ -74,11 +74,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
74
74
 
75
75
  def self.add_touch_callbacks(model, reflection)
76
76
  foreign_key = reflection.foreign_key
77
- n = reflection.name
77
+ name = reflection.name
78
78
  touch = reflection.options[:touch]
79
79
 
80
80
  callback = lambda { |changes_method| lambda { |record|
81
- BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
81
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
82
82
  }}
83
83
 
84
84
  if reflection.counter_cache_column
@@ -123,5 +123,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
123
123
  model.validates_presence_of reflection.name, message: :required
124
124
  end
125
125
  end
126
+
127
+ private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
128
+ :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
126
129
  end
127
130
  end
@@ -20,11 +20,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
20
  }
21
21
  end
22
22
 
23
- def self.define_extensions(model, name)
23
+ def self.define_extensions(model, name, &block)
24
24
  if block_given?
25
- extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
26
- extension = Module.new(&Proc.new)
27
- model.module_parent.const_set(extension_module_name, extension)
25
+ extension_module_name = "#{name.to_s.camelize}AssociationExtension"
26
+ extension = Module.new(&block)
27
+ model.const_set(extension_module_name, extension)
28
28
  end
29
29
  end
30
30
 
@@ -67,16 +67,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
67
67
  CODE
68
68
  end
69
69
 
70
- def self.wrap_scope(scope, mod)
71
- if scope
72
- if scope.arity > 0
73
- proc { |owner| instance_exec(owner, &scope).extending(mod) }
74
- else
75
- proc { instance_exec(&scope).extending(mod) }
76
- end
77
- else
78
- proc { extending(mod) }
79
- end
80
- end
70
+ private_class_method :valid_options, :define_callback, :define_extensions, :define_readers, :define_writers
81
71
  end
82
72
  end
@@ -63,7 +63,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
63
63
 
64
64
  def middle_reflection(join_model)
65
65
  middle_name = [lhs_model.name.downcase.pluralize,
66
- association_name].join("_").gsub("::", "_").to_sym
66
+ association_name.to_s].sort.join("_").gsub("::", "_").to_sym
67
67
  middle_options = middle_options join_model
68
68
 
69
69
  HasMany.create_reflection(lhs_model,
@@ -13,5 +13,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
13
13
  def self.valid_dependent_options
14
14
  [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
15
15
  end
16
+
17
+ private_class_method :macro, :valid_options, :valid_dependent_options
16
18
  end
17
19
  end
@@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:as]
10
+ valid = super + [:as, :touch]
11
11
  valid += [:through, :source, :source_type] if options[:through]
12
12
  valid
13
13
  end
@@ -16,6 +16,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
16
16
  [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
17
17
  end
18
18
 
19
+ def self.define_callbacks(model, reflection)
20
+ super
21
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
22
+ end
23
+
19
24
  def self.add_destroy_callbacks(model, reflection)
20
25
  super unless reflection.options[:through]
21
26
  end
@@ -26,5 +31,34 @@ module ActiveRecord::Associations::Builder # :nodoc:
26
31
  model.validates_presence_of reflection.name, message: :required
27
32
  end
28
33
  end
34
+
35
+ def self.touch_record(o, name, touch)
36
+ record = o.send name
37
+
38
+ return unless record && record.persisted?
39
+
40
+ if touch != true
41
+ record.touch(touch)
42
+ else
43
+ record.touch
44
+ end
45
+ end
46
+
47
+ def self.add_touch_callbacks(model, reflection)
48
+ name = reflection.name
49
+ touch = reflection.options[:touch]
50
+
51
+ callback = lambda { |record|
52
+ HasOne.touch_record(record, name, touch)
53
+ }
54
+
55
+ model.after_create callback, if: :saved_changes?
56
+ model.after_update callback, if: :saved_changes?
57
+ model.after_destroy callback
58
+ model.after_touch callback
59
+ end
60
+
61
+ private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
62
+ :define_callbacks, :define_validations, :add_touch_callbacks
29
63
  end
30
64
  end
@@ -38,5 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
38
38
  end
39
39
  CODE
40
40
  end
41
+
42
+ private_class_method :valid_options, :define_accessors, :define_constructors
41
43
  end
42
44
  end
@@ -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 <tt><<</tt> 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,8 +1027,9 @@ module ActiveRecord
1057
1027
  end
1058
1028
  alias_method :push, :<<
1059
1029
  alias_method :append, :<<
1030
+ alias_method :concat, :<<
1060
1031
 
1061
- def prepend(*args)
1032
+ def prepend(*args) # :nodoc:
1062
1033
  raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
1063
1034
  end
1064
1035