activerecord 4.1.15 → 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 -2176
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/association_scope.rb +53 -21
  7. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  8. data/lib/active_record/associations/builder/association.rb +16 -5
  9. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  10. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  11. data/lib/active_record/associations/builder/has_one.rb +2 -2
  12. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  13. data/lib/active_record/associations/collection_association.rb +32 -44
  14. data/lib/active_record/associations/collection_proxy.rb +1 -10
  15. data/lib/active_record/associations/has_many_association.rb +60 -14
  16. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  17. data/lib/active_record/associations/has_one_association.rb +0 -1
  18. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  19. data/lib/active_record/associations/join_dependency.rb +7 -9
  20. data/lib/active_record/associations/preloader/association.rb +9 -5
  21. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/singular_association.rb +16 -1
  24. data/lib/active_record/associations/through_association.rb +6 -22
  25. data/lib/active_record/associations.rb +58 -33
  26. data/lib/active_record/attribute.rb +131 -0
  27. data/lib/active_record/attribute_assignment.rb +19 -11
  28. data/lib/active_record/attribute_decorators.rb +66 -0
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  30. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  31. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  32. data/lib/active_record/attribute_methods/read.rb +14 -57
  33. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  35. data/lib/active_record/attribute_methods/write.rb +8 -23
  36. data/lib/active_record/attribute_methods.rb +53 -90
  37. data/lib/active_record/attribute_set/builder.rb +32 -0
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attributes.rb +122 -0
  40. data/lib/active_record/autosave_association.rb +11 -21
  41. data/lib/active_record/base.rb +9 -19
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  52. data/lib/active_record/connection_adapters/column.rb +13 -244
  53. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  55. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  56. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  59. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  60. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  85. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  86. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  87. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  88. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  89. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  90. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  91. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  93. data/lib/active_record/connection_handling.rb +1 -1
  94. data/lib/active_record/core.rb +119 -22
  95. data/lib/active_record/counter_cache.rb +60 -6
  96. data/lib/active_record/enum.rb +9 -10
  97. data/lib/active_record/errors.rb +27 -26
  98. data/lib/active_record/explain.rb +1 -1
  99. data/lib/active_record/fixtures.rb +52 -45
  100. data/lib/active_record/gem_version.rb +3 -3
  101. data/lib/active_record/inheritance.rb +33 -8
  102. data/lib/active_record/integration.rb +4 -4
  103. data/lib/active_record/locking/optimistic.rb +34 -16
  104. data/lib/active_record/migration/command_recorder.rb +19 -2
  105. data/lib/active_record/migration/join_table.rb +1 -1
  106. data/lib/active_record/migration.rb +22 -32
  107. data/lib/active_record/model_schema.rb +39 -48
  108. data/lib/active_record/nested_attributes.rb +8 -18
  109. data/lib/active_record/persistence.rb +39 -22
  110. data/lib/active_record/query_cache.rb +3 -3
  111. data/lib/active_record/querying.rb +1 -8
  112. data/lib/active_record/railtie.rb +17 -10
  113. data/lib/active_record/railties/databases.rake +47 -42
  114. data/lib/active_record/readonly_attributes.rb +0 -1
  115. data/lib/active_record/reflection.rb +225 -92
  116. data/lib/active_record/relation/batches.rb +0 -2
  117. data/lib/active_record/relation/calculations.rb +28 -32
  118. data/lib/active_record/relation/delegation.rb +1 -1
  119. data/lib/active_record/relation/finder_methods.rb +42 -20
  120. data/lib/active_record/relation/merger.rb +0 -1
  121. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  122. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/query_methods.rb +98 -62
  125. data/lib/active_record/relation/spawn_methods.rb +6 -7
  126. data/lib/active_record/relation.rb +35 -11
  127. data/lib/active_record/result.rb +16 -9
  128. data/lib/active_record/sanitization.rb +8 -1
  129. data/lib/active_record/schema.rb +0 -1
  130. data/lib/active_record/schema_dumper.rb +51 -9
  131. data/lib/active_record/schema_migration.rb +4 -0
  132. data/lib/active_record/scoping/default.rb +5 -4
  133. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  134. data/lib/active_record/statement_cache.rb +79 -5
  135. data/lib/active_record/store.rb +5 -5
  136. data/lib/active_record/tasks/database_tasks.rb +37 -5
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  138. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  139. data/lib/active_record/timestamp.rb +9 -7
  140. data/lib/active_record/transactions.rb +35 -21
  141. data/lib/active_record/type/binary.rb +40 -0
  142. data/lib/active_record/type/boolean.rb +19 -0
  143. data/lib/active_record/type/date.rb +46 -0
  144. data/lib/active_record/type/date_time.rb +43 -0
  145. data/lib/active_record/type/decimal.rb +40 -0
  146. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  147. data/lib/active_record/type/float.rb +19 -0
  148. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  149. data/lib/active_record/type/integer.rb +23 -0
  150. data/lib/active_record/type/mutable.rb +16 -0
  151. data/lib/active_record/type/numeric.rb +36 -0
  152. data/lib/active_record/type/serialized.rb +51 -0
  153. data/lib/active_record/type/string.rb +36 -0
  154. data/lib/active_record/type/text.rb +11 -0
  155. data/lib/active_record/type/time.rb +26 -0
  156. data/lib/active_record/type/time_value.rb +38 -0
  157. data/lib/active_record/type/type_map.rb +48 -0
  158. data/lib/active_record/type/value.rb +101 -0
  159. data/lib/active_record/type.rb +20 -0
  160. data/lib/active_record/validations/uniqueness.rb +9 -23
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record.rb +2 -1
  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
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = Active Record -- Object-relational mapping put on rails
1
+ = Active Record -- Object-relational mapping in Rails
2
2
 
