activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  attr_reader :join_table
6
6
 
7
7
  def initialize(owner, reflection)
8
- @join_table = Arel::Table.new(reflection.options[:join_table])
8
+ @join_table = Arel::Table.new(reflection.join_table)
9
9
  super
10
10
  end
11
11
 
@@ -26,32 +26,34 @@ module ActiveRecord
26
26
  join_table[reflection.association_foreign_key] => record.id
27
27
  )
28
28
 
29
- owner.connection.insert stmt
29
+ owner.class.connection.insert stmt
30
30
  end
31
31
 
32
32
  record
33
33
  end
34
34
 
35
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
36
- # SQL-only implementation.
37
- alias delete_all_on_destroy delete_all
38
-
39
35
  private
40
36
 
41
37
  def count_records
42
- load_target.size
38
+ load_target.reject { |r| r.new_record? }.size
43
39
  end
44
40
 
45
41
  def delete_records(records, method)
46
42
  if sql = options[:delete_sql]
47
43
  records = load_target if records == :all
48
- records.each { |record| owner.connection.delete(interpolate(sql, record)) }
44
+ records.each { |record| owner.class.connection.delete(interpolate(sql, record)) }
49
45
  else
50
- relation = join_table
51
- stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
52
- and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
53
- ).compile_delete
54
- owner.connection.delete stmt
46
+ relation = join_table
47
+ condition = relation[reflection.foreign_key].eq(owner.id)
48
+
49
+ unless records == :all
50
+ condition = condition.and(
51
+ relation[reflection.association_foreign_key]
52
+ .in(records.map { |x| x.id }.compact)
53
+ )
54
+ end
55
+
56
+ owner.class.connection.delete(relation.where(condition).compile_delete)
55
57
  end
56
58
  end
57
59
 
@@ -7,6 +7,29 @@ module ActiveRecord
7
7
  # is provided by its child HasManyThroughAssociation.
8
8
  class HasManyAssociation < CollectionAssociation #:nodoc:
9
9
 
10
+ def handle_dependency
11
+ case options[:dependent]
12
+ when :restrict, :restrict_with_exception
13
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
14
+
15
+ when :restrict_with_error
16
+ unless empty?
17
+ record = klass.human_attribute_name(reflection.name).downcase
18
+ owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
19
+ false
20
+ end
21
+
22
+ else
23
+ if options[:dependent] == :destroy
24
+ # No point in executing the counter update since we're going to destroy the parent anyway
25
+ load_target.each { |t| t.destroyed_by_association = reflection }
26
+ destroy_all
27
+ else
28
+ delete_all
29
+ end
30
+ end
31
+ end
32
+
10
33
  def insert_record(record, validate = true, raise = false)
11
34
  set_owner_attributes(record)
12
35
  set_inverse_instance(record)
@@ -39,7 +62,7 @@ module ActiveRecord
39
62
  elsif options[:counter_sql] || options[:finder_sql]
40
63
  reflection.klass.count_by_sql(custom_counter_sql)
41
64
  else
42
- scoped.count
65
+ scope.count
43
66
  end
44
67
 
45
68
  # If there's nothing in the database and @target has no new records
@@ -47,18 +70,18 @@ module ActiveRecord
47
70
  # documented side-effect of the method that may avoid an extra SELECT.
48
71
  @target ||= [] and loaded! if count == 0
49
72
 
50
- [options[:limit], count].compact.min
73
+ [association_scope.limit_value, count].compact.min
51
74
  end
52
75
 
53
- def has_cached_counter?(reflection = self.reflection)
76
+ def has_cached_counter?(reflection = reflection())
54
77
  owner.attribute_present?(cached_counter_attribute_name(reflection))
55
78
  end
56
79
 
57
- def cached_counter_attribute_name(reflection = self.reflection)
58
- "#{reflection.name}_count"
80
+ def cached_counter_attribute_name(reflection = reflection())
81
+ options[:counter_cache] || "#{reflection.name}_count"
59
82
  end
60
83
 
61
- def update_counter(difference, reflection = self.reflection)
84
+ def update_counter(difference, reflection = reflection())
62
85
  if has_cached_counter?(reflection)
63
86
  counter = cached_counter_attribute_name(reflection)
64
87
  owner.class.update_counters(owner.id, counter => difference)
@@ -77,7 +100,7 @@ module ActiveRecord
77
100
  # it will be decremented twice.
78
101
  #
79
102
  # Hence this method.
