activerecord 4.0.4 → 4.1.16

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1632 -1797
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/examples/performance.rb +30 -18
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +4 -0
  9. data/lib/active_record/associations/alias_tracker.rb +49 -29
  10. data/lib/active_record/associations/association.rb +9 -17
  11. data/lib/active_record/associations/association_scope.rb +59 -49
  12. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
  14. data/lib/active_record/associations/builder/association.rb +84 -54
  15. data/lib/active_record/associations/builder/belongs_to.rb +90 -58
  16. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
  18. data/lib/active_record/associations/builder/has_many.rb +3 -3
  19. data/lib/active_record/associations/builder/has_one.rb +5 -7
  20. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  21. data/lib/active_record/associations/collection_association.rb +121 -111
  22. data/lib/active_record/associations/collection_proxy.rb +73 -18
  23. data/lib/active_record/associations/has_many_association.rb +14 -11
  24. data/lib/active_record/associations/has_many_through_association.rb +33 -6
  25. data/lib/active_record/associations/has_one_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
  27. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  28. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  29. data/lib/active_record/associations/join_dependency.rb +208 -168
  30. data/lib/active_record/associations/preloader/association.rb +69 -27
  31. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  35. data/lib/active_record/associations/preloader.rb +63 -49
  36. data/lib/active_record/associations/singular_association.rb +6 -5
  37. data/lib/active_record/associations/through_association.rb +30 -9
  38. data/lib/active_record/associations.rb +116 -42
  39. data/lib/active_record/attribute_assignment.rb +6 -3
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  41. data/lib/active_record/attribute_methods/dirty.rb +35 -26
  42. data/lib/active_record/attribute_methods/primary_key.rb +8 -1
  43. data/lib/active_record/attribute_methods/read.rb +56 -29
  44. data/lib/active_record/attribute_methods/serialization.rb +44 -12
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
  46. data/lib/active_record/attribute_methods/write.rb +59 -26
  47. data/lib/active_record/attribute_methods.rb +82 -43
  48. data/lib/active_record/autosave_association.rb +209 -194
  49. data/lib/active_record/base.rb +6 -2
  50. data/lib/active_record/callbacks.rb +2 -2
  51. data/lib/active_record/coders/json.rb +13 -0
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
  76. data/lib/active_record/connection_handling.rb +39 -5
  77. data/lib/active_record/core.rb +38 -54
  78. data/lib/active_record/counter_cache.rb +9 -10
  79. data/lib/active_record/dynamic_matchers.rb +6 -2
  80. data/lib/active_record/enum.rb +199 -0
  81. data/lib/active_record/errors.rb +22 -5
  82. data/lib/active_record/fixture_set/file.rb +2 -1
  83. data/lib/active_record/fixtures.rb +173 -76
  84. data/lib/active_record/gem_version.rb +15 -0
  85. data/lib/active_record/inheritance.rb +23 -9
  86. data/lib/active_record/integration.rb +54 -1
  87. data/lib/active_record/locking/optimistic.rb +7 -2
  88. data/lib/active_record/locking/pessimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +6 -13
  90. data/lib/active_record/migration/command_recorder.rb +8 -2
  91. data/lib/active_record/migration.rb +91 -56
  92. data/lib/active_record/model_schema.rb +7 -14
  93. data/lib/active_record/nested_attributes.rb +25 -13
  94. data/lib/active_record/no_touching.rb +52 -0
  95. data/lib/active_record/null_relation.rb +26 -6
  96. data/lib/active_record/persistence.rb +23 -29
  97. data/lib/active_record/querying.rb +15 -12
  98. data/lib/active_record/railtie.rb +12 -61
  99. data/lib/active_record/railties/databases.rake +37 -56
  100. data/lib/active_record/readonly_attributes.rb +0 -6
  101. data/lib/active_record/reflection.rb +230 -79
  102. data/lib/active_record/relation/batches.rb +74 -24
  103. data/lib/active_record/relation/calculations.rb +52 -48
  104. data/lib/active_record/relation/delegation.rb +54 -39
  105. data/lib/active_record/relation/finder_methods.rb +210 -67
  106. data/lib/active_record/relation/merger.rb +15 -12
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
  109. data/lib/active_record/relation/predicate_builder.rb +81 -40
  110. data/lib/active_record/relation/query_methods.rb +185 -108
  111. data/lib/active_record/relation/spawn_methods.rb +8 -5
  112. data/lib/active_record/relation.rb +79 -84
  113. data/lib/active_record/result.rb +45 -6
  114. data/lib/active_record/runtime_registry.rb +5 -0
  115. data/lib/active_record/sanitization.rb +4 -4
  116. data/lib/active_record/schema_dumper.rb +18 -6
  117. data/lib/active_record/schema_migration.rb +31 -18
  118. data/lib/active_record/scoping/default.rb +5 -18
  119. data/lib/active_record/scoping/named.rb +14 -29
  120. data/lib/active_record/scoping.rb +5 -0
  121. data/lib/active_record/store.rb +67 -18
  122. data/lib/active_record/tasks/database_tasks.rb +66 -26
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
  124. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  125. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  126. data/lib/active_record/timestamp.rb +6 -6
  127. data/lib/active_record/transactions.rb +10 -12
  128. data/lib/active_record/validations/presence.rb +1 -1
  129. data/lib/active_record/validations/uniqueness.rb +19 -9
  130. data/lib/active_record/version.rb +4 -7
  131. data/lib/active_record.rb +5 -7
  132. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  133. data/lib/rails/generators/active_record/migration.rb +18 -0
  134. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  135. data/lib/rails/generators/active_record.rb +2 -8
  136. metadata +18 -30
  137. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  138. data/lib/active_record/associations/join_helper.rb +0 -45
  139. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  141. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  142. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  143. data/lib/active_record/test_case.rb +0 -96