3
3
  Active Record connects classes to relational database tables to establish an
4
4
  almost zero-configuration persistence layer for applications. The library
@@ -20,8 +20,10 @@ A short rundown of some of the major features:
20
20
  class Product < ActiveRecord::Base
21
21
  end
22
22
 
23
- The Product class is automatically mapped to the table named "products",
24
- which might look like this:
23
+ {Learn more}[link:classes/ActiveRecord/Base.html]
24
+
25
+ The Product class is automatically mapped to the table named "products",
26
+ which might look like this:
25
27
 
26
28
  CREATE TABLE products (
27
29
  id int(11) NOT NULL auto_increment,
@@ -29,10 +31,8 @@ A short rundown of some of the major features:
29
31
  PRIMARY KEY (id)
30
32
  );
31
33
 
32
- This would also define the following accessors: `Product#name` and
33
- `Product#name=(new_name)`
34
-
35
- {Learn more}[link:classes/ActiveRecord/Base.html]
34
+ This would also define the following accessors: `Product#name` and
35
+ `Product#name=(new_name)`.
36
36
 
37
37
 
38
38
  * Associations between objects defined by simple class methods.
@@ -130,7 +130,7 @@ A short rundown of some of the major features:
130
130
  SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
131
131
 
132
132
 
133
- * Logging support for Log4r[http://log4r.rubyforge.org] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
133
+ * Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
134
134
 
135
135
  ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
136
136
  ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
@@ -192,7 +192,7 @@ The latest version of Active Record can be installed with RubyGems:
192
192
 
193
193
  Source code can be downloaded as part of the Rails project on GitHub:
194
194
 
195
- * https://github.com/rails/rails/tree/4-1-stable/activerecord
195
+ * https://github.com/rails/rails/tree/master/activerecord
196
196
 
197
197
 
198
198
  == License
@@ -208,6 +208,11 @@ API documentation is at:
208
208
 
209
209
  * http://api.rubyonrails.org
210
210
 
211
- Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
211
+ Bug reports can be filed for the Ruby on Rails project here:
212
212
 
213
213
  * https://github.com/rails/rails/issues
214
+
215
+ Feature requests should be discussed on the rails-core mailing list here:
216
+
217
+ * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
218
+
@@ -129,10 +129,10 @@ module ActiveRecord
129
129
  # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
130
130
  # converted to an instance of value class if necessary.
131
131
  #
132
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
133
- # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
134
- # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
135
- # values can be assigned to the value object using either another NetAddr::CIDR object, a string
132
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be
133
+ # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html).
134
+ # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
135
+ # New values can be assigned to the value object using either another NetAddr::CIDR object, a string
136
136
  # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
137
137
  # these requirements:
138
138
  #
@@ -230,8 +230,8 @@ module ActiveRecord
230
230
  private
231
231
  def reader_method(name, class_name, mapping, allow_nil, constructor)
232
232
  define_method(name) do
233
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
234
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
233
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? })
234
+ attrs = mapping.collect {|key, _| read_attribute(key)}
235
235
  object = constructor.respond_to?(:call) ?
