activerecord 4.1.0 → 4.2.0

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  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 +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. 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/4-2-stable/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
@@ -9,6 +9,10 @@ module ActiveRecord
9
9
  @association
10
10
  end
11
11
 
12
+ def ==(other)
13
+ other == to_a
14
+ end
15
+
12
16
  private
13
17
 
14
18
  def exec_queries
@@ -32,8 +32,18 @@ module ActiveRecord
32
32
  join.left.downcase.scan(
33
33
  /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
34
34
  ).size
35
- else
35
+ elsif join.respond_to? :left
36
36
  join.left.table_name == name ? 1 : 0
37
+ else
38
+ # this branch is reached by two tests:
39
+ #
40
+ # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
41
+ # with :posts
42
+ #
43
+ # activerecord/test/cases/associations/eager_test.rb:1133
44
+ # with :comments
45
+ #
46
+ 0
37
47
  end
38
48
  end
39
49
 
@@ -47,20 +57,10 @@ module ActiveRecord
47
57
  end
48
58
 
49
59
  def aliased_table_for(table_name, aliased_name)
50
- table_alias = aliased_name_for(table_name, aliased_name)
51
-
52
- if table_alias == table_name
53
- Arel::Table.new(table_name)
54
- else
55
- Arel::Table.new(table_name).alias(table_alias)
56
- end
57
- end
58
-
59
- def aliased_name_for(table_name, aliased_name)
60
60
  if aliases[table_name].zero?
61
61
  # If it's zero, we can have our table_name
62
62
  aliases[table_name] = 1
63
- table_name
63
+ Arel::Table.new(table_name)
64
64
  else
65
65
  # Otherwise, we need to use an alias
66
66
  aliased_name = connection.table_alias_for(aliased_name)
@@ -68,11 +68,12 @@ module ActiveRecord
68
68
  # Update the count
69
69
  aliases[aliased_name] += 1
70
70
 
71
- if aliases[aliased_name] > 1
71
+ table_alias = if aliases[aliased_name] > 1
72
72
  "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
73
73
  else
74
74
  aliased_name
75
75
  end
76
+ Arel::Table.new(table_name).alias(table_alias)
76
77
  end
77
78
  end
78
79
 
@@ -160,7 +160,7 @@ module ActiveRecord
160
160
  def marshal_load(data)
161
161
  reflection_name, ivars = data
162
162
  ivars.each { |name, val| instance_variable_set(name, val) }
163
- @reflection = @owner.class.reflect_on_association(reflection_name)
163
+ @reflection = @owner.class._reflect_on_association(reflection_name)
164
164
  end
165
165
 
166
166
  def initialize_attributes(record) #:nodoc:
@@ -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,33 @@
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(column)
15
+ scope.bind_values += [[column, @block.call(value)]]
16
+ substitute
17
+ end
18
+ end
19
+
20
+ def self.create(&block)
21
+ block = block ? block : lambda { |val| val }
22
+ new BindSubstitution.new(block)
23
+ end
24
+
25
+ def initialize(bind_substitution)
26
+ @bind_substitution = bind_substitution
27
+ end
28
+
29
+ INSTANCE = create
30
+
10
31
  def scope(association, connection)
11
32
  klass = association.klass
12
33
  reflection = association.reflection
@@ -22,6 +43,23 @@ module ActiveRecord
22
43
  Arel::Nodes::InnerJoin
23
44
  end
24
45
 
46
+ def self.get_bind_values(owner, chain)
47
+ binds = []
48
+ last_reflection = chain.last
49
+
50
+ binds << last_reflection.join_id_for(owner)
51
+ if last_reflection.type
52
+ binds << owner.class.base_class.name
53
+ end
54
+
55
+ chain.each_cons(2).each do |reflection, next_reflection|
56
+ if reflection.type
57
+ binds << next_reflection.klass.base_class.name
58
+ end
59
+ end
60
+ binds
61
+ end
62
+
25
63
  private
26
64
 
27
65
  def construct_tables(chain, klass, refl, alias_tracker)
@@ -49,10 +87,7 @@ module ActiveRecord
49
87
  end
50
88
 
51
89
  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
90
+ @bind_substitution.bind_value scope, column, value, alias_tracker
56
91
  end
57
92
 
58
93
  def bind(scope, table_name, column_name, value, tracker)
@@ -60,47 +95,55 @@ module ActiveRecord
60
95
  bind_value scope, column, value, tracker
61
96
  end
62
97
 