@@ -2,7 +2,6 @@ module ActiveRecord
2
2
  module Associations
3
3
  class Preloader
4
4
  module ThroughAssociation #:nodoc:
5
-
6
5
  def through_reflection
7
6
  reflection.through_reflection
8
7
  end
@@ -11,51 +10,84 @@ module ActiveRecord
11
10
  reflection.source_reflection
12
11
  end
13
12
 
14
- def associated_records_by_owner
15
- through_records = through_records_by_owner
13
+ def associated_records_by_owner(preloader)
14
+ preloader.preload(owners,
15
+ through_reflection.name,
16
+ through_scope)
16
17
 
17
- Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run
18
+ through_records = owners.map do |owner|
19
+ association = owner.association through_reflection.name
18
20
 
19
- through_records.each do |owner, records|
20
- records.map! { |r| r.send(source_reflection.name) }.flatten!
21
- records.compact!
21
+ [owner, Array(association.reader)]
22
22
  end
23
- end
24
23
 
25
- private
24
+ reset_association owners, through_reflection.name
25
+
26
+ middle_records = through_records.map { |(_,rec)| rec }.flatten
27
+
28
+ preloaders = preloader.preload(middle_records,
29
+ source_reflection.name,
30
+ reflection_scope)
26
31
 
27
- def through_records_by_owner
28
- Preloader.new(owners, through_reflection.name, through_scope).run
32
+ @preloaded_records = preloaders.flat_map(&:preloaded_records)
33
+
34
+ middle_to_pl = preloaders.each_with_object({}) do |pl,h|
35
+ pl.owners.each { |middle|
36
+ h[middle] = pl
37
+ }
38
+ end
39
+
40
+ record_offset = {}
41
+ @preloaded_records.each_with_index do |record,i|
42
+ record_offset[record] = i
43
+ end
29
44
 
30
- Hash[owners.map do |owner|
31
- through_records = Array.wrap(owner.send(through_reflection.name))
45
+ through_records.each_with_object({}) { |(lhs,center),records_by_owner|
46
+ pl_to_middle = center.group_by { |record| middle_to_pl[record] }
32
47
 
33
- # Dont cache the association - we would only be caching a subset
34
- if (through_scope != through_reflection.klass.unscoped) ||
35
- (reflection.options[:source_type] && through_reflection.collection?)
36
- owner.association(through_reflection.name).reset
48
+ records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
49
+ rhs_records = middles.flat_map { |r|
50
+ association = r.association source_reflection.name
51
+
52
+ association.reader
53
+ }.compact
54
+
55
+ rhs_records.sort_by { |rhs| record_offset[rhs] }
37
56
  end
57
+ }
58
+ end
59
+
60
+ private
61
+
62
+ def reset_association(owners, association_name)
63
+ should_reset = (through_scope != through_reflection.klass.unscoped) ||
64
+ (reflection.options[:source_type] && through_reflection.collection?)
38
65
 
39
- [owner, through_records]
40
- end]
66
+ # Dont cache the association - we would only be caching a subset
67
+ if should_reset
68
+ owners.each { |owner|
69
+ owner.association(association_name).reset
70
+ }
71
+ end
41
72
  end
42
73
 
74
+
43
75
  def through_scope
44
- through_scope = through_reflection.klass.unscoped
76
+ scope = through_reflection.klass.unscoped
45
77
 
46
78
  if options[:source_type]
47
- through_scope.where! reflection.foreign_type => options[:source_type]
79
+ scope.where! reflection.foreign_type => options[:source_type]
48
80
  else
49
81
  unless reflection_scope.where_values.empty?
50
- through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
51
- through_scope.where_values = reflection_scope.values[:where]
82
+ scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
83
+ scope.where_values = reflection_scope.values[:where]
52
84
  end
53
85
 
