diff options
Diffstat (limited to 'activerecord')
26 files changed, 286 insertions, 237 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 62f1470287..8f283e1117 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,7 +1,19 @@ *Rails 3.1.0 (unreleased)* -* No changes +* The following code: + Model.limit(10).scoping { Model.count } + + now generates the following SQL: + + SELECT COUNT(*) FROM models LIMIT 10 + + This may not return what you want. Instead, you may with to do something + like this: + + Model.limit(10).scoping { Model.all.size } + + [Aaron Patterson] *Rails 3.0.0 (August 29, 2010)* diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 63224377a3..715c868598 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -209,23 +209,20 @@ module ActiveRecord records.each {|record| record.send("set_#{reflection.name}_target", nil)} if options[:through] through_records = preload_through_records(records, reflection, options[:through]) - through_reflection = reflections[options[:through]] - through_primary_key = through_reflection.primary_key_name + unless through_records.empty? + through_reflection = reflections[options[:through]] + through_primary_key = through_reflection.primary_key_name source = reflection.source_reflection.name through_records.first.class.preload_associations(through_records, source) if through_reflection.macro == :belongs_to - rev_id_to_record_map = construct_id_map(records, through_primary_key).first - rev_primary_key = through_reflection.klass.primary_key - through_records.each do |through_record| - add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s], - reflection.name, through_record.send(source)) - end - else - through_records.each do |through_record| - add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s], - reflection.name, through_record.send(source)) - end + id_to_record_map = construct_id_map(records, through_primary_key).first + through_primary_key = through_reflection.klass.primary_key + end + + through_records.each do |through_record| + add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s], + reflection.name, through_record.send(source)) end end else @@ -299,9 +296,10 @@ module ActiveRecord options = reflection.options primary_key_name = reflection.primary_key_name + klasses_and_ids = {} + if options[:polymorphic] polymorph_type = options[:foreign_type] - klasses_and_ids = {} # Construct a mapping from klass to a list of ids to load and a mapping of those ids back # to their parent_records @@ -310,27 +308,20 @@ module ActiveRecord klass_id = record.send(primary_key_name) if klass_id id_map = klasses_and_ids[klass] ||= {} - id_list_for_klass_id = (id_map[klass_id.to_s] ||= []) - id_list_for_klass_id << record + (id_map[klass_id.to_s] ||= []) << record end end end - klasses_and_ids = klasses_and_ids.to_a else id_map = {} records.each do |record| key = record.send(primary_key_name) - if key - mapped_records = (id_map[key.to_s] ||= []) - mapped_records << record - end + (id_map[key.to_s] ||= []) << record if key end - klasses_and_ids = [[reflection.klass.name, id_map]] + klasses_and_ids[reflection.klass.name] = id_map unless id_map.empty? end - klasses_and_ids.each do |klass_and_id| - klass_name, id_map = *klass_and_id - next if id_map.empty? + klasses_and_ids.each do |klass_name, id_map| klass = klass_name.constantize table_name = klass.quoted_table_name diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 4bf206d589..565ebf8197 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1416,8 +1416,8 @@ module ActiveRecord include Module.new { class_eval <<-RUBY, __FILE__, __LINE__ + 1 def destroy # def destroy - super # super #{reflection.name}.clear # posts.clear + super # super end # end RUBY } @@ -1841,7 +1841,7 @@ module ActiveRecord @reflections = [] @base_records_hash = {} @base_records_in_order = [] - @table_aliases = Hash.new { |aliases, table| aliases[table] = 0 } + @table_aliases = Hash.new(0) @table_aliases[base.table_name] = 1 build(associations) end @@ -1855,7 +1855,7 @@ module ActiveRecord end def join_associations - @joins[1..-1].to_a + @joins.last(@joins.length - 1) end def join_base @@ -1951,53 +1951,54 @@ module ActiveRecord def construct(parent, associations, joins, row) case associations - when Symbol, String - join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name } + when Symbol, String + join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name } + raise(ConfigurationError, "No such association") if join.nil? + + joins.delete(join) + construct_association(parent, join, row) + when Array + associations.each do |association| + construct(parent, association, joins, row) + end + when Hash + associations.sort_by { |k,_| k.to_s }.each do |name, assoc| + join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name } raise(ConfigurationError, "No such association") if join.nil? + association = construct_association(parent, join, row) joins.delete(join) - construct_association(parent, join, row) - when Array - associations.each do |association| - construct(parent, association, joins, row) - end - when Hash - associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name| - join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name } - raise(ConfigurationError, "No such association") if join.nil? - - association = construct_association(parent, join, row) - joins.delete(join) - construct(association, associations[name], joins, row) if association - end - else - raise ConfigurationError, associations.inspect + construct(association, assoc, joins, row) if association + end + else + raise ConfigurationError, associations.inspect end end def construct_association(record, join, row) - case join.reflection.macro + return if record.id.to_s != join.parent.record_id(row).to_s + + macro = join.reflection.macro + if macro == :has_one + return if record.instance_variable_defined?("@#{join.reflection.name}") + association = join.instantiate(row) unless row[join.aliased_primary_key].nil? + set_target_and_inverse(join, association, record) + else + return if row[join.aliased_primary_key].nil? + association = join.instantiate(row) + case macro when :has_many, :has_and_belongs_to_many collection = record.send(join.reflection.name) collection.loaded - - return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil? - association = join.instantiate(row) collection.target.push(association) collection.__send__(:set_inverse_instance, association, record) - when :has_one - return if record.id.to_s != join.parent.record_id(row).to_s - return if record.instance_variable_defined?("@#{join.reflection.name}") - association = join.instantiate(row) unless row[join.aliased_primary_key].nil? - set_target_and_inverse(join, association, record) when :belongs_to - return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil? - association = join.instantiate(row) set_target_and_inverse(join, association, record) else raise ConfigurationError, "unknown macro: #{join.reflection.macro}" + end end - return association + association end def set_target_and_inverse(join, association, record) @@ -2046,7 +2047,7 @@ module ActiveRecord end def extract_record(row) - column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record} + Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}] end def record_id(row) @@ -2107,71 +2108,15 @@ module ActiveRecord def association_join return @join if @join - aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine) - parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine) - - @join = case reflection.macro - when :has_and_belongs_to_many - join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine) - fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key - klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key + aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, + :engine => arel_engine, + :columns => klass.columns) - [ - join_table[fk].eq(parent_table[reflection.active_record.primary_key]), - aliased_table[klass.primary_key].eq(join_table[klass_fk]) - ] - when :has_many, :has_one - if reflection.options[:through] - join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine) - 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 = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name) - else - jt_foreign_key = through_reflection.primary_key_name - end + parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, + :engine => arel_engine, + :columns => parent.active_record.columns) - 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 - as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(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 = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name) - end - when :belongs_to - first_key = primary_key - if reflection.options[:source_type] - second_key = source_reflection.association_foreign_key - jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type]) - else - second_key = source_reflection.primary_key_name - end - end - - [ - [parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? }, - aliased_table[first_key].eq(join_table[second_key]) - ] - elsif reflection.options[:as] - id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key]) - type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name) - [id_rel, type_rel] - else - foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key - [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])] - end - when :belongs_to - [aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])] - end + @join = send("build_#{reflection.macro}", aliased_table, parent_table) unless klass.descends_from_active_record? sti_column = aliased_table[klass.inheritance_column] @@ -2191,7 +2136,9 @@ module ActiveRecord end def relation - aliased = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine) + aliased = Arel::Table.new(table_name, :as => @aliased_table_name, + :engine => arel_engine, + :columns => klass.columns) if reflection.macro == :has_and_belongs_to_many [Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased] @@ -2244,6 +2191,77 @@ module ActiveRecord def interpolate_sql(sql) instance_eval("%@#{sql.gsub('@', '\@')}@", __FILE__, __LINE__) end + + private + + def build_has_and_belongs_to_many(aliased_table, parent_table) + join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine) + fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key + klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key + + [ + join_table[fk].eq(parent_table[reflection.active_record.primary_key]), + aliased_table[klass.primary_key].eq(join_table[klass_fk]) + ] + end + + def build_has_many(aliased_table, parent_table) + if reflection.options[:through] + join_table = Arel::Table.new(through_reflection.klass.table_name, + :as => aliased_join_table_name, + :engine => arel_engine) + jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil + first_key = second_key = nil + + if through_reflection.options[:as] # has_many :through against a polymorphic join + as_key = through_reflection.options[:as].to_s + jt_foreign_key = as_key + '_id' + jt_as_extra = join_table[as_key + '_type'].eq(parent.active_record.base_class.name) + else + jt_foreign_key = through_reflection.primary_key_name + end + + case source_reflection.macro + when :has_many + second_key = options[:foreign_key] || primary_key + + if source_reflection.options[:as] + first_key = "#{source_reflection.options[:as]}_id" + else + first_key = through_reflection.klass.base_class.to_s.foreign_key + end + + unless through_reflection.klass.descends_from_active_record? + jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name) + end + when :belongs_to + first_key = primary_key + if reflection.options[:source_type] + second_key = source_reflection.association_foreign_key + jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type]) + else + second_key = source_reflection.primary_key_name + end + end + + [ + [parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].compact, + aliased_table[first_key].eq(join_table[second_key]) + ] + elsif reflection.options[:as] + id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key]) + type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name) + [id_rel, type_rel] + else + foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key + [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])] + end + end + alias :build_has_one :build_has_many + + def build_belongs_to(aliased_table, parent_table) + [aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])] + end end end end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 1221a56bbc..91e0a9f2f8 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -338,13 +338,12 @@ module ActiveRecord def uniq(collection = self) seen = Set.new - collection.inject([]) do |kept, record| + collection.map do |record| unless seen.include?(record.id) - kept << record seen << record.id + record end - kept - end + end.compact end # Replace this collection with +other_array+ 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 c0ec65bd40..eb65234dfb 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 @@ -49,23 +49,20 @@ module ActiveRecord timestamps = record_timestamp_columns(record) timezone = record.send(:current_time_from_proper_timezone) if timestamps.any? - attributes = columns.inject({}) do |attrs, column| + attributes = Hash[columns.map do |column| name = column.name - case name.to_s + value = case name.to_s when @reflection.primary_key_name.to_s - attrs[relation[name]] = @owner.id + @owner.id when @reflection.association_foreign_key.to_s - attrs[relation[name]] = record.id + record.id when *timestamps - attrs[relation[name]] = timezone + timezone else - if record.has_attribute?(name) - value = @owner.send(:quote_value, record[name], column) - attrs[relation[name]] = value unless value.nil? - end + @owner.send(:quote_value, record[name], column) if record.has_attribute?(name) end - attrs - end + [relation[name], value] unless value.nil? + end] relation.insert(attributes) end @@ -79,7 +76,7 @@ module ActiveRecord else relation = Arel::Table.new(@reflection.options[:join_table]) relation.where(relation[@reflection.primary_key_name].eq(@owner.id). - and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id })) + and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact)) ).delete end end diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index a4e144f233..23195b02f7 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -13,10 +13,7 @@ module ActiveRecord # Returns a hash of attributes before typecasting and deserialization. def attributes_before_type_cast - self.attribute_names.inject({}) do |attrs, name| - attrs[name] = read_attribute_before_type_cast(name) - attrs - end + Hash[attribute_names.map { |name| [name, read_attribute_before_type_cast(name)] }] end private diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5da4eb169b..e86d4984a6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -884,21 +884,13 @@ module ActiveRecord #:nodoc: # single-table inheritance model that makes it possible to create # objects of different types from the same table. def instantiate(record) - object = find_sti_class(record[inheritance_column]).allocate - - object.instance_variable_set(:@attributes, record) - object.instance_variable_set(:@attributes_cache, {}) - object.instance_variable_set(:@new_record, false) - object.instance_variable_set(:@readonly, false) - object.instance_variable_set(:@destroyed, false) - object.instance_variable_set(:@marked_for_destruction, false) - object.instance_variable_set(:@previously_changed, {}) - object.instance_variable_set(:@changed_attributes, {}) - - object.send(:_run_find_callbacks) - object.send(:_run_initialize_callbacks) - - object + find_sti_class(record[inheritance_column]).allocate.instance_eval do + @attributes, @attributes_cache, @previously_changed, @changed_attributes = record, {}, {}, {} + @new_record = @readonly = @destroyed = @marked_for_destruction = false + _run_find_callbacks + _run_initialize_callbacks + self + end end def find_sti_class(type_name) 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 8e74eff0ab..ec7035e540 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -67,8 +67,8 @@ module ActiveRecord begin require "active_record/connection_adapters/#{spec[:adapter]}_adapter" - rescue LoadError - raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})" + rescue LoadError => e + raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e})" end adapter_method = "#{spec[:adapter]}_connection" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0245e63294..310423bb20 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -340,12 +340,10 @@ module ActiveRecord end if index_name.length > index_name_length - @logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.") - return + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" end if index_name_exists?(table_name, index_name, false) - @logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.") - return + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end quoted_column_names = quoted_columns_for_index(column_names, options).join(", ") @@ -365,8 +363,7 @@ module ActiveRecord def remove_index(table_name, options = {}) index_name = index_name(table_name, options) unless index_name_exists?(table_name, index_name, true) - @logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.") - return + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" end remove_index!(table_name, index_name) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index d797f9c6c3..e7f7b37b27 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -19,11 +19,11 @@ module ActiveRecord begin require 'mysql' rescue LoadError - raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql', '2.8.1'" + raise "!!! Missing the mysql2 gem. Add it to your Gemfile: gem 'mysql2'" end unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash) - raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'" + raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'" end end @@ -276,7 +276,7 @@ module ActiveRecord rows = [] result.each { |row| rows << row } result.free - @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped + @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end @@ -358,10 +358,10 @@ module ActiveRecord sql = "SHOW TABLES" end - select_all(sql).inject("") do |structure, table| + select_all(sql).map do |table| table.delete('Table_type') - structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n" - end + select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n" + end.join("") end def recreate_database(name, options = {}) #:nodoc: @@ -620,7 +620,7 @@ module ActiveRecord rows = [] result.each_hash { |row| rows << row } result.free - @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped + @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 33611b410c..18519a712b 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -386,7 +386,7 @@ module ActiveRecord association.to_a else attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact - attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : [] + attribute_ids.empty? ? [] : association.all(:conditions => {association.primary_key => attribute_ids}) end attributes_collection.each do |attributes| diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 1b016f0895..f80e304c5d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -279,10 +279,9 @@ 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 - self.class.columns.inject({}) do |attributes, column| - attributes[column.name] = column.default unless column.name == self.class.primary_key - attributes - end + Hash[self.class.columns.map do |column| + [column.name, column.default] unless column.name == self.class.primary_key + end] end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 8e5332214c..ccaa1f01f6 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -67,8 +67,8 @@ namespace :db do # Create the SQLite database ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection - rescue - $stderr.puts $!, *($!.backtrace) + rescue Exception => e + $stderr.puts e, *(e.backtrace) $stderr.puts "Couldn't create database for #{config.inspect}" end end @@ -113,8 +113,8 @@ namespace :db do ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding)) ActiveRecord::Base.establish_connection(config) - rescue - $stderr.puts $!, *($!.backtrace) + rescue Exception => e + $stderr.puts e, *(e.backtrace) $stderr.puts "Couldn't create database for #{config.inspect}" end end @@ -299,8 +299,7 @@ namespace :db do desc 'Load the seed data from db/seeds.rb' task :seed => 'db:abort_if_pending_migrations' do - seed_file = File.join(Rails.root, 'db', 'seeds.rb') - load(seed_file) if File.exist?(seed_file) + Rails.application.load_seed end namespace :fixtures do diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 5584397439..b41e935ed5 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -1,7 +1,7 @@ require 'active_support/core_ext/object/blank' module ActiveRecord - module Batches # :nodoc: + module Batches # Yields each record that was found by the find +options+. The find is # performed by find_in_batches with a batch size of 1000 (or as # specified by the <tt>:batch_size</tt> option). diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 398ab75b69..12a2c6aec3 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -225,13 +225,11 @@ module ActiveRecord key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } end - calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| - key = type_cast_calculated_value(row[group_alias], group_column) - key = key_records[key] if associated - value = row[aggregate_alias] - all[key] = type_cast_calculated_value(value, column_for(column_name), operation) - all - end + ActiveSupport::OrderedHash[calculated_data.map do |row| + key = type_cast_calculated_value(row[group_alias], group_column) + key = key_records[key] if associated + [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)] + end] end # Converts the given keys to the value that the database adapter returns as diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index de57725af2..6a33edeb97 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -222,7 +222,7 @@ module ActiveRecord def build_joins(relation, joins) association_joins = [] - joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq + joins = joins.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq joins.each do |join| association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join) @@ -260,8 +260,8 @@ module ActiveRecord def build_select(arel, selects) unless selects.empty? @implicit_readonly = false - # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array. - # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array + # TODO: fix this ugly hack, we should refactor the callers to get an Arel compatible array. + # Before this change we were passing to Arel the last element only, and Arel is capable of handling an array case select = selects.last when Arel::Expression, Arel::SqlLiteral arel.project(select) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 8bb8edde0e..2bdf9d8971 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -425,7 +425,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_removing_associations_on_destroy david = DeveloperWithBeforeDestroyRaise.find(1) assert !david.projects.empty? - assert_nothing_raised { david.destroy } + assert_raise(RuntimeError) { david.destroy } assert david.projects.empty? assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index b83966e91c..f131dc01f6 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -44,6 +44,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert !authors(:mary).unique_categorized_posts.loaded? end + def test_column_caching + # pre-heat our cache + Post.arel_table.columns + Comment.columns + + Post.connection.column_calls = 0 + 2.times { Post.joins(:comments).to_a } + assert_equal 0, Post.connection.column_calls + end + def test_has_many_uniq_through_find assert_equal 1, authors(:mary).unique_categorized_posts.find(:all).size end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index dcb1da7d91..d87f259f4b 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -659,6 +659,10 @@ class BasicsTest < ActiveRecord::TestCase cloned_topic.save assert !cloned_topic.new_record? assert_not_equal cloned_topic.id, topic.id + + cloned_topic.reload + # FIXME: I think this is poor behavior, and will fix it with #5686 + assert_equal({'a' => 'c'}.to_s, cloned_topic.title) end def test_clone_with_aggregate_of_same_name_as_attribute @@ -1433,19 +1437,4 @@ class BasicsTest < ActiveRecord::TestCase ensure Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost) end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end - - def with_active_record_default_timezone(zone) - old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone - yield - ensure - ActiveRecord::Base.default_timezone = old_zone - end end diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index a8b4b7a096..3deb0dac99 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -4,17 +4,21 @@ require 'models/task' class DateTimeTest < ActiveRecord::TestCase def test_saves_both_date_and_time - time_values = [1807, 2, 10, 15, 30, 45] - # create DateTime value with local time zone offset - local_offset = Rational(Time.local_time(*time_values).utc_offset, 86400) - now = DateTime.civil(*(time_values + [local_offset])) + with_env_tz 'America/New_York' do + with_active_record_default_timezone :utc do + time_values = [1807, 2, 10, 15, 30, 45] + # create DateTime value with local time zone offset + local_offset = Rational(Time.local_time(*time_values).utc_offset, 86400) + now = DateTime.civil(*(time_values + [local_offset])) - task = Task.new - task.starting = now - task.save! + task = Task.new + task.starting = now + task.save! - # check against Time.local_time, since some platforms will return a Time instead of a DateTime - assert_equal Time.local_time(*time_values), Task.find(task.id).starting + # check against Time.local_time, since some platforms will return a Time instead of a DateTime + assert_equal Time.local_time(*time_values), Task.find(task.id).starting + end + end end def test_assign_empty_date_time diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 4bf3c25d28..2d3047c875 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -31,6 +31,20 @@ def current_adapter?(*types) end end +def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield +ensure + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') +end + +def with_active_record_default_timezone(zone) + old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone + yield +ensure + ActiveRecord::Base.default_timezone = old_zone +end + ActiveRecord::Base.connection.class.class_eval do IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/] @@ -43,6 +57,18 @@ ActiveRecord::Base.connection.class.class_eval do alias_method_chain :execute, :query_record end +ActiveRecord::Base.connection.class.class_eval { + attr_accessor :column_calls + + def columns_with_calls(*args) + @column_calls ||= 0 + @column_calls += 1 + columns_without_calls(*args) + end + + alias_method_chain :columns, :calls +} + unless ENV['FIXTURE_DEBUG'] module ActiveRecord::TestFixtures::ClassMethods def try_to_load_dependency_with_silence(*args) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 03a8cc90f6..bcae46c7e8 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -127,16 +127,17 @@ if ActiveRecord::Base.connection.supports_migrations? def test_add_index_length_limit good_index_name = 'x' * Person.connection.index_name_length too_long_index_name = good_index_name + 'x' - assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => too_long_index_name) } + assert_raise(ArgumentError) { Person.connection.add_index("people", "first_name", :name => too_long_index_name) } assert !Person.connection.index_name_exists?("people", too_long_index_name, false) assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) } assert Person.connection.index_name_exists?("people", good_index_name, false) + Person.connection.remove_index("people", :name => good_index_name) end def test_remove_nonexistent_index # we do this by name, so OpenBase is a wash as noted above unless current_adapter?(:OpenBaseAdapter) - assert_nothing_raised { Person.connection.remove_index("people", "no_such_index") } + assert_raise(ArgumentError) { Person.connection.remove_index("people", "no_such_index") } end end @@ -154,7 +155,7 @@ if ActiveRecord::Base.connection.supports_migrations? def test_double_add_index unless current_adapter?(:OpenBaseAdapter) Person.connection.add_index('people', [:first_name], :name => 'some_idx') - assert_nothing_raised { Person.connection.add_index('people', [:first_name], :name => 'some_idx') } + assert_raise(ArgumentError) { Person.connection.add_index('people', [:first_name], :name => 'some_idx') } end end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index f538d2a326..b571e9a8b8 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -161,7 +161,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase def test_merge_inner_scope_has_priority Developer.limit(5).scoping do Developer.limit(10).scoping do - assert_equal 10, Developer.count + assert_equal 10, Developer.all.size end end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 649fe79fd3..3bc3671b77 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -679,24 +679,36 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.order(Post.arel_table[:title]).all, Post.order("title").all end - def test_relations_limit_with_conditions_or_limit - assert_equal Post.limit(2).size, Post.limit(2).all.size - end - def test_order_with_find_with_order - assert_equal 'zyke', Car.order('name desc').find(:first, :order => 'id').name + assert_equal 'zyke', CoolCar.order('name desc').find(:first, :order => 'id').name + assert_equal 'zyke', FastCar.order('name desc').find(:first, :order => 'id').name end def test_default_scope_order_with_named_scope_order - assert_equal 'zyke', Car.order_using_new_style.limit(1).first.name - assert_equal 'zyke', Car.order_using_old_style.limit(1).first.name + assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name + assert_equal 'zyke', CoolCar.order_using_old_style.limit(1).first.name + assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name + assert_equal 'zyke', FastCar.order_using_old_style.limit(1).first.name end def test_order_using_scoping - car = Car.order('id DESC').scoping do - Car.find(:first, :order => 'id asc') + car1 = CoolCar.order('id DESC').scoping do + CoolCar.find(:first, :order => 'id asc') + end + assert_equal 'zyke', car1.name + + car2 = FastCar.order('id DESC').scoping do + FastCar.find(:first, :order => 'id asc') end - assert_equal 'zyke', car.name + assert_equal 'zyke', car2.name + end + + def test_unscoped_block_style + assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name} + assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_old_style.limit(1).first.name} + + assert_equal 'honda', FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name} + assert_equal 'honda', FastCar.unscoped { FastCar.order_using_old_style.limit(1).first.name} end def test_intersection_with_array diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index a1e351b54e..e7db3d3423 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -1,4 +1,5 @@ class Car < ActiveRecord::Base + has_many :bulbs has_many :tyres has_many :engines @@ -7,9 +8,15 @@ class Car < ActiveRecord::Base scope :incl_tyres, includes(:tyres) scope :incl_engines, includes(:engines) - default_scope :order => 'name desc' - scope :order_using_new_style, order('name asc') scope :order_using_old_style, :order => 'name asc' end + +class CoolCar < Car + default_scope :order => 'name desc' +end + +class FastCar < Car + default_scope order('name desc') +end diff --git a/activerecord/test/models/engine.rb b/activerecord/test/models/engine.rb index 751c3f02d1..851ff8c22b 100644 --- a/activerecord/test/models/engine.rb +++ b/activerecord/test/models/engine.rb @@ -1,3 +1,4 @@ class Engine < ActiveRecord::Base belongs_to :my_car, :class_name => 'Car', :foreign_key => 'car_id', :counter_cache => :engines_count end + |