98
+ def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
99
+ join_keys = reflection.join_keys(assoc_klass)
100
+ key = join_keys.key
101
+ foreign_key = join_keys.foreign_key
102
+
103
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
104
+ scope = scope.where(table[key].eq(bind_val))
105
+
106
+ if reflection.type
107
+ value = owner.class.base_class.name
108
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
109
+ scope = scope.where(table[reflection.type].eq(bind_val))
110
+ else
111
+ scope
112
+ end
113
+ end
114
+
115
+ def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
116
+ join_keys = reflection.join_keys(assoc_klass)
117
+ key = join_keys.key
118
+ foreign_key = join_keys.foreign_key
119
+
120
+ constraint = table[key].eq(foreign_table[foreign_key])
121
+
122
+ if reflection.type
123
+ value = next_reflection.klass.base_class.name
124
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
125
+ scope = scope.where(table[reflection.type].eq(bind_val))
126
+ end
127
+
128
+ scope = scope.joins(join(foreign_table, constraint))
129
+ end
130
+
63
131
  def add_constraints(scope, owner, assoc_klass, refl, tracker)
64
132
  chain = refl.chain
65
133
  scope_chain = refl.scope_chain
66
134
 
67
135
  tables = construct_tables(chain, assoc_klass, refl, tracker)
68
136
 
137
+ owner_reflection = chain.last
138
+ table = tables.last
139
+ scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
140
+
69
141
  chain.each_with_index do |reflection, i|
70
142
  table, foreign_table = tables.shift, tables.first
71
143
 
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
84
-
85
- if reflection == chain.last
86
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
87
- scope = scope.where(table[key].eq(bind_val))
88
-
89
- if reflection.type
90
- value = owner.class.base_class.name
91
- bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
92
- scope = scope.where(table[reflection.type].eq(bind_val))
93
- end
94
- else
95
- constraint = table[key].eq(foreign_table[foreign_key])
96
-
97
- if reflection.type
98
- value = chain[i + 1].klass.base_class.name
99
- bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
100
- scope = scope.where(table[reflection.type].eq(bind_val))
101
- end
102
-
103
- scope = scope.joins(join(foreign_table, constraint))
144
+ unless reflection == chain.last
145
+ next_reflection = chain[i + 1]
146
+ scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
104
147
  end
105
148
 
106
149
  is_first_chain = i == 0
@@ -120,6 +163,7 @@ module ActiveRecord
120
163
  end
121
164
 
122
165
  scope.where_values += item.where_values
166
+ scope.bind_values += item.bind_values
123
167
  scope.order_values |= item.order_values
124
168
  end
125
169
  end
@@ -143,11 +187,7 @@ module ActiveRecord
143
187
  end
144
188
 
145
189
  def eval_scope(klass, scope, owner)
146
- if scope.is_a?(Relation)
147
- scope
148
- else
149
- klass.unscoped.instance_exec(owner, &scope)
150
- end
190
+ klass.unscoped.instance_exec(owner, &scope)
151
191
  end
152
192
  end
153
193
  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
@@ -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
@@ -11,11 +11,14 @@ module ActiveRecord::Associations::Builder
11
11
  end
12
12
 
13
13
  def join_table
14
- @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
14
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
15
15
  end
16
16
 
17
17
  private
18
- def klass; @rhs_class_name.constantize; end
18
+
19
+ def klass
20
+ @lhs_class.send(:compute_type, @rhs_class_name)
21
+ end
19
22
  end
20
23
 
21
24
  def self.build(lhs_class, name, options)
@@ -60,13 +63,13 @@ module ActiveRecord::Associations::Builder
60
63
 
61
64
  def self.add_left_association(name, options)
62
65
  belongs_to name, options
63
- self.left_reflection = reflect_on_association(name)
66
+ self.left_reflection = _reflect_on_association(name)
64
67
  end
65
68
 
66
69
  def self.add_right_association(name, options)
67
70
  rhs_name = name.to_s.singularize.to_sym
68
71
  belongs_to rhs_name, options
69
- self.right_reflection = reflect_on_association(rhs_name)
72
+ self.right_reflection = _reflect_on_association(rhs_name)
70
73
  end
71
74
 
72
75
  }
@@ -95,7 +98,7 @@ module ActiveRecord::Associations::Builder
95
98
 
96
99
  def middle_options(join_model)
97
100
  middle_options = {}
98
- middle_options[:class] = join_model
101
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
99
102
  middle_options[:source] = join_model.left_reflection.name
100
103
  if options.key? :foreign_key
101
104
  middle_options[:foreign_key] = options[:foreign_key]
@@ -107,7 +110,7 @@ module ActiveRecord::Associations::Builder
107
110
  rhs_options = {}
108
111
 
109
112
  if options.key? :class_name
110
- rhs_options[:foreign_key] = options[:class_name].foreign_key
113
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
111
114
  rhs_options[:class_name] = options[:class_name]
112
115
  end
113
116
 
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
5
5
  end
6
6
 
7
7
  def valid_options
8
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
8
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
9
9
  end
10
10
 
11
11
  def self.valid_dependent_options
@@ -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, :foreign_type]
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