activerecord 1.14.4 → 1.15.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 (159) hide show
  1. data/CHANGELOG +400 -1
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +21 -3
  4. data/Rakefile +55 -10
  5. data/lib/active_record.rb +10 -4
  6. data/lib/active_record/acts/list.rb +15 -4
  7. data/lib/active_record/acts/nested_set.rb +11 -12
  8. data/lib/active_record/acts/tree.rb +13 -14
  9. data/lib/active_record/aggregations.rb +46 -22
  10. data/lib/active_record/associations.rb +213 -162
  11. data/lib/active_record/associations/association_collection.rb +45 -15
  12. data/lib/active_record/associations/association_proxy.rb +32 -13
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
  14. data/lib/active_record/associations/has_many_association.rb +37 -17
  15. data/lib/active_record/associations/has_many_through_association.rb +120 -30
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +75 -0
  18. data/lib/active_record/base.rb +282 -203
  19. data/lib/active_record/calculations.rb +95 -54
  20. data/lib/active_record/callbacks.rb +13 -24
  21. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  28. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
  29. data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
  30. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  31. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
  32. data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
  33. data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
  35. data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
  36. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
  37. data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
  38. data/lib/active_record/deprecated_associations.rb +24 -10
  39. data/lib/active_record/deprecated_finders.rb +4 -1
  40. data/lib/active_record/fixtures.rb +37 -23
  41. data/lib/active_record/locking/optimistic.rb +106 -0
  42. data/lib/active_record/locking/pessimistic.rb +77 -0
  43. data/lib/active_record/migration.rb +8 -5
  44. data/lib/active_record/observer.rb +73 -34
  45. data/lib/active_record/reflection.rb +21 -7
  46. data/lib/active_record/schema_dumper.rb +33 -5
  47. data/lib/active_record/timestamp.rb +23 -34
  48. data/lib/active_record/transactions.rb +37 -30
  49. data/lib/active_record/validations.rb +46 -30
  50. data/lib/active_record/vendor/mysql.rb +20 -5
  51. data/lib/active_record/version.rb +2 -2
  52. data/lib/active_record/wrappings.rb +1 -2
  53. data/lib/active_record/xml_serialization.rb +308 -0
  54. data/test/aaa_create_tables_test.rb +5 -1
  55. data/test/abstract_unit.rb +18 -8
  56. data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
  57. data/test/adapter_test.rb +9 -7
  58. data/test/adapter_test_sqlserver.rb +81 -0
  59. data/test/aggregations_test.rb +29 -0
  60. data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
  61. data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
  62. data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
  63. data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
  64. data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
  65. data/test/associations_test.rb +339 -45
  66. data/test/attribute_methods_test.rb +49 -0
  67. data/test/base_test.rb +321 -67
  68. data/test/calculations_test.rb +48 -10
  69. data/test/callbacks_test.rb +13 -0
  70. data/test/connection_test_firebird.rb +8 -0
  71. data/test/connections/native_db2/connection.rb +18 -17
  72. data/test/connections/native_firebird/connection.rb +19 -17
  73. data/test/connections/native_frontbase/connection.rb +27 -0
  74. data/test/connections/native_mysql/connection.rb +18 -15
  75. data/test/connections/native_openbase/connection.rb +14 -15
  76. data/test/connections/native_oracle/connection.rb +16 -12
  77. data/test/connections/native_postgresql/connection.rb +16 -17
  78. data/test/connections/native_sqlite/connection.rb +3 -6
  79. data/test/connections/native_sqlite3/connection.rb +3 -6
  80. data/test/connections/native_sqlserver/connection.rb +16 -17
  81. data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
  82. data/test/connections/native_sybase/connection.rb +16 -17
  83. data/test/datatype_test_postgresql.rb +52 -0
  84. data/test/defaults_test.rb +52 -10
  85. data/test/deprecated_associations_test.rb +151 -107
  86. data/test/deprecated_finder_test.rb +83 -66
  87. data/test/empty_date_time_test.rb +25 -0
  88. data/test/finder_test.rb +118 -11
  89. data/test/fixtures/accounts.yml +6 -1
  90. data/test/fixtures/author.rb +27 -4
  91. data/test/fixtures/categorizations.yml +8 -2
  92. data/test/fixtures/category.rb +1 -2
  93. data/test/fixtures/comments.yml +0 -6
  94. data/test/fixtures/companies.yml +6 -1
  95. data/test/fixtures/company.rb +23 -1
  96. data/test/fixtures/company_in_module.rb +8 -10
  97. data/test/fixtures/customer.rb +2 -2
  98. data/test/fixtures/customers.yml +9 -0
  99. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  100. data/test/fixtures/db_definitions/db2.sql +9 -0
  101. data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
  102. data/test/fixtures/db_definitions/firebird.sql +13 -1
  103. data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
  104. data/test/fixtures/db_definitions/frontbase.sql +262 -0
  105. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  107. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  108. data/test/fixtures/db_definitions/mysql.sql +23 -14
  109. data/test/fixtures/db_definitions/openbase.sql +13 -1
  110. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  111. data/test/fixtures/db_definitions/oracle.sql +29 -2
  112. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  113. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  114. data/test/fixtures/db_definitions/schema.rb +29 -1
  115. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  116. data/test/fixtures/db_definitions/sqlite.sql +12 -3
  117. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  118. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  119. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  120. data/test/fixtures/db_definitions/sybase.sql +13 -4
  121. data/test/fixtures/developer.rb +12 -0
  122. data/test/fixtures/edge.rb +5 -0
  123. data/test/fixtures/edges.yml +6 -0
  124. data/test/fixtures/funny_jokes.yml +3 -7
  125. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  126. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  127. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  128. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  129. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  130. data/test/fixtures/mixin.rb +15 -0
  131. data/test/fixtures/mixins.yml +38 -0
  132. data/test/fixtures/post.rb +3 -2
  133. data/test/fixtures/project.rb +3 -1
  134. data/test/fixtures/topic.rb +6 -1
  135. data/test/fixtures/topics.yml +4 -4
  136. data/test/fixtures/vertex.rb +9 -0
  137. data/test/fixtures/vertices.yml +4 -0
  138. data/test/fixtures_test.rb +45 -0
  139. data/test/inheritance_test.rb +67 -6
  140. data/test/lifecycle_test.rb +40 -19
  141. data/test/locking_test.rb +170 -26
  142. data/test/method_scoping_test.rb +2 -2
  143. data/test/migration_test.rb +387 -110
  144. data/test/migration_test_firebird.rb +124 -0
  145. data/test/mixin_nested_set_test.rb +14 -2
  146. data/test/mixin_test.rb +56 -18
  147. data/test/modules_test.rb +8 -2
  148. data/test/multiple_db_test.rb +2 -2
  149. data/test/pk_test.rb +1 -0
  150. data/test/reflection_test.rb +8 -2
  151. data/test/schema_authorization_test_postgresql.rb +75 -0
  152. data/test/schema_dumper_test.rb +40 -4
  153. data/test/table_name_test_sqlserver.rb +23 -0
  154. data/test/threaded_connections_test.rb +19 -16
  155. data/test/transactions_test.rb +86 -72
  156. data/test/validations_test.rb +126 -56
  157. data/test/xml_serialization_test.rb +125 -0
  158. metadata +45 -11
  159. data/lib/active_record/locking.rb +0 -79
