diff options
Diffstat (limited to 'activerecord')
50 files changed, 688 insertions, 64 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index bd8a0bc039..1d682e03bf 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,185 @@ +## Rails 3.2.11 (unreleased) + +* Serialized attributes can be serialized in integer columns. + Fix #8575. + + *Rafael Mendonça França* + +* Keep index names when using `alter_table` with sqlite3. + Fix #3489. + Backport #8522. + + *Yves Senn* + +* Recognize migrations placed in directories containing numbers and 'rb'. + Fix #8492. + Backport of #8500. + + *Yves Senn* + +* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control + the format of the timestamp value in the cache key. + This allows users to improve the precision of the cache key. + Fixes #8195. + + *Rafael Mendonça França* + +* Add `:nsec` date format. This can be used to improve the precision of cache key. + Please note that this format only works with Ruby 1.9, Ruby 1.8 will ignore it completely. + + *Jamie Gaskins* + +* Unscope `update_column(s)` query to ignore default scope. + + When applying `default_scope` to a class with a where clause, using + `update_column(s)` could generate a query that would not properly update + the record due to the where clause from the `default_scope` being applied + to the update query. + + class User < ActiveRecord::Base + default_scope where(active: true) + end + + user = User.first + user.active = false + user.save! + + user.update_column(:active, true) # => false + + In this situation we want to skip the default_scope clause and just + update the record based on the primary key. With this change: + + user.update_column(:active, true) # => true + + Backport of #8436 fix. + + *Carlos Antonio da Silva* + +* Fix performance problem with primary_key method in PostgreSQL adapter when having many schemas. + Uses pg_constraint table instead of pg_depend table which has many records in general. + Fix #8414 + + *kennyj* + +* Do not instantiate intermediate Active Record objects when eager loading. + These records caused `after_find` to run more than expected. + Fix #3313 + Backport of #8403 + + *Yves Senn* + +* Fix `pluck` to work with joins. Backport of #4942. + + *Carlos Antonio da Silva* + +* Fix a problem with `translate_exception` method in a non English environment. + Backport of #6397. + + *kennyj* + +* Fix dirty attribute checks for TimeZoneConversion with nil and blank + datetime attributes. Setting a nil datetime to a blank string should not + result in a change being flagged. + Fixes #8310. + Backport of #8311. + + *Alisdair McDiarmid* + +* Prevent mass assignment to the type column of polymorphic associations when using `build`. + Fixes #8265. + Backport of #8291. + + *Yves Senn* + +* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is + silently dropped. + Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits + on these types. + + *Victor Costan* + +* Calling `include?` on `has_many` associations on unsaved records no longer + returns `true` when passed a record with a `nil` foreign key. + Fixes #7950. + + *George Brocklehurst* + +* `#pluck` can be used on a relation with `select` clause. + Fixes #7551. + Backport of #8176. + + Example: + + Topic.select([:approved, :id]).order(:id).pluck(:id) + + *Yves Senn* + +* Use `nil?` instead of `blank?` to check whether dynamic finder with a bang + should raise RecordNotFound. + Fixes #7238. + + *Nikita Afanasenko* + +* Fix deleting from a HABTM join table upon destroying an object of a model + with optimistic locking enabled. + Fixes #5332. + + *Nick Rogers* + +* Use query cache/uncache when using ENV["DATABASE_URL"]. + Fixes #6951. + Backport of #8074. + + *kennyj* + +* Do not create useless database transaction when building `has_one` association. + + Example: + + User.has_one :profile + User.new.build_profile + + Backport of #8154. + + *Bogdan Gusiev* + +* `AR::Base#attributes_before_type_cast` now returns unserialized values for serialized attributes. + + *Nikita Afanasenko* + +* Fix issue that raises `NameError` when overriding the `accepts_nested_attributes` in child classes. + + Before: + + class Shared::Person < ActiveRecord::Base + has_one :address + + accepts_nested_attributes :address, :reject_if => :all_blank + end + + class Person < Shared::Person + accepts_nested_attributes :address + end + + Person + #=> NameError: method `address_attributes=' not defined in Person + + After: + + Person + #=> Person(id: integer, ...) + + Fixes #8131. + + *Gabriel Sobrinho, Ricardo Henrique* + + +## Rails 3.2.9 (Nov 12, 2012) ## + +* Fix `find_in_batches` crashing when IDs are strings and start option is not specified. + + *Alexis Bernard* + ## Rails 3.2.10 ## * CVE-2012-5664 options hashes should only be extracted if there are extra diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 59c1bad559..ab0d888b16 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -231,7 +231,8 @@ module ActiveRecord def build_record(attributes, options) reflection.build_association(attributes, options) do |record| - attributes = create_scope.except(*(record.changed - [reflection.foreign_key])) + skip_assign = [reflection.foreign_key, reflection.type].compact + attributes = create_scope.except(*(record.changed - skip_assign)) record.assign_attributes(attributes, :without_protection => true) end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index f9cffa40c8..226639ad37 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -33,6 +33,18 @@ module ActiveRecord private + def column_for(table_name, column_name) + columns = alias_tracker.connection.schema_cache.columns_hash[table_name] + columns[column_name] + end + + def bind(scope, column, value) + substitute = alias_tracker.connection.substitute_at( + column, scope.bind_values.length) + scope.bind_values += [[column, value]] + substitute + end + def add_constraints(scope) tables = construct_tables @@ -67,7 +79,9 @@ module ActiveRecord conditions = self.conditions[i] if reflection == chain.last - scope = scope.where(table[key].eq(owner[foreign_key])) + column = column_for(table.table_name, key.to_s) + bind_val = bind(scope, column, owner[foreign_key]) + scope = scope.where(table[key].eq(bind_val)) if reflection.type scope = scope.where(table[reflection.type].eq(owner.class.base_class.name)) diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 501ebe7c5b..56f9013d61 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -11,7 +11,7 @@ module ActiveRecord # If target and record are nil, or target is equal to record, # we don't need to have transaction. if (target || record) && target != record - reflection.klass.transaction do + transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? if record @@ -70,6 +70,14 @@ module ActiveRecord def nullify_owner_attributes(record) record[reflection.foreign_key] = nil end + + def transaction_if(value) + if value + reflection.klass.transaction { yield } + else + yield + end + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 00023b0b6c..deb89f3ac1 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -90,6 +90,14 @@ module ActiveRecord end end + def _field_changed?(attr, old, value) + if self.class.serialized_attributes.include?(attr) + old != value + else + super + end + end + def read_attribute_before_type_cast(attr_name) if serialized_attributes.include?(attr_name) super.unserialized_value @@ -97,6 +105,16 @@ module ActiveRecord super end end + + def attributes_before_type_cast + super.dup.tap do |attributes| + self.class.serialized_attributes.each_key do |key| + if attributes.key?(key) + attributes[key] = attributes[key].unserialized_value + end + end + end + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 4c3d5eed4c..39d81cf6ef 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -37,6 +37,7 @@ module ActiveRecord if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body, line = <<-EOV, __LINE__ + 1 def #{attr_name}=(original_time) + original_time = nil if original_time.blank? time = original_time unless time.acts_like?(:time) time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 62c8110274..612114bbce 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -557,7 +557,7 @@ module ActiveRecord #:nodoc: end # Backport dup from 1.9 so that initialize_dup() gets called - unless Object.respond_to?(:initialize_dup) + unless Object.respond_to?(:initialize_dup, true) def dup # :nodoc: copy = super copy.initialize_dup(self) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index db99c3fbef..c49aed7069 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -4,6 +4,7 @@ module ActiveRecord # Converts an arel AST to SQL def to_sql(arel, binds = []) if arel.respond_to?(:ast) + binds = binds.dup visitor.accept(arel.ast) do quote(*binds.shift.reverse) end @@ -20,14 +21,14 @@ module ActiveRecord # Returns a record hash with the column names as keys and column values # as values. - def select_one(arel, name = nil) - result = select_all(arel, name) + def select_one(arel, name = nil, binds = []) + result = select_all(arel, name, binds) result.first if result end # Returns a single value from a record - def select_value(arel, name = nil) - if result = select_one(arel, name) + def select_value(arel, name = nil, binds = []) + if result = select_one(arel, name, binds) result.values.first end end @@ -266,7 +267,7 @@ module ActiveRecord # Inserts the given fixture into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). def insert_fixture(fixture, table_name) - columns = Hash[columns(table_name).map { |c| [c.name, c] }] + columns = schema_cache.columns_hash(table_name) key_list = [] value_list = fixture.map do |name, value| diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c132692249..4850b68051 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -2,7 +2,7 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/hash/keys' -gem 'mysql', '~> 2.8.1' +gem 'mysql', '~> 2.8' require 'mysql' class Mysql diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 80a0c6d13c..5a53135901 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -988,12 +988,11 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table) row = exec_query(<<-end_sql, 'SCHEMA').rows.first - SELECT DISTINCT(attr.attname) + SELECT attr.attname FROM pg_attribute attr - INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] WHERE cons.contype = 'p' - AND dep.refobjid = '#{quote_table_name(table)}'::regclass + AND cons.conrelid = '#{quote_table_name(table)}'::regclass end_sql row && row.first @@ -1078,6 +1077,13 @@ module ActiveRecord when nil, 0..0x3fffffff; super(type) else raise(ActiveRecordError, "No binary type has byte size #{limit}.") end + when 'text' + # PostgreSQL doesn't support limits on text columns. + # The hard limit is 1Gb, according to section 8.3 in the manual. + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") + end when 'integer' return 'integer' unless limit @@ -1135,11 +1141,15 @@ module ActiveRecord @connection.server_version end + # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html + FOREIGN_KEY_VIOLATION = "23503" + UNIQUE_VIOLATION = "23505" + def translate_exception(exception, message) - case exception.message - when /duplicate key value violates unique constraint/ + case exception.result.error_field(PGresult::PG_DIAG_SQLSTATE) + when UNIQUE_VIOLATION RecordNotUnique.new(message, exception) - when /violates foreign key constraint/ + when FOREIGN_KEY_VIOLATION InvalidForeignKey.new(message, exception) else super diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 4e8932a695..bc8d24a03f 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -1,7 +1,7 @@ module ActiveRecord module ConnectionAdapters class SchemaCache - attr_reader :columns, :columns_hash, :primary_keys, :tables + attr_reader :primary_keys, :tables attr_reader :connection def initialize(conn) @@ -30,6 +30,25 @@ module ActiveRecord @tables[name] = connection.table_exists?(name) end + # Get the columns for a table + def columns(table = nil) + if table + @columns[table] + else + @columns + end + end + + # Get the columns for a table as a hash, key is the column name + # value is the column object. + def columns_hash(table = nil) + if table + @columns_hash[table] + else + @columns_hash + end + end + # Clears out internal caches def clear! @columns.clear diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index e80b465bab..ca84c95bdc 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -530,7 +530,7 @@ module ActiveRecord unless columns.empty? # index name can't be the same - opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") } + opts = { :name => name.gsub(/(^|_)(#{from})_/, "\\1#{to}_") } opts[:unique] = true if index.unique add_index(to, columns, opts) end diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 2c42f4cca5..f2ace18d44 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -1,5 +1,16 @@ module ActiveRecord module Integration + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # Indicates the format used to generate the timestamp format in the cache key. + # This is +:number+, by default. + class_attribute :cache_timestamp_format, :instance_writer => false + self.cache_timestamp_format = :number + end + # Returns a String, which Action Pack uses for constructing an URL to this # object. The default implementation returns this record's id as a String, # or nil if this record's unsaved. @@ -39,7 +50,7 @@ module ActiveRecord when new_record? "#{self.class.model_name.cache_key}/new" when timestamp = self[:updated_at] - timestamp = timestamp.utc.to_s(:number) + timestamp = timestamp.utc.to_s(cache_timestamp_format) "#{self.class.model_name.cache_key}/#{id}-#{timestamp}" else "#{self.class.model_name.cache_key}/#{id}" diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 7deac2588a..b2881991d5 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -102,6 +102,8 @@ module ActiveRecord def destroy #:nodoc: return super unless locking_enabled? + destroy_associations + if persisted? table = self.class.arel_table lock_col = self.class.locking_column diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index e6686597b7..e8ec93470d 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -458,7 +458,7 @@ module ActiveRecord say_with_time "#{method}(#{arg_list})" do unless reverting? unless arguments.empty? || method == :execute - arguments[0] = Migrator.proper_table_name(arguments.first) + arguments[0] = Migrator.proper_table_name(arguments.first) unless method == :assume_migrated_upto_version arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table end end @@ -627,7 +627,7 @@ module ActiveRecord seen = Hash.new false migrations = files.map do |file| - version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first + version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first raise IllegalMigrationNameError.new(file) unless version version = version.to_i diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index d2065d701f..05091654c0 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -277,13 +277,14 @@ module ActiveRecord type = (reflection.collection? ? :collection : :one_to_one) + # remove_possible_method :pirate_attributes= + # # 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 + remove_possible_method(:#{association_name}_attributes=) + def #{association_name}_attributes=(attributes) assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index fd3380a53c..36b6b5fce2 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -194,7 +194,7 @@ module ActiveRecord raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name) raise ActiveRecordError, "can not update on a new record object" unless persisted? - updated_count = self.class.update_all({ name => value }, self.class.primary_key => id) + updated_count = self.class.unscoped.update_all({ name => value }, self.class.primary_key => id) raw_write_attribute(name, value) diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 466d148901..2156889a0f 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -6,19 +6,19 @@ module ActiveRecord module ClassMethods # Enable the query cache within the block if Active Record is configured. def cache(&block) - if ActiveRecord::Base.configurations.blank? - yield - else + if ActiveRecord::Base.connected? connection.cache(&block) + else + yield end end # Disable the query cache within the block if Active Record is configured. def uncached(&block) - if ActiveRecord::Base.configurations.blank? - yield - else + if ActiveRecord::Base.connected? connection.uncached(&block) + else + yield end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ae6d9c8653..1bdb841398 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -64,10 +64,21 @@ db_namespace = namespace :db do end end + # If neither encoding nor collation is specified, use the utf-8 defaults. def mysql_creation_options(config) - @charset = ENV['CHARSET'] || 'utf8' - @collation = ENV['COLLATION'] || 'utf8_unicode_ci' - {:charset => (config['encoding'] || @charset), :collation => (config['collation'] || @collation)} + default_charset = ENV['CHARSET'] || 'utf8' + default_collation = ENV['COLLATION'] || 'utf8_unicode_ci' + + Hash.new.tap do |options| + options[:charset] = config['encoding'] if config.include? 'encoding' + options[:collation] = config['collation'] if config.include? 'collation' + + # Set default charset only when collation isn't set. + options[:charset] ||= default_charset unless options[:collation] + + # Set default collation only when charset is also default. + options[:collation] ||= default_collation if options[:charset] == default_charset + end end def create_database(config) @@ -101,9 +112,12 @@ db_namespace = namespace :db do error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error end access_denied_error = 1045 + + create_options = mysql_creation_options(config) + begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) - ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config)) + ActiveRecord::Base.connection.create_database(config['database'], create_options) ActiveRecord::Base.establish_connection(config) rescue error_class => sqlerr if sqlerr.errno == access_denied_error @@ -119,7 +133,7 @@ db_namespace = namespace :db do ActiveRecord::Base.establish_connection(config) else $stderr.puts sqlerr.error - $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['encoding'] || @charset}, collation: #{config['collation'] || @collation}" + $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{create_options[:charset]}, collation: #{create_options[:collation]}" $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['encoding'] end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4b3b30d6ed..a727f40194 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -464,7 +464,12 @@ module ActiveRecord node.left.relation.name == table_name } - Hash[equalities.map { |where| [where.left.name, where.right] }] + binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] + + Hash[equalities.map { |where| + name = where.left.name + [name, binds.fetch(name.to_s) { where.right }] + }] end def scope_for_create diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 2fd89882ff..14701f668c 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -59,11 +59,11 @@ module ActiveRecord relation = apply_finder_options(finder_options) end - start = options.delete(:start).to_i + start = options.delete(:start) batch_size = options.delete(:batch_size) || 1000 relation = relation.reorder(batch_order).limit(batch_size) - records = relation.where(table[primary_key].gteq(start)).all + records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a while records.any? records_size = records.size diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 802059db21..20c5fa03a2 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -177,8 +177,15 @@ module ActiveRecord # Person.where(:confirmed => true).limit(5).pluck(:id) # def pluck(column_name) - column_name = column_name.to_s - klass.connection.select_all(select(column_name).arel).map! do |attributes| + if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) + column_name = "#{table_name}.#{column_name}" + else + column_name = column_name.to_s + end + + relation = clone + relation.select_values = [column_name] + klass.connection.select_all(relation.arel, nil, bind_values).map! do |attributes| klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes)) end end @@ -240,7 +247,8 @@ module ActiveRecord query_builder = relation.arel end - type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation) + result = @klass.connection.select_value(query_builder, nil, relation.bind_values) + type_cast_calculated_value(result, column_for(column_name), operation) end def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: @@ -286,7 +294,7 @@ module ActiveRecord relation = except(:group).group(group) relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation) + calculated_data = @klass.connection.select_all(relation, nil, bind_values) if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index abc67d9c15..cbb2f676c8 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -199,7 +199,7 @@ module ActiveRecord relation = relation.where(table[primary_key].eq(id)) if id end - connection.select_value(relation, "#{name} Exists") ? true : false + connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false rescue ThrowResult false end @@ -253,9 +253,11 @@ module ActiveRecord orders = relation.order_values.map { |val| val.presence }.compact values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders) - relation = relation.dup + relation = relation.dup.select(values) + + id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values) + ids_array = id_rows.map {|row| row[primary_key]} - ids_array = relation.select(values).collect {|row| row[primary_key]} ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array) end @@ -263,7 +265,7 @@ module ActiveRecord conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}] result = where(conditions).send(match.finder) - if match.bang? && result.blank? + if match.bang? && result.nil? raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" else yield(result) if block_given? @@ -332,7 +334,7 @@ module ActiveRecord substitute = connection.substitute_at(column, @bind_values.length) relation = where(table[primary_key].eq(substitute)) - relation.bind_values = [[column, id]] + relation.bind_values += [[column, id]] record = relation.first unless record diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index c25570d758..3eead56e47 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -22,7 +22,7 @@ module ActiveRecord end end - (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method| + (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order, :binds]).each do |method| value = r.send(:"#{method}_values") merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present? end @@ -31,6 +31,8 @@ module ActiveRecord merged_wheres = @where_values + r.where_values + merged_binds = (@bind_values + r.bind_values).uniq(&:first) + unless @where_values.empty? # Remove duplicates, last one wins. seen = Hash.new { |h,table| h[table] = {} } @@ -47,6 +49,7 @@ module ActiveRecord end merged_relation.where_values = merged_wheres + merged_relation.bind_values = merged_binds (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method| value = r.send(:"#{method}_value") diff --git a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb new file mode 100644 index 0000000000..d7d40f6385 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb @@ -0,0 +1,18 @@ +require "cases/helper" + +class SqlTypesTest < ActiveRecord::TestCase + def test_binary_types + assert_equal 'bytea', type_to_sql(:binary, 100_000) + assert_raise ActiveRecord::ActiveRecordError do + type_to_sql :binary, 4294967295 + end + assert_equal 'text', type_to_sql(:text, 100_000) + assert_raise ActiveRecord::ActiveRecordError do + type_to_sql :text, 4294967295 + end + end + + def type_to_sql(*args) + ActiveRecord::Base.connection.type_to_sql(*args) + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index ec69a36174..614be8760a 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -157,7 +157,6 @@ module ActiveRecord binary = DualEncoding.new :name => 'いただきます!', :data => str binary.save! assert_equal str, binary.data - ensure if "<3".respond_to?(:encode) DualEncoding.connection.drop_table('dual_encodings') @@ -167,8 +166,9 @@ module ActiveRecord def test_type_cast_should_not_mutate_encoding return skip('only test encoding on 1.9') unless "<3".encoding_aware? - name = 'hello'.force_encoding(Encoding::ASCII_8BIT) - owner = Owner.create(:name => name) + name = 'hello'.force_encoding(Encoding::ASCII_8BIT) + Owner.create(:name => name) + assert_equal Encoding::ASCII_8BIT, name.encoding end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 588adc38e3..ee338a3b99 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -11,23 +11,30 @@ if ActiveRecord::Base.connection.supports_migrations? def teardown @connection.drop_table :fruits rescue nil + @connection.drop_table :"_pre_fruits_suf_" rescue nil + @connection.drop_table :"_pre_schema_migrations_suf_" rescue nil end def test_schema_define - ActiveRecord::Schema.define(:version => 7) do - create_table :fruits do |t| - t.column :color, :string - t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle - t.column :texture, :string - t.column :flavor, :string - end - end + perform_schema_define! assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } assert_equal 7, ActiveRecord::Migrator::current_version end + def test_schema_define_with_table_prefix_and_suffix + ActiveRecord::Base.table_name_prefix = '_pre_' + ActiveRecord::Base.table_name_suffix = '_suf_' + + perform_schema_define! + + assert_equal 7, ActiveRecord::Migrator::current_version + ensure + ActiveRecord::Base.table_name_prefix = '' + ActiveRecord::Base.table_name_suffix = '' + end + def test_schema_raises_an_error_for_invalid_column_type assert_raise NoMethodError do ActiveRecord::Schema.define(:version => 8) do @@ -39,4 +46,17 @@ if ActiveRecord::Base.connection.supports_migrations? end end + private + + def perform_schema_define! + ActiveRecord::Schema.define(:version => 7) do + create_table :fruits do |t| + t.column :color, :string + t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle + t.column :texture, :string + t.column :flavor, :string + end + end + end + end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index bf01b46852..d3601e0dba 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -916,6 +916,12 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size end + def test_dont_create_temporary_active_record_instances + Developer.instance_count = 0 + developers = Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).to_a + assert_equal developers.count, Developer.instance_count + end + def test_order_on_join_table_with_include_and_limit assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ed4475770b..b4788e0a3d 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1225,6 +1225,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert companies(:first_firm).clients.include?(Client.find(2)) end + def test_included_in_collection_for_new_records + client = Client.create(:name => 'Persisted') + assert_nil client.client_of + assert !Firm.new.clients_of_firm.include?(client), + 'includes a client that does not belong to any firm' + end + def test_adding_array_and_collection assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients } end @@ -1697,6 +1704,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [tagging], post.taggings end + def test_build_with_polymotphic_has_many_does_not_allow_to_override_type_and_id + welcome = posts(:welcome) + tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange') + + assert_equal welcome.id, tagging.taggable_id + assert_equal 'Post', tagging.taggable_type + end + def test_dont_call_save_callbacks_twice_on_has_many firm = companies(:first_firm) contract = firm.contracts.create! diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 08831a42ba..31aa3788c7 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -173,6 +173,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal account, firm.account end + def test_build_association_dont_create_transaction + assert_no_queries { + Firm.new.build_account + } + end + def test_build_and_create_should_not_happen_within_scope pirate = pirates(:blackbeard) scoped_count = pirate.association(:foo_bulb).scoped.where_values.count diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 073e856e5e..b849eead26 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -23,6 +23,8 @@ require 'models/edge' require 'models/joke' require 'models/bulb' require 'models/bird' +require 'models/car' +require 'models/bulb' require 'rexml/document' require 'active_support/core_ext/exception' require 'bcrypt' @@ -1293,6 +1295,16 @@ class BasicsTest < ActiveRecord::TestCase assert_equal({ :foo => :bar }, t.content_before_type_cast) end + def test_serialized_attributes_before_type_cast_returns_unserialized_value + Topic.serialize :content, Hash + + t = Topic.new(:content => { :foo => :bar }) + assert_equal({ :foo => :bar }, t.attributes_before_type_cast["content"]) + t.save! + t.reload + assert_equal({ :foo => :bar }, t.attributes_before_type_cast["content"]) + end + def test_serialized_attribute_calling_dup_method klass = Class.new(ActiveRecord::Base) klass.table_name = "topics" @@ -1430,6 +1442,29 @@ class BasicsTest < ActiveRecord::TestCase Topic.serialize(:content) end + def test_serialize_attribute_via_select_method_when_time_zone_available + ActiveRecord::Base.time_zone_aware_attributes = true + Topic.serialize(:content, MyObject) + + myobj = MyObject.new('value1', 'value2') + topic = Topic.create(:content => myobj) + + ActiveRecord::IdentityMap.without do + assert_equal(myobj, Topic.select(:content).find(topic.id).content) + assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content } + end + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end + + def test_serialize_attribute_can_be_serialized_in_an_integer_column + insures = ['life'] + person = SerializedPerson.new(:first_name => 'David', :insures => insures) + assert person.save + person = person.reload + assert_equal(insures, person.insures) + end + def test_quote author_name = "\\ \001 ' \n \\n \"" topic = Topic.create('author_name' => author_name) @@ -2144,6 +2179,29 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key end + def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format + return skip "Only in Ruby 1.9" if RUBY_VERSION < '1.9' + + dev = CachedDeveloper.first + assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key + end + + def test_cache_key_changes_when_child_touched + return skip "Only in Ruby 1.9" if RUBY_VERSION < '1.9' + + old_timestamp_format = Car.cache_timestamp_format + Car.cache_timestamp_format = :nsec + car = Car.create + Bulb.create(:car => car) + + key = car.cache_key + car.bulb.touch + car.reload + assert_not_equal key, car.cache_key + ensure + Car.cache_timestamp_format = old_timestamp_format + end + def test_cache_key_format_for_existing_record_with_nil_updated_at dev = Developer.first dev.update_attribute(:updated_at, nil) diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 660098b9ad..ad2a749ab4 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -1,8 +1,9 @@ require 'cases/helper' require 'models/post' +require 'models/subscriber' class EachTest < ActiveRecord::TestCase - fixtures :posts + fixtures :posts, :subscribers def setup @posts = Post.order("id asc") @@ -136,4 +137,24 @@ class EachTest < ActiveRecord::TestCase assert_equal special_posts_ids, posts.map(&:id) end + def test_find_in_batches_should_use_any_column_as_primary_key + nick_order_subscribers = Subscriber.order('nick asc') + start_nick = nick_order_subscribers.second.nick + + subscribers = [] + Subscriber.find_in_batches(:batch_size => 1, :start => start_nick) do |batch| + subscribers.concat(batch) + end + + assert_equal nick_order_subscribers[1..-1].map(&:id), subscribers.map(&:id) + end + + def test_find_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified + Subscriber.count('nick') # preheat arel's table cache + assert_queries(Subscriber.count + 1) do + Subscriber.find_each(:batch_size => 1) do |subscriber| + assert_kind_of Subscriber, subscriber + end + end + end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index cf1181e829..f931b39998 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -487,4 +487,20 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_with_qualified_column_name assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id") end + + def test_pluck_replaces_select_clause + taks_relation = Topic.select([:approved, :id]).order(:id) + assert_equal [1,2,3,4], taks_relation.pluck(:id) + assert_equal [false, true, true, true], taks_relation.pluck(:approved) + end + + def test_pluck_auto_table_name_prefix + c = Company.create!(:name => "test", :contracts => [Contract.new]) + assert_equal [c.id], Company.joins(:contracts).pluck(:id) + end + + def test_pluck_not_auto_table_name_prefix_if_column_joined + Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + assert_equal [7], Company.joins(:contracts).pluck(:developer_id).map(&:to_i) + end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 4fc7e77786..86a28d95ad 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -201,6 +201,20 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_nullable_datetime_not_marked_as_changed_if_new_value_is_blank + in_time_zone 'Edinburgh' do + target = Class.new(ActiveRecord::Base) + target.table_name = 'topics' + + topic = target.create + assert_equal nil, topic.written_on + + topic.written_on = "" + assert_equal nil, topic.written_on + assert !topic.written_on_changed? + end + end + def test_integer_zero_to_string_zero_not_marked_as_changed pirate = Pirate.new pirate.parrot_id = 0 diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 77ca3b574d..7d63d76c34 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -664,6 +664,11 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") } end + def test_find_by_one_attribute_bang_with_blank_defined + blank_topic = BlankTopic.create(:title => "The Blank One") + assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One") + end + def test_find_by_one_attribute_with_order_option assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id') assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC') diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index e352a55104..cf49b125d7 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -4,7 +4,7 @@ require 'config' require 'test/unit' require 'stringio' -require 'mocha' +require 'mocha/setup' require 'active_record' require 'active_support/dependencies' diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 015a3ccefd..066a60f81a 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -7,6 +7,7 @@ require 'models/ship' require 'models/legacy_thing' require 'models/reference' require 'models/string_key_object' +require 'models/treasure' class LockWithoutDefault < ActiveRecord::Base; end @@ -20,7 +21,7 @@ class ReadonlyNameShip < Ship end class OptimisticLockingTest < ActiveRecord::TestCase - fixtures :people, :legacy_things, :references, :string_key_objects + fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures def test_non_integer_lock_existing s1 = StringKeyObject.find("record1") @@ -267,6 +268,15 @@ class SetLockingColumnTest < ActiveRecord::TestCase assert_equal "omg", k.original_locking_column end end + + def test_removing_has_and_belongs_to_many_associations_upon_destroy + p = RichPerson.create! :first_name => 'Jon' + p.treasures.create! + assert !p.treasures.empty? + p.destroy + assert p.treasures.empty? + assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty? + end end class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f0c55b1535..7bb71b7b6e 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1051,6 +1051,18 @@ if ActiveRecord::Base.connection.supports_migrations? Person.connection.remove_column("people", "administrator") rescue nil end + def test_change_column_with_custom_index_name + Person.connection.add_column "people", "category", :string, :default => 'human' + Person.connection.add_index :people, :category, :name => 'people_categories_idx' + + assert_equal ['people_categories_idx'], Person.connection.indexes('people').map(&:name) + Person.connection.change_column "people", "category", :string, :null => false, :default => 'article' + + assert_equal ['people_categories_idx'], Person.connection.indexes('people').map(&:name) + ensure + Person.connection.remove_column("people", "category") rescue nil + end + def test_change_column_default Person.connection.change_column_default "people", "first_name", "Tester" Person.reset_column_information @@ -1430,6 +1442,12 @@ if ActiveRecord::Base.connection.supports_migrations? end end + def test_finds_migrations_in_numbered_directory + migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + '/10_urban'] + assert_equal 9, migrations[0].version + assert_equal 'AddExpressions', migrations[0].name + end + def test_dump_schema_information_outputs_lexically_ordered_versions migration_path = MIGRATIONS_ROOT + '/valid_with_timestamps' ActiveRecord::Migrator.run(:up, migration_path, 20100301010101) diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 16b1eb040e..85b9d3c1a1 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -172,6 +172,17 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}] assert_equal man.interests.first.topic, man.interests[0].topic end + + def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses + Pirate.accepts_nested_attributes_for(:parrot) + + mean_pirate_class = Class.new(Pirate) do + accepts_nested_attributes_for :parrot + end + mean_pirate = mean_pirate_class.new + mean_pirate.parrot_attributes = { :name => "James" } + assert_equal "James", mean_pirate.parrot.name + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index a35cb3c4a5..00dc1f6d72 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -519,6 +519,14 @@ class PersistencesTest < ActiveRecord::TestCase assert return_value end + def test_update_column_with_default_scope + developer = DeveloperCalledDavid.first + developer.name = 'John' + developer.save! + + assert developer.update_column(:name, 'Will'), 'did not update record due to default scope' + end + def test_update_attributes topic = Topic.find(1) assert !topic.approved? diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index dd881f8230..dfc6ea2457 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -173,6 +173,17 @@ class QueryCacheTest < ActiveRecord::TestCase assert_queries(2) { task.lock!; task.lock! } end end + + def test_cache_is_available_when_connection_is_connected + conf = ActiveRecord::Base.configurations + + ActiveRecord::Base.configurations = {} + Task.cache do + assert_queries(1) { Task.find(1); Task.find(1) } + end + ensure + ActiveRecord::Base.configurations = conf + end end class QueryCacheExpiryTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index b24df47efd..ada4294401 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1185,4 +1185,18 @@ class RelationTest < ActiveRecord::TestCase end assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name) end + + test 'group with select and includes' do + authors_count = Post.select('author_id, COUNT(author_id) AS num_posts'). + group('author_id').order('author_id').includes(:author).to_a + + assert_no_queries do + result = authors_count.map do |post| + [post.num_posts.to_i, post.author.try(:name)] + end + + expected = [[1, nil], [5, "David"], [3, "Mary"], [2, "Bob"]] + assert_equal expected, result + end + end end diff --git a/activerecord/test/fixtures/peoples_treasures.yml b/activerecord/test/fixtures/peoples_treasures.yml new file mode 100644 index 0000000000..a72b190d0c --- /dev/null +++ b/activerecord/test/fixtures/peoples_treasures.yml @@ -0,0 +1,3 @@ +michael_diamond: + rich_person_id: <%= ActiveRecord::Fixtures.identify(:michael) %> + treasure_id: <%= ActiveRecord::Fixtures.identify(:diamond) %> diff --git a/activerecord/test/migrations/10_urban/9_add_expressions.rb b/activerecord/test/migrations/10_urban/9_add_expressions.rb new file mode 100644 index 0000000000..79a342e574 --- /dev/null +++ b/activerecord/test/migrations/10_urban/9_add_expressions.rb @@ -0,0 +1,11 @@ +class AddExpressions < ActiveRecord::Migration + def self.up + create_table("expressions") do |t| + t.column :expression, :string + end + end + + def self.down + drop_table "expressions" + end +end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 888afc7604..ab3caa3f79 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,6 +1,6 @@ class Bulb < ActiveRecord::Base default_scope where(:name => 'defaulty') - belongs_to :car + belongs_to :car, :touch => true attr_protected :car_id, :frickinawesome diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 4dc9fff9fd..f8b758b3c2 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -63,6 +63,15 @@ class Developer < ActiveRecord::Base self.all end end + + after_find :track_instance_count + cattr_accessor :instance_count + + def track_instance_count + self.class.instance_count ||= 0 + self.class.instance_count += 1 + end + private :track_instance_count end class AuditLog < ActiveRecord::Base @@ -236,3 +245,8 @@ class ThreadsafeDeveloper < ActiveRecord::Base limit(1) end end + +class CachedDeveloper < ActiveRecord::Base + self.table_name = "developers" + self.cache_timestamp_format = :nsec +end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 5b92227f4a..07c529d685 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -86,3 +86,30 @@ class TightPerson < ActiveRecord::Base end class TightDescendant < TightPerson; end + +class RichPerson < ActiveRecord::Base + self.table_name = 'people' + + has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures' +end + +class Insure + INSURES = %W{life annuality} + + def self.load mask + INSURES.select do |insure| + (1 << INSURES.index(insure)) & mask.to_i > 0 + end + end + + def self.dump insures + numbers = insures.map { |insure| INSURES.index(insure) } + numbers.inject(0) { |sum, n| sum + (1 << n) } + end +end + +class SerializedPerson < ActiveRecord::Base + self.table_name = 'people' + + serialize :insures, Insure +end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 1a1a18166a..5166fefe81 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -112,6 +112,12 @@ class ImportantTopic < Topic serialize :important, Hash end +class BlankTopic < Topic + def blank? + true + end +end + module Web class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply' diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 34e24b5a2e..b2c655ddcd 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -132,5 +132,10 @@ _SQL _SQL rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table end + + create_table :limitless_fields, :force => true do |t| + t.binary :binary, :limit => 100_000 + t.text :text, :limit => 100_000 + end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 8a3dfbb35a..1a993fef11 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -112,6 +112,7 @@ ActiveRecord::Schema.define do t.string :name t.integer :engines_count t.integer :wheels_count + t.timestamps end create_table :categories, :force => true do |t| @@ -478,9 +479,15 @@ ActiveRecord::Schema.define do t.integer :followers_count, :default => 0 t.references :best_friend t.references :best_friend_of + t.integer :insures, :null => false, :default => 0 t.timestamps end + create_table :peoples_treasures, :id => false, :force => true do |t| + t.column :rich_person_id, :integer + t.column :treasure_id, :integer + end + create_table :pets, :primary_key => :pet_id ,:force => true do |t| t.string :name t.integer :owner_id, :integer |