80
- def inverse_updates_counter_cache?(reflection = self.reflection)
103
+ def inverse_updates_counter_cache?(reflection = reflection())
81
104
  counter_name = cached_counter_attribute_name(reflection)
82
105
  reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
83
106
  inverse_reflection.counter_cache_column == counter_name
@@ -90,7 +113,11 @@ module ActiveRecord
90
113
  records.each { |r| r.destroy }
91
114
  update_counter(-records.length) unless inverse_updates_counter_cache?
92
115
  else
93
- scope = self.scoped.where(reflection.klass.primary_key => records)
116
+ if records == :all
117
+ scope = self.scope
118
+ else
119
+ scope = self.scope.where(reflection.klass.primary_key => records)
120
+ end
94
121
 
95
122
  if method == :delete_all
96
123
  update_counter(-scope.delete_all)
@@ -1,4 +1,3 @@
1
- require 'active_support/core_ext/object/blank'
2
1
 
3
2
  module ActiveRecord
4
3
  # = Active Record Has Many Through Association
@@ -30,7 +29,7 @@ module ActiveRecord
30
29
  def concat(*records)
31
30
  unless owner.new_record?
32
31
  records.flatten.each do |record|
33
- raise_on_type_mismatch(record)
32
+ raise_on_type_mismatch!(record)
34
33
  record.save! if record.new_record?
35
34
  end
36
35
  end
@@ -38,6 +37,20 @@ module ActiveRecord
38
37
  super
39
38
  end
40
39
 
40
+ def concat_records(records)
41
+ ensure_not_nested
42
+
43
+ records = super
44
+
45
+ if owner.new_record? && records
46
+ records.flatten.each do |record|
47
+ build_through_record(record)
48
+ end
49
+ end
50
+
51
+ records
52
+ end
53
+
41
54
  def insert_record(record, validate = true, raise = false)
42
55
  ensure_not_nested
43
56
 
@@ -54,10 +67,6 @@ module ActiveRecord
54
67
  record
55
68
  end
56
69
 
57
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
58
- # SQL-only implementation.
59
- alias delete_all_on_destroy delete_all
60
-
61
70
  private
62
71
 
63
72
  def through_association
@@ -75,22 +84,32 @@ module ActiveRecord
75
84
  @through_records[record.object_id] ||= begin
76
85
  ensure_mutable
77
86
 
78
- through_record = through_association.build
87
+ through_record = through_association.build(*options_for_through_record)
79
88
  through_record.send("#{source_reflection.name}=", record)
80
89
  through_record
81
90
  end
82
91
  end
83
92
 
93
+ def options_for_through_record
94
+ [through_scope_attributes]
95
+ end
96
+
97
+ def through_scope_attributes
98
+ scope.where_values_hash(through_association.reflection.name.to_s).
99
+ except!(through_association.reflection.foreign_key,
100
+ through_association.reflection.klass.inheritance_column.to_sym)
101
+ end
102
+
84
103
  def save_through_record(record)
85
104
  build_through_record(record).save!
86
105
  ensure
87
106
  @through_records.delete(record.object_id)
88
107
  end
89
108
 
90
- def build_record(attributes, options = {})
109
+ def build_record(attributes)
91
110
  ensure_not_nested
92
111
 
93
- record = super(attributes, options)
112
+ record = super(attributes)
94
113
 
95
114
  inverse = source_reflection.inverse_of
96
115
  if inverse
@@ -105,11 +124,7 @@ module ActiveRecord
105
124
  end
106
125
 
107
126
  def target_reflection_has_associated_record?
108
- if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?
109
- false
110
- else
111
- true
112
- end
127
+ !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
113
128
  end
114
129
 
115
130
  def update_through_counter?(method)
@@ -126,7 +141,12 @@ module ActiveRecord
126
141
  def delete_records(records, method)
127
142
  ensure_not_nested
128
143
 
129
- scope = through_association.scoped.where(construct_join_attributes(*records))
144
+ # This is unoptimised; it will load all the target records
145
+ # even when we just want to delete everything.
146
+ records = load_target if records == :all
147
+
148
+ scope = through_association.scope
149
+ scope.where! construct_join_attributes(*records)
130
150
 
131
151
  case method
132
152
  when :destroy
@@ -139,7 +159,7 @@ module ActiveRecord
139
159
 
140
160
  delete_through_records(records)
141
161
 
142
- if source_reflection.options[:counter_cache]
162
+ if source_reflection.options[:counter_cache] && method != :destroy
143
163
  counter = source_reflection.counter_cache_column