@@ -10,65 +10,54 @@ require 'active_record/deprecated_associations'
10
10
 
11
11
  module ActiveRecord
12
12
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
13
- def initialize(reflection)
14
- @reflection = reflection
15
- end
16
-
17
- def message
18
- "Could not find the association #{@reflection.options[:through].inspect} in model #{@reflection.klass}"
13
+ def initialize(owner_class_name, reflection)
14
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
19
15
  end
20
16
  end
21
17
 
22
18
  class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
23
19
  def initialize(owner_class_name, reflection, source_reflection)
24
- @owner_class_name = owner_class_name
25
- @reflection = reflection
26
- @source_reflection = source_reflection
27
- end
28
-
29
- def message
30
- "Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'."
20
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
31
21
  end
32
22
  end
33
23
 
34
24
  class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
35
25
  def initialize(reflection)
36
- @reflection = reflection
37
- @through_reflection = reflection.through_reflection
38
- @source_reflection_names = reflection.source_reflection_names
39
- @source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
40
- end
41
-
42
- def message
43
- "Could not find the source association(s) #{@source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{@through_reflection.klass}. Try 'has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}, :source => <name>'. Is it one of #{@source_associations.to_sentence :connector => 'or'}?"
26
+ through_reflection = reflection.through_reflection
27
+ source_reflection_names = reflection.source_reflection_names
28
+ source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
29
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
44
30
  end
45
31
  end
46
32
 
47
- class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc
33
+ class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
48
34
  def initialize(reflection)
49
- @reflection = reflection
50
- @through_reflection = reflection.through_reflection
51
- @source_reflection = reflection.source_reflection
35
+ through_reflection = reflection.through_reflection
36
+ source_reflection = reflection.source_reflection
37
+ super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
52
38
  end
53
-
54
- def message
55
- "Invalid source reflection macro :#{@source_reflection.macro}#{" :through" if @source_reflection.options[:through]} for has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}. Use :source to specify the source reflection."
39
+ end
40
+
41
+ class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
42
+ def initialize(owner, reflection)
43
+ super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
56
44
  end
57
45
  end
58
46
 
59
47
  class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
60
48
  def initialize(reflection)
61
- @reflection = reflection
49
+ super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
62
50
  end
63
-
64
- def message
65
- "Can not eagerly load the polymorphic association #{@reflection.name.inspect}"
51
+ end
52
+
53
+ class ReadOnlyAssociation < ActiveRecordError #:nodoc:
54
+ def initialize(reflection)
55
+ super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
66
56
  end
67
57
  end
68
58
 
