activerecord 3.0.0 → 4.0.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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,16 @@
1
+ #FIXME Remove if ArJdbcMysql will give.
2
+ module ArJdbcMySQL #:nodoc:
3
+ class Error < StandardError #:nodoc:
4
+ attr_accessor :error_number, :sql_state
5
+
6
+ def initialize msg
7
+ super
8
+ @error_number = nil
9
+ @sql_state = nil
10
+ end
11
+
12
+ # Mysql gem compatibility
13
+ alias_method :errno, :error_number
14
+ alias_method :error, :message
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module ActiveRecord
3
+ module ReadonlyAttributes
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :_attr_readonly, instance_accessor: false
8
+ self._attr_readonly = []
9
+ end
10
+
11
+ module ClassMethods
12
+ # Attributes listed as readonly will be used to create a new record but update operations will
13
+ # ignore these fields.
14
+ def attr_readonly(*attributes)
15
+ self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
16
+ end
17
+
18
+ # Returns an array of all the attributes that have been specified as readonly.
19
+ def readonly_attributes
20
+ self._attr_readonly
21
+ end
22
+ end
23
+
24
+ def _attr_readonly
25
+ message = "Instance level _attr_readonly method is deprecated, please use class level method."
26
+ ActiveSupport::Deprecation.warn message
27
+ defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
28
+ end
29
+ end
30
+ end
@@ -3,6 +3,11 @@ module ActiveRecord
3
3
  module Reflection # :nodoc:
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ included do
7
+ class_attribute :reflections
8
+ self.reflections = {}
9
+ end
10
+
6
11
  # Reflection enables to interrogate Active Record classes and objects
7
12
  # about their associations and aggregations. This information can,
8
13
  # for example, be used in a form builder that takes an Active Record object
@@ -12,31 +17,22 @@ module ActiveRecord
12
17
  # MacroReflection class has info for AggregateReflection and AssociationReflection
13
18
  # classes.
14
19
  module ClassMethods
15
- def create_reflection(macro, name, options, active_record)
20
+ def create_reflection(macro, name, scope, options, active_record)
16
21
  case macro
17
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
18
- klass = options[:through] ? ThroughReflection : AssociationReflection
19
- reflection = klass.new(macro, name, options, active_record)
20
- when :composed_of
21
- reflection = AggregateReflection.new(macro, name, options, active_record)
22
+ when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
23
+ klass = options[:through] ? ThroughReflection : AssociationReflection
24
+ reflection = klass.new(macro, name, scope, options, active_record)
25
+ when :composed_of
26
+ reflection = AggregateReflection.new(macro, name, scope, options, active_record)
22
27
  end
23
- write_inheritable_hash :reflections, name => reflection
24
- reflection
25
- end
26
28
 
27
- # Returns a hash containing all AssociationReflection objects for the current class.
28
- # Example:
29
- #
30
- # Invoice.reflections
31
- # Account.reflections
32
- #
33
- def reflections
34
- read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
29
+ self.reflections = self.reflections.merge(name => reflection)
30
+ reflection
35
31
  end
36
32
 
37
33
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
38
34
  def reflect_on_all_aggregations
39
- reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
35
+ reflections.values.grep(AggregateReflection)
40
36
  end
41
37
 
42
38
  # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
@@ -44,7 +40,8 @@ module ActiveRecord
44
40
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
45
41
  #
46
42
  def reflect_on_aggregation(aggregation)
47
- reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
43
+ reflection = reflections[aggregation]
44
+ reflection if reflection.is_a?(AggregateReflection)
48
45
  end
49
46
 
50
47
  # Returns an array of AssociationReflection objects for all the
@@ -58,7 +55,7 @@ module ActiveRecord
58
55
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
59
56
  #
60
57
  def reflect_on_all_associations(macro = nil)
61
- association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
58
+ association_reflections = reflections.values.grep(AssociationReflection)
62
59
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
63
60
  end
64
61
 
@@ -68,7 +65,8 @@ module ActiveRecord
68
65
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
69
66
  #
70
67
  def reflect_on_association(association)
71
- reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
68
+ reflection = reflections[association]
69
+ reflection if reflection.is_a?(AssociationReflection)
72
70
  end
73
71
 
74
72
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -77,37 +75,51 @@ module ActiveRecord
77
75
  end