54
- through_scope.references! reflection_scope.values[:references]
55
- through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading?
86
+ scope.references! reflection_scope.values[:references]
87
+ scope.order! reflection_scope.values[:order] if scope.eager_loading?
56
88
  end
57
89
 
58
- through_scope
90
+ scope
59
91
  end
60
92
  end
61
93
  end
@@ -42,12 +42,9 @@ module ActiveRecord
42
42
  autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
43
43
  autoload :HasOne, 'active_record/associations/preloader/has_one'
44
44
  autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
45
- autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
46
45
  autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
47
46
  end
48
47
 
49
- attr_reader :records, :associations, :preload_scope, :model
50
-
51
48
  # Eager loads the named associations for the given Active Record record(s).
52
49
  #
53
50
  # In this description, 'association name' shall refer to the name passed
@@ -82,38 +79,48 @@ module ActiveRecord
82
79
  # [ :books, :author ]
83
80
  # { author: :avatar }
84
81
  # [ :books, { author: :avatar } ]
85
- def initialize(records, associations, preload_scope = nil)
86
- @records = Array.wrap(records).compact.uniq
87
- @associations = Array.wrap(associations)
88
- @preload_scope = preload_scope || Relation.new(nil, nil)
89
- end
90
82
 
91
- def run
92
- unless records.empty?
93
- associations.each { |association| preload(association) }
83
+ NULL_RELATION = Struct.new(:values).new({})
84
+
85
+ def preload(records, associations, preload_scope = nil)
86
+ records = Array.wrap(records).compact.uniq
87
+ associations = Array.wrap(associations)
88
+ preload_scope = preload_scope || NULL_RELATION
89
+
90
+ if records.empty?
91
+ []
92
+ else
93
+ associations.flat_map { |association|
94
+ preloaders_on association, records, preload_scope
95
+ }
94
96
  end
95
97
  end
96
98
 
97
99
  private
98
100
 
99
- def preload(association)
101
+ def preloaders_on(association, records, scope)
100
102
  case association
101
103
  when Hash
102
- preload_hash(association)
104
+ preloaders_for_hash(association, records, scope)
103
105
  when Symbol
104
- preload_one(association)
106
+ preloaders_for_one(association, records, scope)
105
107
  when String
106
- preload_one(association.to_sym)
108
+ preloaders_for_one(association.to_sym, records, scope)
107
109
  else
108
110
  raise ArgumentError, "#{association.inspect} was not recognised for preload"
109
111
  end
110
112
  end
111
113
 
112
- def preload_hash(association)
113
- association.each do |parent, child|
114
- Preloader.new(records, parent, preload_scope).run
115
- Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run
116
- end
114
+ def preloaders_for_hash(association, records, scope)
115
+ association.flat_map { |parent, child|
116
+ loaders = preloaders_for_one parent, records, scope
117
+
118
+ recs = loaders.flat_map(&:preloaded_records).uniq
119
+ loaders.concat Array.wrap(child).flat_map { |assoc|
120
+ preloaders_on assoc, recs, scope
121
+ }
122
+ loaders
123
+ }
117
124
  end
118
125
 
119
126
  # Not all records have the same class, so group then preload group on the reflection
@@ -123,53 +130,60 @@ module ActiveRecord
123
130
  # Additionally, polymorphic belongs_to associations can have multiple associated
124
131
  # classes, depending on the polymorphic_type field. So we group by the classes as
125
132
  # well.
126
- def preload_one(association)
127
- grouped_records(association).each do |reflection, klasses|
128
- klasses.each do |klass, records|
129
- preloader_for(reflection).new(klass, records, reflection, preload_scope).run
133
+ def preloaders_for_one(association, records, scope)
134
+ grouped_records(association, records).flat_map do |reflection, klasses|
135
+ klasses.map do |rhs_klass, rs|
136
+ loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
137
+ loader.run self
138
+ loader
130
139
  end
131
140
  end
132
141
  end
133
142
 
134
- def grouped_records(association)
135
- Hash[
136
- records_by_reflection(association).map do |reflection, records|
137
- [reflection, records.group_by { |record| association_klass(reflection, record) }]
138
- end
139
- ]
143
+ def grouped_records(association, records)
144
+ h = {}
145
+ records.each do |record|
146
+ next unless record
147
+ assoc = record.association(association)
148
+ klasses = h[assoc.reflection] ||= {}
149
+ (klasses[assoc.klass] ||= []) << record
150
+ end
151
+ h
140
152
  end
141
153
 
142
- def records_by_reflection(association)
143
- records.group_by do |record|
144
- record_class = record.class
145
- reflection = record_class.reflections[association]
154
+ class AlreadyLoaded
155
+ attr_reader :owners, :reflection
146
156
 
