diff options
Diffstat (limited to 'activerecord/lib/active_record')
34 files changed, 308 insertions, 216 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 2cbfa42718..029d7a9b15 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -191,7 +191,7 @@ module ActiveRecord # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> - # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt> + # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.all(options),</tt> # <tt>Project#milestones.build, Project#milestones.create</tt> # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> # <tt>Project#categories.delete(category1)</tt> @@ -420,7 +420,7 @@ module ActiveRecord # end # end # - # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson") + # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") # person.first_name # => "David" # person.last_name # => "Heinemeier Hansson" # @@ -477,7 +477,7 @@ module ActiveRecord # belongs_to :book # end # - # @author = Author.find :first + # @author = Author.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 # @@ -497,7 +497,7 @@ module ActiveRecord # belongs_to :client # end # - # @firm = Firm.find :first + # @firm = Firm.first # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm # @firm.invoices # selects all invoices by going through the Client join model # @@ -669,7 +669,7 @@ module ActiveRecord # To iterate over these one hundred posts, we'll generate 201 database queries. Let's # first just optimize it for retrieving the author: # - # Post.find(:all, :include => :author).each do |post| + # Post.includes(:author).each do |post| # # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> # symbol. After loading the posts, find will collect the +author_id+ from each one and load @@ -678,7 +678,7 @@ module ActiveRecord # # We can improve upon the situation further by referencing both associations in the finder with: # - # Post.find(:all, :include => [ :author, :comments ]).each do |post| + # Post.includes(:author, :comments).each do |post| # # This will load all comments with a single query. This reduces the total number of queries # to 3. More generally the number of queries will be 1 plus the number of associations @@ -686,7 +686,7 @@ module ActiveRecord # # To include a deep hierarchy of associations, use a hash: # - # Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ]).each do |post| + # Post.includes(:author, {:comments => {:author => :gravatar}}).each do |post| # # That'll grab not only all the comments but all their authors and gravatar pictures. # You can mix and match symbols, arrays and hashes in any combination to describe the @@ -714,13 +714,13 @@ module ActiveRecord # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not. # # If you do want eager load only some members of an association it is usually more natural - # to <tt>:include</tt> an association which has conditions defined on it: + # to include an association which has conditions defined on it: # # class Post < ActiveRecord::Base # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true] # end # - # Post.find(:all, :include => :approved_comments) + # Post.includes(:approved_comments) # # This will load posts and eager load the +approved_comments+ association, which contains # only those comments that have been approved. @@ -732,7 +732,7 @@ module ActiveRecord # has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10 # end # - # Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments. + # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. # # 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. @@ -745,7 +745,7 @@ module ActiveRecord # # A call that tries to eager load the addressable model # - # Address.find(:all, :include => :addressable) + # Address.includes(:addressable) # # This will execute one query to load the addresses and load the addressables with one # query per addressable type. @@ -763,43 +763,43 @@ module ActiveRecord # 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, :joins => :comments + # Post.joins(:comments) # # => SELECT ... FROM posts INNER JOIN comments ON ... - # Post.find :all, :joins => :special_comments # STI + # Post.joins(:special_comments) # STI # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' - # Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name + # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts # # Acts as tree example: # - # TreeMixin.find :all, :joins => :children + # TreeMixin.joins(:children) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # TreeMixin.find :all, :joins => {:children => :parent} + # TreeMixin.joins(:children => :parent) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... - # TreeMixin.find :all, :joins => {:children => {:parent => :children}} + # TreeMixin.joins(:children => {:parent => :children}) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... # INNER 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, :joins => :categories + # Post.joins(:categories) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # Post.find :all, :joins => {:categories => :posts} + # Post.joins(:categories => :posts) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories - # Post.find :all, :joins => {:categories => {:posts => :categories}} + # Post.joins(:categories => {:posts => :categories}) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 # - # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table + # If you wish to specify your own custom joins using <tt>joins</tt> method, those table # names will take precedence over the eager associations: # - # Post.find :all, :joins => :comments, :joins => "inner join comments ..." + # Post.joins(:comments).joins("inner join comments ...") # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... - # Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..." + # Post.joins(:comments, :special_comments).joins("inner join comments ...") # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... # INNER JOIN comments special_comments_posts ... # INNER JOIN comments ... @@ -1031,7 +1031,7 @@ module ActiveRecord # === Example # # Example: A Firm class declares <tt>has_many :clients</tt>, which will add: - # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>) + # * <tt>Firm#clients</tt> (similar to <tt>Clients.all :conditions => ["firm_id = ?", id]</tt>) # * <tt>Firm#clients<<</tt> # * <tt>Firm#clients.delete</tt> # * <tt>Firm#clients=</tt> @@ -1195,7 +1195,7 @@ module ActiveRecord # === Example # # An Account class declares <tt>has_one :beneficiary</tt>, which will add: - # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>) + # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(:conditions => "account_id = #{id}")</tt>) # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index bd98cf2f46..92ed844a2e 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -9,7 +9,7 @@ module ActiveRecord # table_joins is an array of arel joins which might conflict with the aliases we assign here def initialize(table_joins = []) - @aliases = Hash.new + @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) } @table_joins = table_joins end @@ -26,8 +26,6 @@ module ActiveRecord def aliased_name_for(table_name, aliased_name = nil) aliased_name ||= table_name - initialize_count_for(table_name) if aliases[table_name].nil? - if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 @@ -36,8 +34,6 @@ module ActiveRecord # Otherwise, we need to use an alias aliased_name = connection.table_alias_for(aliased_name) - initialize_count_for(aliased_name) if aliases[aliased_name].nil? - # Update the count aliases[aliased_name] += 1 @@ -49,32 +45,24 @@ module ActiveRecord end end - def pluralize(table_name, base) - base.pluralize_table_names ? table_name.to_s.pluralize : table_name.to_s - end - private - def initialize_count_for(name) - aliases[name] = 0 - - unless Arel::Table === table_joins - # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase - quoted_name = connection.quote_table_name(name).downcase + def initial_count_for(name) + return 0 if Arel::Table === table_joins - aliases[name] += table_joins.map { |join| - # Table names + table aliases - join.left.downcase.scan( - /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ - ).size - }.sum - end + # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase + quoted_name = connection.quote_table_name(name).downcase - aliases[name] + table_joins.map { |join| + # Table names + table aliases + join.left.downcase.scan( + /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ + ).size + }.sum end def truncate(name) - name[0..connection.table_alias_length-3] + name.slice(0, connection.table_alias_length - 2) end def connection diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index c3aee90124..bb519c5703 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -30,7 +30,7 @@ module ActiveRecord @updated = false reset - construct_scope + reset_scope end # Returns the name of the table of the related class: @@ -51,7 +51,7 @@ module ActiveRecord # Reloads the \target and returns +self+ on success. def reload reset - construct_scope + reset_scope load_target self unless target.nil? end @@ -84,21 +84,25 @@ module ActiveRecord end def scoped - target_scope.merge(@association_scope) + target_scope.merge(association_scope) end - # Construct the scope for this association. + # The scope for this association. # # Note that the association_scope is merged into the target_scope only when the # scoped method is called. This is because at that point the call may be surrounded # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which # actually gets built. - def construct_scope + def association_scope if klass - @association_scope = AssociationScope.new(self).scope + @association_scope ||= AssociationScope.new(self).scope end end + def reset_scope + @association_scope = nil + end + # Set the inverse association, if possible def set_inverse_instance(record) if record && invertible_for?(record) @@ -224,6 +228,15 @@ module ActiveRecord def association_class @reflection.klass end + + def build_record(attributes, options) + reflection.build_association(attributes, options) do |record| + record.assign_attributes( + create_scope.except(*record.changed), + :without_protection => true + ) + end + end end end end diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index d7632b2ea9..30fc44b4c2 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -7,24 +7,22 @@ module ActiveRecord::Associations::Builder def build reflection = super check_validity(reflection) - define_after_destroy_method + define_destroy_hook reflection end private - def define_after_destroy_method + def define_destroy_hook name = self.name - model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1) - def #{after_destroy_method_name} - association(#{name.to_sym.inspect}).delete_all - end - eoruby - model.after_destroy after_destroy_method_name - end - - def after_destroy_method_name - "has_and_belongs_to_many_after_destroy_for_#{name}" + model.send(:include, Module.new { + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def destroy_associations + association(#{name.to_sym.inspect}).delete_all + super + end + RUBY + }) end # TODO: These checks should probably be moved into the Reflection, and we should not be diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 337a0d48f1..3e68f973e7 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -50,7 +50,7 @@ module ActiveRecord else column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" - scoped.select(column).except(:includes).map! do |record| + scoped.select(column).map! do |record| record.send(reflection.association_primary_key) end end @@ -78,10 +78,14 @@ module ActiveRecord end def find(*args) - if options[:finder_sql] - find_by_scan(*args) + if block_given? + load_target.find(*args) { |*block_args| yield(*block_args) } else - scoped.find(*args) + if options[:finder_sql] + find_by_scan(*args) + else + scoped.find(*args) + end end end @@ -423,12 +427,6 @@ module ActiveRecord scoped.scope_for_create.stringify_keys end - def build_record(attributes, options) - record = reflection.build_association(attributes, options) - record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) - record - end - def delete_or_destroy(records, method) records = records.flatten records.each { |record| raise_on_type_mismatch(record) } diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 5267116c42..827dfb7ccb 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -12,7 +12,7 @@ module ActiveRecord # has_many :posts # end # - # blog = Blog.find(:first) + # blog = Blog.first # # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and @@ -58,10 +58,10 @@ module ActiveRecord alias_method :new, :build - def respond_to?(*args) + def respond_to?(name, include_private = false) super || - (load_target && target.respond_to?(*args)) || - @association.klass.respond_to?(*args) + (load_target && target.respond_to?(name, include_private)) || + @association.klass.respond_to?(name, include_private) end def method_missing(method, *args, &block) diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 6b8201973a..2131edbc20 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -19,7 +19,7 @@ module ActiveRecord if owner.persisted? && save && !record.save nullify_owner_attributes(record) - set_owner_attributes(target) + set_owner_attributes(target) if target raise RecordNotSaved, "Failed to save the new associated #{reflection.name}." end end diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb index 87e33891a5..f83138195c 100644 --- a/activerecord/lib/active_record/associations/join_helper.rb +++ b/activerecord/lib/active_record/associations/join_helper.rb @@ -32,8 +32,7 @@ module ActiveRecord end def table_alias_for(reflection, join = false) - name = alias_tracker.pluralize(reflection.name, reflection.active_record) - name << "_#{alias_suffix}" + name = "#{reflection.plural_name}_#{alias_suffix}" name << "_join" if join name end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 7256dd5288..779f8164cc 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -68,7 +68,8 @@ module ActiveRecord private def associated_records_by_owner - owner_keys = owners.map { |owner| owner[owner_key_name] }.compact.uniq + owners_map = owners_by_key + owner_keys = owners_map.keys.compact if klass.nil? || owner_keys.empty? records = [] @@ -84,7 +85,7 @@ module ActiveRecord records.each do |record| owner_key = record[association_key_name].to_s - owners_by_key[owner_key].each do |owner| + owners_map[owner_key].each do |owner| records_by_owner[owner] << record end end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index ce1f2a5543..a1a921bcb4 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -18,16 +18,15 @@ module ActiveRecord end def create(attributes = {}, options = {}, &block) - build(attributes, options, &block).tap { |record| record.save } + create_record(attributes, options, &block) end def create!(attributes = {}, options = {}, &block) - build(attributes, options, &block).tap { |record| record.save! } + create_record(attributes, options, true, &block) end def build(attributes = {}, options = {}) - record = reflection.build_association(attributes, options) - record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) + record = build_record(attributes, options) yield(record) if block_given? set_new_record(record) record @@ -51,6 +50,15 @@ module ActiveRecord def set_new_record(record) replace(record) end + + def create_record(attributes, options, raise_error = false) + record = build_record(attributes, options) + yield(record) if block_given? + saved = record.save + set_new_record(record) + raise RecordInvalid.new(record) if !saved && raise_error + record + end end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 5833c65893..d0687ed0b6 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -61,7 +61,7 @@ module ActiveRecord end end - def respond_to?(*args) + def respond_to?(name, include_private = false) self.class.define_attribute_methods unless self.class.attribute_methods_generated? super end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index e3f221c773..8bd898d126 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -7,7 +7,7 @@ module ActiveRecord # the record is not persisted? or has just been destroyed. def to_key key = send(self.class.primary_key) - [key] if key + persisted? && key ? [key] : nil end module ClassMethods diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 7d4182291a..9a50a20fbc 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -99,8 +99,9 @@ module ActiveRecord # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - if respond_to? "_#{attr_name}" - send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s) + method = "_#{attr_name}" + if respond_to? method + send method if @attributes.has_key?(attr_name.to_s) else _read_attribute attr_name end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 036d514a03..085fdba639 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -347,7 +347,7 @@ module ActiveRecord end # reconstruct the scope now that we know the owner's id - association.send(:construct_scope) if association.respond_to?(:construct_scope) + association.send(:reset_scope) if association.respond_to?(:reset_scope) end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 6a5d282973..eb4a16ecf5 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -709,6 +709,12 @@ module ActiveRecord #:nodoc: connection_pool.columns_hash[table_name] end + # Returns a hash where the keys are column names and the values are + # default values when instantiating the AR object for this table. + def column_defaults + connection_pool.column_defaults[table_name] + end + # Returns an array of column names as strings. def column_names @column_names ||= columns.map { |column| column.name } @@ -1052,7 +1058,7 @@ module ActiveRecord #:nodoc: if match.finder? options = arguments.extract_options! relation = options.any? ? scoped(options) : scoped - relation.send :find_by_attributes, match, attribute_names, *arguments + relation.send :find_by_attributes, match, attribute_names, *arguments, &block elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end @@ -1204,11 +1210,11 @@ MSG end def current_scope #:nodoc: - Thread.current[:"#{self}_current_scope"] + Thread.current["#{self}_current_scope"] end def current_scope=(scope) #:nodoc: - Thread.current[:"#{self}_current_scope"] = scope + Thread.current["#{self}_current_scope"] = scope end # Use this macro in your model to set a default scope for all operations on @@ -1527,6 +1533,7 @@ MSG @marked_for_destruction = false @previously_changed = {} @changed_attributes = {} + @relation = nil ensure_proper_type set_serialized_attributes @@ -1535,9 +1542,8 @@ MSG assign_attributes(attributes, options) if attributes - result = yield self if block_given? + yield self if block_given? run_callbacks :initialize - result end # Populate +coder+ with attributes about this record that should be @@ -1568,6 +1574,7 @@ MSG # post.title # => 'hello world' def init_with(coder) @attributes = coder['attributes'] + @relation = nil set_serialized_attributes @@ -1702,27 +1709,24 @@ MSG return unless new_attributes attributes = new_attributes.stringify_keys - role = options[:as] || :default - multi_parameter_attributes = [] + @mass_assignment_options = options unless options[:without_protection] - attributes = sanitize_for_mass_assignment(attributes, role) + attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role) end attributes.each do |k, v| if k.include?("(") multi_parameter_attributes << [ k, v ] + elsif respond_to?("#{k}=") + send("#{k}=", v) else - method_name = "#{k}=" - if respond_to?(method_name) - method(method_name).arity == -2 ? send(method_name, v, options) : send(method_name, v) - else - raise(UnknownAttributeError, "unknown attribute: #{k}") - end + raise(UnknownAttributeError, "unknown attribute: #{k}") end end + @mass_assignment_options = nil assign_multiparameter_attributes(multi_parameter_attributes) end @@ -1777,16 +1781,12 @@ MSG # Note also that destroying a record preserves its ID in the model instance, so deleted # models are still comparable. def ==(comparison_object) - comparison_object.equal?(self) || + super || comparison_object.instance_of?(self.class) && id.present? && comparison_object.id == id end - - # Delegates to == - def eql?(comparison_object) - self == comparison_object - end + alias :eql? :== # Delegates to id in order to allow two records of the same type and id to work with something like: # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] @@ -1804,6 +1804,15 @@ MSG @attributes.frozen? end + # Allows sort on objects + def <=>(other_object) + if other_object.is_a?(self.class) + self.to_key <=> other_object.to_key + else + nil + end + end + # Backport dup from 1.9 so that initialize_dup() gets called unless Object.respond_to?(:initialize_dup) def dup # :nodoc: @@ -1882,12 +1891,33 @@ MSG value end + def mass_assignment_options + @mass_assignment_options ||= {} + end + + def mass_assignment_role + mass_assignment_options[:as] || :default + end + private + # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements + # of the array, and then rescues from the possible NoMethodError. If those elements are + # ActiveRecord::Base's, then this triggers the various method_missing's that we have, + # which significantly impacts upon performance. + # + # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. + # + # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ + def to_ary # :nodoc: + nil + end + def set_serialized_attributes - (@attributes.keys & self.class.serialized_attributes.keys).each do |key| - coder = self.class.serialized_attributes[key] - @attributes[key] = coder.load @attributes[key] + sattrs = self.class.serialized_attributes + + sattrs.each do |key, coder| + @attributes[key] = coder.load @attributes[key] if @attributes.key?(key) end end @@ -1897,8 +1927,9 @@ MSG # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. # No such attribute would be set for objects of the Message class in that example. def ensure_proper_type - unless self.class.descends_from_active_record? - write_attribute(self.class.inheritance_column, self.class.sti_name) + klass = self.class + if klass.finder_needs_type_condition? + write_attribute(klass.inheritance_column, klass.sti_name) end end @@ -2072,8 +2103,10 @@ MSG end def populate_with_current_scope_attributes - self.class.scoped.scope_for_create.each do |att,value| - respond_to?("#{att}=") && send("#{att}=", value) + return unless self.class.scope_attributes? + + self.class.scope_attributes.each do |att,value| + send("#{att}=", value) if respond_to?("#{att}=") end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 090f6bc6fe..ddfdb05297 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -60,6 +60,7 @@ module ActiveRecord attr_accessor :automatic_reconnect attr_reader :spec, :connections attr_reader :columns, :columns_hash, :primary_keys, :tables + attr_reader :column_defaults # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, @@ -106,6 +107,12 @@ module ActiveRecord }] end + @column_defaults = Hash.new do |h, table_name| + h[table_name] = Hash[columns[table_name].map { |col| + [col.name, col.default] + }] + end + @primary_keys = Hash.new do |h, table_name| h[table_name] = with_connection do |conn| table_exists?(table_name) ? conn.primary_key(table_name) : 'id' @@ -133,6 +140,7 @@ module ActiveRecord def clear_cache! @columns.clear @columns_hash.clear + @column_defaults.clear @tables.clear end @@ -140,6 +148,7 @@ module ActiveRecord def clear_table_cache!(table_name) @columns.delete table_name @columns_hash.delete table_name + @column_defaults.delete table_name @primary_keys.delete table_name end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 6d638628df..a84f73c73f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -670,7 +670,7 @@ module ActiveRecord # If the schema is not specified as part of +name+ then it will only find tables within # the current schema search path (regardless of permissions to access tables in other schemas) def table_exists?(name) - schema, table = extract_schema_and_table(name.to_s) + schema, table = Utils.extract_schema_and_table(name.to_s) return false unless table binds = [[nil, table]] @@ -940,13 +940,30 @@ module ActiveRecord # Construct a clean list of column names from the ORDER BY clause, removing # any ASC/DESC modifiers - order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s } + order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') } order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } "DISTINCT #{columns}, #{order_columns * ', '}" end + module Utils + # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. + # +schema_name+ is nil if not specified in +name+. + # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) + # +name+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * <tt>table_name</tt> + # * <tt>"table.name"</tt> + # * <tt>schema_name.table_name</tt> + # * <tt>schema_name."table.name"</tt> + # * <tt>"schema.name"."table name"</tt> + def self.extract_schema_and_table(name) + table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse + [schema, table] + end + end + protected # Returns the version of the connected PostgreSQL server. def postgresql_version @@ -1085,21 +1102,6 @@ module ActiveRecord end end - # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. - # +schema_name+ is nil if not specified in +name+. - # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) - # +name+ supports the range of schema/table references understood by PostgreSQL, for example: - # - # * <tt>table_name</tt> - # * <tt>"table.name"</tt> - # * <tt>schema_name.table_name</tt> - # * <tt>schema_name."table.name"</tt> - # * <tt>"schema.name"."table name"</tt> - def extract_schema_and_table(name) - table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse - [schema, table] - end - def extract_table_ref_from_insert_sql(sql) sql[/into\s+([^\(]*).*values\s*\(/i] $1.strip if $1 diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index ea1709cb1f..ad7d8cd63c 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -87,7 +87,7 @@ module ActiveRecord # # For example, in # - # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362] + # Location.where("lat = ? AND lng = ?", 53.7362) # # two placeholders are given but only one variable to fill them. class PreparedStatementInvalid < ActiveRecordError diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 819743a361..3f36dcde14 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -149,13 +149,13 @@ class FixturesFileNotFound < StandardError; end # self.use_transactional_fixtures = true # # test "godzilla" do -# assert !Foo.find(:all).empty? +# assert !Foo.all.empty? # Foo.destroy_all -# assert Foo.find(:all).empty? +# assert Foo.all.empty? # end # # test "godzilla aftermath" do -# assert !Foo.find(:all).empty? +# assert !Foo.all.empty? # end # end # diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 3afa257a76..6cfce6e573 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -73,7 +73,7 @@ module ActiveRecord # <tt>locking_enabled?</tt> at this point as # <tt>@attributes</tt> may not have been initialized yet. - if lock_optimistically && result.include?(self.class.locking_column) + if result.key?(self.class.locking_column) && lock_optimistically result[self.class.locking_column] ||= 0 end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 3d1bc5c1e0..c459846264 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -66,9 +66,9 @@ module ActiveRecord # create_table :system_settings do |t| # t.string :name # t.string :label - # t.text :value + # t.text :value # t.string :type - # t.integer :position + # t.integer :position # end # # SystemSetting.create :name => "notice", @@ -181,7 +181,7 @@ module ActiveRecord # # class RemoveEmptyTags < ActiveRecord::Migration # def up - # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? } + # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } # end # # def down @@ -227,7 +227,7 @@ module ActiveRecord # def up # add_column :people, :salary, :integer # Person.reset_column_information - # Person.find(:all).each do |p| + # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end @@ -247,7 +247,7 @@ module ActiveRecord # def up # ... # say_with_time "Updating salaries..." do - # Person.find(:all).each do |p| + # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index f01e94169f..2eeff7e36f 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -6,7 +6,7 @@ module ActiveRecord # # * add_column # * add_index - # * add_timestamp + # * add_timestamps # * create_table # * remove_timestamps # * rename_column @@ -48,7 +48,7 @@ module ActiveRecord super || delegate.respond_to?(*args) end - [:create_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| + [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method}(*args) # def create_table(*args) record(:"#{method}", args) # record(:create_table, args) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 14db7a6cd6..0313abe456 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -40,6 +40,25 @@ module ActiveRecord end end + ## + # Collects attributes from scopes that should be applied when creating + # an AR instance for the particular class this is called on. + def scope_attributes # :nodoc: + if current_scope + current_scope.scope_for_create + else + scope = relation.clone + scope.default_scoped = true + scope.scope_for_create + end + end + + ## + # Are there default attributes associated with this scope? + def scope_attributes? # :nodoc: + current_scope || default_scopes.any? + end + # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query, # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>. # diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index f51fd21077..53617059d0 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -276,15 +276,15 @@ module ActiveRecord type = (reflection.collection? ? :collection : :one_to_one) - # def pirate_attributes=(attributes, assignment_opts = {}) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, assignment_opts) + # def pirate_attributes=(attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options) # end class_eval <<-eoruby, __FILE__, __LINE__ + 1 if method_defined?(:#{association_name}_attributes=) remove_method(:#{association_name}_attributes=) end - def #{association_name}_attributes=(attributes, assignment_opts = {}) - assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, assignment_opts) + def #{association_name}_attributes=(attributes) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options) end eoruby else diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 367a1be4d9..d9cd7987b0 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -79,6 +79,8 @@ module ActiveRecord # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). def destroy + destroy_associations + if persisted? IdentityMap.remove(self) if IdentityMap.enabled? pk = self.class.primary_key @@ -284,6 +286,11 @@ module ActiveRecord end private + + # A hook to be overriden by association modules. + def destroy_associations + end + def create_or_update raise ReadOnlyRecord if readonly? result = new_record? ? create : update @@ -319,9 +326,7 @@ module ActiveRecord # that a new instance, or one populated from a passed-in Hash, still has all the attributes # that instances loaded from the database would. def attributes_from_column_definition - Hash[self.class.columns.map do |column| - [column.name, column.default] - end] + self.class.column_defaults.dup end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 83909b0766..44e5520230 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -44,6 +44,12 @@ db_namespace = namespace :db do create_database(ActiveRecord::Base.configurations[Rails.env]) end + def mysql_creation_options(config) + @charset = ENV['CHARSET'] || 'utf8' + @collation = ENV['COLLATION'] || 'utf8_unicode_ci' + {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} + end + def create_database(config) begin if config['adapter'] =~ /sqlite/ @@ -67,9 +73,6 @@ db_namespace = namespace :db do rescue case config['adapter'] when /mysql/ - @charset = ENV['CHARSET'] || 'utf8' - @collation = ENV['COLLATION'] || 'utf8_unicode_ci' - creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} if config['adapter'] =~ /jdbc/ #FIXME After Jdbcmysql gives this class require 'active_record/railties/jdbcmysql_error' @@ -80,7 +83,7 @@ db_namespace = namespace :db do access_denied_error = 1045 begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) - ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config)) ActiveRecord::Base.establish_connection(config) rescue error_class => sqlerr if sqlerr.errno == access_denied_error @@ -377,8 +380,7 @@ db_namespace = namespace :db do dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile'] `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql` when 'sqlserver' - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r` - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r` + `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\#{Rails.env}_structure.sql -A -U` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) @@ -423,7 +425,7 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` when 'sqlserver' - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql` when 'oci', 'oracle' ActiveRecord::Base.establish_connection(:test) IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl| @@ -444,7 +446,7 @@ db_namespace = namespace :db do case abcs['test']['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], abcs['test']) + ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test'])) when /postgresql/ ActiveRecord::Base.clear_active_connections! drop_database(abcs['test']) @@ -453,9 +455,11 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] File.delete(dbfile) if File.exist?(dbfile) when 'sqlserver' - dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}` - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + test = abcs.deep_dup['test'] + test_database = test['database'] + test['database'] = 'master' + ActiveRecord::Base.establish_connection(test) + ActiveRecord::Base.connection.recreate_database!(test_database) when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 6df473d011..a2324039cf 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -80,12 +80,6 @@ module ActiveRecord # Abstract base class for AggregateReflection and AssociationReflection. Objects of # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. class MacroReflection - attr_reader :active_record - - def initialize(macro, name, options, active_record) - @macro, @name, @options, @active_record = macro, name, options, active_record - end - # Returns the name of the macro. # # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt> @@ -104,6 +98,19 @@ module ActiveRecord # <tt>has_many :clients</tt> returns +{}+ attr_reader :options + attr_reader :active_record + + attr_reader :plural_name # :nodoc: + + def initialize(macro, name, options, active_record) + @macro = macro + @name = name + @options = options + @active_record = active_record + @plural_name = active_record.pluralize_table_names ? + name.to_s.pluralize : name.to_s + end + # Returns the class for the macro. # # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class @@ -123,7 +130,11 @@ module ActiveRecord # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, # and +other_aggregation+ has an options hash assigned to it. def ==(other_aggregation) - other_aggregation.kind_of?(self.class) && name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record + super || + other_aggregation.kind_of?(self.class) && + name == other_aggregation.name && + other_aggregation.options && + active_record == other_aggregation.active_record end def sanitized_conditions #:nodoc: @@ -168,25 +179,8 @@ module ActiveRecord # Returns a new, unsaved instance of the associated class. +options+ will # be passed to the class's constructor. - def build_association(*options) - klass.new(*options) - end - - # Creates a new instance of the associated class, and immediately saves it - # with ActiveRecord::Base#save. +options+ will be passed to the class's - # creation method. Returns the newly created object. - def create_association(*options) - klass.create(*options) - end - - # Creates a new instance of the associated class, and immediately saves it - # with ActiveRecord::Base#save!. +options+ will be passed to the class's - # creation method. If the created record doesn't pass validations, then an - # exception will be raised. - # - # Returns the newly created object. - def create_association!(*options) - klass.create!(*options) + def build_association(*options, &block) + klass.new(*options, &block) end def table_name diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 317af8a15d..2d0861d5c9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/module/delegation' module ActiveRecord # = Active Record Relation @@ -6,7 +7,7 @@ module ActiveRecord JoinOperation = Struct.new(:relation, :join_class, :on) ASSOCIATION_METHODS = [:includes, :eager_load, :preload] MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] - SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder, :reverse_order] + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order] include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches @@ -29,6 +30,7 @@ module ActiveRecord SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)} (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])} @extensions = [] + @create_with_value = {} end def insert(values) @@ -248,8 +250,7 @@ module ActiveRecord # Person.update(people.keys, people.values) def update(id, attributes) if id.is_a?(Array) - idx = -1 - id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } + id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])} else object = find(id) object.update_attributes(attributes) @@ -403,11 +404,21 @@ module ActiveRecord end def scope_for_create - @scope_for_create ||= where_values_hash.merge(@create_with_value || {}) + @scope_for_create ||= where_values_hash.merge(create_with_value) end def eager_loading? - @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)) + @should_eager_load ||= + @eager_load_values.any? || + @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) + end + + # Joins that are also marked for preloading. In which case we should just eager load them. + # Note that this is a naive implementation because we could have strings and symbols which + # represent the same association, but that aren't matched by this. Also, we could have + # nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] } + def joined_includes_values + @includes_values & @joins_values end def ==(other) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index d52b84179f..46ab67d1cf 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -20,8 +20,6 @@ module ActiveRecord find_in_batches(options) do |records| records.each { |record| yield record } end - - self end # Yields each batch of records that was found by the find +options+ as diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index aabe5c269b..0ac821b2d7 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -89,8 +89,12 @@ module ActiveRecord # +calculate+ for examples with options. # # Person.sum('age') # => 4562 - def sum(column_name, options = {}) - calculate(:sum, column_name, options) + def sum(*args) + if block_given? + self.to_a.sum(*args) {|*block_args| yield(*block_args)} + else + calculate(:sum, *args) + end end # This calculates aggregate values in the given column. Methods for count, sum, average, @@ -146,10 +150,16 @@ module ActiveRecord if options.except(:distinct).present? apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct]) else - if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) - construct_relation_for_association_calculations.calculate(operation, column_name, options) + relation = with_default_scope + + if relation.equal?(self) + if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) + construct_relation_for_association_calculations.calculate(operation, column_name, options) + else + perform_calculation(operation, column_name, options) + end else - perform_calculation(operation, column_name, options) + relation.calculate(operation, column_name, options) end end rescue ThrowResult @@ -196,7 +206,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = with_default_scope.reorder(nil) + relation = reorder(nil) if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. @@ -245,7 +255,7 @@ module ActiveRecord "#{field} AS #{aliaz}" } - relation = with_default_scope.except(:group).group(group.join(',')) + relation = except(:group).group(group.join(',')) relation.select_values = select_values calculated_data = @klass.connection.select_all(relation.to_sql) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 89f75fe3b2..283115a448 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -259,6 +259,7 @@ module ActiveRecord if match.bang? && result.blank? raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" else + yield(result) if block_given? result end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7fbbefeea2..8bd4732c0c 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -96,11 +96,11 @@ module ActiveRecord relation end - def having(*args) - return self if args.blank? + def having(opts, *rest) + return self if opts.blank? relation = clone - relation.having_values += build_where(*args) + relation.having_values += build_where(opts, rest) relation end @@ -137,7 +137,7 @@ module ActiveRecord def create_with(value) relation = clone - relation.create_with_value = value && (@create_with_value || {}).merge(value) + relation.create_with_value = value ? create_with_value.merge(value) : {} relation end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 69706b5ead..ba882beca9 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -55,7 +55,7 @@ module ActiveRecord merged_relation.lock_value = r.lock_value unless merged_relation.lock_value - merged_relation = merged_relation.create_with(r.create_with_value) if r.create_with_value + merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty? # Apply scope extension modules merged_relation.send :apply_modules, r.extensions diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 43fe1cdee5..838aa8fb1e 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,9 +1,9 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end |