78
76
  end
79
77
 
80
-
81
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
78
+ # Base class for AggregateReflection and AssociationReflection. Objects of
82
79
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
80
+ #
81
+ # MacroReflection
82
+ # AggregateReflection
83
+ # AssociationReflection
84
+ # ThroughReflection
83
85
  class MacroReflection
84
- attr_reader :active_record
85
-
86
- def initialize(macro, name, options, active_record)
87
- @macro, @name, @options, @active_record = macro, name, options, active_record
88
- end
89
-
90
86
  # Returns the name of the macro.
91
87
  #
92
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
88
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
93
89
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
94
90
  attr_reader :name
95
91
 
96
92
  # Returns the macro type.
97
93
  #
98
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
94
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
99
95
  # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
100
96
  attr_reader :macro
101
97
 
98
+ attr_reader :scope
99
+
102
100
  # Returns the hash of options used for the macro.
103
101
  #
104
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
102
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
105
103
  # <tt>has_many :clients</tt> returns +{}+
106
104
  attr_reader :options
107
105
 
106
+ attr_reader :active_record
107
+
108
+ attr_reader :plural_name # :nodoc:
109
+
110
+ def initialize(macro, name, scope, options, active_record)
111
+ @macro = macro
112
+ @name = name
113
+ @scope = scope
114
+ @options = options
115
+ @active_record = active_record
116
+ @plural_name = active_record.pluralize_table_names ?
117
+ name.to_s.pluralize : name.to_s
118
+ end
119
+
108
120
  # Returns the class for the macro.
109
121
  #
110
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
122
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
111
123
  # <tt>has_many :clients</tt> returns the Client class
112
124
  def klass
113
125
  @klass ||= class_name.constantize
@@ -115,20 +127,20 @@ module ActiveRecord
115
127
 
116
128
  # Returns the class name for the macro.
117
129
  #
118
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
130
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
119
131
  # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
120
132
  def class_name
121
- @class_name ||= options[:class_name] || derive_class_name
133
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
122
134
  end
123
135
 
124
136
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
125
137
  # and +other_aggregation+ has an options hash assigned to it.
126
138
  def ==(other_aggregation)
127
- other_aggregation.kind_of?(self.class) && name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
128
- end
129
-
130
- def sanitized_conditions #:nodoc:
131
- @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
139
+ super ||
140
+ other_aggregation.kind_of?(self.class) &&
141
+ name == other_aggregation.name &&
142
+ other_aggregation.options &&
143
+ active_record == other_aggregation.active_record
132
144
  end
133
145
 
134
146
  private
@@ -141,6 +153,10 @@ module ActiveRecord
141
153
  # Holds all the meta-data about an aggregation as it was specified in the
142
154
  # Active Record class.
143
155
  class AggregateReflection < MacroReflection #:nodoc:
156
+ def mapping
157
+ mapping = options[:mapping] || [name, name]
158
+ mapping.first.is_a?(Array) ? mapping : [mapping]
159
+ end
144
160
  end
145
161
 
146
162
  # Holds all the meta-data about an association as it was specified in the
@@ -162,32 +178,15 @@ module ActiveRecord
162
178
  @klass ||= active_record.send(:compute_type, class_name)
163
179
  end
164
180
 
165
- def initialize(macro, name, options, active_record)
181
+ def initialize(*args)
166
182
  super
167
183
  @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
168
184
  end
169
185
 
170
- # Returns a new, unsaved instance of the associated class. +options+ will
186
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
171
187
  # be passed to the class's constructor.
172
- def build_association(*options)
173
- klass.new(*options)
174
- end
175
-
176
- # Creates a new instance of the associated class, and immediately saves it
177
- # with ActiveRecord::Base#save. +options+ will be passed to the class's
178
- # creation method. Returns the newly created object.
179
- def create_association(*options)
180
- klass.create(*options)
181
- end
182
-
183
- # Creates a new instance of the associated class, and immediately saves it
184
- # with ActiveRecord::Base#save!. +options+ will be passed to the class's
185
- # creation method. If the created record doesn't pass validations, then an
186
- # exception will be raised.
187
- #
188
- # Returns the newly created object.
189
- def create_association!(*options)
190
- klass.create!(*options)
188
+ def build_association(attributes, &block)
189
+ klass.new(attributes, &block)
191
190
  end