147
- unless reflection
148
- raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found on #{record_class.name}; " \
149
- "perhaps you misspelled it?"
150
- end
157
+ def initialize(klass, owners, reflection, preload_scope)
158
+ @owners = owners
159
+ @reflection = reflection
160
+ end
151
161
 
152
- reflection
162
+ def run(preloader); end
163
+
164
+ def preloaded_records
165
+ owners.flat_map { |owner| owner.association(reflection.name).target }
153
166
  end
154
167
  end
155
168
 
156
- def association_klass(reflection, record)
157
- if reflection.macro == :belongs_to && reflection.options[:polymorphic]
158
- klass = record.send(reflection.foreign_type)
159
- klass && klass.constantize
160
- else
161
- reflection.klass
162
- end
169
+ class NullPreloader
170
+ def self.new(klass, owners, reflection, preload_scope); self; end
171
+ def self.run(preloader); end
172
+ def self.preloaded_records; []; end
163
173
  end
164
174
 
165
- def preloader_for(reflection)
175
+ def preloader_for(reflection, owners, rhs_klass)
176
+ return NullPreloader unless rhs_klass
177
+
178
+ if owners.first.association(reflection.name).loaded?
179
+ return AlreadyLoaded
180
+ end
181
+
166
182
  case reflection.macro
167
183
  when :has_many
168
184
  reflection.options[:through] ? HasManyThrough : HasMany
169
185
  when :has_one
170
186
  reflection.options[:through] ? HasOneThrough : HasOne
171
- when :has_and_belongs_to_many
172
- HasAndBelongsToMany
173
187
  when :belongs_to
174
188
  BelongsTo
175
189
  end
@@ -18,11 +18,11 @@ module ActiveRecord
18
18
  end
19
19
 
20
20
  def create(attributes = {}, &block)
21
- create_record(attributes, &block)
21
+ _create_record(attributes, &block)
22
22
  end
23
23
 
24
24
  def create!(attributes = {}, &block)
25
- create_record(attributes, true, &block)
25
+ _create_record(attributes, true, &block)
26
26
  end
27
27
 
28
28
  def build(attributes = {})
@@ -39,10 +39,11 @@ module ActiveRecord
39
39
  end
40
40
 
41
41
  def find_target
42
- scope.take.tap { |record| set_inverse_instance(record) }
42
+ if record = scope.take
43
+ set_inverse_instance record
44
+ end
43
45
  end
44
46
 
45
- # Implemented by subclasses
46
47
  def replace(record)
47
48
  raise NotImplementedError, "Subclasses must implement a replace(record) method"
48
49
  end
@@ -51,7 +52,7 @@ module ActiveRecord
51
52
  replace(record)
52
53
  end
53
54
 
54
- def create_record(attributes, raise_error = false)
55
+ def _create_record(attributes, raise_error = false)
55
56
  record = build_record(attributes)
56
57
  yield(record) if block_given?
57
58
  saved = record.save
@@ -13,10 +13,16 @@ module ActiveRecord
13
13
  # 2. To get the type conditions for any STI models in the chain
14
14
  def target_scope
15
15
  scope = super
16
- chain[1..-1].each do |reflection|
16
+ chain.drop(1).each do |reflection|
17
+ relation = reflection.klass.all
18
+
19
+ reflection_scope = reflection.scope
20
+ if reflection_scope && reflection_scope.arity.zero?
21
+ relation.merge!(reflection_scope)
22
+ end
23
+
17
24
  scope.merge!(
18
- reflection.klass.all.with_default_scope.
19
- except(:select, :create_with, :includes, :preload, :joins, :eager_load)
25
+ relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
20
26
  )
21
27
  end
22
28
  scope
@@ -39,12 +45,16 @@ module ActiveRecord
39
45
  def construct_join_attributes(*records)
40
46
  ensure_mutable
41
47
 
42
- join_attributes = {
43
- source_reflection.foreign_key =>
44
- records.map { |record|
45
- record.send(source_reflection.association_primary_key(reflection.klass))
46
- }
47
- }
48
+ if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
49
+ join_attributes = { source_reflection.name => records }
50
+ else
51
+ join_attributes = {
52
+ source_reflection.foreign_key =>
53
+ records.map { |record|
54
+ record.send(source_reflection.association_primary_key(reflection.klass))
55
+ }
56
+ }
57
+ end
48
58
 
49
59
  if options[:source_type]
50
60
  join_attributes[source_reflection.foreign_type] =
@@ -82,6 +92,17 @@ module ActiveRecord
82
92
  raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
83
93
  end
84
94
  end
95
+
96
+ def build_record(attributes)
97
+ inverse = source_reflection.inverse_of
98
+ target = through_association.target
99
+
100
+ if inverse && target && !target.is_a?(Array)
101
+ attributes[inverse.foreign_key] = target.id
102
+ end
103
+
104
+ super(attributes)
105
+ end
85
106
  end
86
107
  end
87
108
  end