69
59
  module Associations # :nodoc:
70
- def self.append_features(base)
71
- super
60
+ def self.included(base)
72
61
  base.extend(ClassMethods)
73
62
  end
74
63
 
@@ -95,7 +84,7 @@ module ActiveRecord
95
84
  # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
96
85
  # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
97
86
  # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
98
- # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
87
+ # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt>
99
88
  # <tt>Project#milestones.build, Project#milestones.create</tt>
100
89
  # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
101
90
  # <tt>Project#categories.delete(category1)</tt>
@@ -109,25 +98,27 @@ module ActiveRecord
109
98
  # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
110
99
  # saying belongs_to. Example:
111
100
  #
112
- # class Post < ActiveRecord::Base
113
- # has_one :author
101
+ # class User < ActiveRecord::Base
102
+ # # I reference an account.
103
+ # belongs_to :account
114
104
  # end
115
105
  #
116
- # class Author < ActiveRecord::Base
117
- # belongs_to :post
106
+ # class Account < ActiveRecord::Base
107
+ # # One user references me.
108
+ # has_one :user
118
109
  # end
119
110
  #
120
111
  # The tables for these classes could look something like:
121
112
  #
122
- # CREATE TABLE posts (
113
+ # CREATE TABLE users (
123
114
  # id int(11) NOT NULL auto_increment,
124
- # title varchar default NULL,
115
+ # account_id int(11) default NULL,
116
+ # name varchar default NULL,
125
117
  # PRIMARY KEY (id)
126
118
  # )
127
119
  #
128
- # CREATE TABLE authors (
120
+ # CREATE TABLE accounts (
129
121
  # id int(11) NOT NULL auto_increment,
130
- # post_id int(11) default NULL,
131
122
  # name varchar default NULL,
132
123
  # PRIMARY KEY (id)
133
124
  # )
@@ -215,6 +206,21 @@ module ActiveRecord
215
206
  # has_many :people, :extend => FindOrCreateByNameExtension
216
207
  # end
217
208
  #
209
+ # If you need to use multiple named extension modules, you can specify an array of modules with the :extend option.
210
+ # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
211
+ # those earlier in the array. Example:
212
+ #
213
+ # class Account < ActiveRecord::Base
214
+ # has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
215
+ # end
216
+ #
217
+ # Some extensions can only be made to work with knowledge of the association proxy's internals.
218
+ # Extensions can access relevant state using accessors on the association proxy:
219
+ #
220
+ # * +proxy_owner+ - Returns the object the association is part of.
221
+ # * +proxy_reflection+ - Returns the reflection object that describes the association.
222
+ # * +proxy_target+ - Returns the associated object for belongs_to and has_one, or the collection of associated objects for has_many and has_and_belongs_to_many.
223
+ #
218
224
  # === Association Join Models
219
225
  #
220
226
  # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This
@@ -273,6 +279,30 @@ module ActiveRecord
273
279
  # This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
274
280
  # an attachable_id integer column and an attachable_type string column.
275
281
  #
282
+ # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
283
+ # for the associations to work as expected, ensure that you store the base model for the STI models in the
284
+ # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
285
+ # and member posts that use the posts table for STI. So there will be an additional 'type' column in the posts table.
286
+ #
287
+ # class Asset < ActiveRecord::Base
288
+ # belongs_to :attachable, :polymorphic => true
289
+ #
290
+ # def attachable_type=(sType)
291
+ # super(sType.to_s.classify.constantize.base_class.to_s)
292
+ # end
293
+ # end
294
+ #
295
+ # class Post < ActiveRecord::Base
296
+ # # because we store "Post" in attachable_type now :dependent => :destroy will work
297
+ # has_many :assets, :as => :attachable, :dependent => :destroy
298
+ # end
299
+ #
300
+ # class GuestPost < ActiveRecord::Base
301
+ # end
302
+ #
303
+ # class MemberPost < ActiveRecord::Base
304
+ # end
305
+ #
276
306
  # == Caching
277
307
  #
278
308
  # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
@@ -319,22 +349,17 @@ module ActiveRecord
319
349
  # But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
320
350
  # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
321
351
  # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
352
+ #
353
+ # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
354
+ # :order => "posts.id DESC" will work while :order => "id DESC" will not. Because eager loading generates the SELECT statement too, the
355
+ # :select option is ignored.
322
356
  #
323
- # Please note that limited eager loading with has_many and has_and_belongs_to_many associations is not compatible with describing conditions
324
- # on these eager tables. This will work:
325
- #
326
- # Post.find(:all, :include => :comments, :conditions => "posts.title = 'magic forest'", :limit => 2)
327
- #
328
- # ...but this will not (and an ArgumentError will be raised):
329
- #
330
- # Post.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%'", :limit => 2)
331
- #
332
- # Also have in mind that since the eager loading is pulling from multiple tables, you'll have to disambiguate any column references
333
- # in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that
334
- # you alter the :order and :conditions on the association definitions themselves.
335
- #
336
- # It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will not pull
337
- # additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading.
357
+ # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
358
+ # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
359
+ # associations" with has_and_belongs_to_many are not a good fit for eager loading.
360
+ #
361
+ # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
362
+ # before the actual model exists.
338
363
  #
