activerecord 2.0.2 → 2.0.4

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 (53) hide show
  1. data/CHANGELOG +25 -0
  2. data/README +0 -0
  3. data/Rakefile +5 -3
  4. data/lib/active_record.rb +0 -0
  5. data/lib/active_record/associations.rb +232 -193
  6. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +14 -14
  7. data/lib/active_record/associations/has_many_association.rb +2 -2
  8. data/lib/active_record/associations/has_many_through_association.rb +25 -6
  9. data/lib/active_record/attribute_methods.rb +4 -3
  10. data/lib/active_record/base.rb +75 -41
  11. data/lib/active_record/calculations.rb +2 -1
  12. data/lib/active_record/callbacks.rb +0 -0
  13. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +1 -1
  14. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -6
  15. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +12 -11
  16. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -0
  18. data/lib/active_record/connection_adapters/postgresql_adapter.rb +3 -3
  19. data/lib/active_record/connection_adapters/sqlite_adapter.rb +13 -6
  20. data/lib/active_record/fixtures.rb +29 -31
  21. data/lib/active_record/migration.rb +4 -5
  22. data/lib/active_record/observer.rb +1 -1
  23. data/lib/active_record/transactions.rb +3 -5
  24. data/lib/active_record/validations.rb +5 -3
  25. data/lib/active_record/version.rb +1 -1
  26. data/test/abstract_unit.rb +0 -0
  27. data/test/active_schema_test_mysql.rb +5 -2
  28. data/test/adapter_test.rb +1 -0
  29. data/test/all.sh +0 -0
  30. data/test/associations/callbacks_test.rb +1 -1
  31. data/test/associations/eager_test.rb +5 -0
  32. data/test/associations/join_model_test.rb +11 -3
  33. data/test/associations_test.rb +36 -6
  34. data/test/attribute_methods_test.rb +0 -0
  35. data/test/base_test.rb +92 -10
  36. data/test/calculations_test.rb +9 -1
  37. data/test/debug.log +358 -0
  38. data/test/deprecated_finder_test.rb +0 -0
  39. data/test/fixtures/author.rb +1 -1
  40. data/test/fixtures/company.rb +0 -0
  41. data/test/fixtures/fixture_database.sqlite3 +0 -0
  42. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  43. data/test/fixtures/reply.rb +0 -0
  44. data/test/fixtures/topic.rb +0 -0
  45. data/test/fixtures_test.rb +20 -12
  46. data/test/inheritance_test.rb +0 -0
  47. data/test/lifecycle_test.rb +0 -0
  48. data/test/migration_test.rb +46 -0
  49. data/test/query_cache_test.rb +22 -2
  50. data/test/readonly_test.rb +0 -0
  51. data/test/unconnected_test.rb +0 -0
  52. data/test/validations_test.rb +50 -38
  53. metadata +8 -4
data/CHANGELOG CHANGED
@@ -1,3 +1,28 @@
1
+ *2.0.4* (2nd September 2008)
2
+
3
+ * Migrations: create_table supports primary_key_prefix_type. #10314 [student, thechrisoshow]
4
+
5
+ * Ensure that ActiveRecord::Calculations disambiguates field names with the table name. #11027 [cavalle]
6
+
7
+ * Ensure that modifying has_and_belongs_to_many actions clear the query cache. Closes #10840 [john.andrews]
8
+
9
+ * Fix issue where Table#references doesn't pass a :null option to a *_type attribute for polymorphic associations. Closes #10753 [railsjitsu]
10
+
11
+ * update_all ignores scoped :order and :limit, so post.comments.update_all doesn't try to include the comment order in the update statement. #10686 [Brendan Ribera]
12
+
13
+ * Added by parameter to increment, decrement, and their bang varieties so you can do player1.increment!(:points, 5) #10542 [Sam]
14
+
15
+ * Optimize ActiveRecord::Base#exists? to use #select_all instead of #find. Closes #10605 [jamesh, fcheung, protocool]
16
+
17
+ * Don't unnecessarily load has_many associations in after_update callbacks. Closes #6822 [stopdropandrew, canadaduane]
18
+
19
+ * Eager belongs_to :include infers the foreign key from the association name rather than the class name. #10517 [Jonathan Viney]
20
+
21
+ * SQLite: fix rename_ and remove_column for columns with unique indexes. #10576 [Brandon Keepers]
22
+
23
+ * Ruby 1.9 compatibility. [Jeremy Kemper]
24
+
25
+
1
26
  *2.0.2* (December 16th, 2007)
2
27
 
3
28
  * Ensure optimistic locking handles nil #lock_version values properly. Closes #10510 [rick]
data/README CHANGED
File without changes
data/Rakefile CHANGED
@@ -4,6 +4,7 @@ require 'rake/testtask'
4
4
  require 'rake/rdoctask'
5
5
  require 'rake/packagetask'
6
6
  require 'rake/gempackagetask'
7
+ require 'rake/contrib/sshpublisher'
7
8
  require 'rake/contrib/rubyforgepublisher'
8
9
  require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
9
10
 
@@ -173,7 +174,7 @@ spec = Gem::Specification.new do |s|
173
174
  s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
174
175
  end
175
176
 
176
- s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD)
177
+ s.add_dependency('activesupport', '= 2.0.4' + PKG_BUILD)
177
178
 
178
179
  s.files.delete "test/fixtures/fixture_database.sqlite"
179
180
  s.files.delete "test/fixtures/fixture_database_2.sqlite"
@@ -227,8 +228,8 @@ end
227
228
 
228
229
  desc "Publish the beta gem"
229
230
  task :pgem => [:package] do
