activerecord 4.1.16 → 4.2.0.beta1

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2185
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +2 -1
  5. data/lib/active_record/aggregations.rb +12 -8
  6. data/lib/active_record/associations.rb +58 -33
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +53 -21
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  13. data/lib/active_record/associations/builder/has_one.rb +2 -2
  14. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  15. data/lib/active_record/associations/collection_association.rb +32 -44
  16. data/lib/active_record/associations/collection_proxy.rb +1 -10
  17. data/lib/active_record/associations/has_many_association.rb +60 -14
  18. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  19. data/lib/active_record/associations/has_one_association.rb +0 -1
  20. data/lib/active_record/associations/join_dependency.rb +7 -9
  21. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/preloader/association.rb +9 -5
  24. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  25. data/lib/active_record/associations/singular_association.rb +16 -1
  26. data/lib/active_record/associations/through_association.rb +6 -22
  27. data/lib/active_record/attribute.rb +131 -0
  28. data/lib/active_record/attribute_assignment.rb +19 -11
  29. data/lib/active_record/attribute_decorators.rb +66 -0
  30. data/lib/active_record/attribute_methods.rb +53 -90
  31. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  32. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  33. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  34. data/lib/active_record/attribute_methods/read.rb +14 -57
  35. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  36. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  37. data/lib/active_record/attribute_methods/write.rb +8 -23
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attribute_set/builder.rb +32 -0
  40. data/lib/active_record/attributes.rb +122 -0
  41. data/lib/active_record/autosave_association.rb +11 -21
  42. data/lib/active_record/base.rb +9 -19
  43. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  44. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  53. data/lib/active_record/connection_adapters/column.rb +13 -244
  54. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  56. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  57. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  58. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  60. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  90. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  92. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  94. data/lib/active_record/connection_handling.rb +1 -1
  95. data/lib/active_record/core.rb +119 -22
  96. data/lib/active_record/counter_cache.rb +60 -6
  97. data/lib/active_record/enum.rb +9 -10
  98. data/lib/active_record/errors.rb +27 -26
  99. data/lib/active_record/explain.rb +1 -1
  100. data/lib/active_record/fixtures.rb +52 -45
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +33 -8
  103. data/lib/active_record/integration.rb +4 -4
  104. data/lib/active_record/locking/optimistic.rb +34 -16
  105. data/lib/active_record/migration.rb +22 -32
  106. data/lib/active_record/migration/command_recorder.rb +19 -2
  107. data/lib/active_record/migration/join_table.rb +1 -1
  108. data/lib/active_record/model_schema.rb +39 -48
  109. data/lib/active_record/nested_attributes.rb +8 -18
  110. data/lib/active_record/persistence.rb +39 -22
  111. data/lib/active_record/query_cache.rb +3 -3
  112. data/lib/active_record/querying.rb +1 -8
  113. data/lib/active_record/railtie.rb +17 -10
  114. data/lib/active_record/railties/databases.rake +47 -42
  115. data/lib/active_record/readonly_attributes.rb +0 -1
  116. data/lib/active_record/reflection.rb +225 -92
  117. data/lib/active_record/relation.rb +35 -11
  118. data/lib/active_record/relation/batches.rb +0 -2
  119. data/lib/active_record/relation/calculations.rb +28 -32
  120. data/lib/active_record/relation/delegation.rb +1 -1
  121. data/lib/active_record/relation/finder_methods.rb +42 -20
  122. data/lib/active_record/relation/merger.rb +0 -1
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  125. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  126. data/lib/active_record/relation/query_methods.rb +98 -62
  127. data/lib/active_record/relation/spawn_methods.rb +6 -7
  128. data/lib/active_record/result.rb +16 -9
  129. data/lib/active_record/sanitization.rb +8 -1
  130. data/lib/active_record/schema.rb +0 -1
  131. data/lib/active_record/schema_dumper.rb +51 -9
  132. data/lib/active_record/schema_migration.rb +4 -0
  133. data/lib/active_record/scoping/default.rb +5 -4
  134. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  135. data/lib/active_record/statement_cache.rb +79 -5
  136. data/lib/active_record/store.rb +5 -5
  137. data/lib/active_record/tasks/database_tasks.rb +37 -5
  138. data/lib/active_record/tasks/mysql_database_tasks.rb +10 -16
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  140. data/lib/active_record/timestamp.rb +9 -7
  141. data/lib/active_record/transactions.rb +35 -21
  142. data/lib/active_record/type.rb +20 -0
  143. data/lib/active_record/type/binary.rb +40 -0
  144. data/lib/active_record/type/boolean.rb +19 -0
  145. data/lib/active_record/type/date.rb +46 -0
  146. data/lib/active_record/type/date_time.rb +43 -0
  147. data/lib/active_record/type/decimal.rb +40 -0
  148. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  149. data/lib/active_record/type/float.rb +19 -0
  150. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  151. data/lib/active_record/type/integer.rb +23 -0
  152. data/lib/active_record/type/mutable.rb +16 -0
  153. data/lib/active_record/type/numeric.rb +36 -0
  154. data/lib/active_record/type/serialized.rb +51 -0
  155. data/lib/active_record/type/string.rb +36 -0
  156. data/lib/active_record/type/text.rb +11 -0
  157. data/lib/active_record/type/time.rb +26 -0
  158. data/lib/active_record/type/time_value.rb +38 -0
  159. data/lib/active_record/type/type_map.rb +48 -0
  160. data/lib/active_record/type/value.rb +101 -0
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record/validations/uniqueness.rb +9 -23
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Has Many Through Association
4
3
  module Associations