339
364
  # == Table Aliasing
340
365
  #
@@ -432,6 +457,7 @@ module ActiveRecord
432
457
  # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
433
458
  # This will also destroy the objects if they're declared as belongs_to and dependent on this model.
434
459
  # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
460
+ # * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
435
461
  # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
436
462
  # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
437
463
  # are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
@@ -451,6 +477,7 @@ module ActiveRecord
451
477
  # * <tt>Firm#clients<<</tt>
452
478
  # * <tt>Firm#clients.delete</tt>
453
479
  # * <tt>Firm#clients=</tt>
480
+ # * <tt>Firm#client_ids</tt>
454
481
  # * <tt>Firm#client_ids=</tt>
455
482
  # * <tt>Firm#clients.clear</tt>
456
483
  # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
@@ -502,6 +529,7 @@ module ActiveRecord
502
529
  # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
503
530
  # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
504
531
  # +:subscriber+ on +Subscription+, unless a +:source+ is given.
532
+ # * <tt>:uniq</tt> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
505
533
  #
506
534
  # Option examples:
507
535
  # has_many :comments, :order => "posted_on"
@@ -562,25 +590,28 @@ module ActiveRecord
562
590
  # sql fragment, such as "rank = 5".
563
591
  # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
564
592
  # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
565
- # * <tt>:dependent</tt> - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also,
566
- # association is assigned.
593
+ # * <tt>:dependent</tt> - if set to :destroy (or true) the associated object is destroyed when this object is. If set to
594
+ # :delete the associated object is deleted *without* calling its destroy method. If set to :nullify the associated
595
+ # object's foreign key is set to NULL. Also, association is assigned.
567
596
  # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
568
597
  # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
569
598
  # as the default foreign_key.
570
599
  # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
571
- #
600
+ # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
601
+ #
572
602
  # Option examples:
573
603
  # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
574
604
  # has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it
575
605
  # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
576
606
  # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
607
+ # has_one :attachment, :as => :attachable
577
608
  def has_one(association_id, options = {})
578
609
  reflection = create_has_one_reflection(association_id, options)
579
610
 
580
611
  module_eval do
581
612
  after_save <<-EOF
582
613
  association = instance_variable_get("@#{reflection.name}")
583
- unless association.nil?
614
+ if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
584
615
  association["#{reflection.primary_key_name}"] = id
585
616
  association.save(true)
586
617
  end
@@ -644,6 +675,12 @@ module ActiveRecord
644
675
  # :conditions => 'discounts > #{payments_count}'
645
676
  # belongs_to :attachable, :polymorphic => true
646
677
  def belongs_to(association_id, options = {})
678
+ if options.include?(:class_name) && !options.include?(:foreign_key)
679
+ ::ActiveSupport::Deprecation.warn(
680
+ "The inferred foreign_key name will change in Rails 2.0 to use the association name instead of its class name when they differ. When using :class_name in belongs_to, use the :foreign_key option to explicitly set the key name to avoid problems in the transition.",
681
+ caller)
682
+ end
683
+
647
684
  reflection = create_belongs_to_reflection(association_id, options)
648
685
 
649
686
  if reflection.options[:polymorphic]
@@ -652,7 +689,7 @@ module ActiveRecord
652
689
  module_eval do
653
690
  before_save <<-EOF
654
691
  association = instance_variable_get("@#{reflection.name}")
655
- if !association.nil?
692
+ if association && association.target
656
693
  if association.new_record?
657
694
  association.save(true)
658
695
  end
@@ -708,7 +745,13 @@ module ActiveRecord
708
745
 
709
746
  # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
710
747
  # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
711
- # will give the default join table name of "developers_projects" because "D" outranks "P".
748
+ # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
749
+ # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
750
+ # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
751
+ # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
752
+ # to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>,
753
+ # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the
754
+ # custom <tt>join_table</tt> option if you need to.
712
755
  #
713
756
  # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
714
757
  # has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as
@@ -729,24 +772,31 @@ module ActiveRecord
729
772
  # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
730
773
  # This does not destroy the objects.
731
774
  # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
775
+ # * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
732
776
  # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
733
777
  # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
734
778
  # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
735
779
  # * <tt>collection.size</tt> - returns the number of associated objects.
736
780
  # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
737
781
  # meets the condition that it has to be associated with this object.
782
+ # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
783
+ # with +attributes+ and linked to this object through the join table but has not yet been saved.
784
+ # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
785
+ # with +attributes+ and linked to this object through the join table and that has already been saved (if it passed the validation).
738
786
  #
739
787
  # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
740
788
  # * <tt>Developer#projects</tt>
741
789
  # * <tt>Developer#projects<<</tt>
742
- # * <tt>Developer#projects.push_with_attributes</tt>
743
790
  # * <tt>Developer#projects.delete</tt>