144
164
  klass.decrement_counter counter, records.map(&:id)
145
165
  end
@@ -175,7 +195,7 @@ module ActiveRecord
175
195
 
176
196
  def find_target
177
197
  return [] unless target_reflection_has_associated_record?
178
- scoped.all
198
+ scope.to_a
179
199
  end
180
200
 
181
201
  # NOTE - not sure that we can actually cope with inverses here
@@ -1,24 +1,44 @@
1
- require 'active_support/core_ext/object/inclusion'
2
1
 
3
2
  module ActiveRecord
4
3
  # = Active Record Belongs To Has One Association
5
4
  module Associations
6
5
  class HasOneAssociation < SingularAssociation #:nodoc:
6
+
7
+ def handle_dependency
8
+ case options[:dependent]
9
+ when :restrict, :restrict_with_exception
10
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
11
+
12
+ when :restrict_with_error
13
+ if load_target
14
+ record = klass.human_attribute_name(reflection.name).downcase
15
+ owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
16
+ false
17
+ end
18
+
19
+ else
20
+ delete
21
+ end
22
+ end
23
+
7
24
  def replace(record, save = true)
8
- raise_on_type_mismatch(record) if record
25
+ raise_on_type_mismatch!(record) if record
9
26
  load_target
10
27
 
11
- # If target and record are nil, or target is equal to record,
12
- # we don't need to have transaction.
13
- if (target || record) && target != record
28
+ return self.target if !(target || record)
29
+
30
+ assigning_another_record = target != record
31
+ if assigning_another_record || record.changed?
32
+ save &&= owner.persisted?
33
+
14
34
  transaction_if(save) do
15
- remove_target!(options[:dependent]) if target && !target.destroyed?
16
-
35
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
36
+
17
37
  if record
18
38
  set_owner_attributes(record)
19
39
  set_inverse_instance(record)
20
-
21
- if owner.persisted? && save && !record.save
40
+
41
+ if save && !record.save
22
42
  nullify_owner_attributes(record)
23
43
  set_owner_attributes(target) if target
24
44
  raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
@@ -38,7 +58,7 @@ module ActiveRecord
38
58
  when :destroy
39
59
  target.destroy
40
60
  when :nullify
41
- target.update_attribute(reflection.foreign_key, nil)
61
+ target.update_columns(reflection.foreign_key => nil)
42
62
  end
43
63
  end
44
64
  end
@@ -54,16 +74,19 @@ module ActiveRecord
54
74
  end
55
75
 
56
76
  def remove_target!(method)
57
- if method.in?([:delete, :destroy])
58
- target.send(method)
59
- else
60
- nullify_owner_attributes(target)
77
+ case method
78
+ when :delete
79
+ target.delete
80
+ when :destroy
81
+ target.destroy
82
+ else
83
+ nullify_owner_attributes(target)
61
84
 
62
- if target.persisted? && owner.persisted? && !target.save
63
- set_owner_attributes(target)
64
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
65
- "The record failed to save when after its foreign key was set to nil."
66
- end
85
+ if target.persisted? && owner.persisted? && !target.save
86
+ set_owner_attributes(target)
87
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
88
+ "The record failed to save after its foreign key was set to nil."
89
+ end
67
90
  end
68
91
  end
69
92
 
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  attributes = construct_join_attributes(record)
24
24
 
25
25
  if through_record
26
- through_record.update_attributes(attributes)
26
+ through_record.update(attributes)
27
27
  elsif owner.new_record?
28
28
  through_proxy.build(attributes)
29
29
  else
@@ -57,17 +57,17 @@ module ActiveRecord
57
57
  other_join_dependency.join_parts.detect do |join_part|
58
58
  case parent
59
59
  when JoinBase
60
- parent.active_record == join_part.active_record
60
+ parent.base_klass == join_part.base_klass
61
61
  else
62
62
  parent == join_part
63
63
  end
64
64
  end
65
65
  end
66
66
 
67
- def join_to(relation)
67
+ def join_to(manager)
68
68
  tables = @tables.dup
69
69
  foreign_table = parent_table
70
- foreign_klass = parent.active_record
70
+ foreign_klass = parent.base_klass
71
71
 
72
72
  # The chain starts with the target table, but we want to end with it here (makes
73
73
  # more sense in this context), so we reverse
@@ -80,7 +80,7 @@ module ActiveRecord
80
80
  foreign_key = reflection.foreign_key
81
81
  when :has_and_belongs_to_many