236
236
  constructor.call(*attrs) :
237
237
  class_name.constantize.send(constructor, *attrs)
@@ -244,15 +244,19 @@ module ActiveRecord
244
244
  def writer_method(name, class_name, mapping, allow_nil, converter)
245
245
  define_method("#{name}=") do |part|
246
246
  klass = class_name.constantize
247
+ if part.is_a?(Hash)
248
+ part = klass.new(*part.values)
249
+ end
250
+
247
251
  unless part.is_a?(klass) || converter.nil? || part.nil?
248
252
  part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
249
253
  end
250
254
 
251
255
  if part.nil? && allow_nil
252
- mapping.each { |pair| self[pair.first] = nil }
256
+ mapping.each { |key, _| self[key] = nil }
253
257
  @aggregation_cache[name] = nil
254
258
  else
255
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
259
+ mapping.each { |key, value| self[key] = part.send(value) }
256
260
  @aggregation_cache[name] = part.freeze
257
261
  end
258
262
  end
@@ -179,7 +179,7 @@ module ActiveRecord
179
179
  def creation_attributes
180
180
  attributes = {}
181
181
 
182
- if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through]
182
+ if (reflection.has_one? || reflection.collection?) && !options[:through]
183
183
  attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
184
184
 
185
185
  if reflection.options[:as]
@@ -1,12 +1,34 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class AssociationScope #:nodoc:
4
- INSTANCE = new
5
-
6
4
  def self.scope(association, connection)
7
5
  INSTANCE.scope association, connection
8
6
  end
9
7
 
8
+ class BindSubstitution
9
+ def initialize(block)
10
+ @block = block
11
+ end
12
+
13
+ def bind_value(scope, column, value, alias_tracker)
14
+ substitute = alias_tracker.connection.substitute_at(
15
+ column, scope.bind_values.length)
16
+ scope.bind_values += [[column, @block.call(value)]]
17
+ substitute
18
+ end
19
+ end
20
+
21
+ def self.create(&block)
22
+ block = block ? block : lambda { |val| val }
23
+ new BindSubstitution.new(block)
24
+ end
25
+
26
+ def initialize(bind_substitution)
27
+ @bind_substitution = bind_substitution
28
+ end
29
+
30
+ INSTANCE = create
31
+
10
32
  def scope(association, connection)
11
33
  klass = association.klass
12
34
  reflection = association.reflection
@@ -22,6 +44,23 @@ module ActiveRecord
22
44
  Arel::Nodes::InnerJoin
23
45
  end
24
46
 
47
+ def self.get_bind_values(owner, chain)
48
+ bvs = []
49
+ chain.each_with_index do |reflection, i|
50
+ if reflection == chain.last
51
+ bvs << reflection.join_id_for(owner)
52
+ if reflection.type
53
+ bvs << owner.class.base_class.name
54
+ end
55
+ else
56
+ if reflection.type
57
+ bvs << chain[i + 1].klass.base_class.name
58
+ end
59
+ end
60
+ end
61
+ bvs
62
+ end
63
+
25
64
  private
26
65
 
27
66
  def construct_tables(chain, klass, refl, alias_tracker)
@@ -49,10 +88,7 @@ module ActiveRecord
49
88
  end
50
89
 
51
90
  def bind_value(scope, column, value, alias_tracker)
52
- substitute = alias_tracker.connection.substitute_at(
53
- column, scope.bind_values.length)
54
- scope.bind_values += [[column, value]]
55
- substitute
91
+ @bind_substitution.bind_value scope, column, value, alias_tracker
56
92
  end
57
93
 
58
94
  def bind(scope, table_name, column_name, value, tracker)