192
191
 
193
192
  def table_name
@@ -198,8 +197,20 @@ module ActiveRecord
198
197
  @quoted_table_name ||= klass.quoted_table_name
199
198
  end
200
199
 
201
- def primary_key_name
202
- @primary_key_name ||= options[:foreign_key] || derive_primary_key_name
200
+ def join_table
201
+ @join_table ||= options[:join_table] || derive_join_table
202
+ end
203
+
204
+ def foreign_key
205
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key
206
+ end
207
+
208
+ def foreign_type
209
+ @foreign_type ||= options[:foreign_type] || "#{name}_type"
210
+ end
211
+
212
+ def type
213
+ @type ||= options[:as] && "#{options[:as]}_type"
203
214
  end
204
215
 
205
216
  def primary_key_column
@@ -207,19 +218,28 @@ module ActiveRecord
207
218
  end
208
219
 
209
220
  def association_foreign_key
210
- @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
221
+ @association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
222
+ end
223
+
224
+ # klass option is necessary to support loading polymorphic associations
225
+ def association_primary_key(klass = nil)
226
+ options[:primary_key] || primary_key(klass || self.klass)
227
+ end
228
+
229
+ def active_record_primary_key
230
+ @active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
211
231
  end
212
232
 
213
233
  def counter_cache_column
214
234
  if options[:counter_cache] == true
215
235
  "#{active_record.name.demodulize.underscore.pluralize}_count"
216
236
  elsif options[:counter_cache]
217
- options[:counter_cache]
237
+ options[:counter_cache].to_s
218
238
  end
219
239
  end
220
240
 
221
- def columns(tbl_name, log_msg)
222
- @columns ||= klass.connection.columns(tbl_name, log_msg)
241
+ def columns(tbl_name)
242
+ @columns ||= klass.connection.columns(tbl_name)
223
243
  end
224
244
 
225
245
  def reset_column_information
@@ -228,6 +248,10 @@ module ActiveRecord
228
248
 
229
249
  def check_validity!
230
250
  check_validity_of_inverse!
251
+
252
+ if has_and_belongs_to_many? && association_foreign_key == foreign_key
253
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
254
+ end
231
255
  end
232
256
 
233
257
  def check_validity_of_inverse!
@@ -239,18 +263,33 @@ module ActiveRecord
239
263
  end
240
264
 
241
265
  def through_reflection
242
- false
243
- end
244
-
245
- def through_reflection_primary_key_name
266
+ nil
246
267
  end
247
268
 
248
269
  def source_reflection
249
270
  nil
250
271
  end
251
272
 
273
+ # A chain of reflections from this one back to the owner. For more see the explanation in
274
+ # ThroughReflection.
275
+ def chain
276
+ [self]
277
+ end
278
+
279
+ def nested?
280
+ false
281
+ end
282
+
283
+ # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
284
+ # in the #chain.
285
+ def scope_chain
286
+ scope ? [[scope]] : [[]]
287
+ end
288
+
289
+ alias :source_macro :macro
290
+
252
291
  def has_inverse?
253
- !@options[:inverse_of].nil?
292
+ @options[:inverse_of]
254
293
  end
255
294
 
256
295
  def inverse_of
@@ -280,31 +319,53 @@ module ActiveRecord
280
319
  # the parent's validation.
281
320
  #
282
321
  # Unless you explicitly disable validation with
283
- # <tt>:validate => false</tt>, validation will take place when:
322
+ # <tt>validate: false</tt>, validation will take place when:
284
323
  #
285
- # * you explicitly enable validation; <tt>:validate => true</tt>
286
- # * you use autosave; <tt>:autosave => true</tt>
324
+ # * you explicitly enable validation; <tt>validate: true</tt>
325
+ # * you use autosave; <tt>autosave: true</tt>
287
326
  # * the association is a +has_many+ association
288
327
  def validate?
289
328
  !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
290
329
  end
291
330
 
292
- def dependent_conditions(record, base_class, extra_conditions)
293
- dependent_conditions = []
294
- dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
295
- dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
296
- dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
297
- dependent_conditions << extra_conditions if extra_conditions
298
- dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
299
- dependent_conditions = dependent_conditions.gsub('@', '\@')
300
- dependent_conditions
301
- end
302
-
303
331
  # Returns +true+ if +self+ is a +belongs_to+ reflection.