82
82
  # Join the join table first...
83
- relation.from(join(
83
+ manager.from(join(
84
84
  table,
85
85
  table[reflection.foreign_key].
86
86
  eq(foreign_table[reflection.active_record_primary_key])
@@ -97,22 +97,49 @@ module ActiveRecord
97
97
 
98
98
  constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
99
99
 
100
- conditions = self.conditions[i].dup
101
- conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
100
+ scope_chain_items = scope_chain[i]
102
101
 
103
- unless conditions.empty?
104
- constraint = constraint.and(sanitize(conditions, table))
102
+ if reflection.type
103
+ scope_chain_items += [
104
+ ActiveRecord::Relation.new(reflection.klass, table)
105
+ .where(reflection.type => foreign_klass.base_class.name)
106
+ ]
105
107
  end
106
108
 
107
- relation.from(join(table, constraint))
109
+ scope_chain_items += [reflection.klass.send(:build_default_scope, ActiveRecord::Relation.new(reflection.klass, table))].compact
110
+
111
+ scope_chain_items.each do |item|
112
+ unless item.is_a?(Relation)
113
+ item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
114
+ end
115
+
116
+ constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
117
+ end
118
+
119
+ manager.from(join(table, constraint))
108
120
 
109
121
  # The current table in this iteration becomes the foreign table in the next
110
122
  foreign_table, foreign_klass = table, reflection.klass
111
123
  end
112
124
 
113
- relation
125
+ manager
114
126
  end
115
127
 
128
+ # Builds equality condition.
129
+ #
130
+ # Example:
131
+ #
132
+ # class Physician < ActiveRecord::Base
133
+ # has_many :appointments
134
+ # end
135
+ #
136
+ # If I execute `Physician.joins(:appointments).to_a` then
137
+ # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
138
+ # table #=> #<Arel::Table @name="appointments" ...>
139
+ # key #=> physician_id
140
+ # foreign_table #=> #<Arel::Table @name="physicians" ...>
141
+ # foreign_key #=> id
142
+ #
116
143
  def build_constraint(reflection, table, key, foreign_table, foreign_key)
117
144
  constraint = table[key].eq(foreign_table[foreign_key])
118
145
 
@@ -139,18 +166,8 @@ module ActiveRecord
139
166
  table.table_alias || table.name
140
167
  end
141
168
 
142
- def conditions
143
- @conditions ||= reflection.conditions.reverse
144
- end
145
-
146
- private
147
-
148
- def interpolate(conditions)
149
- if conditions.respond_to?(:to_proc)
150
- instance_eval(&conditions)
151
- else
152
- conditions
153
- end
169
+ def scope_chain
170
+ @scope_chain ||= reflection.scope_chain.reverse
154
171
  end
155
172
 
156
173
  end
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  class JoinBase < JoinPart # :nodoc:
5
5
  def ==(other)
6
6
  other.class == self.class &&
7
- other.active_record == active_record
7
+ other.base_klass == base_klass
8
8
  end
9
9
 
10
10
  def aliased_prefix
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  end
17
17
 
18
18
  def aliased_table_name
19
- active_record.table_name
19
+ base_klass.table_name
20
20
  end
21
21
  end
22
22
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class JoinDependency # :nodoc:
4
- # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
4
+ # A JoinPart represents a part of a JoinDependency. It is inherited
5
5
  # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
6
6
  # everything else is being joined onto. A JoinAssociation represents an association which
7
7
  # is joining to the base. A JoinAssociation may result in more than one actual join
@@ -11,12 +11,12 @@ module ActiveRecord
11
11
  # The Active Record class which this join part is associated 'about'; for a JoinBase
12
12
  # this is the actual base model, for a JoinAssociation this is the target model of the
13
13
  # association.
14
- attr_reader :active_record
14
+ attr_reader :base_klass
15
15
 
16
- delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
16
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
17
17
 
18
- def initialize(active_record)
19
- @active_record = active_record
18
+ def initialize(base_klass)
19
+ @base_klass = base_klass
20
20
  @cached_record = {}
21
21
  @column_names_with_alias = nil
22
22
  end
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  unless @column_names_with_alias
55
55
  @column_names_with_alias = []
56
56
 
57
- ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
57
+ ([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i|
58
58
  @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
59
59
  end
60
60
  end
@@ -62,7 +62,20 @@ module ActiveRecord
62
62
  end
63
63
 
64
64
  def extract_record(row)
65
- Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
65
+ # This code is performance critical as it is called per row.
66
+ # see: https://github.com/rails/rails/pull/12185
67
+ hash = {}
68
+
69
+ index = 0
70
+ length = column_names_with_alias.length
71
+
72
+ while index < length
73
+ column_name, alias_name = column_names_with_alias[index]
74
+ hash[column_name] = row[alias_name]
75
+ index += 1
76
+ end
77
+
78
+ hash
66
79
  end
67
80
 
68
81
  def record_id(row)
@@ -70,7 +83,7 @@ module ActiveRecord
70
83
  end
71
84
 
72
85
  def instantiate(row)
73
- @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
86
+ @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
74
87
  end
75
88
  end
76
89
  end
@@ -5,10 +5,31 @@ module ActiveRecord
5
5
  autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
6
6
  autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
7
7
 
8
- attr_reader :join_parts, :reflections, :alias_tracker, :active_record
9
-
8
+ attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
9
+
10
+ # base is the base class on which operation is taking place.
11
+ # associations is the list of associations which are joined using hash, symbol or array.
12
+ # joins is the list of all string join commnads and arel nodes.
13
+ #
14
+ # Example :
15
+ #
16
+ # class Physician < ActiveRecord::Base
17
+ # has_many :appointments
18
+ # has_many :patients, through: :appointments
19
+ # end
20
+ #
21
+ # If I execute `@physician.patients.to_a` then
22
+ # base #=> Physician
23
+ # associations #=> []
24
+ # joins #=> [#<Arel::Nodes::InnerJoin: ...]
25
+ #
26
+ # However if I execute `Physician.joins(:appointments).to_a` then
27
+ # base #=> Physician
28
+ # associations #=> [:appointments]
29
+ # joins #=> []
30
+ #
10
31
  def initialize(base, associations, joins)
11
- @active_record = base
32
+ @base_klass = base
12
33
  @table_joins = joins
13
34
  @join_parts = [JoinBase.new(base)]
14
35
  @associations = {}
@@ -54,10 +75,12 @@ module ActiveRecord
54
75
  parent
55
76
  }.uniq
56
77
 
57
- remove_duplicate_results!(active_record, records, @associations)
78
+ remove_duplicate_results!(base_klass, records, @associations)
58
79
  records
59
80
  end
60
81
 
82
+ protected
83
+
61
84
  def remove_duplicate_results!(base, records, associations)
62
85
  case associations
63
86
  when Symbol, String
@@ -68,7 +91,7 @@ module ActiveRecord
68
91
  remove_duplicate_results!(base, records, association)
69
92
  end
70
93
  when Hash
71
- associations.keys.each do |name|
94
+ associations.each_key do |name|
72
95
  reflection = base.reflections[name]
73
96
  remove_uniq_by_reflection(reflection, records)
74
97
 
@@ -88,8 +111,6 @@ module ActiveRecord
88
111
  end
89
112
  end
90
113
 
91
- protected
92
-
93
114
  def cache_joined_association(association)
94
115
  associations = []
95
116
  parent = association.parent
@@ -108,8 +129,8 @@ module ActiveRecord
108
129
  parent ||= join_parts.last
109
130
  case associations
110
131
  when Symbol, String
111
- reflection = parent.reflections[associations.to_s.intern] or
112
- raise ConfigurationError, "Association named '#{ associations }' was not found on #{parent.active_record.name}; perhaps you misspelled it?"
132
+ reflection = parent.reflections[associations.intern] or
133
+ raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
113
134
  unless join_association = find_join_association(reflection, parent)
114
135
  @reflections << reflection
115
136
  join_association = build_join_association(reflection, parent)
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
 
20
20
  if reflection.source_macro == :has_and_belongs_to_many
21
21
  tables << alias_tracker.aliased_table_for(
22
- (reflection.source_reflection || reflection).options[:join_table],
22
+ (reflection.source_reflection || reflection).join_table,
23
23
  table_alias_for(reflection, true)
24
24
  )
25
25
  end
@@ -40,16 +40,6 @@ module ActiveRecord
40
40
  def join(table, constraint)
41
41
  table.create_join(table, table.create_on(constraint), join_type)
42
42
  end
43
-
44
- def sanitize(conditions, table)
45
- conditions = conditions.map do |condition|
46
- condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
47
- condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
48
- condition
49
- end
50
-
51
- conditions.length == 1 ? conditions.first : Arel::Nodes::And.new(conditions)
52
- end
53
43
  end
54
44
  end
55
45
  end