@@ -69,18 +105,9 @@ module ActiveRecord
69
105
  chain.each_with_index do |reflection, i|
70
106
  table, foreign_table = tables.shift, tables.first
71
107
 
72
- if reflection.source_macro == :belongs_to
73
- if reflection.options[:polymorphic]
74
- key = reflection.association_primary_key(assoc_klass)
75
- else
76
- key = reflection.association_primary_key
77
- end
78
-
79
- foreign_key = reflection.foreign_key
80
- else
81
- key = reflection.foreign_key
82
- foreign_key = reflection.active_record_primary_key
83
- end
108
+ join_keys = reflection.join_keys(assoc_klass)
109
+ key = join_keys.key
110
+ foreign_key = join_keys.foreign_key
84
111
 
85
112
  if reflection == chain.last
86
113
  bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
@@ -88,7 +115,7 @@ module ActiveRecord
88
115
 
89
116
  if reflection.type
90
117
  value = owner.class.base_class.name
91
- bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
118
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
92
119
  scope = scope.where(table[reflection.type].eq(bind_val))
93
120
  end
94
121
  else
@@ -96,7 +123,7 @@ module ActiveRecord
96
123
 
97
124
  if reflection.type
98
125
  value = chain[i + 1].klass.base_class.name
99
- bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
126
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
100
127
  scope = scope.where(table[reflection.type].eq(bind_val))
101
128
  end
102
129
 
@@ -120,6 +147,7 @@ module ActiveRecord
120
147
  end
121
148
 
122
149
  scope.where_values += item.where_values
150
+ scope.bind_values += item.bind_values
123
151
  scope.order_values |= item.order_values
124
152
  end
125
153
  end
@@ -143,7 +171,11 @@ module ActiveRecord
143
171
  end
144
172
 
145
173
  def eval_scope(klass, scope, owner)
146
- klass.unscoped.instance_exec(owner, &scope)
174
+ if scope.is_a?(Relation)
175
+ scope
176
+ else
177
+ klass.unscoped.instance_exec(owner, &scope)
178
+ end
147
179
  end
148
180
  end
149
181
  end
@@ -31,6 +31,14 @@ module ActiveRecord
31
31
  @updated
32
32
  end
33
33
 
34
+ def decrement_counters # :nodoc:
35
+ with_cache_name { |name| decrement_counter name }
36
+ end
37
+
38
+ def increment_counters # :nodoc:
39
+ with_cache_name { |name| increment_counter name }
40
+ end
41
+
34
42
  private
35
43
 
36
44
  def find_target?
@@ -51,13 +59,15 @@ module ActiveRecord
51
59
  end
52
60
  end
53
61
 
54
- def decrement_counters
55
- with_cache_name { |name| decrement_counter name }
62
+ def decrement_counter(counter_cache_name)
63
+ if foreign_key_present?
64
+ klass.decrement_counter(counter_cache_name, target_id)
65
+ end
56
66
  end
57
67
 
58
- def decrement_counter counter_cache_name
68
+ def increment_counter(counter_cache_name)
59
69
  if foreign_key_present?
60
- klass.decrement_counter(counter_cache_name, target_id)
70
+ klass.increment_counter(counter_cache_name, target_id)
61
71
  end
62
72
  end
63
73
 
@@ -82,7 +92,7 @@ module ActiveRecord
82
92
  # has_one associations.
83
93
  def invertible_for?(record)
84
94
  inverse = inverse_reflection_for(record)
85
- inverse && inverse.macro == :has_one
95
+ inverse && inverse.has_one?
86
96
  end
87
97
 
88
98
  def target_id
@@ -21,7 +21,7 @@ module ActiveRecord::Associations::Builder
21
21
  end
22
22
  self.extensions = []
23
23
 
24
- self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
24
+ self.valid_options = [:class_name, :class, :foreign_key, :validate]
25
25
 
26
26
  attr_reader :name, :scope, :options
27
27
 
@@ -36,6 +36,7 @@ module ActiveRecord::Associations::Builder
36
36
  reflection = builder.build(model)
37
37
  define_accessors model, reflection
38
38
  define_callbacks model, reflection
