Changeset 8481
- Timestamp:
- 12/22/07 11:26:03 (1 year ago)
- Files:
-
- trunk/activerecord/lib/active_record/associations.rb (modified) (74 diffs)
- trunk/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/associations/has_many_association.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/associations/has_many_through_association.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/attribute_methods.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/base.rb (modified) (9 diffs)
- trunk/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/migration.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/transactions.rb (modified) (3 diffs)
- trunk/activerecord/test/active_schema_test_mysql.rb (modified) (2 diffs)
- trunk/activerecord/test/adapter_test.rb (modified) (1 diff)
- trunk/activerecord/test/associations/callbacks_test.rb (modified) (1 diff)
- trunk/activerecord/test/associations/join_model_test.rb (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activerecord/lib/active_record/associations.rb
r8456 r8481 20 20 end 21 21 end 22 22 23 23 class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc: 24 24 def initialize(owner_class_name, reflection, source_reflection) … … 26 26 end 27 27 end 28 28 29 29 class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc: 30 30 def initialize(reflection) … … 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| … … 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 … … 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 133 # #others.build(attributes={}) | X | X | X 134 # #others.create(attributes={}) | X | X | 134 # #others.create(attributes={}) | X | X | 135 135 # #others.create!(attributes={}) | X | X | X 136 136 # #others.size | X | 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 … … 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. … … 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 # … … 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}] … … 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. … … 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 … … 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. … … 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. … … 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 … … 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 # … … 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: … … 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 … … 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 490 # == Table Aliasing 491 491 # … … 493 493 # 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 494 # for any more successive uses of the table name. 495 # 495 # 496 496 # Post.find :all, :include => :comments 497 497 # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... … … 500 500 # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name 501 501 # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts 502 # 502 # 503 503 # Acts as tree example: 504 # 504 # 505 505 # TreeMixin.find :all, :include => :children 506 506 # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... 507 507 # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes 508 # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... 508 # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... 509 509 # 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 ... 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 513 # LEFT OUTER JOIN mixins childrens_mixins_2 514 # 514 # 515 515 # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix: 516 # 516 # 517 517 # Post.find :all, :include => :categories 518 518 # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ... … … 524 524 # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories 525 525 # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts 526 # 526 # 527 527 # 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 # 528 # 529 529 # Post.find :all, :include => :comments, :joins => "inner join comments ..." 530 530 # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ... 531 531 # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..." 532 # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ... 532 # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ... 533 533 # LEFT OUTER JOIN comments special_comments_posts ... 534 534 # INNER JOIN comments ... 535 # 535 # 536 536 # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database. 537 # 537 # 538 538 # == Modules 539 539 # … … 576 576 module ClassMethods 577 577 # 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 578 # +collection+ is replaced with the symbol passed as the first argument, so 579 579 # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. 580 580 # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. 581 581 # An empty array is returned if none are found. 582 582 # * <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. 583 # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL. 584 584 # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model. 585 585 # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate. … … 593 593 # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find. 594 594 # * <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 595 # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an 596 596 # associated object already exists, not if it's +nil+! 597 597 # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated … … 613 613 # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) 614 614 # The declaration can also include an options hash to specialize the behavior of the association. 615 # 615 # 616 616 # Options are: 617 617 # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred … … 638 638 # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned. 639 639 # * <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 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 641 641 # but not include the joined columns. 642 642 # * <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> 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> 644 644 # 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 645 # 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 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 647 647 # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or 648 648 # <tt>:subscriber</tt> on +Subscription+, unless a <tt>:source</tt> is given. … … 670 670 671 671 if options[:through] 672 collection_reader_method(reflection, HasManyThroughAssociation)673 672 collection_accessor_methods(reflection, HasManyThroughAssociation, false) 674 673 else … … 680 679 681 680 # 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 681 # +association+ is replaced with the symbol passed as the first argument, so 683 682 # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. 684 683 # * <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, 684 # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key, 686 685 # and saves the associate object. 687 686 # * <tt>association.nil?</tt> - returns +true+ if there is no associated object. … … 700 699 # 701 700 # The declaration can also include an options hash to specialize the behavior of the association. 702 # 701 # 703 702 # Options are: 704 703 # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred … … 727 726 reflection = create_has_one_reflection(association_id, options) 728 727 728 ivar = "@#{reflection.name}" 729 729 730 module_eval do 730 731 after_save <<-EOF 731 association = instance_variable_get("@#{reflection.name}") 732 association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") 733 732 734 if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id) 733 735 association["#{reflection.primary_key_name}"] = id … … 736 738 EOF 737 739 end 738 740 739 741 association_accessor_methods(reflection, HasOneAssociation) 740 742 association_constructor_method(:build, reflection, HasOneAssociation) 741 743 association_constructor_method(:create, reflection, HasOneAssociation) 742 744 743 745 configure_dependency_for_has_one(reflection) 744 746 end 745 747 746 748 # 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 749 # +association+ is replaced with the symbol passed as the first argument, so 748 750 # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. 749 751 # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found. … … 763 765 # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) 764 766 # The declaration can also include an options hash to specialize the behavior of the association. 765 # 767 # 766 768 # Options are: 767 769 # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred … … 775 777 # 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+. 776 778 # Similarly, +belongs_to :favorite_person, :class_name => "Person"+ will use a foreign key of +favorite_person_id+. 777 # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+ 779 # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+ 778 780 # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's 779 781 # 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 782 # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing 781 783 # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.) 782 784 # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly. 783 785 # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded. 784 786 # * <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 787 # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute 786 788 # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). 787 789 # … … 789 791 # belongs_to :firm, :foreign_key => "client_of" 790 792 # belongs_to :author, :class_name => "Person", :foreign_key => "author_id" 791 # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", 793 # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", 792 794 # :conditions => 'discounts > #{payments_count}' 793 795 # belongs_to :attachable, :polymorphic => true 794 796 def belongs_to(association_id, options = {}) 795 797 reflection = create_belongs_to_reflection(association_id, options) 796 798 799 ivar = "@#{reflection.name}" 800 797 801 if reflection.options[:polymorphic] 798 802 association_accessor_methods(reflection, BelongsToPolymorphicAssociation) … … 800 804 module_eval do 801 805 before_save <<-EOF 802 association = instance_variable_get("@#{reflection.name}") 806 association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") 807 803 808 if association && association.target 804 809 if association.new_record? 805 810 association.save(true) 806 811 end 807 812 808 813 if association.updated? 809 814 self["#{reflection.primary_key_name}"] = association.id … … 820 825 module_eval do 821 826 before_save <<-EOF 822 association = instance_variable_get("@#{reflection.name}") 823 if !association.nil? 827 association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") 828 829 if !association.nil? 824 830 if association.new_record? 825 831 association.save(true) 826 832 end 827 833 828 834 if association.updated? 829 835 self["#{reflection.primary_key_name}"] = association.id 830 836 end 831 end 837 end 832 838 EOF 833 839 end … … 849 855 " unless #{reflection.name}.nil?'" 850 856 ) 851 857 852 858 module_eval( 853 859 "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)" … … 859 865 # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+ 860 866 # 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, 867 # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths, 862 868 # 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> 869 # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt> 864 870 # 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 871 # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the 866 872 # custom <tt>join_table</tt> option if you need to. 867 873 # … … 872 878 # 873 879 # Adds the following methods for retrieval and query: 874 # +collection+ is replaced with the symbol passed as the first argument, so 880 # +collection+ is replaced with the symbol passed as the first argument, so 875 881 # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. 876 882 # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. 877 883 # 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 884 # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table 879 885 # (<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. 886 # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table. 881 887 # This does not destroy the objects. 882 888 # * <tt>collection=objects</tt> - replaces the collection's content by deleting and adding objects as appropriate. … … 907 913 # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>) 908 914 # The declaration may include an options hash to specialize the behavior of the association. 909 # 915 # 910 916 # Options are: 911 917 # * <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 918 # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the 913 919 # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option. 914 920 # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want. … … 927 933 # * <tt>:uniq</tt> - if set to +true+, duplicate associated objects will be ignored by accessors and query methods 928 934 # * <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 935 # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated 930 936 # classes with a manual statement 931 937 # * <tt>:insert_sql</tt> - overwrite the default generated SQL statement used to add links between the associated classes … … 944 950 # has_and_belongs_to_many :nations, :class_name => "Country" 945 951 # has_and_belongs_to_many :categories, :join_table => "prods_cats" 946 # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => 952 # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => 947 953 # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' 948 954 def has_and_belongs_to_many(association_id, options = {}, &extension) 949 955 reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) 950 956 951 957 add_multiple_associated_save_callbacks(reflection.name) 952 958 collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) … … 982 988 table_name_prefix + join_table + table_name_suffix 983 989 end 984 990 985 991 def association_accessor_methods(reflection, association_proxy_class) 992 ivar = "@#{reflection.name}" 993 986 994 define_method(reflection.name) do |*params| 987 995 force_reload = params.first unless params.empty? 988 association = instance_variable_get("@#{reflection.name}") 996 997 association = instance_variable_get(ivar) if instance_variable_defined?(ivar) 989 998 990 999 if association.nil? || force_reload … … 992 1001 retval = association.reload 993 1002 if retval.nil? and association_proxy_class == BelongsToAssociation 994 instance_variable_set( "@#{reflection.name}", nil)1003 instance_variable_set(ivar, nil) 995 1004 return nil 996 1005 end 997 instance_variable_set( "@#{reflection.name}", association)1006 instance_variable_set(ivar, association) 998 1007 end 999 1008 … … 1002 1011 1003 1012 define_method("#{reflection.name}=") do |new_value| 1004 association = instance_variable_get("@#{reflection.name}") 1013 association = instance_variable_get(ivar) if instance_variable_defined?(ivar) 1014 1005 1015 if association.nil? || association.target != new_value 1006 1016 association = association_proxy_class.new(self, reflection) … … 1009 1019 association.replace(new_value) 1010 1020 1011 unless new_value.nil? 1012 instance_variable_set("@#{reflection.name}", association) 1013 else 1014 instance_variable_set("@#{reflection.name}", nil) 1015 end 1021 instance_variable_set(ivar, new_value.nil? ? nil : association) 1016 1022 end 1017 1023 … … 1020 1026 association = association_proxy_class.new(self, reflection) 1021 1027 association.target = target 1022 instance_variable_set( "@#{reflection.name}", association)1028 instance_variable_set(ivar, association) 1023 1029 end 1024 1030 end … … 1026 1032 def collection_reader_method(reflection, association_proxy_class) 1027 1033 define_method(reflection.name) do |*params| 1034 ivar = "@#{reflection.name}" 1035 1028 1036 force_reload = params.first unless params.empty? 1029 association = instance_variable_get( "@#{reflection.name}")1037 association = instance_variable_get(ivar) if instance_variable_defined?(ivar) 1030 1038 1031 1039 unless association.respond_to?(:loaded?) 1032 1040 association = association_proxy_class.new(self, reflection) 1033 instance_variable_set( "@#{reflection.name}", association)1041 instance_variable_set(ivar, association) 1034 1042 end 1035 1043 … … 1037 1045 1038 1046 association 1047 end 1048 1049 define_method("#{reflection.name.to_s.singularize}_ids") do 1050 send(reflection.name).map(&:id) 1039 1051 end 1040 1052 end … … 1043 1055 collection_reader_method(reflection, association_proxy_class) 1044 1056 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 1051 1052 define_method("#{reflection.name.to_s.singularize}_ids") do 1053 send(reflection.name).map(&:id) 1054 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 1057 if writer 1058 define_method("#{reflection.name}=") do |new_value| 1059 # Loads proxy class instance (defined in collection_reader_method) if not already loaded 1060 association = send(reflection.name) 1061 association.replace(new_value) 1062 association 1063 end 1064 1065 define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| 1066 ids = (new_value || []).reject { |nid| nid.blank? } 1067 send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) 1068 end 1069 end 1060 1070 end 1061 1071 1062 1072 def add_multiple_associated_save_callbacks(association_name) 1063 1073 method_name = "validate_associated_records_for_#{association_name}".to_sym 1074 ivar = "@#{association_name}" 1075 1064 1076 define_method(method_name) do 1065 association = instance_variable_get("@#{association_name}") 1077 association = instance_variable_get(ivar) if instance_variable_defined?(ivar) 1078 1066 1079 if association.respond_to?(:loaded?) 1067 1080 if new_record? … … 1079 1092 1080 1093 after_callback = <<-end_eval 1081 association = instance_variable_get(" @#{association_name}")1094 association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") 1082 1095 1083 1096 records_to_save = if @new_record_before_save … … 1090 1103 1091 1104 records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank? 1092 1105 1093 1106 # reconstruct the SQL queries now that we know the owner's id 1094 1107 association.send(:construct_sql) if association.respond_to?(:construct_sql) … … 1102 1115 def association_constructor_method(constructor, reflection, association_proxy_class) 1103 1116 define_method("#{constructor}_#{reflection.name}") do |*params| 1117 ivar = "@#{reflection.name}" 1118 1104 1119 attributees = params.first unless params.empty? 1105 1120 replace_existing = params[1].nil? ? true : params[1] 1106 association = instance_variable_get( "@#{reflection.name}")1121 association = instance_variable_get(ivar) if instance_variable_defined?(ivar) 1107 1122 1108 1123 if association.nil? 1109 1124 association = association_proxy_class.new(self, reflection) 1110 instance_variable_set( "@#{reflection.name}", association)1125 instance_variable_set(ivar, association) 1111 1126 end 1112 1127 … … 1118 1133 end 1119 1134 end 1120 1135 1121 1136 def find_with_associations(options = {}) 1122 1137 catch :invalid_query do … … 1174 1189 :as, :through, :source, :source_type, 1175 1190 :uniq, 1176 :finder_sql, :counter_sql, 1177 :before_add, :after_add, :before_remove, :after_remove, 1191 :finder_sql, :counter_sql, 1192 :before_add, :after_add, :before_remove, :after_remove, 1178 1193 :extend 1179 1194 ) 1180 1195 1181 options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?1196 options[:extend] = create_extension_modules(association_id, extension, options[:extend]) 1182 1197 1183 1198 create_reflection(:has_many, association_id, options, self) … … 1194 1209 def create_belongs_to_reflection(association_id, options) 1195 1210 options.assert_valid_keys( 1196 :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, 1211 :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, 1197 1212 :counter_cache, :extend, :polymorphic 1198 1213 ) 1199 1214 1200 1215 reflection = create_reflection(:belongs_to, association_id, options, self) 1201 1216 … … 1206 1221 reflection 1207 1222 end 1208 1223 1209 1224 def create_has_and_belongs_to_many_reflection(association_id, options, &extension) 1210 1225 options.assert_valid_keys( 1211 :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, 1226 :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, 1212 1227 :select, :conditions, :include, :order, :group, :limit, :offset, 1213 :uniq, 1228 :uniq, 1214 1229 :finder_sql, :delete_sql, :insert_sql, 1215 :before_add, :after_add, :before_remove, :after_remove, 1230 :before_add, :after_add, :before_remove, :after_remove, 1216 1231 :extend 1217 1232 ) 1218 1233 1219 options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?1234 options[:extend] = create_extension_modules(association_id, extension, options[:extend]) 1220 1235 1221 1236 reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) 1222 1237 1223 1238 reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) 1224 1239 1225 1240 reflection 1226 1241 end &hell