744
791
  # * <tt>Developer#projects=</tt>
792
+ # * <tt>Developer#project_ids</tt>
745
793
  # * <tt>Developer#project_ids=</tt>
746
794
  # * <tt>Developer#projects.clear</tt>
747
795
  # * <tt>Developer#projects.empty?</tt>
748
796
  # * <tt>Developer#projects.size</tt>
749
797
  # * <tt>Developer#projects.find(id)</tt>
798
+ # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
799
+ # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
750
800
  # The declaration may include an options hash to specialize the behavior of the association.
751
801
  #
752
802
  # Options are:
@@ -831,14 +881,14 @@ module ActiveRecord
831
881
  if association.nil? || force_reload
832
882
  association = association_proxy_class.new(self, reflection)
833
883
  retval = association.reload
834
- unless retval.nil?
835
- instance_variable_set("@#{reflection.name}", association)
836
- else
884
+ if retval.nil? and association_proxy_class == BelongsToAssociation
837
885
  instance_variable_set("@#{reflection.name}", nil)
838
886
  return nil
839
887
  end
888
+ instance_variable_set("@#{reflection.name}", association)
840
889
  end
841
- association
890
+
891
+ association.target.nil? ? nil : association
842
892
  end
843
893
 
844
894
  define_method("#{reflection.name}=") do |new_value|
@@ -860,7 +910,7 @@ module ActiveRecord
860
910
  end
861
911
 
862
912
  define_method("set_#{reflection.name}_target") do |target|
863
- return if target.nil?
913
+ return if target.nil? and association_proxy_class == BelongsToAssociation
864
914
  association = association_proxy_class.new(self, reflection)
865
915
  association.target = target
866
916
  instance_variable_set("@#{reflection.name}", association)
@@ -887,22 +937,20 @@ module ActiveRecord
887
937
  collection_reader_method(reflection, association_proxy_class)
888
938
 
889
939
  define_method("#{reflection.name}=") do |new_value|
890
- association = instance_variable_get("@#{reflection.name}")
891
- unless association.respond_to?(:loaded?)
892
- association = association_proxy_class.new(self, reflection)
893
- instance_variable_set("@#{reflection.name}", association)
894
- end
940
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
941
+ association = send(reflection.name)
895
942
  association.replace(new_value)
896
943
  association
897
944
  end
898
945
 
899
- define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
900
- send("#{reflection.name}=", reflection.class_name.constantize.find(new_value))
946
+ define_method("#{reflection.name.to_s.singularize}_ids") do
947
+ send(reflection.name).map(&:id)
901
948
  end
902
- end
903
949
 
904
- def require_association_class(class_name)
905
- require_association(Inflector.underscore(class_name)) if class_name
950
+ define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
951
+ ids = (new_value || []).reject { |nid| nid.blank? }
952
+ send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
953
+ end
906
954
  end
907
955
 
908
956
  def add_multiple_associated_save_callbacks(association_name)
@@ -961,14 +1009,6 @@ module ActiveRecord
961
1009
  end
962
1010
  end
963
1011
 
964
- def count_with_associations(options = {})
965
- catch :invalid_query do
966
- join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
967
- return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
968
- end
969
- 0
970
- end
971
-
972
1012
  def find_with_associations(options = {})
973
1013
  catch :invalid_query do
974
1014
  join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
@@ -979,13 +1019,17 @@ module ActiveRecord
979
1019
  end
980
1020
 
981
1021
  def configure_dependency_for_has_many(reflection)
1022
+ if reflection.options[:dependent] == true
1023
+ ::ActiveSupport::Deprecation.warn("The :dependent => true option is deprecated and will be removed from Rails 2.0. Please use :dependent => :destroy instead. See http://www.rubyonrails.org/deprecation for details.", caller)
1024
+ end
1025
+
982
1026
  if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
983
1027
  raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
984
1028
  end
985
1029
 
986
1030
  if reflection.options[:exclusively_dependent]
987
1031
  reflection.options[:dependent] = :delete_all
988
- #warn "The :exclusively_dependent option is deprecated. Please use :dependent => :delete_all instead.")
1032
+ ::ActiveSupport::Deprecation.warn("The :exclusively_dependent option is deprecated and will be removed from Rails 2.0. Please use :dependent => :delete_all instead. See http://www.rubyonrails.org/deprecation for details.", caller)
989
1033
  end
990
1034
 
991
1035
  # See HasManyAssociation#delete_records. Dependent associations
@@ -998,7 +1042,7 @@ module ActiveRecord
998
1042
  end
999
1043
 
1000
1044
  case reflection.options[:dependent]
1001
- when :destroy, true
1045
+ when :destroy, true
1002
1046
  module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
1003
1047
  when :delete_all
1004
1048
  module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
@@ -1007,20 +1051,22 @@ module ActiveRecord
1007
1051
  when nil, false
1008
1052
  # pass
1009
1053
  else
1010
- raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
1054
+ raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
1011
1055
  end