39
+ define_validations model, reflection
39
40
  builder.define_extensions model
40
41
  reflection
41
42
  end
@@ -85,7 +86,11 @@ module ActiveRecord::Associations::Builder
85
86
  end
86
87
 
87
88
  def self.define_callbacks(model, reflection)
88
- add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
89
+ if dependent = reflection.options[:dependent]
90
+ check_dependent_options(dependent)
91
+ add_destroy_callbacks(model, reflection)
92
+ end
93
+
89
94
  Association.extensions.each do |extension|
90
95
  extension.build model, reflection
91
96
  end
@@ -120,17 +125,23 @@ module ActiveRecord::Associations::Builder
120
125
  CODE
121
126
  end
122
127
 
128
+ def self.define_validations(model, reflection)
129
+ # noop
130
+ end
131
+
123
132
  def self.valid_dependent_options
124
133
  raise NotImplementedError
125
134
  end
126
135
 
127
136
  private
128
137
 
129
- def self.add_before_destroy_callbacks(model, reflection)
130
- unless valid_dependent_options.include? reflection.options[:dependent]
131
- raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
138
+ def self.check_dependent_options(dependent)
139
+ unless valid_dependent_options.include? dependent
140
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
132
141
  end
142
+ end
133
143
 
144
+ def self.add_destroy_callbacks(model, reflection)
134
145
  name = reflection.name
135
146
  model.before_destroy lambda { |o| o.association(name).handle_dependency }
136
147
  end
@@ -26,28 +26,9 @@ module ActiveRecord::Associations::Builder
26
26
  private
27
27
 
28
28
  def self.add_counter_cache_methods(mixin)
29
- return if mixin.method_defined? :belongs_to_counter_cache_after_create
29
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
30
30
 
31
31
  mixin.class_eval do
32
- def belongs_to_counter_cache_after_create(reflection)
33
- if record = send(reflection.name)
34
- cache_column = reflection.counter_cache_column
35
- record.class.increment_counter(cache_column, record.id)
36
- @_after_create_counter_called = true
37
- end
38
- end
39
-
40
- def belongs_to_counter_cache_before_destroy(reflection)
41
- foreign_key = reflection.foreign_key.to_sym
42
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
43
- record = send reflection.name
44
- if record && !self.destroyed?
45
- cache_column = reflection.counter_cache_column
46
- record.class.decrement_counter(cache_column, record.id)
47
- end
48
- end
49
- end
50
-
51
32
  def belongs_to_counter_cache_after_update(reflection)
52
33
  foreign_key = reflection.foreign_key
53
34
  cache_column = reflection.counter_cache_column
@@ -73,14 +54,6 @@ module ActiveRecord::Associations::Builder
73
54
  def self.add_counter_cache_callbacks(model, reflection)
74
55
  cache_column = reflection.counter_cache_column
75
56
 
76
- model.after_create lambda { |record|
77
- record.belongs_to_counter_cache_after_create(reflection)
78
- }
79
-
80
- model.before_destroy lambda { |record|
81
- record.belongs_to_counter_cache_before_destroy(reflection)
82
- }
83
-
84
57
  model.after_update lambda { |record|
85
58
  record.belongs_to_counter_cache_after_update(reflection)
86
59
  }
@@ -130,9 +103,14 @@ module ActiveRecord::Associations::Builder
130
103
  BelongsTo.touch_record(record, foreign_key, n, touch)
131
104
  }
132
105
 
133
- model.after_save callback
106
+ model.after_save callback, if: :changed?
134
107
  model.after_touch callback
135
108
  model.after_destroy callback
136
109
  end
110
+
111
+ def self.add_destroy_callbacks(model, reflection)
112
+ name = reflection.name
113
+ model.after_destroy lambda { |o| o.association(name).handle_dependency }
114
+ end
137
115
  end
138
116
  end
@@ -72,22 +72,13 @@ module ActiveRecord::Associations::Builder
72
72
  self.right_reflection = _reflect_on_association(rhs_name)
73
73
  end
74
74
 
75
- def hash
76
- object_id.hash
77
- end
78
-
79
- def ==(other)
80
- equal?(other)
81
- end
82
- alias :eql? :==
83
-
84
75
  }