@@ -12,13 +11,14 @@ module ActiveRecord
12
11
  @through_association = nil
13
12
  end
14
13
 
15
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
16
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
17
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
18
- # SELECT query if you use #length.
14
+ # Returns the size of the collection by executing a SELECT COUNT(*) query
15
+ # if the collection hasn't been loaded, and by calling collection.size if
16
+ # it has. If the collection will likely have a size greater than zero,
17
+ # and if fetching the collection will be needed afterwards, one less
18
+ # SELECT query will be generated by using #length instead.
19
19
  def size
20
20
  if has_cached_counter?
21
- owner.send(:read_attribute, cached_counter_attribute_name)
21
+ owner.read_attribute cached_counter_attribute_name(reflection)
22
22
  elsif loaded?
23
23
  target.size
24
24
  else
@@ -62,7 +62,15 @@ module ActiveRecord
62
62
  end
63
63
 
64
64
  save_through_record(record)
65
- update_counter(1)
65
+ if has_cached_counter? && !through_reflection_updates_counter_cache?
66
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
67
+ Automatic updating of counter caches on through associations has been
68
+ deprecated, and will be removed in Rails 5.0. Instead, please set the
69
+ appropriate counter_cache options on the has_many and belongs_to for
70
+ your associations to #{through_reflection.name}.
71
+ MESSAGE
72
+ update_counter_in_database(1)
73
+ end
66
74
  record
67
75
  end
68
76
 
@@ -72,13 +80,11 @@ module ActiveRecord
72
80
  @through_association ||= owner.association(through_reflection.name)
73
81
  end
74
82
 
75
- # We temporarily cache through record that has been build, because if we build a
76
- # through record in build_record and then subsequently call insert_record, then we
77
- # want to use the exact same object.
83
+ # The through record (built with build_record) is temporarily cached
84
+ # so that it may be reused if insert_record is subsequently called.
78
85
  #
79
- # However, after insert_record has been called, we clear the cache entry because
80
- # we want it to be possible to have multiple instances of the same record in an
81
- # association
86
+ # However, after insert_record has been called, the cache is cleared in
87
+ # order to allow multiple instances of the same record in an association.
82
88
  def build_through_record(record)
83
89
  @through_records[record.object_id] ||= begin
84
90
  ensure_mutable
@@ -112,9 +118,9 @@ module ActiveRecord
112
118
 
113
119
  inverse = source_reflection.inverse_of
114
120
  if inverse
115
- if inverse.macro == :has_many
121
+ if inverse.collection?
116
122
  record.send(inverse.name) << build_through_record(record)
117
- elsif inverse.macro == :has_one
123
+ elsif inverse.has_one?
118
124
  record.send("#{inverse.name}=", build_through_record(record))