230
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
231
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
231
+ Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
232
+ `ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
232
233
  end
233
234
 
234
235
  desc "Publish the API documentation"
@@ -239,6 +240,7 @@ end
239
240
  desc "Publish the release files to RubyForge."
240
241
  task :release => [ :package ] do
241
242
  require 'rubyforge'
243
+ require 'rake/contrib/rubyforgepublisher'
242
244
 
243
245
  packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
244
246
 
File without changes
@@ -19,13 +19,13 @@ module ActiveRecord
19
19
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
20
20
  end
21
21
  end
22
-
22
+
23
23
  class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
24
24
  def initialize(owner_class_name, reflection, source_reflection)
25
25
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
26
26
  end
27
27
  end
28
-
28
+
29
29
  class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
30
30
  def initialize(reflection)
31
31
  through_reflection = reflection.through_reflection
@@ -72,21 +72,21 @@ module ActiveRecord
72
72
  base.extend(ClassMethods)
73
73
  end
74
74
 
75
- # Clears out the association cache
75
+ # Clears out the association cache
76
76
  def clear_association_cache #:nodoc:
77
77
  self.class.reflect_on_all_associations.to_a.each do |assoc|
78
78
  instance_variable_set "@#{assoc.name}", nil
79
79
  end unless self.new_record?
80
80
  end
81
-
82
- # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
83
- # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
84
- # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
81
+
82
+ # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
83
+ # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
84
+ # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
85
85
  # methods. Example:
86
86
  #
87
87
  # class Project < ActiveRecord::Base
88
88
  # belongs_to :portfolio
89
- # has_one :project_manager
89
+ # has_one :project_manager
90
90
  # has_many :milestones
91
91
  # has_and_belongs_to_many :categories
92
92
  # end
@@ -117,38 +117,38 @@ module ActiveRecord
117
117
  # #build_other(attributes={}) | X | | X
118
118
  # #create_other(attributes={}) | X | | X
119
119
  # #other.create!(attributes={}) | | | X
120
- # #other.nil? | X | X |
120
+ # #other.nil? | X | X |
121
121
  #
122
122
  # ===Collection associations (one-to-many / many-to-many)
123
123
  # | | | has_many
124
- # generated methods | habtm | has_many | :through
124
+ # generated methods | habtm | has_many | :through
125
125
  # ----------------------------------+-------+----------+----------
126
126
  # #others | X | X | X
127
- # #others=(other,other,...) | X | X |
127
+ # #others=(other,other,...) | X | X |
128
128
  # #other_ids | X | X | X
129
- # #other_ids=(id,id,...) | X | X |
129
+ # #other_ids=(id,id,...) | X | X |
130
130
  # #others<< | X | X | X
131
131
  # #others.push | X | X | X
132
132
  # #others.concat | X | X | X
133
- # #others.build(attributes={}) | X | X | X
134
- # #others.create(attributes={}) | X | X |
133
+ # #others.build(attributes={}) | X | X |
134
+ # #others.create(attributes={}) | X | X |
135
135
  # #others.create!(attributes={}) | X | X | X
136
136
  # #others.size | X | X | X
137
137
  # #others.length | X | X | X
138
138
  # #others.count | | X | X
139
139
  # #others.sum(args*,&block) | X | X | X
140
140
  # #others.empty? | X | X | X
141
- # #others.clear | X | X |
141
+ # #others.clear | X | X |
142
142
  # #others.delete(other,other,...) | X | X | X
143
- # #others.delete_all | X | X |
143
+ # #others.delete_all | X | X |
144
144
  # #others.destroy_all | X | X | X
145
145
  # #others.find(*args) | X | X | X
146
- # #others.find_first | X | |
147
- # #others.uniq | X | X |
146
+ # #others.find_first | X | |
147
+ # #others.uniq | X | X |
148
148
  # #others.reset | X | X | X
149
149
  #
150
150
  # == Cardinality and associations
151
- #
151
+ #
152
152
  # ActiveRecord associations can be used to describe relations with one-to-one, one-to-many
153
153
  # and many-to-many cardinality. Each model uses an association to describe its role in
154
154
  # the relation. In each case, the +belongs_to+ association is used in the model that has
@@ -207,7 +207,7 @@ module ActiveRecord
207
207
  # end
208
208
  #
209
209
  # Choosing which way to build a many-to-many relationship is not always simple.
210
- # If you need to work with the relationship model as its own entity,
210
+ # If you need to work with the relationship model as its own entity,
211
211
  # use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when
212
212
  # you never work directly with the relationship itself.
213
213
  #
@@ -253,7 +253,7 @@ module ActiveRecord
253
253
  # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
254
254
  # is cancelled.
255
255
  # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>#association.build</tt> method (documented below).
256
- # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
256
+ # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
257
257
  # does not save the parent either.
258
258
  #
259
259
  # === Collections
@@ -275,10 +275,10 @@ module ActiveRecord
275
275
  # def evaluate_velocity(developer)
276
276
  # ...
277
277
  # end
278
- # end
278
+ # end
279
279
  #
280
280
  # It's possible to stack callbacks by passing them as an array. Example:
281
- #
281
+ #
282
282
  # class Project
283
283
  # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
284
284
  # end
@@ -334,43 +334,43 @@ module ActiveRecord
334
334
  #
335
335
  # Some extensions can only be made to work with knowledge of the association proxy's internals.
336
336
  # Extensions can access relevant state using accessors on the association proxy:
337
- #
337
+ #
338
338
  # * +proxy_owner+ - Returns the object the association is part of.
339
339
  # * +proxy_reflection+ - Returns the reflection object that describes the association.
340
340
  # * +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+.
341
341
  #
342
342
  # === Association Join Models
343
- #
343
+ #
344
344
  # Has Many associations can be configured with the <tt>:through</tt> option to use an explicit join model to retrieve the data. This
345
345
  # operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
346
346
  # callbacks, and extra attributes on the join model. Consider the following schema:
347
- #
347
+ #
348
348
  # class Author < ActiveRecord::Base
349
349
  # has_many :authorships
350
350
  # has_many :books, :through => :authorships
351
351
  # end
352
- #
352
+ #
353
353
  # class Authorship < ActiveRecord::Base
354
354
  # belongs_to :author
355
355
  # belongs_to :book
356
356
  # end
357
- #
357
+ #
358
358
  # @author = Author.find :first
359
359
  # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
360
360
  # @author.books # selects all books by using the Authorship join model
361
- #
361
+ #
362
362
  # You can also go through a +has_many+ association on the join model:
363
- #
363
+ #
364
364
  # class Firm < ActiveRecord::Base
365
365
  # has_many :clients
366
366
  # has_many :invoices, :through => :clients
367
367
  # end
368
- #
368
+ #
369
369
  # class Client < ActiveRecord::Base
370
370
  # belongs_to :firm
371
371
  # has_many :invoices
372
372
  # end
373
- #
373
+ #
374
374
  # class Invoice < ActiveRecord::Base
375
375
  # belongs_to :client
376
376
  # end
@@ -380,36 +380,36 @@ module ActiveRecord
380
380
  # @firm.invoices # selects all invoices by going through the Client join model.
381
381
  #
382
382
  # === Polymorphic Associations
383
- #
384
- # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
383
+ #
384
+ # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
385
385
  # specify an interface that a +has_many+ association must adhere to.
386
- #
386
+ #
387
387
  # class Asset < ActiveRecord::Base
388
388
  # belongs_to :attachable, :polymorphic => true
389
389
  # end
390
- #
390
+ #
391
391
  # class Post < ActiveRecord::Base
392
392
  # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
393
393
  # end
394
394
  #
395
395
  # @asset.attachable = @post
396
- #
396
+ #
397
397
  # 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
398
398
  # an +attachable_id+ integer column and an +attachable_type+ string column.
399
399
  #
400
400
  # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
401
- # for the associations to work as expected, ensure that you store the base model for the STI models in the
401
+ # for the associations to work as expected, ensure that you store the base model for the STI models in the
402
402
  # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
403
403
  # and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table.
404
404
  #
405
405
  # class Asset < ActiveRecord::Base
406
406
  # belongs_to :attachable, :polymorphic => true
407
- #
407
+ #
408
408
  # def attachable_type=(sType)
409
409
  # super(sType.to_s.classify.constantize.base_class.to_s)
410
410
  # end
411
411
  # end
412
- #
412
+ #
413
413
  # class Post < ActiveRecord::Base
414
414
  # # because we store "Post" in attachable_type now :dependent => :destroy will work
415
415
  # has_many :assets, :as => :attachable, :dependent => :destroy
@@ -424,7 +424,7 @@ module ActiveRecord
424
424
  # == Caching
425
425
  #
426
426
  # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
427
- # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
427
+ # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
428
428
  # worrying too much about performance at the first go. Example:
429
429
  #
430
430
  # project.milestones # fetches milestones from the database
@@ -450,7 +450,7 @@ module ActiveRecord
450
450
  # puts "Post: " + post.title
451
451
  # puts "Written by: " + post.author.name
452
452
  # puts "Last comment on: " + post.comments.first.created_on
453
- # end
453
+ # end
454
454
  #
455
455
  # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
456
456
  #
@@ -475,7 +475,7 @@ module ActiveRecord
475
475
  # All of this power shouldn't fool you into thinking that you can pull out huge amounts of data with no performance penalty just because you've reduced
476
476
  # 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
477
477
  # 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.
478
- #
478
+ #
479
479
  # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
480
480
  # <tt>:order => "posts.id DESC"</tt> will work while <tt>:order => "id DESC"</tt> will not. Because eager loading generates the +SELECT+ statement too, the
481
481
  # <tt>:select</tt> option is ignored.
@@ -483,37 +483,53 @@ module ActiveRecord
483
483
  # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
484
484
  # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
485
485
  # associations" with +has_and_belongs_to_many+ are not a good fit for eager loading.
486
- #
486
+ #
487
487
  # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
488
488
  # before the actual model exists.
489
- #
489
+ #
490
+ # Eager loading is not possible with polymorphic associations. Given
491
+ #
492
+ # class Address < ActiveRecord::Base
493
+ # belongs_to :addressable, :polymorphic => true
494
+ # end
495
+ #
496
+ # a call that tries to eager load the addressable model
497
+ #
498
+ # Address.find(:all, :include => :addressable) # INVALID
499
+ #
500
+ # will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>. The reason is that the parent model's type
501
+ # is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that early query.
502
+ #
503
+ # It does work the other way around though: if the <tt>User</tt> model is <tt>addressable</tt> you can eager load
504
+ # their addresses with <tt>:include</tt> just fine, every piece needed to construct the query is known beforehand.
505
+ #
490
506
  # == Table Aliasing
491
507
  #
492
508
  # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
493
509
  # the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended
494
510
  # for any more successive uses of the table name.
495
- #
511
+ #
496
512
  # Post.find :all, :include => :comments
497
513
  # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
498
514
  # Post.find :all, :include => :special_comments # STI
499
515
  # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
500
516
  # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
501
517
  # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
502
- #
518
+ #
503
519
  # Acts as tree example:
504
- #
520
+ #
505
521
  # TreeMixin.find :all, :include => :children
506
522
  # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
507
523
  # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
508
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
524
+ # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
525
+ # LEFT OUTER JOIN parents_mixins ...
526
+ # TreeMixin.find :all, :include => {:children => {:parent => :children}}
527
+ # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
509
528
  # LEFT OUTER JOIN parents_mixins ...
510
- # TreeMixin.find :all, :include => {:children => {:parent => :children}}
511
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
512
- # LEFT OUTER JOIN parents_mixins ...
513
529
  # LEFT OUTER JOIN mixins childrens_mixins_2
514
- #
530
+ #
515
531
  # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
516
- #
532
+ #
517
533
  # Post.find :all, :include => :categories
518
534
  # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
519
535
  # Post.find :all, :include => {:categories => :posts}
@@ -523,18 +539,18 @@ module ActiveRecord
523
539
  # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
524
540
  # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
525
541
  # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
526
- #
542
+ #
527
543
  # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table names will take precedence over the eager associations:
528
- #
544
+ #
529
545
  # Post.find :all, :include => :comments, :joins => "inner join comments ..."
530
546
  # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
531
547
  # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
532
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
548
+ # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
533
549
  # LEFT OUTER JOIN comments special_comments_posts ...
534
550
  # INNER JOIN comments ...
535
- #
551
+ #
536
552
  # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
537
- #
553
+ #
538
554
  # == Modules
539
555
  #
540
556
  # By default, associations will look for objects within the current module scope. Consider:
@@ -575,12 +591,12 @@ module ActiveRecord
575
591
  # possible.
576
592
  module ClassMethods
577
593
  # Adds the following methods for retrieval and query of collections of associated objects:
578
- # +collection+ is replaced with the symbol passed as the first argument, so
594
+ # +collection+ is replaced with the symbol passed as the first argument, so
579
595
  # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
580
596
  # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
581
597
  # An empty array is returned if none are found.
582
598
  # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
583
- # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
599
+ # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
584
600
  # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
585
601
  # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
586
602
  # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
@@ -592,7 +608,7 @@ module ActiveRecord
592
608
  # * <tt>collection.size</tt> - returns the number of associated objects.
593
609
  # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
594
610
  # * <tt>collection.build(attributes = {}, ...)</tt> - returns one or more new objects of the collection type that have been instantiated
595
- # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
611
+ # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
596
612
  # associated object already exists, not if it's +nil+!
597
613
  # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
598
614
  # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
@@ -612,7 +628,7 @@ module ActiveRecord
612
628
  # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
613
629
  # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
614
630
  # The declaration can also include an options hash to specialize the behavior of the association.
615
- #
631
+ #
616
632
  # Options are:
617
633
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
618
634
  # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
@@ -637,13 +653,13 @@ module ActiveRecord
637
653
  # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
638
654
  # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
639
655
  # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
640
- # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
656
+ # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
641
657
  # but not include the joined columns.
642
658
  # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
643
- # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
659
+ # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
644
660
  # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
645
661
  # or <tt>has_many</tt> association on the join model.
646
- # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
662
+ # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
647
663
  # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
648
664
  # <tt>:subscriber</tt> on +Subscription+, unless a <tt>:source</tt> is given.
649
665
  # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
@@ -669,7 +685,6 @@ module ActiveRecord
669
685
  configure_dependency_for_has_many(reflection)
670
686
 
671
687
  if options[:through]
672
- collection_reader_method(reflection, HasManyThroughAssociation)
673
688
  collection_accessor_methods(reflection, HasManyThroughAssociation, false)
674
689
  else
675
690
  add_multiple_associated_save_callbacks(reflection.name)
@@ -679,10 +694,10 @@ module ActiveRecord
679
694
  end
680
695
 
681
696
  # Adds the following methods for retrieval and query of a single associated object:
682
- # +association+ is replaced with the symbol passed as the first argument, so
697
+ # +association+ is replaced with the symbol passed as the first argument, so
683
698
  # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
684
699
  # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
685
- # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
700
+ # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
686
701
  # and saves the associate object.
687
702
  # * <tt>association.nil?</tt> - returns +true+ if there is no associated object.
688
703
  # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
@@ -699,7 +714,7 @@ module ActiveRecord
699
714
  # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
700
715
  #
701
716
  # The declaration can also include an options hash to specialize the behavior of the association.
702
- #
717
+ #
703
718
  # Options are:
704
719
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
705
720
  # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
@@ -726,25 +741,28 @@ module ActiveRecord
726
741
  def has_one(association_id, options = {})
727
742
  reflection = create_has_one_reflection(association_id, options)
728
743
 
744
+ ivar = "@#{reflection.name}"
745
+
729
746
  module_eval do
730
747
  after_save <<-EOF
731
- association = instance_variable_get("@#{reflection.name}")
748
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
749
+
732
750
  if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
733
751
  association["#{reflection.primary_key_name}"] = id
734
752
  association.save(true)
735
753
  end
736
754
  EOF
737
755
  end
738
-
756
+
739
757
  association_accessor_methods(reflection, HasOneAssociation)
740
758
  association_constructor_method(:build, reflection, HasOneAssociation)
741
759
  association_constructor_method(:create, reflection, HasOneAssociation)
742
-
760
+
743
761
  configure_dependency_for_has_one(reflection)
744
762
  end
745
763
 
746
764
  # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
747
- # +association+ is replaced with the symbol passed as the first argument, so
765
+ # +association+ is replaced with the symbol passed as the first argument, so
748
766
  # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
749
767
  # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
750
768
  # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
@@ -762,7 +780,7 @@ module ActiveRecord
762
780
  # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
763
781
  # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
764
782
  # The declaration can also include an options hash to specialize the behavior of the association.
765
- #
783
+ #
766
784
  # Options are:
767
785
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
768
786
  # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
@@ -772,39 +790,43 @@ module ActiveRecord
772
790
  # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
773
791
  # such as <tt>last_name, first_name DESC</tt>
774
792
  # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
775
- # of the associated class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +belongs_to+ association to a
776
- # +Boss+ class will use +boss_id+ as the default +foreign_key+.
777
- # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+
793
+ # of the association with an +_id+ suffix. So a class that defines a +belongs_to :person+ association will use +person_id+ as the default +foreign_key+.
794
+ # Similarly, +belongs_to :favorite_person, :class_name => "Person"+ will use a foreign key of +favorite_person_id+.
795
+ # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+
778
796
  # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
779
797
  # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging +Comment+ class)
780
- # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing
798
+ # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing
781
799
  # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
782
800
  # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly.
783
801
  # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
802
+ # Not allowed if the association is polymorphic.
784
803
  # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing +true+.
785
- # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
804
+ # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
786
805
  # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end).
787
806
  #
788
807
  # Option examples:
789
808
  # belongs_to :firm, :foreign_key => "client_of"
790
809
  # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
791
- # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
810
+ # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
792
811
  # :conditions => 'discounts > #{payments_count}'
793
812
  # belongs_to :attachable, :polymorphic => true
794
813
  def belongs_to(association_id, options = {})
795
814
  reflection = create_belongs_to_reflection(association_id, options)
796
-
815
+
816
+ ivar = "@#{reflection.name}"
817
+
797
818
  if reflection.options[:polymorphic]
798
819
  association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
799
820
 
800
821
  module_eval do
801
822
  before_save <<-EOF
802
- association = instance_variable_get("@#{reflection.name}")
823
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
824
+
803
825
  if association && association.target
804
826
  if association.new_record?
805
827
  association.save(true)
806
828
  end
807
-
829
+
808
830
  if association.updated?
809
831
  self["#{reflection.primary_key_name}"] = association.id
810
832
  self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
@@ -819,16 +841,17 @@ module ActiveRecord
819
841
 
820
842
  module_eval do
821
843
  before_save <<-EOF
822
- association = instance_variable_get("@#{reflection.name}")
823
- if !association.nil?
844
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
845
+
846
+ if !association.nil?
824
847
  if association.new_record?
825
848
  association.save(true)
826
849
  end
827
-
850
+
828
851
  if association.updated?
829
852
  self["#{reflection.primary_key_name}"] = association.id
830
853
  end
831
- end
854
+ end
832
855
  EOF
833
856
  end
834
857
  end
@@ -848,7 +871,7 @@ module ActiveRecord
848
871
  "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
849
872
  " unless #{reflection.name}.nil?'"
850
873
  )
851
-
874
+
852
875
  module_eval(
853
876
  "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
854
877
  )
@@ -858,11 +881,11 @@ module ActiveRecord
858
881
  # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
859
882
  # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+
860
883
  # will give the default join table name of +developers_projects+ because "D" outranks "P". Note that this precedence
861
- # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
884
+ # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
862
885
  # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
863
- # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
886
+ # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
864
887
  # to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>,
865
- # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the
888
+ # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the
866
889
  # custom <tt>join_table</tt> option if you need to.
867
890
  #
868
891
  # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
@@ -871,13 +894,13 @@ module ActiveRecord
871
894
  # associations with attributes to a real join model (see introduction).
872
895
  #
873
896
  # Adds the following methods for retrieval and query:
874
- # +collection+ is replaced with the symbol passed as the first argument, so
897
+ # +collection+ is replaced with the symbol passed as the first argument, so
875
898
  # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
876
899
  # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
877
900
  # An empty array is returned if none are found.
878
- # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
901
+ # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
879
902
  # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
880
- # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
903
+ # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
881
904
  # This does not destroy the objects.
882
905
  # * <tt>collection=objects</tt> - replaces the collection's content by deleting and adding objects as appropriate.
883
906
  # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
@@ -906,10 +929,10 @@ module ActiveRecord
906
929
  # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
907
930
  # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
908
931
  # The declaration may include an options hash to specialize the behavior of the association.
909
- #
932
+ #
910
933
  # Options are:
911
934
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
912
- # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
935
+ # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
913
936
  # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
914
937
  # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
915
938
  # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any
@@ -926,7 +949,7 @@ module ActiveRecord
926
949
  # such as <tt>last_name, first_name DESC</tt>
927
950
  # * <tt>:uniq</tt> - if set to +true+, duplicate associated objects will be ignored by accessors and query methods
928
951
  # * <tt>:finder_sql</tt> - overwrite the default generated SQL statement used to fetch the association with a manual statement
929
- # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated
952
+ # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated
930
953
  # classes with a manual statement
931
954
  # * <tt>:insert_sql</tt> - overwrite the default generated SQL statement used to add links between the associated classes
932
955
  # with a manual statement
@@ -943,11 +966,11 @@ module ActiveRecord
943
966
  # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
944
967
  # has_and_belongs_to_many :nations, :class_name => "Country"
945
968
  # has_and_belongs_to_many :categories, :join_table => "prods_cats"
946
- # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
969
+ # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
947
970
  # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
948
971
  def has_and_belongs_to_many(association_id, options = {}, &extension)
949
972
  reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
950
-
973
+
951
974
  add_multiple_associated_save_callbacks(reflection.name)
952
975
  collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
953
976
 
@@ -981,93 +1004,102 @@ module ActiveRecord
981
1004
 
982
1005
  table_name_prefix + join_table + table_name_suffix
983
1006
  end
984
-
1007
+
985
1008
  def association_accessor_methods(reflection, association_proxy_class)
1009
+ ivar = "@#{reflection.name}"
1010
+
986
1011
  define_method(reflection.name) do |*params|
987
1012
  force_reload = params.first unless params.empty?
988
- association = instance_variable_get("@#{reflection.name}")
1013
+
1014
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
989
1015
 
990
1016
  if association.nil? || force_reload
991
1017
  association = association_proxy_class.new(self, reflection)
992
1018
  retval = association.reload
993
1019
  if retval.nil? and association_proxy_class == BelongsToAssociation
994
- instance_variable_set("@#{reflection.name}", nil)
1020
+ instance_variable_set(ivar, nil)
995
1021
  return nil
996
1022
  end
997
- instance_variable_set("@#{reflection.name}", association)
1023
+ instance_variable_set(ivar, association)
998
1024
  end
999
1025
 
1000
1026
  association.target.nil? ? nil : association
1001
1027
  end
1002
1028
 
1003
1029
  define_method("#{reflection.name}=") do |new_value|
1004
- association = instance_variable_get("@#{reflection.name}")
1030
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1031
+
1005
1032
  if association.nil? || association.target != new_value
1006
1033
  association = association_proxy_class.new(self, reflection)
1007
1034
  end
1008
1035
 
1009
1036
  association.replace(new_value)
1010
1037
 
1011
- unless new_value.nil?
1012
- instance_variable_set("@#{reflection.name}", association)
1013
- else
1014
- instance_variable_set("@#{reflection.name}", nil)
1015
- end
1038
+ instance_variable_set(ivar, new_value.nil? ? nil : association)
1016
1039
  end
1017
1040
 
1018
1041
  define_method("set_#{reflection.name}_target") do |target|
1019
1042
  return if target.nil? and association_proxy_class == BelongsToAssociation
1020
1043
  association = association_proxy_class.new(self, reflection)
1021
1044
  association.target = target
1022
- instance_variable_set("@#{reflection.name}", association)
1045
+ instance_variable_set(ivar, association)
1023
1046
  end
1024
1047
  end
1025
1048
 
1026
1049
  def collection_reader_method(reflection, association_proxy_class)
1027
1050
  define_method(reflection.name) do |*params|
1051
+ ivar = "@#{reflection.name}"
1052
+
1028
1053
  force_reload = params.first unless params.empty?
1029
- association = instance_variable_get("@#{reflection.name}")
1054
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1030
1055
 
1031
1056
  unless association.respond_to?(:loaded?)
1032
1057
  association = association_proxy_class.new(self, reflection)
1033
- instance_variable_set("@#{reflection.name}", association)
1058
+ instance_variable_set(ivar, association)
1034
1059
  end
1035
1060
 
1036
1061
  association.reload if force_reload
1037
1062
 
1038
1063
  association
1039
1064
  end
1065
+
1066
+ define_method("#{reflection.name.to_s.singularize}_ids") do
1067
+ send(reflection.name).map(&:id)
1068
+ end
1040
1069
  end
1041
1070
 
1042
1071
  def collection_accessor_methods(reflection, association_proxy_class, writer = true)
1043
1072
  collection_reader_method(reflection, association_proxy_class)
1044
1073
 
1045
- define_method("#{reflection.name}=") do |new_value|
1046
- # Loads proxy class instance (defined in collection_reader_method) if not already loaded
1047
- association = send(reflection.name)
1048
- association.replace(new_value)
1049
- association
1050
- end
1074
+ if writer
1075
+ define_method("#{reflection.name}=") do |new_value|
1076
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
1077
+ association = send(reflection.name)
1078
+ association.replace(new_value)
1079
+ association
1080
+ end
1051
1081
 
1052
- define_method("#{reflection.name.to_s.singularize}_ids") do
1053
- send(reflection.name).map(&:id)
1082
+ define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
1083
+ ids = (new_value || []).reject { |nid| nid.blank? }
1084
+ send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
1085
+ end
1054
1086
  end
1055
-
1056
- define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
1057
- ids = (new_value || []).reject { |nid| nid.blank? }
1058
- send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
1059
- end if writer
1060
1087
  end
1061
1088
 
1062
1089
  def add_multiple_associated_save_callbacks(association_name)
1063
1090
  method_name = "validate_associated_records_for_#{association_name}".to_sym
1091
+ ivar = "@#{association_name}"
1092
+
1064
1093
  define_method(method_name) do
1065
- association = instance_variable_get("@#{association_name}")
1094
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1095
+
1066
1096
  if association.respond_to?(:loaded?)
1067
1097
  if new_record?
1068
1098
  association
1069
- else
1099
+ elsif association.loaded?
1070
1100
  association.select { |record| record.new_record? }
1101
+ else
1102
+ association.target.select { |record| record.new_record? }
1071
1103
  end.each do |record|
1072
1104
  errors.add "#{association_name}" unless record.valid?
1073
1105
  end
@@ -1078,18 +1110,20 @@ module ActiveRecord
1078
1110
  before_save("@new_record_before_save = new_record?; true")
1079
1111
 
1080
1112
  after_callback = <<-end_eval
1081
- association = instance_variable_get("@#{association_name}")
1113
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
1082
1114
 
1083
1115
  records_to_save = if @new_record_before_save
1084
1116
  association
1085
1117
  elsif association.respond_to?(:loaded?) && association.loaded?
1086
1118
  association.select { |record| record.new_record? }
1119
+ elsif association.respond_to?(:loaded?) && !association.loaded?
1120
+ association.target.select { |record| record.new_record? }
1087
1121
  else
1088
1122
  []
1089
1123
  end
1090
1124
 
1091
1125
  records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
1092
-
1126
+
1093
1127
  # reconstruct the SQL queries now that we know the owner's id
1094
1128
  association.send(:construct_sql) if association.respond_to?(:construct_sql)
1095
1129
  end_eval
@@ -1101,13 +1135,15 @@ module ActiveRecord
1101
1135
 
1102
1136
  def association_constructor_method(constructor, reflection, association_proxy_class)
1103
1137
  define_method("#{constructor}_#{reflection.name}") do |*params|
1138
+ ivar = "@#{reflection.name}"
1139
+
1104
1140
  attributees = params.first unless params.empty?
1105
1141
  replace_existing = params[1].nil? ? true : params[1]
1106
- association = instance_variable_get("@#{reflection.name}")
1142
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1107
1143
 
1108
1144
  if association.nil?
1109
1145
  association = association_proxy_class.new(self, reflection)
1110
- instance_variable_set("@#{reflection.name}", association)
1146
+ instance_variable_set(ivar, association)
1111
1147
  end
1112
1148
 
1113
1149
  if association_proxy_class == HasOneAssociation
@@ -1117,7 +1153,7 @@ module ActiveRecord
1117
1153
  end
1118
1154
  end
1119
1155
  end
1120
-
1156
+
1121
1157
  def find_with_associations(options = {})
1122
1158
  catch :invalid_query do
1123
1159
  join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
@@ -1173,12 +1209,12 @@ module ActiveRecord
1173
1209
  :select, :conditions, :include, :order, :group, :limit, :offset,
1174
1210
  :as, :through, :source, :source_type,
1175
1211
  :uniq,
1176
- :finder_sql, :counter_sql,
1177
- :before_add, :after_add, :before_remove, :after_remove,
1212
+ :finder_sql, :counter_sql,
1213
+ :before_add, :after_add, :before_remove, :after_remove,
1178
1214
  :extend
1179
1215
  )
1180
1216
 
1181
- options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?
1217
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1182
1218
 
1183
1219
  create_reflection(:has_many, association_id, options, self)
1184
1220
  end
@@ -1193,10 +1229,10 @@ module ActiveRecord
1193
1229
 
1194
1230
  def create_belongs_to_reflection(association_id, options)
1195
1231
  options.assert_valid_keys(
1196
- :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
1232
+ :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
1197
1233
  :counter_cache, :extend, :polymorphic
1198
1234
  )
1199
-
1235
+
1200
1236
  reflection = create_reflection(:belongs_to, association_id, options, self)
1201
1237
 
1202
1238
  if options[:polymorphic]
@@ -1205,23 +1241,23 @@ module ActiveRecord
1205
1241
 
1206
1242
  reflection
1207
1243
  end
1208
-
1244
+
1209
1245
  def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1210
1246
  options.assert_valid_keys(
1211
- :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
1247
+ :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
1212
1248
  :select, :conditions, :include, :order, :group, :limit, :offset,
1213
- :uniq,
1249
+ :uniq,
1214
1250
  :finder_sql, :delete_sql, :insert_sql,
1215
- :before_add, :after_add, :before_remove, :after_remove,
1251
+ :before_add, :after_add, :before_remove, :after_remove,
1216
1252
  :extend
1217
1253
  )
1218
1254
 
1219
- options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?
1255
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1220
1256
 
1221
1257
  reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1222
1258
 
1223
1259
  reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
1224
-
1260
+
1225
1261
  reflection
1226
1262
  end
1227
1263
 
@@ -1232,7 +1268,7 @@ module ActiveRecord
1232
1268
  def guard_against_unlimitable_reflections(reflections, options)
1233
1269
  if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
1234
1270
  raise(
1235
- ConfigurationError,
1271
+ ConfigurationError,
1236
1272
  "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
1237
1273
  )
1238
1274
  end
@@ -1249,7 +1285,7 @@ module ActiveRecord
1249
1285
  scope = scope(:find)
1250
1286
  sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
1251
1287
  sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1252
-
1288
+
1253
1289
  add_joins!(sql, options, scope)
1254
1290
  add_conditions!(sql, options[:conditions], scope)
1255
1291
  add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
@@ -1258,10 +1294,10 @@ module ActiveRecord
1258
1294
  add_order!(sql, options[:order], scope)
1259
1295
  add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
1260
1296
  add_lock!(sql, options, scope)
1261
-
1297
+
1262
1298
  return sanitize_sql(sql)
1263
1299
  end
1264
-
1300
+
1265
1301
  def add_limited_ids_condition!(sql, options, join_dependency)
1266
1302
  unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
1267
1303
  sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) "
@@ -1324,7 +1360,7 @@ module ActiveRecord
1324
1360
  condition_table_name != table_name
1325
1361
  end
1326
1362
  end
1327
-
1363
+
1328
1364
  # Checks if the query order references a table other than the current model's table.
1329
1365
  def include_eager_order?(options)
1330
1366
  order = options[:order]
@@ -1362,13 +1398,16 @@ module ActiveRecord
1362
1398
  end
1363
1399
 
1364
1400
  def create_extension_modules(association_id, block_extension, extensions)
1365
- extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
1401
+ if block_extension
1402
+ extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
1366
1403
 
1367
- silence_warnings do
1368
- Object.const_set(extension_module_name, Module.new(&block_extension))
1404
+ silence_warnings do
1405
+ Object.const_set(extension_module_name, Module.new(&block_extension))
1406
+ end
1407
+ Array(extensions).push(extension_module_name.constantize)
1408
+ else
1409
+ Array(extensions)
1369
1410
  end
1370
-
1371
- Array(extensions).push(extension_module_name.constantize)
1372
1411
  end
1373
1412
 
1374
1413
  class JoinDependency # :nodoc:
@@ -1526,13 +1565,15 @@ module ActiveRecord
1526
1565
  end
1527
1566
 
1528
1567
  def column_names_with_alias
1529
- unless @column_names_with_alias
1568
+ unless defined?(@column_names_with_alias)
1530
1569
  @column_names_with_alias = []
1570
+
1531
1571
  ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
1532
1572
  @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
1533
1573
  end
1534
1574
  end
1535
- return @column_names_with_alias
1575
+
1576
+ @column_names_with_alias
1536
1577
  end
1537
1578
 
1538
1579
  def extract_record(row)
@@ -1568,7 +1609,7 @@ module ActiveRecord
1568
1609
  if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
1569
1610
  join_dependency.table_aliases[aliased_table_name] += 1
1570
1611
  end
1571
-
1612
+
1572
1613
  unless join_dependency.table_aliases[aliased_table_name].zero?
1573
1614
  # if the table name has been used, then use an alias
1574
1615
  @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
@@ -1578,7 +1619,7 @@ module ActiveRecord
1578
1619
  else
1579
1620
  join_dependency.table_aliases[aliased_table_name] += 1
1580
1621
  end
1581
-
1622
+
1582
1623
  if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
1583
1624
  @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
1584
1625
  unless join_dependency.table_aliases[aliased_join_table_name].zero?
@@ -1601,22 +1642,22 @@ module ActiveRecord
1601
1642
  connection.quote_table_name(aliased_join_table_name),
1602
1643
  options[:foreign_key] || reflection.active_record.to_s.foreign_key,
1603
1644
  connection.quote_table_name(parent.aliased_table_name),
1604
- reflection.active_record.primary_key] +
1645
+ reflection.active_record.primary_key] +
1605
1646
  " #{join_type} %s ON %s.%s = %s.%s " % [
1606
1647
  table_name_and_alias,
1607
- connection.quote_table_name(aliased_table_name),
1608
- klass.primary_key,
1648
+ connection.quote_table_name(aliased_table_name),
1649
+ klass.primary_key,
1609
1650
  connection.quote_table_name(aliased_join_table_name),
1610
- options[:association_foreign_key] || klass.to_s.foreign_key
1651
+ options[:association_foreign_key] || klass.to_s.foreign_key
1611
1652
  ]
1612
1653
  when :has_many, :has_one
1613
1654
  case
1614
1655
  when reflection.macro == :has_many && reflection.options[:through]
1615
1656
  through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
1616
-
1617
- jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1618
- first_key = second_key = as_extra = nil
1619
-
1657
+
1658
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1659
+ first_key = second_key = as_extra = nil
1660
+
1620
1661
  if through_reflection.options[:as] # has_many :through against a polymorphic join
1621
1662
  jt_foreign_key = through_reflection.options[:as].to_s + '_id'
1622
1663
  jt_as_extra = " AND %s.%s = %s" % [
@@ -1625,24 +1666,24 @@ module ActiveRecord
1625
1666
  klass.quote_value(parent.active_record.base_class.name)
1626
1667
  ]
1627
1668
  else
1628
- jt_foreign_key = through_reflection.primary_key_name
1669
+ jt_foreign_key = through_reflection.primary_key_name
1629
1670
  end
1630
-
1671
+
1631
1672
  case source_reflection.macro
1632
1673
  when :has_many
1633
- if source_reflection.options[:as]
1634
- first_key = "#{source_reflection.options[:as]}_id"
1635
- second_key = options[:foreign_key] || primary_key
1674
+ if source_reflection.options[:as]
1675
+ first_key = "#{source_reflection.options[:as]}_id"
1676
+ second_key = options[:foreign_key] || primary_key
1636
1677
  as_extra = " AND %s.%s = %s" % [
1637
1678
  connection.quote_table_name(aliased_table_name),
1638
1679
  connection.quote_column_name("#{source_reflection.options[:as]}_type"),
1639
- klass.quote_value(source_reflection.active_record.base_class.name)
1680
+ klass.quote_value(source_reflection.active_record.base_class.name)
1640
1681
  ]
1641
1682
  else
1642
1683
  first_key = through_reflection.klass.base_class.to_s.foreign_key
1643
1684
  second_key = options[:foreign_key] || primary_key
1644
1685
  end
1645
-
1686
+
1646
1687
  unless through_reflection.klass.descends_from_active_record?
1647
1688
  jt_sti_extra = " AND %s.%s = %s" % [
1648
1689
  connection.quote_table_name(aliased_join_table_name),
@@ -1666,17 +1707,17 @@ module ActiveRecord
1666
1707
  " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [
1667
1708
  table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1668
1709
  connection.quote_table_name(parent.aliased_table_name),
1669
- connection.quote_column_name(parent.primary_key),
1710
+ connection.quote_column_name(parent.primary_key),
1670
1711
  connection.quote_table_name(aliased_join_table_name),
1671
- connection.quote_column_name(jt_foreign_key),
1712
+ connection.quote_column_name(jt_foreign_key),
1672
1713
  jt_as_extra, jt_source_extra, jt_sti_extra
1673
1714
  ] +
1674
1715
  " #{join_type} %s ON (%s.%s = %s.%s%s) " % [
1675
- table_name_and_alias,
1716
+ table_name_and_alias,
1676
1717
  connection.quote_table_name(aliased_table_name),
1677
- connection.quote_column_name(first_key),
1718
+ connection.quote_column_name(first_key),
1678
1719
  connection.quote_table_name(aliased_join_table_name),
1679
- connection.quote_column_name(second_key),
1720
+ connection.quote_column_name(second_key),
1680
1721
  as_extra
1681
1722
  ]
1682
1723
 
@@ -1684,11 +1725,11 @@ module ActiveRecord
1684
1725
  " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [
1685
1726
  table_name_and_alias,
1686
1727
  connection.quote_table_name(aliased_table_name),
1687
- "#{reflection.options[:as]}_id",
1728
+ "#{reflection.options[:as]}_id",
1688
1729
  connection.quote_table_name(parent.aliased_table_name),
1689
- parent.primary_key,
1730
+ parent.primary_key,
1690
1731
  connection.quote_table_name(aliased_table_name),
1691
- "#{reflection.options[:as]}_type",
1732
+ "#{reflection.options[:as]}_type",
1692
1733
  klass.quote_value(parent.active_record.base_class.name)
1693
1734
  ]
1694
1735
  else
@@ -1696,18 +1737,18 @@ module ActiveRecord
1696
1737
  " #{join_type} %s ON %s.%s = %s.%s " % [
1697
1738
  table_name_and_alias,
1698
1739
  aliased_table_name,
1699
- foreign_key,
1740
+ foreign_key,
1700
1741
  parent.aliased_table_name,
1701
- parent.primary_key
1742
+ parent.primary_key
1702
1743
  ]
1703
1744
  end
1704
1745
  when :belongs_to
1705
1746
  " #{join_type} %s ON %s.%s = %s.%s " % [
1706
1747
  table_name_and_alias,
1707
- connection.quote_table_name(aliased_table_name),
1708
- reflection.klass.primary_key,
1748
+ connection.quote_table_name(aliased_table_name),
1749
+ reflection.klass.primary_key,
1709
1750
  connection.quote_table_name(parent.aliased_table_name),
1710
- options[:foreign_key] || klass.to_s.foreign_key
1751
+ options[:foreign_key] || reflection.primary_key_name
1711
1752
  ]
1712
1753
  else
1713
1754
  ""
@@ -1723,15 +1764,14 @@ module ActiveRecord
1723
1764
 
1724
1765
  join
1725
1766
  end
1726
-
1727
- protected
1728
1767
 
1768
+ protected
1729
1769
  def pluralize(table_name)
1730
1770
  ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
1731
1771
  end
1732
-
1772
+
1733
1773
  def table_alias_for(table_name, table_alias)
1734
- "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip
1774
+ "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip
1735
1775
  end
1736
1776
 
1737
1777
  def table_name_and_alias
@@ -1739,11 +1779,10 @@ module ActiveRecord
1739
1779
  end
1740
1780
 
1741
1781
  def interpolate_sql(sql)
1742
- instance_eval("%@#{sql.gsub('@', '\@')}@")
1743
- end
1782
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
1783
+ end
1744
1784
 
1745
1785
  private
1746
-
1747
1786
  def join_type
1748
1787
  "LEFT OUTER JOIN"
1749
1788
  end