85
76
 
86
77
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
87
78
  join_model.table_name_resolver = habtm
88
79
  join_model.class_resolver = lhs_model
89
80
 
90
- join_model.add_left_association :left_side, anonymous_class: lhs_model
81
+ join_model.add_left_association :left_side, class: lhs_model
91
82
  join_model.add_right_association association_name, belongs_to_options(options)
92
83
  join_model
93
84
  end
@@ -107,7 +98,7 @@ module ActiveRecord::Associations::Builder
107
98
 
108
99
  def middle_options(join_model)
109
100
  middle_options = {}
110
- middle_options[:anonymous_class] = join_model
101
+ middle_options[:class] = join_model
111
102
  middle_options[:source] = join_model.left_reflection.name
112
103
  if options.key? :foreign_key
113
104
  middle_options[:foreign_key] = options[:foreign_key]
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
5
5
  end
6
6
 
7
7
  def valid_options
8
- valid = super + [:order, :as]
8
+ valid = super + [:as]
9
9
  valid += [:through, :source, :source_type] if options[:through]
10
10
  valid
11
11
  end
@@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder
16
16
 
17
17
  private
18
18
 
19
- def self.add_before_destroy_callbacks(model, reflection)
19
+ def self.add_destroy_callbacks(model, reflection)
20
20
  super unless reflection.options[:through]
21
21
  end
22
22
  end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord::Associations::Builder
4
4
  class SingularAssociation < Association #:nodoc:
5
5
  def valid_options
6
- super + [:remote, :dependent, :primary_key, :inverse_of]
6
+ super + [:dependent, :primary_key, :inverse_of, :required]
7
7
  end
8
8
 
9
9
  def self.define_accessors(model, reflection)
@@ -27,5 +27,12 @@ module ActiveRecord::Associations::Builder
27
27
  end
28
28
  CODE
29
29
  end
30
+
31
+ def self.define_validations(model, reflection)
32
+ super
33
+ if reflection.options[:required]
34
+ model.validates_presence_of reflection.name
35
+ end
36
+ end
30
37
  end
31
38
  end
@@ -33,13 +33,7 @@ module ActiveRecord
33
33
  reload
34
34
  end
35
35
 
36
- if owner.new_record?
37
- # Cache the proxy separately before the owner has an id
38
- # or else a post-save proxy will still lack the id
39
- @new_record_proxy ||= CollectionProxy.create(klass, self)
40
- else
41
- @proxy ||= CollectionProxy.create(klass, self)
42
- end
36
+ @proxy ||= CollectionProxy.create(klass, self)
43
37
  end
44
38
 
45
39
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -61,9 +55,9 @@ module ActiveRecord
61
55
 
62
56
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
63
57
  def ids_writer(ids)
64
- pk_column = reflection.primary_key_column
58
+ pk_type = reflection.primary_key_type
65
59
  ids = Array(ids).reject { |id| id.blank? }
66
- ids.map! { |i| pk_column.type_cast(i) }
60
+ ids.map! { |i| pk_type.type_cast_from_user(i) }
67
61
  replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
68
62
  end
69
63
 
@@ -129,16 +123,6 @@ module ActiveRecord
129
123
  first_nth_or_last(:last, *args)
130
124
  end
131
125
 
132
- def take(n = nil)
133
- if loaded?
134
- n ? target.take(n) : target.first
135
- else
136
- scope.take(n).tap do |record|
137
- set_inverse_instance record if record.is_a? ActiveRecord::Base
138
- end
139
- end
140
- end
141
-
142
126
  def build(attributes = {}, &block)
143
127
  if attributes.is_a?(Array)
144
128
  attributes.collect { |attr| build(attr, &block) }
@@ -161,9 +145,8 @@ module ActiveRecord
161
145
  # be chained. Since << flattens its argument list and inserts each record,
162
146
  # +push+ and +concat+ behave identically.
163
147
  def concat(*records)
164
- load_target if owner.new_record?
165
-
166
148
  if owner.new_record?
149
+ load_target
167
150
  concat_records(records)
168
151
  else
169
152
  transaction { concat_records(records) }