119
125
  end
120
126
  end
@@ -123,7 +129,7 @@ module ActiveRecord
123
129
  end
124
130
 
125
131
  def target_reflection_has_associated_record?
126
- !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
132
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
127
133
  end
128
134
 
129
135
  def update_through_counter?(method)
@@ -137,13 +143,13 @@ module ActiveRecord
137
143
  end
138
144
  end
139
145
 
146
+ def delete_or_nullify_all_records(method)
147
+ delete_records(load_target, method)
148
+ end
149
+
140
150
  def delete_records(records, method)
141
151
  ensure_not_nested
142
152
 
143
- # This is unoptimised; it will load all the target records
144
- # even when we just want to delete everything.
145
- records = load_target if records == :all
146
-
147
153
  scope = through_association.scope
148
154
  scope.where! construct_join_attributes(*records)
149
155
 
@@ -177,7 +183,7 @@ module ActiveRecord
177
183
  klass.decrement_counter counter, records.map(&:id)
178
184
  end
179
185
 
180
- if through_reflection.macro == :has_many && update_through_counter?(method)
186
+ if through_reflection.collection? && update_through_counter?(method)
181
187
  update_counter(-count, through_reflection)
182
188
  end
183
189
 
@@ -198,7 +204,7 @@ module ActiveRecord
198
204
  records.each do |record|
199
205
  through_records = through_records_for(record)
200
206
 
201
- if through_reflection.macro == :has_many
207
+ if through_reflection.collection?
202
208
  through_records.each { |r| through_association.target.delete(r) }
203
209
  else
204
210
  if through_records.include?(through_association.target)
@@ -212,13 +218,18 @@ module ActiveRecord
212
218
 
213
219
  def find_target
214
220
  return [] unless target_reflection_has_associated_record?
215
- scope.to_a
221
+ get_records
216
222
  end
217
223
 
218
224
  # NOTE - not sure that we can actually cope with inverses here
219
225
  def invertible_for?(record)
220
226
  false
221
227
  end
228
+
229
+ def through_reflection_updates_counter_cache?
230
+ counter_name = cached_counter_attribute_name
231
+ inverse_updates_counter_named?(counter_name, through_reflection)
232
+ end
222
233
  end
223
234
  end
224
235
  end
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Belongs To Has One Association
4
3
  module Associations
@@ -131,7 +131,6 @@ module ActiveRecord
131
131
 
132
132
  def instantiate(result_set, aliases)
133
133
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
134
- type_caster = result_set.column_type primary_key
135
134
 