304
332
  def belongs_to?
305
333
  macro == :belongs_to
306
334
  end
307
335
 
336
+ def has_and_belongs_to_many?
337
+ macro == :has_and_belongs_to_many
338
+ end
339
+
340
+ def association_class
341
+ case macro
342
+ when :belongs_to
343
+ if options[:polymorphic]
344
+ Associations::BelongsToPolymorphicAssociation
345
+ else
346
+ Associations::BelongsToAssociation
347
+ end
348
+ when :has_and_belongs_to_many
349
+ Associations::HasAndBelongsToManyAssociation
350
+ when :has_many
351
+ if options[:through]
352
+ Associations::HasManyThroughAssociation
353
+ else
354
+ Associations::HasManyAssociation
355
+ end
356
+ when :has_one
357
+ if options[:through]
358
+ Associations::HasOneThroughAssociation
359
+ else
360
+ Associations::HasOneAssociation
361
+ end
362
+ end
363
+ end
364
+
365
+ def polymorphic?
366
+ options.key? :polymorphic
367
+ end
368
+
308
369
  private
309
370
  def derive_class_name
310
371
  class_name = name.to_s.camelize
@@ -312,7 +373,7 @@ module ActiveRecord
312
373
  class_name
313
374
  end
314
375
 
315
- def derive_primary_key_name
376
+ def derive_foreign_key
316
377
  if belongs_to?
317
378
  "#{name}_id"
318
379
  elsif options[:as]
@@ -321,19 +382,40 @@ module ActiveRecord
321
382
  active_record.name.foreign_key
322
383
  end
323
384
  end
385
+
386
+ def derive_join_table
387
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
388
+ end
389
+
390
+ def primary_key(klass)
391
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
392
+ end
324
393
  end
325
394
 
326
395
  # Holds all the meta-data about a :through association as it was specified
327
396
  # in the Active Record class.
328
397
  class ThroughReflection < AssociationReflection #:nodoc:
329
- # Gets the source of the through reflection. It checks both a singularized
398
+ delegate :foreign_key, :foreign_type, :association_foreign_key,
399
+ :active_record_primary_key, :type, :to => :source_reflection
400
+
401
+ # Gets the source of the through reflection. It checks both a singularized
330
402
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
331
403
  #
332
404
  # class Post < ActiveRecord::Base
333
405
  # has_many :taggings
334
- # has_many :tags, :through => :taggings
406
+ # has_many :tags, through: :taggings
407
+ # end
408
+ #
409
+ # class Tagging < ActiveRecord::Base
410
+ # belongs_to :post
411
+ # belongs_to :tag
335
412
  # end
336
413
  #
414
+ # tags_reflection = Post.reflect_on_association(:tags)
415
+ #
416
+ # taggings_reflection = tags_reflection.source_reflection
417
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
418
+ #
337
419
  def source_reflection
338
420
  @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
339
421
  end
@@ -343,7 +425,7 @@ module ActiveRecord
343
425
  #
344
426
  # class Post < ActiveRecord::Base
345
427
  # has_many :taggings
346
- # has_many :tags, :through => :taggings
428
+ # has_many :tags, through: :taggings
347
429
  # end
348
430
  #
349
431
  # tags_reflection = Post.reflect_on_association(:tags)
@@ -353,19 +435,125 @@ module ActiveRecord
353
435
  @through_reflection ||= active_record.reflect_on_association(options[:through])
354
436
  end
355
437
 
356
- # Gets an array of possible <tt>:through</tt> source reflection names:
438
+ # Returns an array of reflections which are involved in this association. Each item in the
439
+ # array corresponds to a table which will be part of the query for this association.
440
+ #
441
+ # The chain is built by recursively calling #chain on the source reflection and the through
442
+ # reflection. The base case for the recursion is a normal association, which just returns
443
+ # [self] as its #chain.
444
+ #
445
+ # class Post < ActiveRecord::Base
446
+ # has_many :taggings
447
+ # has_many :tags, through: :taggings
448
+ # end
449
+ #
450
+ # tags_reflection = Post.reflect_on_association(:tags)
451
+ # tags_reflection.chain
452
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
453
+ # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
454
+ #
455
+ def chain
456
+ @chain ||= begin
457
+ chain = source_reflection.chain + through_reflection.chain
458
+ chain[0] = self # Use self so we don't lose the information from :source_type
459
+ chain
460
+ end
461
+ end
462
+
463
+ # Consider the following example:
464
+ #
465
+ # class Person
466
+ # has_many :articles
467
+ # has_many :comment_tags, through: :articles
468
+ # end
469
+ #
470
+ # class Article
471
+ # has_many :comments
472
+ # has_many :comment_tags, through: :comments, source: :tags
473
+ # end
357
474
  #