@@ -199,11 +182,11 @@ module ActiveRecord
199
182
  #
200
183
  # See delete for more info.
201
184
  def delete_all(dependent = nil)
202
- if dependent.present? && ![:nullify, :delete_all].include?(dependent)
185
+ if dependent && ![:nullify, :delete_all].include?(dependent)
203
186
  raise ArgumentError, "Valid values are :nullify or :delete_all"
204
187
  end
205
188
 
206
- dependent = if dependent.present?
189
+ dependent = if dependent
207
190
  dependent
208
191
  elsif options[:dependent] == :destroy
209
192
  :delete_all
@@ -211,7 +194,7 @@ module ActiveRecord
211
194
  options[:dependent]
212
195
  end
213
196
 
214
- delete(:all, dependent: dependent).tap do
197
+ delete_or_nullify_all_records(dependent).tap do
215
198
  reset
216
199
  loaded!
217
200
  end
@@ -261,19 +244,12 @@ module ActiveRecord
261
244
  # are actually removed from the database, that depends precisely on
262
245
  # +delete_records+. They are in any case removed from the collection.
263
246
  def delete(*records)
247
+ return if records.empty?
264
248
  _options = records.extract_options!
265
249
  dependent = _options[:dependent] || options[:dependent]
266
250
 
267
- if records.first == :all
268
- if (loaded? || dependent == :destroy) && dependent != :delete_all
269
- delete_or_destroy(load_target, dependent)
270
- else
271
- delete_records(:all, dependent)
272
- end
273
- else
274
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
275
- delete_or_destroy(records, dependent)
276
- end
251
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
252
+ delete_or_destroy(records, dependent)
277
253
  end
278
254
 
279
255
  # Deletes the +records+ and removes them from this association calling
@@ -282,6 +258,7 @@ module ActiveRecord
282
258
  # Note that this method removes records from the database ignoring the
283
259
  # +:dependent+ option.
284
260
  def destroy(*records)
261
+ return if records.empty?
285
262
  records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
286
263
  delete_or_destroy(records, :destroy)
287
264
  end
@@ -375,7 +352,9 @@ module ActiveRecord
375
352
  if owner.new_record?
376
353
  replace_records(other_array, original_target)
377
354
  else
378
- transaction { replace_records(other_array, original_target) }
355
+ if other_array != original_target
356
+ transaction { replace_records(other_array, original_target) }
357
+ end
379
358
  end
380
359
  end
381
360
 
@@ -384,7 +363,7 @@ module ActiveRecord
384
363
  if record.new_record?
385
364
  include_in_memory?(record)
386
365
  else
387
- loaded? ? target.include?(record) : scope.exists?(record)
366
+ loaded? ? target.include?(record) : scope.exists?(record.id)
388
367
  end
389
368
  else
390
369
  false
@@ -427,9 +406,23 @@ module ActiveRecord
427
406
  end
428
407
 
429
408
  private
409
+ def get_records
410
+ return scope.to_a if reflection.scope_chain.any?(&:any?)
411
+
412
+ conn = klass.connection
413
+ sc = reflection.association_scope_cache(conn, owner) do
414
+ StatementCache.create(conn) { |params|
415
+ as = AssociationScope.create { params.bind }
416
+ target_scope.merge as.scope(self, conn)
417
+ }
418
+ end
419
+
420
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
421
+ sc.execute binds, klass, klass.connection
422
+ end
430
423
 
431
424
  def find_target
432
- records = scope.to_a
425
+ records = get_records
433
426
  records.each { |record| set_inverse_instance(record) }
434
427
  records
435
428
  end
@@ -576,13 +569,8 @@ module ActiveRecord
576
569
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
577
570
  assoc = owner.association(reflection.through_reflection.name)
578
571
  assoc.reader.any? { |source|
579
- target_association = source.send(reflection.source_reflection.name)
580
-
581
- if target_association.respond_to?(:include?)
582
- target_association.include?(record)
583
- else
584
- target_association == record
585
- end
572
+ target = source.send(reflection.source_reflection.name)
573
+ target.respond_to?(:include?) ? target.include?(record) : target == record
586
574
  } || target.include?(record)
587
575
  else
588
576
  target.include?(record)