aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG63
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/aggregations.rb7
-rw-r--r--activerecord/lib/active_record/associations.rb10
-rw-r--r--activerecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb65
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb3
-rw-r--r--activerecord/lib/active_record/errors.rb23
-rw-r--r--activerecord/lib/active_record/fixtures.rb24
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb5
-rw-r--r--activerecord/lib/active_record/query_cache.rb6
-rw-r--r--activerecord/lib/active_record/railties/databases.rake10
-rw-r--r--activerecord/lib/active_record/reflection.rb14
-rw-r--r--activerecord/lib/active_record/store.rb49
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb35
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb35
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/statement_pool_test.rb16
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb4
-rw-r--r--activerecord/test/cases/fixtures_test.rb34
-rw-r--r--activerecord/test/cases/locking_test.rb19
-rw-r--r--activerecord/test/cases/migration_test.rb48
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb8
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb4
-rw-r--r--activerecord/test/cases/query_cache_test.rb2
-rw-r--r--activerecord/test/cases/reflection_test.rb16
-rw-r--r--activerecord/test/cases/store_test.rb29
-rw-r--r--activerecord/test/cases/unconnected_test.rb2
-rw-r--r--activerecord/test/config.example.yml2
-rw-r--r--activerecord/test/models/admin/user.rb3
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb13
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb11
-rw-r--r--activerecord/test/schema/schema.rb1
44 files changed, 581 insertions, 79 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index a54526dd41..46076dac61 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,8 +1,25 @@
-Wed Sep 7 15:25:02 2011 Aaron Patterson <aaron@tenderlovemaking.com>
+*Rails 3.2.0 (unreleased)*
- * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache
- keys are per process id.
- * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto
+* Added ActiveRecord::Base.store for declaring simple single-column key/value stores [DHH]
+
+ class User < ActiveRecord::Base
+ store :settings, accessors: [ :color, :homepage ]
+ end
+
+ u = User.new(color: 'black', homepage: '37signals.com')
+ u.color # Accessor stored attribute
+ u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+
+
+* MySQL: case-insensitive uniqueness validation avoids calling LOWER when
+ the column already uses a case-insensitive collation. Fixes #561.
+
+ [Joseph Palermo]
+
+* Transactional fixtures enlist all active database connections. You can test
+ models on different connections without disabling transactional fixtures.
+
+ [Jeremy Kemper]
* Add first_or_create, first_or_create!, first_or_initialize methods to Active Record. This is a
better approach over the old find_or_create_by dynamic methods because it's clearer which
@@ -12,6 +29,43 @@ Wed Sep 7 15:25:02 2011 Aaron Patterson <aaron@tenderlovemaking.com>
[Andrés Mejía]
+* Fix nested attributes bug where _destroy parameter is taken into account
+ during :reject_if => :all_blank (fixes #2937)
+
+ [Aaron Christy]
+
+*Rails 3.1.1 (October 7, 2011)*
+
+* Add deprecation for the preload_associations method. Fixes #3022.
+
+ [Jon Leighton]
+
+* Don't require a DB connection when loading a model that uses set_primary_key. GH #2807.
+
+ [Jon Leighton]
+
+* Fix using select() with a habtm association, e.g. Person.friends.select(:name). GH #3030 and
+ #2923.
+
+ [Hendy Tanata]
+
+* Fix belongs_to polymorphic with custom primary key on target. GH #3104.
+
+ [Jon Leighton]
+
+* CollectionProxy#replace should change the DB records rather than just mutating the array.
+ Fixes #3020.
+
+ [Jon Leighton]
+
+* LRU cache in mysql and sqlite are now per-process caches.
+
+ * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache
+ keys are per process id.
+ * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto
+
+ [Aaron Patterson]
+
* Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton]
* If multiple parameters are sent representing a date, and some are blank, the
@@ -36,6 +90,7 @@ a URI that specifies the connection configuration. For example:
[Prem Sichanugrist]
+
*Rails 3.1.0 (August 30, 2011)*
* Add a proxy_association method to association proxies, which can be called by association
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 132dc12680..3572c640eb 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -69,6 +69,7 @@ module ActiveRecord
autoload :Schema
autoload :SchemaDumper
autoload :Serialization
+ autoload :Store
autoload :SessionStore
autoload :Timestamp
autoload :Transactions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 81ddbba51e..5a8addc4e4 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -172,8 +172,8 @@ module ActiveRecord
# with this option.
# * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
# object. Each mapping is represented as an array where the first item is the name of the
- # entity attribute and the second item is the name the attribute in the value object. The
- # order in which mappings are defined determine the order in which attributes are sent to the
+ # entity attribute and the second item is the name of the attribute in the value object. The
+ # order in which mappings are defined determines the order in which attributes are sent to the
# value class constructor.
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
@@ -191,7 +191,8 @@ module ActiveRecord
#
# Option examples:
# composed_of :temperature, :mapping => %w(reading celsius)
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
+ # :converter => Proc.new { |balance| balance.to_money }
# composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
# composed_of :gps_location
# composed_of :gps_location, :allow_nil => true
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 0952ea2829..34684ad2f5 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1424,18 +1424,18 @@ module ActiveRecord
# join table with a migration such as this:
#
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
- # def self.up
+ # def change
# create_table :developers_projects, :id => false do |t|
# t.integer :developer_id
# t.integer :project_id
# end
# end
- #
- # def self.down
- # drop_table :developers_projects
- # end
# end
#
+ # It's also a good idea to add indexes to each of those columns to speed up the joins process.
+ # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
+ # uses one index per table during the lookup.
+ #
# Adds the following methods for retrieval and query:
#
# [collection(force_reload = false)]
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 78159d13d4..360e494af1 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -115,8 +115,8 @@ module ActiveRecord #:nodoc:
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
# can be used to qualify the table name of a particular condition. For instance:
#
- # Student.joins(:schools).where(:schools => { :type => 'public' })
- # Student.joins(:schools).where('schools.type' => 'public' )
+ # Student.joins(:schools).where(:schools => { :category => 'public' })
+ # Student.joins(:schools).where('schools.category' => 'public' )
#
# == Overwriting default accessors
#
@@ -2145,7 +2145,7 @@ MSG
# AutosaveAssociation needs to be included before Transactions, because we want
# #save_with_autosave_associations to be wrapped inside a transaction.
include AutosaveAssociation, NestedAttributes
- include Aggregations, Transactions, Reflection, Serialization
+ include Aggregations, Transactions, Reflection, Serialization, Store
NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 20863e73aa..77a5fe1efb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -314,7 +314,7 @@ module ActiveRecord
end
def current_connection_id #:nodoc:
- Thread.current.object_id
+ ActiveRecord::Base.connection_id ||= Thread.current.object_id
end
def checkout_new_connection
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index c08c0263b9..3d0f146fed 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -115,6 +115,14 @@ module ActiveRecord
retrieve_connection
end
+ def connection_id
+ Thread.current['ActiveRecord::Base.connection_id']
+ end
+
+ def connection_id=(connection_id)
+ Thread.current['ActiveRecord::Base.connection_id'] = connection_id
+ end
+
# Returns the configuration of the associated connection as a hash:
#
# ActiveRecord::Base.connection_config
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 82f564e41d..989a4fcbca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -252,7 +252,7 @@ module ActiveRecord
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table.
def timestamps(*args)
- options = args.extract_options!
+ options = { :null => false }.merge(args.extract_options!)
column(:created_at, :datetime, options)
column(:updated_at, :datetime, options)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 8e3ba1297e..b4a9e29ef1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -507,8 +507,8 @@ module ActiveRecord
# ===== Examples
# add_timestamps(:suppliers)
def add_timestamps(table_name)
- add_column table_name, :created_at, :datetime
- add_column table_name, :updated_at, :datetime
+ add_column table_name, :created_at, :datetime, :null => false
+ add_column table_name, :updated_at, :datetime, :null => false
end
# Removes the timestamp columns (created_at and updated_at) from the table definition.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 443e61b527..4c3a8f7233 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -238,6 +238,10 @@ module ActiveRecord
node
end
+ def case_insensitive_comparison(table, attribute, column, value)
+ table[attribute].lower.eq(table.lower(value))
+ end
+
def current_savepoint_name
"active_record_#{open_transactions}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 4b7c74e0b8..dd573ba569 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -4,6 +4,13 @@ module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
class Column < ConnectionAdapters::Column # :nodoc:
+ attr_reader :collation
+
+ def initialize(name, default, sql_type = nil, null = true, collation = nil)
+ super(name, default, sql_type, null)
+ @collation = collation
+ end
+
def extract_default(default)
if sql_type =~ /blob/i || type == :text
if default.blank?
@@ -28,6 +35,10 @@ module ActiveRecord
raise NotImplementedError
end
+ def case_sensitive?
+ collation && !collation.match(/_ci$/)
+ end
+
private
def simplified_type(field_type)
@@ -157,8 +168,8 @@ module ActiveRecord
end
# Overridden by the adapters to instantiate their specific Column type.
- def new_column(field, default, type, null) # :nodoc:
- Column.new(field, default, type, null)
+ def new_column(field, default, type, null, collation) # :nodoc:
+ Column.new(field, default, type, null, collation)
end
# Must return the Mysql error number from the exception, if the exception has an
@@ -393,10 +404,10 @@ module ActiveRecord
# Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name, name = nil)#:nodoc:
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
+ sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
end
end
end
@@ -501,6 +512,14 @@ module ActiveRecord
Arel::Nodes::Bin.new(node)
end
+ def case_insensitive_comparison(table, attribute, column, value)
+ if column.case_sensitive?
+ super
+ else
+ table[attribute].eq(value)
+ end
+ end
+
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
where_sql
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 8b574518e5..971f3c35f3 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -47,8 +47,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null) # :nodoc:
- Column.new(field, default, type, null)
+ def new_column(field, default, type, null, collation) # :nodoc:
+ Column.new(field, default, type, null, collation)
end
def error_number(exception)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index a1824fe396..f092edecda 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -150,8 +150,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null) # :nodoc:
- Column.new(field, default, type, null)
+ def new_column(field, default, type, null, collation) # :nodoc:
+ Column.new(field, default, type, null, collation)
end
def error_number(exception) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 5402918b1d..3d084bb178 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -278,13 +278,24 @@ module ActiveRecord
cache.clear
end
+ def delete(sql_key)
+ dealloc cache[sql_key]
+ cache.delete sql_key
+ end
+
private
def cache
@cache[$$]
end
def dealloc(key)
- @connection.query "DEALLOCATE #{key}"
+ @connection.query "DEALLOCATE #{key}" if connection_active?
+ end
+
+ def connection_active?
+ @connection.status == PGconn::CONNECTION_OK
+ rescue PGError
+ false
end
end
@@ -1030,26 +1041,54 @@ module ActiveRecord
end
private
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
+
def exec_no_cache(sql, binds)
@connection.async_exec(sql)
end
def exec_cache(sql, binds)
- unless @statements.key? sql
- nextkey = @statements.next_key
- @connection.prepare nextkey, sql
- @statements[sql] = nextkey
+ begin
+ stmt_key = prepare_statement sql
+
+ # Clear the queue
+ @connection.get_last_result
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
+ type_cast(val, col)
+ })
+ @connection.block
+ @connection.get_last_result
+ rescue PGError => e
+ # Get the PG code for the failure. Annoyingly, the code for
+ # prepared statements whose return value may have changed is
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ if FEATURE_NOT_SUPPORTED == code
+ @statements.delete sql_key(sql)
+ retry
+ else
+ raise e
+ end
end
+ end
- key = @statements[sql]
+ # Returns the statement identifier for the client side cache
+ # of statements
+ def sql_key(sql)
+ "#{schema_search_path}-#{sql}"
+ end
- # Clear the queue
- @connection.get_last_result
- @connection.send_query_prepared(key, binds.map { |col, val|
- type_cast(val, col)
- })
- @connection.block
- @connection.get_last_result
+ # Prepare the statement if it hasn't been prepared, return
+ # the statement key.
+ def prepare_statement(sql)
+ sql_key = sql_key(sql)
+ unless @statements.key? sql_key
+ nextkey = @statements.next_key
+ @connection.prepare nextkey, sql
+ @statements[sql_key] = nextkey
+ end
+ @statements[sql_key]
end
# The internal PostgreSQL identifier of the money data type.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 1932a849ee..e0e957a12c 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -413,6 +413,8 @@ module ActiveRecord
self.limit = options[:limit] if options.include?(:limit)
self.default = options[:default] if include_default
self.null = options[:null] if options.include?(:null)
+ self.precision = options[:precision] if options.include?(:precision)
+ self.scale = options[:scale] if options.include?(:scale)
end
end
end
@@ -467,6 +469,7 @@ module ActiveRecord
@definition.column(column_name, column.type,
:limit => column.limit, :default => column.default,
+ :precision => column.precision, :scale => column.scale,
:null => column.null)
end
@definition.primary_key(primary_key(from)) if primary_key(from)
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index ad7d8cd63c..fc80f3081e 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -99,6 +99,16 @@ module ActiveRecord
#
# Read more about optimistic locking in ActiveRecord::Locking module RDoc.
class StaleObjectError < ActiveRecordError
+ attr_reader :record, :attempted_action
+
+ def initialize(record, attempted_action)
+ @record = record
+ @attempted_action = attempted_action
+ end
+
+ def message
+ "Attempted to #{attempted_action} a stale object: #{record.class.name}"
+ end
end
# Raised when association is being configured improperly or
@@ -169,4 +179,17 @@ module ActiveRecord
@errors = errors
end
end
+
+ # Raised when a primary key is needed, but there is not one specified in the schema or model.
+ class UnknownPrimaryKey < ActiveRecordError
+ attr_reader :model
+
+ def initialize(model)
+ @model = model
+ end
+
+ def message
+ "Unknown primary key for table #{model.table_name} in model #{model}."
+ end
+ end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6f1ec7f9b3..cad9417216 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -842,9 +842,12 @@ module ActiveRecord
@loaded_fixtures = load_fixtures
@@already_loaded_fixtures[self.class] = @loaded_fixtures
end
- ActiveRecord::Base.connection.increment_open_transactions
- ActiveRecord::Base.connection.transaction_joinable = false
- ActiveRecord::Base.connection.begin_db_transaction
+ @fixture_connections = enlist_fixture_connections
+ @fixture_connections.each do |connection|
+ connection.increment_open_transactions
+ connection.transaction_joinable = false
+ connection.begin_db_transaction
+ end
# Load fixtures for every test.
else
ActiveRecord::Fixtures.reset_cache
@@ -864,13 +867,22 @@ module ActiveRecord
end
# Rollback changes if a transaction is active.
- if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0
- ActiveRecord::Base.connection.rollback_db_transaction
- ActiveRecord::Base.connection.decrement_open_transactions
+ if run_in_transaction?
+ @fixture_connections.each do |connection|
+ if connection.open_transactions != 0
+ connection.rollback_db_transaction
+ connection.decrement_open_transactions
+ end
+ end
+ @fixture_connections.clear
end
ActiveRecord::Base.clear_active_connections!
end
+ def enlist_fixture_connections
+ ActiveRecord::Base.connection_handler.connection_pools.values.map(&:connection)
+ end
+
private
def load_fixtures
fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index d9ad7e4132..2df3309648 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -103,7 +103,7 @@ module ActiveRecord
affected_rows = connection.update stmt
unless affected_rows == 1
- raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
+ raise ActiveRecord::StaleObjectError.new(self, "update")
end
affected_rows
@@ -127,7 +127,7 @@ module ActiveRecord
affected_rows = self.class.unscoped.where(predicate).delete_all
unless affected_rows == 1
- raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 2dbebfcaf8..d2065d701f 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -220,7 +220,7 @@ module ActiveRecord
# validates_presence_of :member
# end
module ClassMethods
- REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
# Defines an attributes writer for the specified association(s). If you
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
@@ -239,7 +239,8 @@ module ActiveRecord
# is specified, a record will be built for all attribute hashes that
# do not have a <tt>_destroy</tt> value that evaluates to true.
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
- # that will reject a record where all the attributes are blank.
+ # that will reject a record where all the attributes are blank excluding
+ # any value for _destroy.
# [:limit]
# Allows you to specify the maximum number of the associated records that
# can be processed with the nested attributes. If the size of the
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 10c0dc6f2a..466d148901 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -28,9 +28,10 @@ module ActiveRecord
end
class BodyProxy # :nodoc:
- def initialize(original_cache_value, target)
+ def initialize(original_cache_value, target, connection_id)
@original_cache_value = original_cache_value
@target = target
+ @connection_id = connection_id
end
def method_missing(method_sym, *arguments, &block)
@@ -48,6 +49,7 @@ module ActiveRecord
def close
@target.close if @target.respond_to?(:close)
ensure
+ ActiveRecord::Base.connection_id = @connection_id
ActiveRecord::Base.connection.clear_query_cache
unless @original_cache_value
ActiveRecord::Base.connection.disable_query_cache!
@@ -60,7 +62,7 @@ module ActiveRecord
ActiveRecord::Base.connection.enable_query_cache!
status, headers, body = @app.call(env)
- [status, headers, BodyProxy.new(old, body)]
+ [status, headers, BodyProxy.new(old, body, ActiveRecord::Base.connection_id)]
rescue Exception => e
ActiveRecord::Base.connection.clear_query_cache
unless old
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 1009af850c..705ffe9dd7 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -279,7 +279,7 @@ db_namespace = namespace :db do
pending_migrations.each do |pending_migration|
puts ' %4d %s' % [pending_migration.version, pending_migration.name]
end
- abort %{Run "rake db:migrate" to update your database then try again.}
+ abort %{Run `rake db:migrate` to update your database then try again.}
end
end
end
@@ -332,7 +332,7 @@ db_namespace = namespace :db do
namespace :schema do
desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
- task :dump => :load_config do
+ task :dump => [:environment, :load_config] do
require 'active_record/schema_dumper'
filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
File.open(filename, "w:utf-8") do |file|
@@ -348,7 +348,7 @@ db_namespace = namespace :db do
if File.exists?(file)
load(file)
else
- abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded}
+ abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded}
end
end
end
@@ -415,10 +415,10 @@ db_namespace = namespace :db do
ENV['PGHOST'] = abcs['test']['host'] if abcs['test']['host']
ENV['PGPORT'] = abcs['test']['port'].to_s if abcs['test']['port']
ENV['PGPASSWORD'] = abcs['test']['password'].to_s if abcs['test']['password']
- `psql -U "#{abcs['test']['username']}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs['test']['database']} #{abcs['test']['template']}`
+ `psql -U "#{abcs['test']['username']}" -f "#{Rails.root}/db/#{Rails.env}_structure.sql" #{abcs['test']['database']} #{abcs['test']['template']}`
when /sqlite/
dbfile = abcs['test']['database'] || abcs['test']['dbfile']
- `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
+ `sqlite3 #{dbfile} < "#{Rails.root}/db/#{Rails.env}_structure.sql"`
when 'sqlserver'
`sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql`
when 'oci', 'oracle'
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 1929a808ed..5285060288 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -212,12 +212,12 @@ module ActiveRecord
end
# klass option is necessary to support loading polymorphic associations
- def association_primary_key(klass = self.klass)
- options[:primary_key] || klass.primary_key
+ def association_primary_key(klass = nil)
+ options[:primary_key] || primary_key(klass || self.klass)
end
def active_record_primary_key
- @active_record_primary_key ||= options[:primary_key] || active_record.primary_key
+ @active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
end
def counter_cache_column
@@ -357,6 +357,10 @@ module ActiveRecord
active_record.name.foreign_key
end
end
+
+ def primary_key(klass)
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
+ end
end
# Holds all the meta-data about a :through association as it was specified
@@ -461,7 +465,7 @@ module ActiveRecord
# We want to use the klass from this reflection, rather than just delegate straight to
# the source_reflection, because the source_reflection may be polymorphic. We still
# need to respect the source_reflection's :primary_key option, though.
- def association_primary_key(klass = self.klass)
+ def association_primary_key(klass = nil)
# Get the "actual" source reflection if the immediate source reflection has a
# source reflection itself
source_reflection = self.source_reflection
@@ -469,7 +473,7 @@ module ActiveRecord
source_reflection = source_reflection.source_reflection
end
- source_reflection.options[:primary_key] || klass.primary_key
+ source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
# Gets an array of possible <tt>:through</tt> source reflection names:
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
new file mode 100644
index 0000000000..d5910df891
--- /dev/null
+++ b/activerecord/lib/active_record/store.rb
@@ -0,0 +1,49 @@
+module ActiveRecord
+ # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
+ # It's like a simple key/value store backed into your record when you don't care about being able to
+ # query that store outside the context of a single record.
+ #
+ # You can then declare accessors to this store that are then accessible just like any other attribute
+ # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
+ # already built around just accessing attributes on the model.
+ #
+ # Make sure that you declare the database column used for the serialized store as a text, so there's
+ # plenty of room.
+ #
+ # Examples:
+ #
+ # class User < ActiveRecord::Base
+ # store :settings, accessors: [ :color, :homepage ]
+ # end
+ #
+ # u = User.new(color: 'black', homepage: '37signals.com')
+ # u.color # Accessor stored attribute
+ # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+ #
+ # # Add additional accessors to an existing store through store_accessor
+ # class SuperUser < User
+ # store_accessor :settings, :privileges, :servants
+ # end
+ module Store
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def store(store_attribute, options = {})
+ serialize store_attribute, Hash
+ store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
+ end
+
+ def store_accessor(store_attribute, *keys)
+ Array(keys).flatten.each do |key|
+ define_method("#{key}=") do |value|
+ send(store_attribute)[key] = value
+ end
+
+ define_method(key) do
+ send(store_attribute)[key]
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 484b1d369b..2e2ea8c42b 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -57,8 +57,8 @@ module ActiveRecord
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
if !options[:case_sensitive] && value && column.text?
- # will use SQL LOWER function before comparison
- relation = table[attribute].lower.eq(table.lower(value))
+ # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
+ relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
value = klass.connection.case_sensitive_modifier(value)
relation = table[attribute].eq(value)
diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
new file mode 100644
index 0000000000..97adb6b297
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
@@ -0,0 +1,35 @@
+require "cases/helper"
+require 'models/person'
+
+class MysqlCaseSensitivityTest < ActiveRecord::TestCase
+ class CollationTest < ActiveRecord::Base
+ validates_uniqueness_of :string_cs_column, :case_sensitive => false
+ validates_uniqueness_of :string_ci_column, :case_sensitive => false
+ end
+
+ def test_columns_include_collation_different_from_table
+ assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
+ assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
+ end
+
+ def test_case_sensitive
+ assert !CollationTest.columns_hash['string_ci_column'].case_sensitive?
+ assert CollationTest.columns_hash['string_cs_column'].case_sensitive?
+ end
+
+ def test_case_insensitive_comparison_for_ci_column
+ CollationTest.create!(:string_ci_column => 'A')
+ invalid = CollationTest.new(:string_ci_column => 'a')
+ queries = assert_sql { invalid.save }
+ ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
+ assert_no_match(/lower/i, ci_uniqueness_query)
+ end
+
+ def test_case_insensitive_comparison_for_cs_column
+ CollationTest.create!(:string_cs_column => 'A')
+ invalid = CollationTest.new(:string_cs_column => 'a')
+ queries = assert_sql { invalid.save }
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
+ assert_match(/lower/i, cs_uniqueness_query)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
new file mode 100644
index 0000000000..6bcc113482
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -0,0 +1,35 @@
+require "cases/helper"
+require 'models/person'
+
+class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
+ class CollationTest < ActiveRecord::Base
+ validates_uniqueness_of :string_cs_column, :case_sensitive => false
+ validates_uniqueness_of :string_ci_column, :case_sensitive => false
+ end
+
+ def test_columns_include_collation_different_from_table
+ assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
+ assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
+ end
+
+ def test_case_sensitive
+ assert !CollationTest.columns_hash['string_ci_column'].case_sensitive?
+ assert CollationTest.columns_hash['string_cs_column'].case_sensitive?
+ end
+
+ def test_case_insensitive_comparison_for_ci_column
+ CollationTest.create!(:string_ci_column => 'A')
+ invalid = CollationTest.new(:string_ci_column => 'a')
+ queries = assert_sql { invalid.save }
+ ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
+ assert_no_match(/lower/i, ci_uniqueness_query)
+ end
+
+ def test_case_insensitive_comparison_for_cs_column
+ CollationTest.create!(:string_cs_column => 'A')
+ invalid = CollationTest.new(:string_cs_column => 'a')
+ queries = assert_sql { invalid.save }
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)}
+ assert_match(/lower/i, cs_uniqueness_query)
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 76c73e9dfa..c8f8714f66 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -38,6 +38,10 @@ class SchemaTest < ActiveRecord::TestCase
set_table_name 'test_schema."Things"'
end
+ class Thing5 < ActiveRecord::Base
+ set_table_name 'things'
+ end
+
def setup
@connection = ActiveRecord::Base.connection
@connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
@@ -58,6 +62,14 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
end
+ def test_schema_change_with_prepared_stmt
+ @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "alter table developers add column zomg int", 'sql', []
+ @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ ensure
+ @connection.exec_query "alter table developers drop column if exists zomg", 'sql', []
+ end
+
def test_table_exists?
[Thing1, Thing2, Thing3, Thing4].each do |klass|
name = klass.table_name
@@ -236,6 +248,21 @@ class SchemaTest < ActiveRecord::TestCase
end
end
+ def test_prepared_statements_with_multiple_schemas
+
+ @connection.schema_search_path = SCHEMA_NAME
+ Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now)
+
+ @connection.schema_search_path = SCHEMA2_NAME
+ Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now)
+
+ @connection.schema_search_path = SCHEMA_NAME
+ assert_equal 1, Thing5.count
+
+ @connection.schema_search_path = SCHEMA2_NAME
+ assert_equal 1, Thing5.count
+ end
+
def test_schema_exists?
{
'public' => true,
diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
index a82c6f67d6..f1c4b85126 100644
--- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
@@ -2,6 +2,16 @@ require 'cases/helper'
module ActiveRecord::ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
+ class InactivePGconn
+ def query(*args)
+ raise PGError
+ end
+
+ def status
+ PGconn::CONNECTION_BAD
+ end
+ end
+
class StatementPoolTest < ActiveRecord::TestCase
def test_cache_is_per_pid
return skip('must support fork') unless Process.respond_to?(:fork)
@@ -18,6 +28,12 @@ module ActiveRecord::ConnectionAdapters
Process.waitpid pid
assert $?.success?, 'process should exit successfully'
end
+
+ def test_dealloc_does_not_raise_on_inactive_connection
+ cache = StatementPool.new InactivePGconn.new, 10
+ cache['foo'] = 'bar'
+ assert_nothing_raised { cache.clear }
+ end
end
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 2b598220ee..eb6f071dc1 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -5,6 +5,8 @@ require 'models/owner'
module ActiveRecord
module ConnectionAdapters
class SQLite3AdapterTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
class DualEncoding < ActiveRecord::Base
end
@@ -155,6 +157,8 @@ module ActiveRecord
binary = DualEncoding.new :name => 'いただきます!', :data => str
binary.save!
assert_equal str, binary.data
+
+ DualEncoding.connection.drop_table('dual_encodings')
end
def test_execute
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 866dcefbab..7e2dafcd01 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -48,11 +48,11 @@ class FixturesTest < ActiveRecord::TestCase
def test_broken_yaml_exception
badyaml = Tempfile.new ['foo', '.yml']
- badyaml.write 'a: !ruby.yaml.org,2002:str |\nfoo'
+ badyaml.write 'a: : '
badyaml.flush
dir = File.dirname badyaml.path
- name =File.basename badyaml.path, '.yml'
+ name = File.basename badyaml.path, '.yml'
assert_raises(ActiveRecord::Fixture::FormatError) do
ActiveRecord::Fixtures.create_fixtures(dir, name)
end
@@ -451,14 +451,36 @@ end
class CustomConnectionFixturesTest < ActiveRecord::TestCase
set_fixture_class :courses => Course
fixtures :courses
- # Set to false to blow away fixtures cache and ensure our fixtures are loaded
- # and thus takes into account our set_fixture_class
self.use_transactional_fixtures = false
def test_connection
assert_kind_of Course, courses(:ruby)
assert_equal Course.connection, courses(:ruby).connection
end
+
+ def test_leaky_destroy
+ assert_nothing_raised { courses(:ruby) }
+ courses(:ruby).destroy
+ end
+
+ def test_it_twice_in_whatever_order_to_check_for_fixture_leakage
+ test_leaky_destroy
+ end
+end
+
+class TransactionalFixturesOnCustomConnectionTest < ActiveRecord::TestCase
+ set_fixture_class :courses => Course
+ fixtures :courses
+ self.use_transactional_fixtures = true
+
+ def test_leaky_destroy
+ assert_nothing_raised { courses(:ruby) }
+ courses(:ruby).destroy
+ end
+
+ def test_it_twice_in_whatever_order_to_check_for_fixture_leakage
+ test_leaky_destroy
+ end
end
class InvalidTableNameFixturesTest < ActiveRecord::TestCase
@@ -496,7 +518,9 @@ class ManyToManyFixturesWithClassDefined < ActiveRecord::TestCase
end
class FixturesBrokenRollbackTest < ActiveRecord::TestCase
- def blank_setup; end
+ def blank_setup
+ @fixture_connections = [ActiveRecord::Base.connection]
+ end
alias_method :ar_setup_fixtures, :setup_fixtures
alias_method :setup_fixtures, :blank_setup
alias_method :setup, :blank_setup
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 61baa55027..e9bd7f07b6 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -125,6 +125,24 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
end
+ def test_lock_exception_record
+ p1 = Person.new(:first_name => 'mira')
+ assert_equal 0, p1.lock_version
+
+ p1.first_name = 'mira2'
+ p1.save!
+ p2 = Person.find(p1.id)
+ assert_equal 0, p1.lock_version
+ assert_equal 0, p2.lock_version
+
+ p1.first_name = 'mira3'
+ p1.save!
+
+ p2.first_name = 'sue'
+ error = assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
+ assert_equal(error.record.object_id, p2.object_id)
+ end
+
def test_lock_new_with_nil
p1 = Person.new(:first_name => 'anika')
p1.save!
@@ -141,7 +159,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 1, p1.lock_version
end
-
def test_lock_column_name_existing
t1 = LegacyThing.find(1)
t2 = LegacyThing.find(1)
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 93a1249e43..49944eced9 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -389,8 +389,8 @@ if ActiveRecord::Base.connection.supports_migrations?
created_at_column = created_columns.detect {|c| c.name == 'created_at' }
updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
- assert created_at_column.null
- assert updated_at_column.null
+ assert !created_at_column.null
+ assert !updated_at_column.null
ensure
Person.connection.drop_table table_name rescue nil
end
@@ -471,11 +471,13 @@ if ActiveRecord::Base.connection.supports_migrations?
# Do a manual insertion
if current_adapter?(:OracleAdapter)
- Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
+ Person.connection.execute "insert into people (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, 0, 0)"
elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
- Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')"
+ Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)"
+ elsif current_adapter?(:PostgreSQLAdapter)
+ Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())"
else
- Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)"
+ Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)"
end
# SELECT
@@ -516,6 +518,42 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 7, wealth_column.scale
end
+ # Test SQLite adapter specifically for decimal types with precision and scale
+ # attributes, since these need to be maintained in schema but aren't actually
+ # used in SQLite itself
+ if current_adapter?(:SQLite3Adapter)
+ def test_change_column_with_new_precision_and_scale
+ Person.delete_all
+ Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
+ Person.reset_column_information
+
+ Person.connection.change_column 'people', 'wealth', :decimal, :precision => 12, :scale => 8
+ Person.reset_column_information
+
+ wealth_column = Person.columns_hash['wealth']
+ assert_equal 12, wealth_column.precision
+ assert_equal 8, wealth_column.scale
+ end
+
+ def test_change_column_preserve_other_column_precision_and_scale
+ Person.delete_all
+ Person.connection.add_column 'people', 'last_name', :string
+ Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
+ Person.reset_column_information
+
+ wealth_column = Person.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+
+ Person.connection.change_column 'people', 'last_name', :string, :null => false
+ Person.reset_column_information
+
+ wealth_column = Person.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+ end
+ end
+
def test_native_types
Person.delete_all
Person.connection.add_column "people", "last_name", :string
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 67a9ed6cd8..2ae9cb4888 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -45,6 +45,14 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
end
+ def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
+ pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => '', :_destroy => '0'}]
+ pirate.save!
+
+ assert pirate.birds_with_reject_all_blank.empty?
+ end
+
def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}]
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 379cf5b44e..434b8a677a 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -3,6 +3,8 @@ require "models/project"
require "timeout"
class PooledConnectionsTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
def setup
@per_test_teardown = []
@connection = ActiveRecord::Base.remove_connection
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 489c7d8310..4bb5752096 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -145,6 +145,10 @@ class PrimaryKeysTest < ActiveRecord::TestCase
k.set_primary_key "bar"
assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key
end
+end
+
+class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
def test_set_primary_key_with_no_connection
return skip("disconnect wipes in-memory db") if in_memory_db?
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 7feac2b920..b2429d631f 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -237,7 +237,7 @@ class QueryCacheBodyProxyTest < ActiveRecord::TestCase
test "is polite to it's body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
- proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body)
+ proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body, ActiveRecord::Base.connection_id)
assert proxy.respond_to?(:to_path)
assert_equal proxy.to_path, "/path"
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 0a48f418b1..69e9fc8d61 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -18,6 +18,7 @@ require 'models/subscriber'
require 'models/subscription'
require 'models/tag'
require 'models/sponsor'
+require 'models/edge'
class ReflectionTest < ActiveRecord::TestCase
include ActiveRecord::Reflection
@@ -244,6 +245,7 @@ class ReflectionTest < ActiveRecord::TestCase
# Normal association
assert_equal "id", Author.reflect_on_association(:posts).association_primary_key.to_s
assert_equal "name", Author.reflect_on_association(:essay).association_primary_key.to_s
+ assert_equal "name", Essay.reflect_on_association(:writer).association_primary_key.to_s
# Through association (uses the :primary_key option from the source reflection)
assert_equal "nick", Author.reflect_on_association(:subscribers).association_primary_key.to_s
@@ -251,11 +253,25 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested
end
+ def test_association_primary_key_raises_when_missing_primary_key
+ reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :edge, {}, Author)
+ assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key }
+
+ through = ActiveRecord::Reflection::ThroughReflection.new(:fuu, :edge, {}, Author)
+ through.stubs(:source_reflection).returns(stub_everything(:options => {}, :class_name => 'Edge'))
+ assert_raises(ActiveRecord::UnknownPrimaryKey) { through.association_primary_key }
+ end
+
def test_active_record_primary_key
assert_equal "nick", Subscriber.reflect_on_association(:subscriptions).active_record_primary_key.to_s
assert_equal "name", Author.reflect_on_association(:essay).active_record_primary_key.to_s
end
+ def test_active_record_primary_key_raises_when_missing_primary_key
+ reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :author, {}, Edge)
+ assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key }
+ end
+
def test_foreign_type
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:sponsorable).foreign_type.to_s
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:thing).foreign_type.to_s
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
new file mode 100644
index 0000000000..fb77220875
--- /dev/null
+++ b/activerecord/test/cases/store_test.rb
@@ -0,0 +1,29 @@
+require 'cases/helper'
+require 'models/admin'
+require 'models/admin/user'
+
+class StoreTest < ActiveRecord::TestCase
+ setup do
+ @john = Admin::User.create(:name => 'John Doe', :color => 'black')
+ end
+
+ test "reading store attributes through accessors" do
+ assert_equal 'black', @john.color
+ assert_nil @john.homepage
+ end
+
+ test "writing store attributes through accessors" do
+ @john.color = 'red'
+ @john.homepage = '37signals.com'
+
+ assert_equal 'red', @john.color
+ assert_equal '37signals.com', @john.homepage
+ end
+
+ test "accessing attributes not exposed by accessors" do
+ @john.settings[:icecream] = 'graeters'
+ @john.save
+
+ assert 'graeters', @john.reload.settings[:icecream]
+ end
+end
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index f85fb4e5da..e82ca3f93d 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -4,7 +4,7 @@ class TestRecord < ActiveRecord::Base
end
class TestUnconnectedAdapter < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_fixtures = false
def setup
@underlying = ActiveRecord::Base.connection
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index 8c1a45430e..f450efd839 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -37,11 +37,13 @@ connections:
db2:
arunit:
+ adapter: ibm_db
host: localhost
username: arunit
password: arunit
database: arunit
arunit2:
+ adapter: ibm_db
host: localhost
username: arunit
password: arunit
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index 74bb21551e..c12c88e195 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -1,3 +1,4 @@
class Admin::User < ActiveRecord::Base
belongs_to :account
-end \ No newline at end of file
+ store :settings, :accessors => [ :color, :homepage ]
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index c78d99f4af..ab2c7ccc10 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -21,4 +21,15 @@ BEGIN
END
SQL
-end
+ ActiveRecord::Base.connection.execute <<-SQL
+DROP TABLE IF EXISTS collation_tests;
+SQL
+
+ ActiveRecord::Base.connection.execute <<-SQL
+CREATE TABLE collation_tests (
+ string_cs_column VARCHAR(1) COLLATE utf8_bin,
+ string_ci_column VARCHAR(1) COLLATE utf8_general_ci
+) CHARACTER SET utf8 COLLATE utf8_general_ci
+SQL
+
+end \ No newline at end of file
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index 30e1c5a167..a0adfe3752 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -32,4 +32,15 @@ BEGIN
END
SQL
+ ActiveRecord::Base.connection.execute <<-SQL
+DROP TABLE IF EXISTS collation_tests;
+SQL
+
+ ActiveRecord::Base.connection.execute <<-SQL
+CREATE TABLE collation_tests (
+ string_cs_column VARCHAR(1) COLLATE utf8_bin,
+ string_ci_column VARCHAR(1) COLLATE utf8_general_ci
+) CHARACTER SET utf8 COLLATE utf8_general_ci
+SQL
+
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 9d5ad16a3c..bb08f5c181 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -37,6 +37,7 @@ ActiveRecord::Schema.define do
create_table :admin_users, :force => true do |t|
t.string :name
+ t.text :settings
t.references :account
end