358
- # [:singularized, :pluralized]
475
+ # class Comment
476
+ # has_many :tags
477
+ # end
478
+ #
479
+ # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
480
+ # but only Comment.tags will be represented in the #chain. So this method creates an array
481
+ # of scopes corresponding to the chain.
482
+ def scope_chain
483
+ @scope_chain ||= begin
484
+ scope_chain = source_reflection.scope_chain.map(&:dup)
485
+
486
+ # Add to it the scope from this reflection (if any)
487
+ scope_chain.first << scope if scope
488
+
489
+ through_scope_chain = through_reflection.scope_chain
490
+
491
+ if options[:source_type]
492
+ through_scope_chain.first <<
493
+ through_reflection.klass.where(foreign_type => options[:source_type])
494
+ end
495
+
496
+ # Recursively fill out the rest of the array from the through reflection
497
+ scope_chain + through_scope_chain
498
+ end
499
+ end
500
+
501
+ # The macro used by the source association
502
+ def source_macro
503
+ source_reflection.source_macro
504
+ end
505
+
506
+ # A through association is nested if there would be more than one join table
507
+ def nested?
508
+ chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
509
+ end
510
+
511
+ # We want to use the klass from this reflection, rather than just delegate straight to
512
+ # the source_reflection, because the source_reflection may be polymorphic. We still
513
+ # need to respect the source_reflection's :primary_key option, though.
514
+ def association_primary_key(klass = nil)
515
+ # Get the "actual" source reflection if the immediate source reflection has a
516
+ # source reflection itself
517
+ source_reflection = self.source_reflection
518
+ while source_reflection.source_reflection
519
+ source_reflection = source_reflection.source_reflection
520
+ end
521
+
522
+ source_reflection.options[:primary_key] || primary_key(klass || self.klass)
523
+ end
524
+
525
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
526
+ #
527
+ # class Post < ActiveRecord::Base
528
+ # has_many :taggings
529
+ # has_many :tags, through: :taggings
530
+ # end
531
+ #
532
+ # tags_reflection = Post.reflect_on_association(:tags)
533
+ # tags_reflection.source_reflection_names
534
+ # # => [:tag, :tags]
359
535
  #
360
536
  def source_reflection_names
361
537
  @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
362
538
  end
363
539
 
540
+ def source_options
541
+ source_reflection.options
542
+ end
543
+
544
+ def through_options
545
+ through_reflection.options
546
+ end
547
+
364
548
  def check_validity!
365
549
  if through_reflection.nil?
366
550
  raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
367
551
  end
368
552
 
553
+ if through_reflection.options[:polymorphic]
554
+ raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
555
+ end
556
+
369
557
  if source_reflection.nil?
370
558
  raise HasManyThroughSourceAssociationNotFoundError.new(self)
371
559
  end
@@ -375,24 +563,16 @@ module ActiveRecord
375
563
  end
376
564
 
377
565
  if source_reflection.options[:polymorphic] && options[:source_type].nil?
378
- raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
566
+ raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
379
567
  end
380
568
 
381
- unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
382
- raise HasManyThroughSourceAssociationMacroError.new(self)
569
+ if macro == :has_one && through_reflection.collection?
570
+ raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
383
571
  end
384
572
 
385
573
  check_validity_of_inverse!
386
574
  end
387
575
 
388
- def through_reflection_primary_key
389
- through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
390
- end
391
-
392
- def through_reflection_primary_key_name
393
- through_reflection.primary_key_name if through_reflection.belongs_to?
394
- end
395
-
396
576
  private
397
577
  def derive_class_name
398
578
  # get the class_name of the belongs_to association of the through reflection