136
135
  seen = Hash.new { |h,parent_klass|
137
136
  h[parent_klass] = Hash.new { |i,parent_id|
@@ -144,9 +143,7 @@ module ActiveRecord
144
143
  column_aliases = aliases.column_aliases join_root
145
144
 
146
145
  result_set.each { |row_hash|
147
- parent_key = primary_key ? row_hash[primary_key] : row_hash
148
- primary_id = type_caster.type_cast parent_key
149
- parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
146
+ parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
150
147
  construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
151
148
  }
152
149
 
@@ -165,17 +162,17 @@ module ActiveRecord
165
162
  def make_outer_joins(parent, child)
166
163
  tables = table_aliases_for(parent, child)
167
164
  join_type = Arel::Nodes::OuterJoin
168
- joins = make_constraints parent, child, tables, join_type
165
+ info = make_constraints parent, child, tables, join_type
169
166
 
170
- joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
167
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
171
168
  end
172
169
 
173
170
  def make_inner_joins(parent, child)
174
171
  tables = child.tables
175
172
  join_type = Arel::Nodes::InnerJoin
176
- joins = make_constraints parent, child, tables, join_type
173
+ info = make_constraints parent, child, tables, join_type
177
174
 
178
- joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
175
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
179
176
  end
180
177
 
181
178
  def table_aliases_for(parent, node)
@@ -216,8 +213,9 @@ module ActiveRecord
216
213
  associations.map do |name, right|
217
214
  reflection = find_reflection base_klass, name
218
215
  reflection.check_validity!
216
+ reflection.check_eager_loadable!
219
217
 
220
- if reflection.options[:polymorphic]
218
+ if reflection.polymorphic?
221
219
  raise EagerLoadPolymorphicError.new(reflection)
222
220
  end
223
221
 
@@ -21,8 +21,11 @@ module ActiveRecord
21
21
  super && reflection == other.reflection
22
22
  end
23
23
 
24
+ JoinInformation = Struct.new :joins, :binds
25
+
24
26
  def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
25
27
  joins = []
28
+ bind_values = []
26
29
  tables = tables.reverse
27
30
 
28
31
  scope_chain_index = 0
@@ -34,14 +37,9 @@ module ActiveRecord
34
37
  table = tables.shift
35
38
  klass = reflection.klass
36
39
 
37
- case reflection.source_macro
38
- when :belongs_to
39
- key = reflection.association_primary_key
40
- foreign_key = reflection.foreign_key
41
- else
42
- key = reflection.foreign_key
43
- foreign_key = reflection.active_record_primary_key
44
- end
40
+ join_keys = reflection.join_keys(klass)
41
+ key = join_keys.key
42
+ foreign_key = join_keys.foreign_key
45
43
 
46
44
  constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
47
45
 
@@ -60,21 +58,27 @@ module ActiveRecord
60
58
  left.merge right
61
59
  end
62
60
 
63
- if reflection.type
64
- constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
65
- end
66
-
67
61
  if rel && !rel.arel.constraints.empty?
62
+ bind_values.concat rel.bind_values
68
63
  constraint = constraint.and rel.arel.constraints
69
64
  end
70
65
 
66
+ if reflection.type
67
+ value = foreign_klass.base_class.name
68
+ column = klass.columns_hash[column.to_s]
69
+
70
+ substitute = klass.connection.substitute_at(column, bind_values.length)
71
+ bind_values.push [column, value]
72
+ constraint = constraint.and table[reflection.type].eq substitute
73
+ end
74
+
71
75
  joins << table.create_join(table, table.create_on(constraint), join_type)
72
76
 
73
77
  # The current table in this iteration becomes the foreign table in the next
74
78
  foreign_table, foreign_klass = table, klass
75
79
  end
76
80
 
77
- joins
81
+ JoinInformation.new joins, bind_values
78
82
  end
79
83
 
80
84
  # Builds equality condition.
@@ -86,7 +90,7 @@ module ActiveRecord
86
90
  # end
87
91
  #
88
92
  # If I execute `Physician.joins(:appointments).to_a` then
89
- # reflection # => #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
93
+ # klass # => Physician
90
94
  # table # => #<Arel::Table @name="appointments" ...>
91
95
  # key # => physician_id
92
96
  # foreign_table # => #<Arel::Table @name="physicians" ...>
@@ -80,7 +80,7 @@ module ActiveRecord
80
80
  # { author: :avatar }
81
81
  # [ :books, { author: :avatar } ]
82
82
 
83
- NULL_RELATION = Struct.new(:values).new({})
83
+ NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
84
84
 
85
85
  def preload(records, associations, preload_scope = nil)
86
86
  records = Array.wrap(records).compact.uniq
@@ -169,7 +169,6 @@ module ActiveRecord
169
169
  class NullPreloader
170
170
  def self.new(klass, owners, reflection, preload_scope); self; end
171
171
  def self.run(preloader); end
172
- def self.preloaded_records; []; end
173
172
  end
174
173
 
175
174
  def preloader_for(reflection, owners, rhs_klass)
@@ -178,6 +177,7 @@ module ActiveRecord
178
177
  if owners.first.association(reflection.name).loaded?
179
178
  return AlreadyLoaded
180
179
  end
180
+ reflection.check_preloadable!
181
181
 
182
182
  case reflection.macro
183
183
  when :has_many
@@ -104,13 +104,11 @@ module ActiveRecord
104
104
  end
105
105
 
106
106
  def association_key_type
107
- column = @klass.column_types[association_key_name.to_s]
108
- column && column.type
107
+ @klass.type_for_attribute(association_key_name.to_s).type
109
108
  end
110
109
 
111
110
  def owner_key_type
112
- column = @model.column_types[owner_key_name.to_s]
113
- column && column.type
111
+ @model.type_for_attribute(owner_key_name.to_s).type
114
112
  end
115
113
 
116
114
  def load_slices(slices)
@@ -134,10 +132,13 @@ module ActiveRecord
134
132
  scope = klass.unscoped
135
133
 
136
134
  values = reflection_scope.values
135
+ reflection_binds = reflection_scope.bind_values
137
136
  preload_values = preload_scope.values
137
+ preload_binds = preload_scope.bind_values
138
138
 
139
139
  scope.where_values = Array(values[:where]) + Array(preload_values[:where])
140
140
  scope.references_values = Array(values[:references]) + Array(preload_values[:references])
141
+ scope.bind_values = (reflection_binds + preload_binds)
141
142
 
142
143
  scope._select! preload_values[:select] || values[:select] || table[Arel.star]
143
144
  scope.includes! preload_values[:includes] || values[:includes]
@@ -150,11 +151,14 @@ module ActiveRecord
150
151
  end
151
152
  end
152
153
 
154
+ if preload_values[:readonly] || values[:readonly]
155
+ scope.readonly!
156
+ end
157
+
153
158
  if options[:as]
154
159
  scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
155
160
  end
156
161
 
157
- scope.unscope_values = Array(values[:unscope])
158
162
  klass.default_scoped.merge(scope)
159
163
  end
160
164
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
 
24
24
  reset_association owners, through_reflection.name
25
25
 
26
- middle_records = through_records.map { |(_,rec)| rec }.flatten
26
+ middle_records = through_records.flat_map { |(_,rec)| rec }
27
27
 
28
28
  preloaders = preloader.preload(middle_records,
29
29
  source_reflection.name,
@@ -63,7 +63,7 @@ module ActiveRecord
63
63
  should_reset = (through_scope != through_reflection.klass.unscoped) ||
64
64
  (reflection.options[:source_type] && through_reflection.collection?)
65
65
 
66
- # Dont cache the association - we would only be caching a subset
66
+ # Don't cache the association - we would only be caching a subset
67
67
  if should_reset
68
68
  owners.each { |owner|
69
69
  owner.association(association_name).reset
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  end
85
85
 
86
86
  scope.references! reflection_scope.values[:references]
87
- scope.order! reflection_scope.values[:order] if scope.eager_loading?
87
+ scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
88
88
  end
89
89
 
90
90
  scope
@@ -38,8 +38,23 @@ module ActiveRecord
38
38
  scope.scope_for_create.stringify_keys.except(klass.primary_key)
39
39
  end
40
40
 
41
+ def get_records
42
+ return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?)
43
+
44
+ conn = klass.connection
45
+ sc = reflection.association_scope_cache(conn, owner) do
46
+ StatementCache.create(conn) { |params|
47
+ as = AssociationScope.create { params.bind }
48
+ target_scope.merge(as.scope(self, conn)).limit(1)
49
+ }
50
+ end
51
+
52
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
53
+ sc.execute binds, klass, klass.connection
54
+ end
55
+
41
56
  def find_target
42
- if record = scope.take
57
+ if record = get_records.first
43
58
  set_inverse_instance record
44
59
  end
45
60
  end
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  module Associations
4
4
  module ThroughAssociation #:nodoc:
5
5
 
6
- delegate :source_reflection, :through_reflection, :chain, :to => :reflection
6
+ delegate :source_reflection, :through_reflection, :to => :reflection
7
7
 
8
8
  protected
9
9
 
@@ -13,13 +13,9 @@ 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.drop(1).each do |reflection|
16
+ reflection.chain.drop(1).each do |reflection|
17
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
18
+ relation.merge!(reflection.scope) if reflection.scope
23
19
 
24
20
  scope.merge!(
25
21
  relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
@@ -71,18 +67,17 @@ module ActiveRecord
71
67
  # Note: this does not capture all cases, for example it would be crazy to try to
72
68
  # properly support stale-checking for nested associations.
73
69
  def stale_state
74
- if through_reflection.macro == :belongs_to
70
+ if through_reflection.belongs_to?
75
71
  owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
76
72
  end
77
73
  end
78
74
 
79
75
  def foreign_key_present?
80
- through_reflection.macro == :belongs_to &&
81
- !owner[through_reflection.foreign_key].nil?
76
+ through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
82
77
  end
83
78
 
84
79
  def ensure_mutable
85
- if source_reflection.macro != :belongs_to
80
+ unless source_reflection.belongs_to?
86
81
  raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
87
82
  end
88
83
  end
@@ -92,17 +87,6 @@ module ActiveRecord
92
87
  raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
93
88
  end
94
89
  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
106
90
  end
107
91
  end
108
92
  end
@@ -0,0 +1,131 @@
1
+ module ActiveRecord
2
+ class Attribute # :nodoc:
3
+ class << self
4
+ def from_database(name, value, type)
5
+ FromDatabase.new(name, value, type)
6
+ end
7
+
8
+ def from_user(name, value, type)
9
+ FromUser.new(name, value, type)
10
+ end
11
+
12
+ def null(name)
13
+ Null.new(name)
14
+ end
15
+
16
+ def uninitialized(name, type)
17
+ Uninitialized.new(name, type)
18
+ end
19
+ end
20
+
21
+ attr_reader :name, :value_before_type_cast, :type
22
+
23
+ # This method should not be called directly.
24
+ # Use #from_database or #from_user
25
+ def initialize(name, value_before_type_cast, type)
26
+ @name = name
27
+ @value_before_type_cast = value_before_type_cast
28
+ @type = type
29
+ end
30
+
31
+ def value
32
+ # `defined?` is cheaper than `||=` when we get back falsy values
33
+ @value = original_value unless defined?(@value)
34
+ @value
35
+ end
36
+
37
+ def original_value
38
+ type_cast(value_before_type_cast)
39
+ end
40
+
41
+ def value_for_database
42
+ type.type_cast_for_database(value)
43
+ end
44
+
45
+ def changed_from?(old_value)
46
+ type.changed?(old_value, value, value_before_type_cast)
47
+ end
48
+
49
+ def changed_in_place_from?(old_value)
50
+ type.changed_in_place?(old_value, value)
51
+ end
52
+
53
+ def with_value_from_user(value)
54
+ self.class.from_user(name, value, type)
55
+ end
56
+
57
+ def with_value_from_database(value)
58
+ self.class.from_database(name, value, type)
59
+ end
60
+
61
+ def type_cast(*)
62
+ raise NotImplementedError
63
+ end
64
+
65
+ def initialized?
66
+ true
67
+ end
68
+
69
+ def ==(other)
70
+ self.class == other.class &&
71
+ name == other.name &&
72
+ value_before_type_cast == other.value_before_type_cast &&
73
+ type == other.type
74
+ end
75
+
76
+ protected
77
+
78
+ def initialize_dup(other)
79
+ if defined?(@value) && @value.duplicable?
80
+ @value = @value.dup
81
+ end
82
+ end
83
+
84
+ class FromDatabase < Attribute # :nodoc:
85
+ def type_cast(value)
86
+ type.type_cast_from_database(value)
87
+ end
88
+ end
89
+
90
+ class FromUser < Attribute # :nodoc:
91
+ def type_cast(value)
92
+ type.type_cast_from_user(value)
93
+ end
94
+ end
95
+
96
+ class Null < Attribute # :nodoc:
97
+ def initialize(name)
98
+ super(name, nil, Type::Value.new)
99
+ end
100
+
101
+ def value
102
+ nil
103
+ end
104
+
105
+ def with_value_from_database(value)
106
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
107
+ end
108
+ alias_method :with_value_from_user, :with_value_from_database
109
+ end
110
+
111
+ class Uninitialized < Attribute # :nodoc:
112
+ def initialize(name, type)
113
+ super(name, nil, type)
114
+ end
115
+
116
+ def value
117
+ if block_given?
118
+ yield name
119
+ end
120
+ end
121
+
122
+ def value_for_database
123
+ end
124
+
125
+ def initialized?
126
+ false
127
+ end
128
+ end
129
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
130
+ end
131
+ end