1012
1056
  end
1013
-
1057
+
1014
1058
  def configure_dependency_for_has_one(reflection)
1015
1059
  case reflection.options[:dependent]
1016
1060
  when :destroy, true
1017
1061
  module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
1062
+ when :delete
1063
+ module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
1018
1064
  when :nullify
1019
- module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
1065
+ module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil) unless #{reflection.name}.nil?'"
1020
1066
  when nil, false
1021
1067
  # pass
1022
1068
  else
1023
- raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
1069
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify."
1024
1070
  end
1025
1071
  end
1026
1072
 
@@ -1042,6 +1088,7 @@ module ActiveRecord
1042
1088
  :exclusively_dependent, :dependent,
1043
1089
  :select, :conditions, :include, :order, :group, :limit, :offset,
1044
1090
  :as, :through, :source,
1091
+ :uniq,
1045
1092
  :finder_sql, :counter_sql,
1046
1093
  :before_add, :after_add, :before_remove, :after_remove,
1047
1094
  :extend
@@ -1079,7 +1126,8 @@ module ActiveRecord
1079
1126
  options.assert_valid_keys(
1080
1127
  :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
1081
1128
  :select, :conditions, :include, :order, :group, :limit, :offset,
1082
- :finder_sql, :delete_sql, :insert_sql, :uniq,
1129
+ :uniq,
1130
+ :finder_sql, :delete_sql, :insert_sql,
1083
1131
  :before_add, :after_add, :before_remove, :after_remove,
1084
1132
  :extend
1085
1133
  )
@@ -1112,31 +1160,6 @@ module ActiveRecord
1112
1160
  "#{name} Load Including Associations"
1113
1161
  )
1114
1162
  end
1115
-
1116
- def construct_counter_sql_with_included_associations(options, join_dependency)
1117
- scope = scope(:find)
1118
- sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
1119
-
1120
- # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
1121
- if !Base.connection.supports_count_distinct?
1122
- sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"
1123
- end
1124
-
1125
- sql << " FROM #{table_name} "
1126
- sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1127
-
1128
- add_joins!(sql, options, scope)
1129
- add_conditions!(sql, options[:conditions], scope)
1130
- add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
1131
-
1132
- add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
1133
-
1134
- if !Base.connection.supports_count_distinct?
1135
- sql << ")"
1136
- end
1137
-
1138
- return sanitize_sql(sql)
1139
- end
1140
1163
 
1141
1164
  def construct_finder_sql_with_included_associations(options, join_dependency)
1142
1165
  scope = scope(:find)
@@ -1145,11 +1168,13 @@ module ActiveRecord
1145
1168
 
1146
1169
  add_joins!(sql, options, scope)
1147
1170
  add_conditions!(sql, options[:conditions], scope)
1148
- add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
1171
+ add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
1149
1172
 
1150
- sql << "ORDER BY #{options[:order]} " if options[:order]
1173
+ sql << "GROUP BY #{options[:group]} " if options[:group]
1151
1174
 
1175
+ add_order!(sql, options[:order], scope)
1152
1176
  add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
1177
+ add_lock!(sql, options, scope)
1153
1178
 
1154
1179
  return sanitize_sql(sql)
1155
1180
  end
@@ -1168,26 +1193,35 @@ module ActiveRecord
1168
1193
  "#{name} Load IDs For Limited Eager Loading"
1169
1194
  ).collect { |row| connection.quote(row[primary_key]) }.join(", ")
1170
1195
  end
1171
-
1196
+
1172
1197
  def construct_finder_sql_for_association_limiting(options, join_dependency)
1173
- scope = scope(:find)
1198
+ scope = scope(:find)
1199
+ is_distinct = include_eager_conditions?(options) || include_eager_order?(options)
1174
1200
  sql = "SELECT "
1175
- sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options)
1176
- sql << primary_key
1177
- sql << ", #{options[:order].split(',').collect { |s| s.split.first } * ', '}" if options[:order] && (include_eager_conditions?(options) || include_eager_order?(options))
1201
+ if is_distinct
1202
+ sql << connection.distinct("#{table_name}.#{primary_key}", options[:order])
1203
+ else
1204
+ sql << primary_key
1205
+ end
1178
1206
  sql << " FROM #{table_name} "
1179
-
1180
- if include_eager_conditions?(options) || include_eager_order?(options)
1181
- sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1207
+
1208
+ if is_distinct
1209
+ sql << join_dependency.join_associations.collect(&:association_join).join
1182
1210
  add_joins!(sql, options, scope)
1183
1211
  end
1184
-
1212
+
1185
1213
  add_conditions!(sql, options[:conditions], scope)
1186
- sql << "ORDER BY #{options[:order]} " if options[:order]
1214
+ if options[:order]
1215
+ if is_distinct
1216
+ connection.add_order_by_for_association_limiting!(sql, options)
1217
+ else
1218
+ sql << "ORDER BY #{options[:order]}"
1219
+ end
1220
+ end
1187
1221
  add_limit!(sql, options, scope)
