diff options
Diffstat (limited to 'activerecord/lib')
11 files changed, 272 insertions, 231 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9c408779a0..3b2c6b7a59 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -19,13 +19,13 @@ module ActiveRecord super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.") end end - + class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection, source_reflection) 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.") end end - + class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc: def initialize(reflection) through_reflection = reflection.through_reflection @@ -72,21 +72,21 @@ module ActiveRecord base.extend(ClassMethods) end - # Clears out the association cache + # Clears out the association cache def clear_association_cache #:nodoc: self.class.reflect_on_all_associations.to_a.each do |assoc| instance_variable_set "@#{assoc.name}", nil end unless self.new_record? end - - # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like - # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are - # 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> + + # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like + # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are + # 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> # methods. Example: # # class Project < ActiveRecord::Base # belongs_to :portfolio - # has_one :project_manager + # has_one :project_manager # has_many :milestones # has_and_belongs_to_many :categories # end @@ -117,38 +117,38 @@ module ActiveRecord # #build_other(attributes={}) | X | | X # #create_other(attributes={}) | X | | X # #other.create!(attributes={}) | | | X - # #other.nil? | X | X | + # #other.nil? | X | X | # # ===Collection associations (one-to-many / many-to-many) # | | | has_many - # generated methods | habtm | has_many | :through + # generated methods | habtm | has_many | :through # ----------------------------------+-------+----------+---------- # #others | X | X | X - # #others=(other,other,...) | X | X | + # #others=(other,other,...) | X | X | # #other_ids | X | X | X - # #other_ids=(id,id,...) | X | X | + # #other_ids=(id,id,...) | X | X | # #others<< | X | X | X # #others.push | X | X | X # #others.concat | X | X | X # #others.build(attributes={}) | X | X | X - # #others.create(attributes={}) | X | X | + # #others.create(attributes={}) | X | X | # #others.create!(attributes={}) | X | X | X # #others.size | X | X | X # #others.length | X | X | X # #others.count | | X | X # #others.sum(args*,&block) | X | X | X # #others.empty? | X | X | X - # #others.clear | X | X | + # #others.clear | X | X | # #others.delete(other,other,...) | X | X | X - # #others.delete_all | X | X | + # #others.delete_all | X | X | # #others.destroy_all | X | X | X # #others.find(*args) | X | X | X - # #others.find_first | X | | - # #others.uniq | X | X | + # #others.find_first | X | | + # #others.uniq | X | X | # #others.reset | X | X | X # # == Cardinality and associations - # + # # ActiveRecord associations can be used to describe relations with one-to-one, one-to-many # and many-to-many cardinality. Each model uses an association to describe its role in # the relation. In each case, the +belongs_to+ association is used in the model that has @@ -207,7 +207,7 @@ module ActiveRecord # end # # Choosing which way to build a many-to-many relationship is not always simple. - # If you need to work with the relationship model as its own entity, + # If you need to work with the relationship model as its own entity, # use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when # you never work directly with the relationship itself. # @@ -253,7 +253,7 @@ module ActiveRecord # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment # is cancelled. # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>#association.build</tt> method (documented below). - # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It + # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It # does not save the parent either. # # === Collections @@ -275,10 +275,10 @@ module ActiveRecord # def evaluate_velocity(developer) # ... # end - # end + # end # # It's possible to stack callbacks by passing them as an array. Example: - # + # # class Project # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] # end @@ -334,43 +334,43 @@ module ActiveRecord # # Some extensions can only be made to work with knowledge of the association proxy's internals. # Extensions can access relevant state using accessors on the association proxy: - # + # # * +proxy_owner+ - Returns the object the association is part of. # * +proxy_reflection+ - Returns the reflection object that describes the association. # * +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+. # # === Association Join Models - # + # # Has Many associations can be configured with the <tt>:through</tt> option to use an explicit join model to retrieve the data. This # operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations, # callbacks, and extra attributes on the join model. Consider the following schema: - # + # # class Author < ActiveRecord::Base # has_many :authorships # has_many :books, :through => :authorships # end - # + # # class Authorship < ActiveRecord::Base # belongs_to :author # belongs_to :book # end - # + # # @author = Author.find :first # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to. # @author.books # selects all books by using the Authorship join model - # + # # You can also go through a +has_many+ association on the join model: - # + # # class Firm < ActiveRecord::Base # has_many :clients # has_many :invoices, :through => :clients # end - # + # # class Client < ActiveRecord::Base # belongs_to :firm # has_many :invoices # end - # + # # class Invoice < ActiveRecord::Base # belongs_to :client # end @@ -380,36 +380,36 @@ module ActiveRecord # @firm.invoices # selects all invoices by going through the Client join model. # # === Polymorphic Associations - # - # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they + # + # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they # specify an interface that a +has_many+ association must adhere to. - # + # # class Asset < ActiveRecord::Base # belongs_to :attachable, :polymorphic => true # end - # + # # class Post < ActiveRecord::Base # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use. # end # # @asset.attachable = @post - # + # # 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 # an +attachable_id+ integer column and an +attachable_type+ string column. # # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order - # for the associations to work as expected, ensure that you store the base model for the STI models in the + # for the associations to work as expected, ensure that you store the base model for the STI models in the # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts # and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table. # # class Asset < ActiveRecord::Base # belongs_to :attachable, :polymorphic => true - # + # # def attachable_type=(sType) # super(sType.to_s.classify.constantize.base_class.to_s) # end # end - # + # # class Post < ActiveRecord::Base # # because we store "Post" in attachable_type now :dependent => :destroy will work # has_many :assets, :as => :attachable, :dependent => :destroy @@ -424,7 +424,7 @@ module ActiveRecord # == Caching # # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically - # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without + # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without # worrying too much about performance at the first go. Example: # # project.milestones # fetches milestones from the database @@ -450,7 +450,7 @@ module ActiveRecord # puts "Post: " + post.title # puts "Written by: " + post.author.name # puts "Last comment on: " + post.comments.first.created_on - # end + # end # # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author: # @@ -475,7 +475,7 @@ module ActiveRecord # 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 # 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 # 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. - # + # # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So # <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 # <tt>:select</tt> option is ignored. @@ -483,37 +483,37 @@ module ActiveRecord # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich # associations" with +has_and_belongs_to_many+ are not a good fit for eager loading. - # + # # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated # before the actual model exists. - # + # # == Table Aliasing # # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once, # the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended # for any more successive uses of the table name. - # + # # Post.find :all, :include => :comments # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... # Post.find :all, :include => :special_comments # STI # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment' # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts - # + # # Acts as tree example: - # + # # TreeMixin.find :all, :include => :children # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes - # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... + # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... + # LEFT OUTER JOIN parents_mixins ... + # TreeMixin.find :all, :include => {:children => {:parent => :children}} + # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... # LEFT OUTER JOIN parents_mixins ... - # TreeMixin.find :all, :include => {:children => {:parent => :children}} - # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... - # LEFT OUTER JOIN parents_mixins ... # LEFT OUTER JOIN mixins childrens_mixins_2 - # + # # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix: - # + # # Post.find :all, :include => :categories # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ... # Post.find :all, :include => {:categories => :posts} @@ -523,18 +523,18 @@ module ActiveRecord # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ... # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts - # + # # 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: - # + # # Post.find :all, :include => :comments, :joins => "inner join comments ..." # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ... # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..." - # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ... + # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ... # LEFT OUTER JOIN comments special_comments_posts ... # INNER JOIN comments ... - # + # # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database. - # + # # == Modules # # By default, associations will look for objects within the current module scope. Consider: @@ -575,12 +575,12 @@ module ActiveRecord # possible. module ClassMethods # Adds the following methods for retrieval and query of collections of associated objects: - # +collection+ is replaced with the symbol passed as the first argument, so + # +collection+ is replaced with the symbol passed as the first argument, so # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. # An empty array is returned if none are found. # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key. - # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL. + # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL. # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model. # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate. # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids @@ -592,7 +592,7 @@ module ActiveRecord # * <tt>collection.size</tt> - returns the number of associated objects. # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find. # * <tt>collection.build(attributes = {}, ...)</tt> - returns one or more new objects of the collection type that have been instantiated - # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an + # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an # associated object already exists, not if it's +nil+! # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation). @@ -612,7 +612,7 @@ module ActiveRecord # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) # The declaration can also include an options hash to specialize the behavior of the association. - # + # # Options are: # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but @@ -637,13 +637,13 @@ module ActiveRecord # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause. # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned. # * <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. - # * <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 + # * <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 # but not include the joined columns. # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>). - # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> + # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt> # or <tt>has_many</tt> association on the join model. - # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be + # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or # <tt>:subscriber</tt> on +Subscription+, unless a <tt>:source</tt> is given. # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source @@ -669,7 +669,6 @@ module ActiveRecord configure_dependency_for_has_many(reflection) if options[:through] - collection_reader_method(reflection, HasManyThroughAssociation) collection_accessor_methods(reflection, HasManyThroughAssociation, false) else add_multiple_associated_save_callbacks(reflection.name) @@ -679,10 +678,10 @@ module ActiveRecord end # Adds the following methods for retrieval and query of a single associated object: - # +association+ is replaced with the symbol passed as the first argument, so + # +association+ is replaced with the symbol passed as the first argument, so # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found. - # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key, + # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key, # and saves the associate object. # * <tt>association.nil?</tt> - returns +true+ if there is no associated object. # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated @@ -699,7 +698,7 @@ module ActiveRecord # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) # # The declaration can also include an options hash to specialize the behavior of the association. - # + # # Options are: # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but @@ -726,25 +725,28 @@ module ActiveRecord def has_one(association_id, options = {}) reflection = create_has_one_reflection(association_id, options) + ivar = "@#{reflection.name}" + module_eval do after_save <<-EOF - association = instance_variable_get("@#{reflection.name}") + association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") + if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id) association["#{reflection.primary_key_name}"] = id association.save(true) end EOF end - + association_accessor_methods(reflection, HasOneAssociation) association_constructor_method(:build, reflection, HasOneAssociation) association_constructor_method(:create, reflection, HasOneAssociation) - + configure_dependency_for_has_one(reflection) end # Adds the following methods for retrieval and query for a single associated object for which this object holds an id: - # +association+ is replaced with the symbol passed as the first argument, so + # +association+ is replaced with the symbol passed as the first argument, so # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found. # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key. @@ -762,7 +764,7 @@ module ActiveRecord # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) # The declaration can also include an options hash to specialize the behavior of the association. - # + # # Options are: # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but @@ -774,37 +776,40 @@ module ActiveRecord # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name # 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+. # Similarly, +belongs_to :favorite_person, :class_name => "Person"+ will use a foreign key of +favorite_person_id+. - # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+ + # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+ # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging +Comment+ class) - # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing + # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.) # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly. # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded. # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing +true+. - # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute + # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). # # Option examples: # belongs_to :firm, :foreign_key => "client_of" # belongs_to :author, :class_name => "Person", :foreign_key => "author_id" - # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", + # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", # :conditions => 'discounts > #{payments_count}' # belongs_to :attachable, :polymorphic => true def belongs_to(association_id, options = {}) reflection = create_belongs_to_reflection(association_id, options) - + + ivar = "@#{reflection.name}" + if reflection.options[:polymorphic] association_accessor_methods(reflection, BelongsToPolymorphicAssociation) module_eval do before_save <<-EOF - association = instance_variable_get("@#{reflection.name}") + association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") + if association && association.target if association.new_record? association.save(true) end - + if association.updated? self["#{reflection.primary_key_name}"] = association.id self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s @@ -819,16 +824,17 @@ module ActiveRecord module_eval do before_save <<-EOF - association = instance_variable_get("@#{reflection.name}") - if !association.nil? + association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") + + if !association.nil? if association.new_record? association.save(true) end - + if association.updated? self["#{reflection.primary_key_name}"] = association.id end - end + end EOF end end @@ -848,7 +854,7 @@ module ActiveRecord "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" + " unless #{reflection.name}.nil?'" ) - + module_eval( "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)" ) @@ -858,11 +864,11 @@ module ActiveRecord # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+ # will give the default join table name of +developers_projects+ because "D" outranks "P". Note that this precedence - # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths, + # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths, # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher - # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt> + # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt> # to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>, - # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the + # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the # custom <tt>join_table</tt> option if you need to. # # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through @@ -871,13 +877,13 @@ module ActiveRecord # associations with attributes to a real join model (see introduction). # # Adds the following methods for retrieval and query: - # +collection+ is replaced with the symbol passed as the first argument, so + # +collection+ is replaced with the symbol passed as the first argument, so # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. # An empty array is returned if none are found. - # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table + # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). - # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table. + # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table. # This does not destroy the objects. # * <tt>collection=objects</tt> - replaces the collection's content by deleting and adding objects as appropriate. # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids @@ -906,10 +912,10 @@ module ActiveRecord # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>) # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>) # The declaration may include an options hash to specialize the behavior of the association. - # + # # Options are: # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the + # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option. # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want. # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any @@ -926,7 +932,7 @@ module ActiveRecord # such as <tt>last_name, first_name DESC</tt> # * <tt>:uniq</tt> - if set to +true+, duplicate associated objects will be ignored by accessors and query methods # * <tt>:finder_sql</tt> - overwrite the default generated SQL statement used to fetch the association with a manual statement - # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated + # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated # classes with a manual statement # * <tt>:insert_sql</tt> - overwrite the default generated SQL statement used to add links between the associated classes # with a manual statement @@ -943,11 +949,11 @@ module ActiveRecord # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ] # has_and_belongs_to_many :nations, :class_name => "Country" # has_and_belongs_to_many :categories, :join_table => "prods_cats" - # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => + # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) - + add_multiple_associated_save_callbacks(reflection.name) collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) @@ -981,88 +987,95 @@ module ActiveRecord table_name_prefix + join_table + table_name_suffix end - + def association_accessor_methods(reflection, association_proxy_class) + ivar = "@#{reflection.name}" + define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - association = instance_variable_get("@#{reflection.name}") + + association = instance_variable_get(ivar) if instance_variable_defined?(ivar) if association.nil? || force_reload association = association_proxy_class.new(self, reflection) retval = association.reload if retval.nil? and association_proxy_class == BelongsToAssociation - instance_variable_set("@#{reflection.name}", nil) + instance_variable_set(ivar, nil) return nil end - instance_variable_set("@#{reflection.name}", association) + instance_variable_set(ivar, association) end association.target.nil? ? nil : association end define_method("#{reflection.name}=") do |new_value| - association = instance_variable_get("@#{reflection.name}") + association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + if association.nil? || association.target != new_value association = association_proxy_class.new(self, reflection) end association.replace(new_value) - unless new_value.nil? - instance_variable_set("@#{reflection.name}", association) - else - instance_variable_set("@#{reflection.name}", nil) - end + instance_variable_set(ivar, new_value.nil? ? nil : association) end define_method("set_#{reflection.name}_target") do |target| return if target.nil? and association_proxy_class == BelongsToAssociation association = association_proxy_class.new(self, reflection) association.target = target - instance_variable_set("@#{reflection.name}", association) + instance_variable_set(ivar, association) end end def collection_reader_method(reflection, association_proxy_class) define_method(reflection.name) do |*params| + ivar = "@#{reflection.name}" + force_reload = params.first unless params.empty? - association = instance_variable_get("@#{reflection.name}") + association = instance_variable_get(ivar) if instance_variable_defined?(ivar) unless association.respond_to?(:loaded?) association = association_proxy_class.new(self, reflection) - instance_variable_set("@#{reflection.name}", association) + instance_variable_set(ivar, association) end association.reload if force_reload association end + + define_method("#{reflection.name.to_s.singularize}_ids") do + send(reflection.name).map(&:id) + end end def collection_accessor_methods(reflection, association_proxy_class, writer = true) collection_reader_method(reflection, association_proxy_class) - define_method("#{reflection.name}=") do |new_value| - # Loads proxy class instance (defined in collection_reader_method) if not already loaded - association = send(reflection.name) - association.replace(new_value) - association - end + if writer + define_method("#{reflection.name}=") do |new_value| + # Loads proxy class instance (defined in collection_reader_method) if not already loaded + association = send(reflection.name) + association.replace(new_value) + association + end - define_method("#{reflection.name.to_s.singularize}_ids") do - send(reflection.name).map(&:id) + define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| + ids = (new_value || []).reject { |nid| nid.blank? } + send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) + end end - - define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| - ids = (new_value || []).reject { |nid| nid.blank? } - send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) - end if writer end def add_multiple_associated_save_callbacks(association_name) method_name = "validate_associated_records_for_#{association_name}".to_sym + ivar = "@#{association_name}" + define_method(method_name) do - association = instance_variable_get("@#{association_name}") + association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + if association.respond_to?(:loaded?) if new_record? association @@ -1078,7 +1091,7 @@ module ActiveRecord before_save("@new_record_before_save = new_record?; true") after_callback = <<-end_eval - association = instance_variable_get("@#{association_name}") + association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") records_to_save = if @new_record_before_save association @@ -1089,7 +1102,7 @@ module ActiveRecord end records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank? - + # reconstruct the SQL queries now that we know the owner's id association.send(:construct_sql) if association.respond_to?(:construct_sql) end_eval @@ -1101,13 +1114,15 @@ module ActiveRecord def association_constructor_method(constructor, reflection, association_proxy_class) define_method("#{constructor}_#{reflection.name}") do |*params| + ivar = "@#{reflection.name}" + attributees = params.first unless params.empty? replace_existing = params[1].nil? ? true : params[1] - association = instance_variable_get("@#{reflection.name}") + association = instance_variable_get(ivar) if instance_variable_defined?(ivar) if association.nil? association = association_proxy_class.new(self, reflection) - instance_variable_set("@#{reflection.name}", association) + instance_variable_set(ivar, association) end if association_proxy_class == HasOneAssociation @@ -1117,7 +1132,7 @@ module ActiveRecord end end end - + def find_with_associations(options = {}) catch :invalid_query do join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) @@ -1173,12 +1188,12 @@ module ActiveRecord :select, :conditions, :include, :order, :group, :limit, :offset, :as, :through, :source, :source_type, :uniq, - :finder_sql, :counter_sql, - :before_add, :after_add, :before_remove, :after_remove, + :finder_sql, :counter_sql, + :before_add, :after_add, :before_remove, :after_remove, :extend ) - options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? + options[:extend] = create_extension_modules(association_id, extension, options[:extend]) create_reflection(:has_many, association_id, options, self) end @@ -1193,10 +1208,10 @@ module ActiveRecord def create_belongs_to_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, + :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :polymorphic ) - + reflection = create_reflection(:belongs_to, association_id, options, self) if options[:polymorphic] @@ -1205,23 +1220,23 @@ module ActiveRecord reflection end - + def create_has_and_belongs_to_many_reflection(association_id, options, &extension) options.assert_valid_keys( - :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, + :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, :select, :conditions, :include, :order, :group, :limit, :offset, - :uniq, + :uniq, :finder_sql, :delete_sql, :insert_sql, - :before_add, :after_add, :before_remove, :after_remove, + :before_add, :after_add, :before_remove, :after_remove, :extend ) - options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? + options[:extend] = create_extension_modules(association_id, extension, options[:extend]) reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) - + reflection end @@ -1232,7 +1247,7 @@ module ActiveRecord def guard_against_unlimitable_reflections(reflections, options) if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections) raise( - ConfigurationError, + ConfigurationError, "You can not use offset and limit together with has_many or has_and_belongs_to_many associations" ) end @@ -1249,7 +1264,7 @@ module ActiveRecord scope = scope(:find) sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} " sql << join_dependency.join_associations.collect{|join| join.association_join }.join - + add_joins!(sql, options, scope) add_conditions!(sql, options[:conditions], scope) add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) @@ -1258,10 +1273,10 @@ module ActiveRecord add_order!(sql, options[:order], scope) add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections) add_lock!(sql, options, scope) - + return sanitize_sql(sql) end - + def add_limited_ids_condition!(sql, options, join_dependency) unless (id_list = select_limited_ids_list(options, join_dependency)).empty? sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) " @@ -1324,7 +1339,7 @@ module ActiveRecord condition_table_name != table_name end end - + # Checks if the query order references a table other than the current model's table. def include_eager_order?(options) order = options[:order] @@ -1362,13 +1377,16 @@ module ActiveRecord end def create_extension_modules(association_id, block_extension, extensions) - extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension" + if block_extension + extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension" - silence_warnings do - Object.const_set(extension_module_name, Module.new(&block_extension)) + silence_warnings do + Object.const_set(extension_module_name, Module.new(&block_extension)) + end + Array(extensions).push(extension_module_name.constantize) + else + Array(extensions) end - - Array(extensions).push(extension_module_name.constantize) end class JoinDependency # :nodoc: @@ -1526,13 +1544,15 @@ module ActiveRecord end def column_names_with_alias - unless @column_names_with_alias + unless defined?(@column_names_with_alias) @column_names_with_alias = [] + ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i| @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"] end end - return @column_names_with_alias + + @column_names_with_alias end def extract_record(row) @@ -1568,7 +1588,7 @@ module ActiveRecord if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son} join_dependency.table_aliases[aliased_table_name] += 1 end - + unless join_dependency.table_aliases[aliased_table_name].zero? # if the table name has been used, then use an alias @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}" @@ -1578,7 +1598,7 @@ module ActiveRecord else join_dependency.table_aliases[aliased_table_name] += 1 end - + if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through]) @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name unless join_dependency.table_aliases[aliased_join_table_name].zero? @@ -1601,22 +1621,22 @@ module ActiveRecord connection.quote_table_name(aliased_join_table_name), options[:foreign_key] || reflection.active_record.to_s.foreign_key, connection.quote_table_name(parent.aliased_table_name), - reflection.active_record.primary_key] + + reflection.active_record.primary_key] + " #{join_type} %s ON %s.%s = %s.%s " % [ table_name_and_alias, - connection.quote_table_name(aliased_table_name), - klass.primary_key, + connection.quote_table_name(aliased_table_name), + klass.primary_key, connection.quote_table_name(aliased_join_table_name), - options[:association_foreign_key] || klass.to_s.foreign_key + options[:association_foreign_key] || klass.to_s.foreign_key ] when :has_many, :has_one case when reflection.macro == :has_many && reflection.options[:through] through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : '' - - jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil - first_key = second_key = as_extra = nil - + + jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil + first_key = second_key = as_extra = nil + if through_reflection.options[:as] # has_many :through against a polymorphic join jt_foreign_key = through_reflection.options[:as].to_s + '_id' jt_as_extra = " AND %s.%s = %s" % [ @@ -1625,24 +1645,24 @@ module ActiveRecord klass.quote_value(parent.active_record.base_class.name) ] else - jt_foreign_key = through_reflection.primary_key_name + jt_foreign_key = through_reflection.primary_key_name end - + case source_reflection.macro when :has_many - if source_reflection.options[:as] - first_key = "#{source_reflection.options[:as]}_id" - second_key = options[:foreign_key] || primary_key + if source_reflection.options[:as] + first_key = "#{source_reflection.options[:as]}_id" + second_key = options[:foreign_key] || primary_key as_extra = " AND %s.%s = %s" % [ connection.quote_table_name(aliased_table_name), connection.quote_column_name("#{source_reflection.options[:as]}_type"), - klass.quote_value(source_reflection.active_record.base_class.name) + klass.quote_value(source_reflection.active_record.base_class.name) ] else first_key = through_reflection.klass.base_class.to_s.foreign_key second_key = options[:foreign_key] || primary_key end - + unless through_reflection.klass.descends_from_active_record? jt_sti_extra = " AND %s.%s = %s" % [ connection.quote_table_name(aliased_join_table_name), @@ -1666,17 +1686,17 @@ module ActiveRecord " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), connection.quote_table_name(parent.aliased_table_name), - connection.quote_column_name(parent.primary_key), + connection.quote_column_name(parent.primary_key), connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(jt_foreign_key), + connection.quote_column_name(jt_foreign_key), jt_as_extra, jt_source_extra, jt_sti_extra ] + " #{join_type} %s ON (%s.%s = %s.%s%s) " % [ - table_name_and_alias, + table_name_and_alias, connection.quote_table_name(aliased_table_name), - connection.quote_column_name(first_key), + connection.quote_column_name(first_key), connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(second_key), + connection.quote_column_name(second_key), as_extra ] @@ -1684,11 +1704,11 @@ module ActiveRecord " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [ table_name_and_alias, connection.quote_table_name(aliased_table_name), - "#{reflection.options[:as]}_id", + "#{reflection.options[:as]}_id", connection.quote_table_name(parent.aliased_table_name), - parent.primary_key, + parent.primary_key, connection.quote_table_name(aliased_table_name), - "#{reflection.options[:as]}_type", + "#{reflection.options[:as]}_type", klass.quote_value(parent.active_record.base_class.name) ] else @@ -1696,18 +1716,18 @@ module ActiveRecord " #{join_type} %s ON %s.%s = %s.%s " % [ table_name_and_alias, aliased_table_name, - foreign_key, + foreign_key, parent.aliased_table_name, - parent.primary_key + parent.primary_key ] end when :belongs_to " #{join_type} %s ON %s.%s = %s.%s " % [ table_name_and_alias, - connection.quote_table_name(aliased_table_name), - reflection.klass.primary_key, + connection.quote_table_name(aliased_table_name), + reflection.klass.primary_key, connection.quote_table_name(parent.aliased_table_name), - options[:foreign_key] || reflection.primary_key_name + options[:foreign_key] || reflection.primary_key_name ] else "" @@ -1723,15 +1743,14 @@ module ActiveRecord join end - - protected + protected def pluralize(table_name) ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name end - + def table_alias_for(table_name, table_alias) - "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip + "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip end def table_name_and_alias @@ -1739,11 +1758,10 @@ module ActiveRecord end def interpolate_sql(sql) - instance_eval("%@#{sql.gsub('@', '\@')}@") - end + instance_eval("%@#{sql.gsub('@', '\@')}@") + end private - def join_type "LEFT OUTER JOIN" end diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index b26ea586e5..e08bd04ebb 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -33,10 +33,10 @@ module ActiveRecord if ids.size == 1 id = ids.first.to_i - record = load_target.detect { |record| id == record.id } + record = load_target.detect { |r| id == r.id } expects_array ? [record] : record else - load_target.select { |record| ids.include?(record.id) } + load_target.select { |r| ids.include?(r.id) } end else conditions = "#{@finder_sql}" @@ -84,19 +84,19 @@ module ActiveRecord else columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns") - attributes = columns.inject({}) do |attributes, column| + attributes = columns.inject({}) do |attrs, column| case column.name when @reflection.primary_key_name - attributes[column.name] = @owner.quoted_id + attrs[column.name] = @owner.quoted_id when @reflection.association_foreign_key - attributes[column.name] = record.quoted_id + attrs[column.name] = record.quoted_id else - if record.attributes.has_key?(column.name) + if record.has_attribute?(column.name) value = @owner.send(:quote_value, record[column.name], column) - attributes[column.name] = value unless value.nil? + attrs[column.name] = value unless value.nil? end end - attributes + attrs end sql = diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index ead447baa0..f134ae0757 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -41,10 +41,10 @@ module ActiveRecord if ids.size == 1 id = ids.first - record = load_target.detect { |record| id == record.id } + record = load_target.detect { |r| id == r.id } expects_array ? [ record ] : record else - load_target.select { |record| ids.include?(record.id) } + load_target.select { |r| ids.include?(r.id) } end else conditions = "#{@finder_sql}" diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 14f752abe4..883375707d 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -262,12 +262,31 @@ module ActiveRecord end def conditions - @conditions ||= [ - (interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]), - (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]), - (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.source_reflection.options[:conditions])) if @reflection.source_reflection.options[:conditions]), - ("#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}" unless @reflection.through_reflection.klass.descends_from_active_record?) - ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && !@reflection.source_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?) + @conditions = build_conditions unless defined?(@conditions) + @conditions + end + + def build_conditions + association_conditions = @reflection.options[:conditions] + through_conditions = @reflection.through_reflection.options[:conditions] + source_conditions = @reflection.source_reflection.options[:conditions] + uses_sti = !@reflection.through_reflection.klass.descends_from_active_record? + + if association_conditions || through_conditions || source_conditions || uses_sti + all = [] + + [association_conditions, through_conditions, source_conditions].each do |conditions| + all << interpolate_sql(sanitize_sql(conditions)) if conditions + end + + all << build_sti_condition if uses_sti + + all.map { |sql| "(#{sql})" } * ' AND ' + end + end + + def build_sti_condition + "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}" end alias_method :sql_conditions, :conditions diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index dd26c1f749..3ae42ddac7 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -5,7 +5,7 @@ module ActiveRecord def self.included(base) base.extend ClassMethods - base.attribute_method_suffix *DEFAULT_SUFFIXES + base.attribute_method_suffix(*DEFAULT_SUFFIXES) base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 512b04bcdf..ea2ea98327 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -592,7 +592,7 @@ module ActiveRecord #:nodoc: def update(id, attributes) if id.is_a?(Array) idx = -1 - id.collect { |id| idx += 1; update(id, attributes[idx]) } + id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } else object = find(id) object.update_attributes(attributes) @@ -642,7 +642,11 @@ module ActiveRecord #:nodoc: # todos = [1,2,3] # Todo.destroy(todos) def destroy(id) - id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy + if id.is_a?(Array) + id.map { |one_id| destroy(one_id) } + else + find(id).destroy + end end # Updates all records with details given if they match a set of conditions supplied, limits and order can @@ -1075,9 +1079,9 @@ module ActiveRecord #:nodoc: # Returns an array of column objects for the table associated with this class. def columns - unless @columns + unless defined?(@columns) && @columns @columns = connection.columns(table_name, "#{name} Columns") - @columns.each {|column| column.primary = column.name == primary_key} + @columns.each { |column| column.primary = column.name == primary_key } end @columns end @@ -1217,7 +1221,7 @@ module ActiveRecord #:nodoc: # Returns whether this class is a base AR class. If A is a base class and # B descends from A, then B.base_class will return B. def abstract_class? - abstract_class == true + defined?(@abstract_class) && @abstract_class == true end private @@ -1428,7 +1432,7 @@ module ActiveRecord #:nodoc: case join when Symbol, Hash, Array join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil) - sql << " #{join_dependency.join_associations.collect{|join| join.association_join }.join} " + sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} " else sql << " #{join} " end @@ -1962,7 +1966,7 @@ module ActiveRecord #:nodoc: # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet. def new_record? - @new_record + defined?(@new_record) && @new_record end # * No record exists: Creates a new record with values matching those of the object attributes. @@ -2213,7 +2217,7 @@ module ActiveRecord #:nodoc: # Returns +true+ if the record is read only. Records loaded through joins with piggy-back # attributes will be marked as read only since they cannot be saved. def readonly? - @readonly == true + defined?(@readonly) && @readonly == true end # Marks this record as read only. @@ -2334,11 +2338,11 @@ module ActiveRecord #:nodoc: # Returns a copy of the attributes hash where all the values have been safely quoted for use in # an SQL statement. def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true) - quoted = attributes.inject({}) do |quoted, (name, value)| + quoted = attributes.inject({}) do |result, (name, value)| if column = column_for_attribute(name) - quoted[name] = quote_value(value, column) unless !include_primary_key && column.primary + result[name] = quote_value(value, column) unless !include_primary_key && column.primary end - quoted + result end include_readonly_attributes ? quoted : remove_readonly_attributes(quoted) end @@ -2454,9 +2458,9 @@ module ActiveRecord #:nodoc: end def clone_attributes(reader_method = :read_attribute, attributes = {}) - self.attribute_names.inject(attributes) do |attributes, name| - attributes[name] = clone_attribute_value(reader_method, name) - attributes + self.attribute_names.inject(attributes) do |attrs, name| + attrs[name] = clone_attribute_value(reader_method, name) + attrs end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 51b90d3c64..d950181566 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -71,7 +71,7 @@ module ActiveRecord # also be used to "borrow" the connection to do database work unrelated # to any of the specific Active Records. def connection - if @active_connection_name && (conn = active_connections[@active_connection_name]) + if defined?(@active_connection_name) && (conn = active_connections[@active_connection_name]) conn else # retrieve_connection sets the cache key. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index b8a6b19f04..48ff9de1cf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -192,14 +192,14 @@ module ActiveRecord end def fallback_string_to_date(string) - new_date *ParseDate.parsedate(string)[0..2] + new_date(*ParseDate.parsedate(string)[0..2]) end def fallback_string_to_time(string) time_hash = Date._parse(string) time_hash[:sec_fraction] = microseconds(time_hash) - new_time *time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction) + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 940707a9e9..e223baadbc 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -30,6 +30,7 @@ module ActiveRecord @connection, @logger = connection, logger @runtime = 0 @last_verification = 0 + @query_cache_enabled = false end # Returns the human-readable name of the adapter. Use mixed case - one diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index cbdef1dd45..635eab8428 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -230,7 +230,7 @@ module ActiveRecord # recursively. We use @ignore_new_methods as a guard to indicate whether # it is safe for the call to proceed. def singleton_method_added(sym) #:nodoc: - return if @ignore_new_methods + return if defined?(@ignore_new_methods) && @ignore_new_methods begin @ignore_new_methods = true @@ -356,15 +356,14 @@ module ActiveRecord private def migration_classes - migrations = migration_files.inject([]) do |migrations, migration_file| + classes = migration_files.inject([]) do |migrations, migration_file| load(migration_file) version, name = migration_version_and_name(migration_file) assert_unique_migration_version(migrations, version.to_i) migrations << migration_class(name, version.to_i) - end + end.sort_by(&:version) - sorted = migrations.sort_by { |m| m.version } - down? ? sorted.reverse : sorted + down? ? classes.reverse : classes end def assert_unique_migration_version(migrations, version) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 45a6418492..9c68435b0c 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -15,7 +15,7 @@ module ActiveRecord end end - # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. + # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs. # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or @@ -116,7 +116,7 @@ module ActiveRecord def rollback_active_record_state! id_present = has_attribute?(self.class.primary_key) previous_id = id - previous_new_record = @new_record + previous_new_record = new_record? yield rescue Exception @new_record = previous_new_record @@ -125,7 +125,7 @@ module ActiveRecord else @attributes.delete(self.class.primary_key) @attributes_cache.delete(self.class.primary_key) - end + end raise end end |