aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md183
-rw-r--r--activerecord/lib/active_record/associations/association.rb3
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb18
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb1
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb13
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb7
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/railties/databases.rake24
-rw-r--r--activerecord/lib/active_record/relation.rb7
-rw-r--r--activerecord/lib/active_record/relation/batches.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb16
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb12
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/sql_types_test.rb18
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb6
-rw-r--r--activerecord/test/cases/ar_schema_test.rb36
-rw-r--r--activerecord/test/cases/associations/eager_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb15
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb6
-rw-r--r--activerecord/test/cases/base_test.rb58
-rw-r--r--activerecord/test/cases/batches_test.rb23
-rw-r--r--activerecord/test/cases/calculations_test.rb16
-rw-r--r--activerecord/test/cases/dirty_test.rb14
-rw-r--r--activerecord/test/cases/finder_test.rb5
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb12
-rw-r--r--activerecord/test/cases/migration_test.rb18
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb11
-rw-r--r--activerecord/test/cases/persistence_test.rb8
-rw-r--r--activerecord/test/cases/query_cache_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb14
-rw-r--r--activerecord/test/fixtures/peoples_treasures.yml3
-rw-r--r--activerecord/test/migrations/10_urban/9_add_expressions.rb11
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/developer.rb14
-rw-r--r--activerecord/test/models/person.rb27
-rw-r--r--activerecord/test/models/topic.rb6
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb5
-rw-r--r--activerecord/test/schema/schema.rb7
50 files changed, 688 insertions, 65 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index bd8a0bc039..bd99ba7c09 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,10 +1,191 @@
-## Rails 3.2.10 ##
+## 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.10 (Jan 2, 2013) ##
* CVE-2012-5664 options hashes should only be extracted if there are extra
parameters
+
## Rails 3.2.9 (Nov 12, 2012) ##
+* Fix `find_in_batches` crashing when IDs are strings and start option is not specified.
+
+ *Alexis Bernard*
+
* Fix issue with collection associations calling first(n)/last(n) and attempting
to set the inverse association when `:inverse_of` was used. Fixes #8087.
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