1188
1222
  return sanitize_sql(sql)
1189
1223
  end
1190
-
1224
+
1191
1225
  # Checks if the conditions reference a table other than the current model table
1192
1226
  def include_eager_conditions?(options)
1193
1227
  # look in both sets of conditions
@@ -1199,7 +1233,7 @@ module ActiveRecord
1199
1233
  end
1200
1234
  end
1201
1235
  return false unless conditions.any?
1202
- conditions.join(' ').scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name|
1236
+ conditions.join(' ').scan(/([\.\w]+)\.\w+/).flatten.any? do |condition_table_name|
1203
1237
  condition_table_name != table_name
1204
1238
  end
1205
1239
  end
@@ -1208,7 +1242,7 @@ module ActiveRecord
1208
1242
  def include_eager_order?(options)
1209
1243
  order = options[:order]
1210
1244
  return false unless order
1211
- order.scan(/(\w+)\.\w+/).flatten.any? do |order_table_name|
1245
+ order.scan(/([\.\w]+)\.\w+/).flatten.any? do |order_table_name|
1212
1246
  order_table_name != table_name
1213
1247
  end
1214
1248
  end
@@ -1225,7 +1259,7 @@ module ActiveRecord
1225
1259
  def add_association_callbacks(association_name, options)
1226
1260
  callbacks = %w(before_add after_add before_remove after_remove)
1227
1261
  callbacks.each do |callback_name|
1228
- full_callback_name = "#{callback_name.to_s}_for_#{association_name.to_s}"
1262
+ full_callback_name = "#{callback_name}_for_#{association_name}"
1229
1263
  defined_callbacks = options[callback_name.to_sym]
1230
1264
  if options.has_key?(callback_name.to_sym)
1231
1265
  class_inheritable_reader full_callback_name.to_sym
@@ -1248,7 +1282,7 @@ module ActiveRecord
1248
1282
  extension_module_name.constantize
1249
1283
  end
1250
1284
 
1251
- class JoinDependency
1285
+ class JoinDependency # :nodoc:
1252
1286
  attr_reader :joins, :reflections, :table_aliases
1253
1287
 
1254
1288
  def initialize(base, associations, joins)
@@ -1334,11 +1368,15 @@ module ActiveRecord
1334
1368
  when :has_many, :has_and_belongs_to_many
1335
1369
  collection = record.send(join.reflection.name)
1336
1370
  collection.loaded
1337
-
1371
+
1338
1372
  return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1339
1373
  association = join.instantiate(row)
1340
1374
  collection.target.push(association) unless collection.target.include?(association)
1341
- when :has_one, :belongs_to
1375
+ when :has_one
1376
+ return if record.id.to_s != join.parent.record_id(row).to_s
1377
+ association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
1378
+ record.send("set_#{join.reflection.name}_target", association)
1379
+ when :belongs_to
1342
1380
  return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1343
1381
  association = join.instantiate(row)
1344
1382
  record.send("set_#{join.reflection.name}_target", association)
@@ -1348,7 +1386,7 @@ module ActiveRecord
1348
1386
  return association
1349
1387
  end
1350
1388
 
1351
- class JoinBase
1389
+ class JoinBase # :nodoc:
1352
1390
  attr_reader :active_record, :table_joins
1353
1391
  delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
1354
1392
 
@@ -1393,7 +1431,7 @@ module ActiveRecord
1393
1431
  end
1394
1432
  end
1395
1433
 
1396
- class JoinAssociation < JoinBase
1434
+ class JoinAssociation < JoinBase # :nodoc:
1397
1435
  attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
1398
1436
  delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
1399
1437
 
@@ -1407,7 +1445,7 @@ module ActiveRecord
1407
1445
  @parent = parent
1408
1446
  @reflection = reflection
1409
1447
  @aliased_prefix = "t#{ join_dependency.joins.size }"
1410
- @aliased_table_name = table_name # start with the table name
1448
+ @aliased_table_name = table_name #.tr('.', '_') # start with the table name, sub out any .'s
1411
1449
  @parent_table_name = parent.active_record.table_name
1412
1450
 
1413
1451
  if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
@@ -1418,18 +1456,22 @@ module ActiveRecord
1418
1456
  # if the table name has been used, then use an alias
1419
1457
  @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
1420
1458
  table_index = join_dependency.table_aliases[aliased_table_name]
1459
+ join_dependency.table_aliases[aliased_table_name] += 1
1421
1460
  @aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
1461
+ else
1462
+ join_dependency.table_aliases[aliased_table_name] += 1
1422
1463
  end
1423
- join_dependency.table_aliases[aliased_table_name] += 1
1424
1464
 
1425
1465
  if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
1426
1466
  @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
1427
1467
  unless join_dependency.table_aliases[aliased_join_table_name].zero?
1428
1468
  @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
1429
1469
  table_index = join_dependency.table_aliases[aliased_join_table_name]
1470
+ join_dependency.table_aliases[aliased_join_table_name] += 1
1430
1471
  @aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
1472
+ else
1473
+ join_dependency.table_aliases[aliased_join_table_name] += 1
1431
1474
  end
1432
- join_dependency.table_aliases[aliased_join_table_name] += 1
1433
1475
  end
1434
1476
  end
1435
1477
 
@@ -1440,7 +1482,7 @@ module ActiveRecord
1440
1482
  table_alias_for(options[:join_table], aliased_join_table_name),
1441
1483
  aliased_join_table_name,
1442
1484
  options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
1443
- reflection.active_record.table_name, reflection.active_record.primary_key] +
1485
+ parent.aliased_table_name, reflection.active_record.primary_key] +
1444
1486
  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1445
1487
  table_name_and_alias, aliased_table_name, klass.primary_key,
1446
1488
  aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
@@ -1457,7 +1499,7 @@ module ActiveRecord
1457
1499
  table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1458
1500
  aliased_join_table_name, polymorphic_foreign_key,
1459
1501
  parent.aliased_table_name, parent.primary_key,
1460
- aliased_join_table_name, polymorphic_foreign_type, klass.quote(parent.active_record.base_class.name)] +
1502
+ aliased_join_table_name, polymorphic_foreign_type, klass.quote_value(parent.active_record.base_class.name)] +
1461
1503
  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
1462
1504
  aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
1463
1505
  ]
@@ -1472,23 +1514,28 @@ module ActiveRecord
1472
1514
  aliased_table_name, "#{source_reflection.options[:as]}_id",
1473
1515
  aliased_join_table_name, options[:foreign_key] || primary_key,
1474
1516
  aliased_table_name, "#{source_reflection.options[:as]}_type",
1475
- klass.quote(source_reflection.active_record.base_class.name)
1517
+ klass.quote_value(source_reflection.active_record.base_class.name)
1476
1518
  ]
1477
1519
  else
1478
1520
  case source_reflection.macro
1479
1521
  when :belongs_to
1480
1522
  first_key = primary_key
1481
- second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
1523
+ second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
1524
+ extra = nil
1482
1525
  when :has_many
1483
- first_key = through_reflection.klass.to_s.classify.foreign_key
1526
+ first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
1484
1527
  second_key = options[:foreign_key] || primary_key
1528
+ extra = through_reflection.klass.descends_from_active_record? ? nil :
1529
+ " AND %s.%s = %s" % [
1530
+ aliased_join_table_name,
1531
+ reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
1532
+ through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
1485
1533
  end
1486
-
1487
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1488
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
1489
- through_reflection.primary_key_name,
1490
- parent.aliased_table_name, parent.primary_key] +
1491
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1534
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
1535
+ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1536
+ aliased_join_table_name, through_reflection.primary_key_name,
1537
+ parent.aliased_table_name, parent.primary_key, extra] +
1538
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s) " % [
1492
1539
  table_name_and_alias,
1493
1540
  aliased_table_name, first_key,
1494
1541
  aliased_join_table_name, second_key
@@ -1502,7 +1549,7 @@ module ActiveRecord
1502
1549
  aliased_table_name, "#{reflection.options[:as]}_id",
1503
1550
  parent.aliased_table_name, parent.primary_key,
1504
1551
  aliased_table_name, "#{reflection.options[:as]}_type",
1505
- klass.quote(parent.active_record.base_class.name)
1552
+ klass.quote_value(parent.active_record.base_class.name)
1506
1553
  ]
1507
1554
  when reflection.macro == :has_one && reflection.options[:as]
1508
1555
  " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
@@ -1510,7 +1557,7 @@ module ActiveRecord
1510
1557
  aliased_table_name, "#{reflection.options[:as]}_id",
1511
1558
  parent.aliased_table_name, parent.primary_key,
1512
1559
  aliased_table_name, "#{reflection.options[:as]}_type",
1513
- klass.quote(reflection.active_record.base_class.name)
1560
+ klass.quote_value(reflection.active_record.base_class.name)
1514
1561
  ]
1515
1562
  else
1516
1563
  foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
@@ -1530,9 +1577,13 @@ module ActiveRecord
1530
1577
  end || ''
1531
1578
  join << %(AND %s.%s = %s ) % [
1532
1579
  aliased_table_name,
1533
- reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
1534
- klass.quote(klass.name)] unless klass.descends_from_active_record?
1535
- join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
1580
+ reflection.active_record.connection.quote_column_name(klass.inheritance_column),
1581
+ klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
1582
+
1583
+ [through_reflection, reflection].each do |ref|
1584
+ join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
1585
+ end
1586
+
1536
1587
  join
1537
1588
  end
1538
1589
 
@@ -1550,8 +1601,8 @@ module ActiveRecord
1550
1601
  end
1551
1602
 
1552
1603
  def interpolate_sql(sql)
1553
- instance_eval("%@#{sql.gsub('@', '\@')}@")
1554
- end
1604
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
1605
+ end
1555
1606
  end
1556
1607
  end
1557
1608
  end