aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/RUNNING_UNIT_TESTS5
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb67
-rw-r--r--activerecord/lib/active_record/base.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb125
-rw-r--r--activerecord/lib/active_record/migration.rb6
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb32
-rw-r--r--activerecord/lib/active_record/query_cache.rb8
-rw-r--r--activerecord/lib/active_record/railties/databases.rake15
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb16
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb15
-rw-r--r--activerecord/lib/active_record/test_case.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb69
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb98
-rw-r--r--activerecord/test/cases/adapters/postgresql/view_test.rb49
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb63
-rw-r--r--activerecord/test/cases/autosave_association_test.rb2
-rw-r--r--activerecord/test/cases/connection_management_test.rb7
-rw-r--r--activerecord/test/cases/helper.rb13
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb305
-rw-r--r--activerecord/test/cases/migration_test.rb12
-rw-r--r--activerecord/test/cases/query_cache_test.rb11
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb7
-rw-r--r--activerecord/test/cases/relations_test.rb19
-rw-r--r--activerecord/test/models/company.rb12
-rw-r--r--activerecord/test/models/developer.rb6
-rw-r--r--activerecord/test/models/person.rb8
-rw-r--r--activerecord/test/support/config.rb12
32 files changed, 874 insertions, 179 deletions
diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS
index 8fe9a357b4..6a2e23b01f 100644
--- a/activerecord/RUNNING_UNIT_TESTS
+++ b/activerecord/RUNNING_UNIT_TESTS
@@ -32,3 +32,8 @@ By default the tests run with the Identity Map turned off. But all tests should
not the identity map is on or off. You can turn it on using the IM env variable:
$ IM=true ruby -Itest test/case/base_test.rb
+
+== Config file
+
+By default, the config file is expected to be at the path test/config.yml. You can specify a
+custom location with the ARCONFIG environment variable.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 7e1a41e84d..337a0d48f1 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -114,19 +114,13 @@ module ActiveRecord
# Add +records+ to this association. Returns +self+ so method calls may be chained.
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
def concat(*records)
- result = true
load_target if owner.new_record?
- transaction do
- records.flatten.each do |record|
- raise_on_type_mismatch(record)
- add_to_target(record) do |r|
- result &&= insert_record(record) unless owner.new_record?
- end
- end
+ if owner.new_record?
+ concat_records(records)
+ else
+ transaction { concat_records(records) }
end
-
- result && records
end
# Starts a transaction in the association class's database connection.
@@ -295,14 +289,10 @@ module ActiveRecord
other_array.each { |val| raise_on_type_mismatch(val) }
original_target = load_target.dup
- transaction do
- delete(target - other_array)
-
- unless concat(other_array - target)
- @target = original_target
- raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
- "new records could not be saved."
- end
+ if owner.new_record?
+ replace_records(other_array, original_target)
+ else
+ transaction { replace_records(other_array, original_target) }
end
end
@@ -444,14 +434,20 @@ module ActiveRecord
records.each { |record| raise_on_type_mismatch(record) }
existing_records = records.reject { |r| r.new_record? }
- transaction do
- records.each { |record| callback(:before_remove, record) }
+ if existing_records.empty?
+ remove_records(existing_records, records, method)
+ else
+ transaction { remove_records(existing_records, records, method) }
+ end
+ end
- delete_records(existing_records, method) if existing_records.any?
- records.each { |record| target.delete(record) }
+ def remove_records(existing_records, records, method)
+ records.each { |record| callback(:before_remove, record) }
- records.each { |record| callback(:after_remove, record) }
- end
+ delete_records(existing_records, method) if existing_records.any?
+ records.each { |record| target.delete(record) }
+
+ records.each { |record| callback(:after_remove, record) }
end
# Delete the given records from the association, using one of the methods :destroy,
@@ -460,6 +456,29 @@ module ActiveRecord
raise NotImplementedError
end
+ def replace_records(new_target, original_target)
+ delete(target - new_target)
+
+ unless concat(new_target - target)
+ @target = original_target
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
+ "new records could not be saved."
+ end
+ end
+
+ def concat_records(records)
+ result = true
+
+ records.flatten.each do |record|
+ raise_on_type_mismatch(record)
+ add_to_target(record) do |r|
+ result &&= insert_record(record) unless owner.new_record?
+ end
+ end
+
+ result && records
+ end
+
def callback(method, record)
callbacks_for(method).each do |callback|
case callback
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 2f283ff6bc..6a5d282973 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -428,10 +428,6 @@ module ActiveRecord #:nodoc:
class_attribute :default_scopes, :instance_writer => false
self.default_scopes = []
- # Boolean flag to prevent infinite recursion when evaluating default scopes
- class_attribute :apply_default_scope, :instance_writer => false
- self.apply_default_scope = true
-
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
class_attribute :serialized_attributes
@@ -1265,11 +1261,11 @@ MSG
self.default_scopes = default_scopes + [scope]
end
- # The apply_default_scope flag is used to prevent an infinite recursion situation where
+ # The @ignore_default_scope flag is used to prevent an infinite recursion situation where
# a default scope references a scope which has a default scope which references a scope...
def build_default_scope #:nodoc:
- return unless apply_default_scope
- self.apply_default_scope = false
+ return if defined?(@ignore_default_scope) && @ignore_default_scope
+ @ignore_default_scope = true
if method(:default_scope).owner != Base.singleton_class
default_scope
@@ -1285,7 +1281,7 @@ MSG
end
end
ensure
- self.apply_default_scope = true
+ @ignore_default_scope = false
end
# Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1717,10 +1713,13 @@ MSG
attributes.each do |k, v|
if k.include?("(")
multi_parameter_attributes << [ k, v ]
- elsif respond_to?("#{k}=")
- send("#{k}=", v)
else
- raise(UnknownAttributeError, "unknown attribute: #{k}")
+ method_name = "#{k}="
+ if respond_to?(method_name)
+ method(method_name).arity == -2 ? send(method_name, v, options) : send(method_name, v)
+ else
+ raise(UnknownAttributeError, "unknown attribute: #{k}")
+ end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 8ffd40f7e5..090f6bc6fe 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -119,6 +119,7 @@ module ActiveRecord
with_connection do |conn|
conn.tables.each { |table| @tables[table] = true }
+ @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name)
end
@tables.key? name
@@ -425,6 +426,14 @@ module ActiveRecord
@testing = testing
end
+ def method_missing(method_sym, *arguments, &block)
+ @body.send(method_sym, *arguments, &block)
+ end
+
+ def respond_to?(method_sym, include_private = false)
+ super || @body.respond_to?(method_sym)
+ end
+
def each(&block)
body.each(&block)
end
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 70a8f6bb58..82f564e41d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -328,7 +328,7 @@ module ActiveRecord
end
# Checks to see if a column exists. See SchemaStatements#column_exists?
- def column_exists?(column_name, type = nil, options = nil)
+ def column_exists?(column_name, type = nil, options = {})
@base.column_exists?(@table_name, column_name, type, options)
end
@@ -386,13 +386,13 @@ module ActiveRecord
# Removes the given index from the table.
#
# ===== Examples
- # ====== Remove the suppliers_name_index in the suppliers table
- # t.remove_index :name
- # ====== Remove the index named accounts_branch_id_index in the accounts table
+ # ====== Remove the index_table_name_on_column in the table_name table
+ # t.remove_index :column
+ # ====== Remove the index named index_table_name_on_branch_id in the table_name table
# t.remove_index :column => :branch_id
- # ====== Remove the index named accounts_branch_id_party_id_index in the accounts table
+ # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
# t.remove_index :column => [:branch_id, :party_id]
- # ====== Remove the index named by_branch_party in the accounts table
+ # ====== Remove the index named by_branch_party in the table_name table
# t.remove_index :name => :by_branch_party
def remove_index(options = {})
@base.remove_index(@table_name, options)
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 74c07c624d..8e3ba1297e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -346,11 +346,11 @@ module ActiveRecord
# Remove the given index from the table.
#
- # Remove the suppliers_name_index in the suppliers table.
- # remove_index :suppliers, :name
- # Remove the index named accounts_branch_id_index in the accounts table.
+ # Remove the index_accounts_on_column in the accounts table.
+ # remove_index :accounts, :column
+ # Remove the index named index_accounts_on_branch_id in the accounts table.
# remove_index :accounts, :column => :branch_id
- # Remove the index named accounts_branch_id_party_id_index in the accounts table.
+ # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
# remove_index :accounts, :column => [:branch_id, :party_id]
# Remove the index named by_branch_party in the accounts table.
# remove_index :accounts, :name => :by_branch_party
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 3eddb69e73..a7856539b7 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -1,3 +1,5 @@
+require 'set'
+
module ActiveRecord
# :stopdoc:
module ConnectionAdapters
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 24d8c8cad2..d6c167ad36 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-gem 'mysql2', '~> 0.3.0'
+gem 'mysql2', '~> 0.3.6'
require 'mysql2'
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 3e390ba994..29dd0a9ea3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -466,10 +466,11 @@ module ActiveRecord
# Executes an INSERT query and returns the new record's ID
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- # Extract the table from the insert sql. Yuck.
- _, table = extract_schema_and_table(sql.split(" ", 4)[2])
-
- pk ||= primary_key(table)
+ unless pk
+ # Extract the table from the insert sql. Yuck.
+ table_ref = extract_table_ref_from_insert_sql(sql)
+ pk = primary_key(table_ref) if table_ref
+ end
if pk
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
@@ -565,9 +566,9 @@ module ActiveRecord
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
unless pk
- _, table = extract_schema_and_table(sql.split(" ", 4)[2])
-
- pk = primary_key(table)
+ # Extract the table from the insert sql. Yuck.
+ table_ref = extract_table_ref_from_insert_sql(sql)
+ pk = primary_key(table_ref) if table_ref
end
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
@@ -665,34 +666,33 @@ module ActiveRecord
SQL
end
+ # Returns true if table exists.
+ # If the schema is not specified as part of +name+ then it will only find tables within
+ # the current schema search path (regardless of permissions to access tables in other schemas)
def table_exists?(name)
schema, table = extract_schema_and_table(name.to_s)
+ return false unless table
- binds = [[nil, table.gsub(/(^"|"$)/,'')]]
+ binds = [[nil, table]]
binds << [nil, schema] if schema
exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
- SELECT COUNT(*)
- FROM pg_tables
- WHERE tablename = $1
- #{schema ? "AND schemaname = $2" : ''}
+ SELECT COUNT(*)
+ FROM pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind in ('v','r')
+ AND c.relname = $1
+ AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
SQL
end
- # Extracts the table and schema name from +name+
- def extract_schema_and_table(name)
- schema, table = name.split('.', 2)
-
- unless table # A table was provided without a schema
- table = schema
- schema = nil
- end
-
- if name =~ /^"/ # Handle quoted table names
- table = name
- schema = nil
- end
- [schema, table]
+ # Returns true if schema exists.
+ def schema_exists?(name)
+ exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0
+ SELECT COUNT(*)
+ FROM pg_namespace
+ WHERE nspname = $1
+ SQL
end
# Returns an array of indexes for the given table.
@@ -742,6 +742,11 @@ module ActiveRecord
query('select current_database()')[0][0]
end
+ # Returns the current schema name.
+ def current_schema
+ query('SELECT current_schema', 'SCHEMA')[0][0]
+ end
+
# Returns the current database encoding format.
def encoding
query(<<-end_sql)[0][0]
@@ -806,7 +811,7 @@ module ActiveRecord
if pk && sequence
quoted_sequence = quote_table_name(sequence)
-
+
select_value <<-end_sql, 'Reset sequence'
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
end_sql
@@ -835,7 +840,7 @@ module ActiveRecord
else
sequence = result.second+'.'+result.last
end
-
+
[result.first, sequence]
rescue
nil
@@ -935,7 +940,7 @@ module ActiveRecord
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
- order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s }
+ order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }
order_columns.delete_if { |c| c.blank? }
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
@@ -960,27 +965,27 @@ module ActiveRecord
end
private
- def exec_no_cache(sql, binds)
- @connection.async_exec(sql)
- end
-
- def exec_cache(sql, binds)
- unless @statements.key? sql
- nextkey = "a#{@statements.length + 1}"
- @connection.prepare nextkey, sql
- @statements[sql] = nextkey
+ def exec_no_cache(sql, binds)
+ @connection.async_exec(sql)
end
- key = @statements[sql]
+ def exec_cache(sql, binds)
+ unless @statements.key? sql
+ nextkey = "a#{@statements.length + 1}"
+ @connection.prepare nextkey, sql
+ @statements[sql] = nextkey
+ 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
- end
+ key = @statements[sql]
+
+ # 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
+ end
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
@@ -1080,9 +1085,29 @@ module ActiveRecord
end
end
- def table_definition
- TableDefinition.new(self)
- end
+ # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
+ # +schema_name+ is nil if not specified in +name+.
+ # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
+ # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
+ #
+ # * <tt>table_name</tt>
+ # * <tt>"table.name"</tt>
+ # * <tt>schema_name.table_name</tt>
+ # * <tt>schema_name."table.name"</tt>
+ # * <tt>"schema.name"."table name"</tt>
+ def extract_schema_and_table(name)
+ table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
+ [schema, table]
+ end
+
+ def extract_table_ref_from_insert_sql(sql)
+ sql[/into\s+([^\(]*).*values\s*\(/i]
+ $1.strip if $1
+ end
+
+ def table_definition
+ TableDefinition.new(self)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index de26b21f1a..3d1bc5c1e0 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -116,8 +116,10 @@ module ActiveRecord
# with the name of the column. Other options include
# <tt>:name</tt> and <tt>:unique</tt> (e.g.
# <tt>{ :name => "users_name_index", :unique => true }</tt>).
- # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified
- # by +index_name+.
+ # * <tt>remove_index(table_name, :column => column_name)</tt>: Removes the index
+ # specified by +column_name+.
+ # * <tt>remove_index(table_name, :name => index_name)</tt>: Removes the index
+ # specified by +index_name+.
#
# == Irreversible transformations
#
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 08b27b6a8e..f51fd21077 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -276,15 +276,15 @@ module ActiveRecord
type = (reflection.collection? ? :collection : :one_to_one)
- # def pirate_attributes=(attributes)
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
+ # def pirate_attributes=(attributes, assignment_opts = {})
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, assignment_opts)
# end
class_eval <<-eoruby, __FILE__, __LINE__ + 1
if method_defined?(:#{association_name}_attributes=)
remove_method(:#{association_name}_attributes=)
end
- def #{association_name}_attributes=(attributes)
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
+ def #{association_name}_attributes=(attributes, assignment_opts = {})
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, assignment_opts)
end
eoruby
else
@@ -319,21 +319,21 @@ module ActiveRecord
# If the given attributes include a matching <tt>:id</tt> attribute, or
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
- def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
- assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
+ assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
- elsif attributes['id'].present?
+ elsif attributes['id'].present? && !assignment_opts[:without_protection]
raise_nested_attributes_record_not_found(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
- send(method, attributes.except(*UNASSIGNABLE_KEYS))
+ send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
end
@@ -367,7 +367,7 @@ module ActiveRecord
# { :name => 'John' },
# { :id => '2', :_destroy => true }
# ])
- def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
options = self.nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
@@ -401,7 +401,7 @@ module ActiveRecord
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
- association.build(attributes.except(*UNASSIGNABLE_KEYS))
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
end
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
unless association.loaded? || call_reject_if(association_name, attributes)
@@ -418,8 +418,10 @@ module ActiveRecord
end
if !call_reject_if(association_name, attributes)
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
end
+ elsif assignment_opts[:without_protection]
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
@@ -428,8 +430,8 @@ module ActiveRecord
# Updates a record with the +attributes+ or marks it for destruction if
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
- def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
- record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts)
+ record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
end
@@ -458,5 +460,9 @@ module ActiveRecord
def raise_nested_attributes_record_not_found(association_name, record_id)
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
+
+ def unassignable_keys(assignment_opts)
+ assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS
+ end
end
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 4e61671473..e485901440 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -33,6 +33,14 @@ module ActiveRecord
@target = target
end
+ def method_missing(method_sym, *arguments, &block)
+ @target.send(method_sym, *arguments, &block)
+ end
+
+ def respond_to?(method_sym, include_private = false)
+ super || @target.respond_to?(method_sym)
+ end
+
def each(&block)
@target.each(&block)
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 6ef24a4eaf..399f3c5d03 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -369,7 +369,7 @@ db_namespace = namespace :db do
ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password']
search_path = abcs[Rails.env]['schema_search_path']
unless search_path.blank?
- search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ")
+ search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ")
end
`pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}`
raise 'Error dumping database' if $?.exitstatus == 1
@@ -377,8 +377,7 @@ db_namespace = namespace :db do
dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile']
`sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql`
when 'sqlserver'
- `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r`
- `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r`
+ `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\#{Rails.env}_structure.sql -A -U`
when "firebird"
set_firebird_env(abcs[Rails.env])
db_string = firebird_db_string(abcs[Rails.env])
@@ -423,7 +422,7 @@ db_namespace = namespace :db do
dbfile = abcs['test']['database'] || abcs['test']['dbfile']
`sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
when 'sqlserver'
- `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql`
+ `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql`
when 'oci', 'oracle'
ActiveRecord::Base.establish_connection(:test)
IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl|
@@ -453,9 +452,11 @@ db_namespace = namespace :db do
dbfile = abcs['test']['database'] || abcs['test']['dbfile']
File.delete(dbfile) if File.exist?(dbfile)
when 'sqlserver'
- dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-')
- `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}`
- `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql`
+ test = abcs.deep_dup['test']
+ test_database = test['database']
+ test['database'] = 'master'
+ ActiveRecord::Base.establish_connection(test)
+ ActiveRecord::Base.connection.recreate_database!(test_database)
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index aabe5c269b..9c5f603c01 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -146,10 +146,16 @@ module ActiveRecord
if options.except(:distinct).present?
apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
else
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
+ relation = with_default_scope
+
+ if relation.equal?(self)
+ if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
+ else
+ perform_calculation(operation, column_name, options)
+ end
else
- perform_calculation(operation, column_name, options)
+ relation.calculate(operation, column_name, options)
end
end
rescue ThrowResult
@@ -196,7 +202,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
# Postgresql doesn't like ORDER BY when there are no GROUP BY
- relation = with_default_scope.reorder(nil)
+ relation = reorder(nil)
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
@@ -245,7 +251,7 @@ module ActiveRecord
"#{field} AS #{aliaz}"
}
- relation = with_default_scope.except(:group).group(group.join(','))
+ relation = except(:group).group(group.join(','))
relation.select_values = select_values
calculated_data = @klass.connection.select_all(relation.to_sql)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 739363415c..7fbbefeea2 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -305,9 +305,18 @@ module ActiveRecord
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
- order_query.join(', ').split(',').collect do |s|
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
- end
+ order_query.map do |o|
+ case o
+ when Arel::Nodes::Ordering
+ o.reverse
+ when String, Symbol
+ o.to_s.split(',').collect do |s|
+ s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
+ end
+ else
+ o
+ end
+ end.flatten
end
def array_of_strings?(o)
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index c61428e104..ffe9b08dce 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -31,27 +31,30 @@ module ActiveRecord
end
def assert_sql(*patterns_to_match)
- $queries_executed = []
+ ActiveRecord::SQLCounter.log = []
yield
- $queries_executed
+ ActiveRecord::SQLCounter.log
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
- failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
+ failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql }
end
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
end
def assert_queries(num = 1)
- $queries_executed = []
+ ActiveRecord::SQLCounter.log = []
yield
ensure
- %w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
- assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
+ assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
end
def assert_no_queries(&block)
+ prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql
+ ActiveRecord::SQLCounter.ignored_sql = []
assert_queries(0, &block)
+ ensure
+ ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql
end
def with_kcode(kcode)
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 7c49236854..d57794daf8 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -10,6 +10,45 @@ module ActiveRecord
@connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
end
+ def test_primary_key
+ assert_equal 'id', @connection.primary_key('ex')
+ end
+
+ def test_non_standard_primary_key
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query('create table ex(data character varying(255) primary key)')
+ assert_equal 'data', @connection.primary_key('ex')
+ end
+
+ def test_primary_key_returns_nil_for_no_pk
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query('create table ex(id integer)')
+ assert_nil @connection.primary_key('ex')
+ end
+
+ def test_primary_key_raises_error_if_table_not_found
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.primary_key('unobtainium')
+ end
+ end
+
+ def test_insert_sql_with_proprietary_returning_clause
+ id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
+ assert_equal "5150", id
+ end
+
+ def test_insert_sql_with_quoted_schema_and_table_name
+ id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
+
+ def test_insert_sql_with_no_space_after_table_name
+ id = @connection.insert_sql("insert into ex(number) values(5150)")
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
+
def test_serial_sequence
assert_equal 'public.accounts_id_seq',
@connection.serial_sequence('accounts', 'id')
@@ -35,6 +74,36 @@ module ActiveRecord
@connection.default_sequence_name('zomg')
end
+ def test_pk_and_sequence_for
+ pk, seq = @connection.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @connection.default_sequence_name('ex', 'id'), seq
+ end
+
+ def test_pk_and_sequence_for_with_non_standard_primary_key
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query('create table ex(code serial primary key)')
+ pk, seq = @connection.pk_and_sequence_for('ex')
+ assert_equal 'code', pk
+ assert_equal @connection.default_sequence_name('ex', 'code'), seq
+ end
+
+ def test_pk_and_sequence_for_returns_nil_if_no_seq
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query('create table ex(id integer primary key)')
+ assert_nil @connection.pk_and_sequence_for('ex')
+ end
+
+ def test_pk_and_sequence_for_returns_nil_if_no_pk
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query('create table ex(id integer)')
+ assert_nil @connection.pk_and_sequence_for('ex')
+ end
+
+ def test_pk_and_sequence_for_returns_nil_if_table_not_found
+ assert_nil @connection.pk_and_sequence_for('unobtainium')
+ end
+
def test_exec_insert_number
insert(@connection, 'number' => 10)
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index a5c3e69af9..27e7b1a1c3 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -20,6 +20,7 @@ class SchemaTest < ActiveRecord::TestCase
'email character varying(50)',
'moment timestamp without time zone default now()'
]
+ PK_TABLE_NAME = 'table_with_pk'
class Thing1 < ActiveRecord::Base
set_table_name "test_schema.things"
@@ -49,6 +50,7 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});"
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
+ @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
end
def teardown
@@ -63,12 +65,30 @@ class SchemaTest < ActiveRecord::TestCase
end
end
+ def test_table_exists_when_on_schema_search_path
+ with_schema_search_path(SCHEMA_NAME) do
+ assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found")
+ end
+ end
+
+ def test_table_exists_when_not_on_schema_search_path
+ with_schema_search_path('PUBLIC') do
+ assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found")
+ end
+ end
+
def test_table_exists_wrong_schema
assert(!@connection.table_exists?("foo.things"), "table should not exist")
end
- def test_table_exists_quoted_table
- assert(@connection.table_exists?('"things.table"'), "table should exist")
+ def test_table_exists_quoted_names
+ [ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given|
+ assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
+ end
+ with_schema_search_path(SCHEMA_NAME) do
+ given = %("#{TABLE_NAME}")
+ assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
+ end
end
def test_with_schema_prefixed_table_name
@@ -91,7 +111,6 @@ class SchemaTest < ActiveRecord::TestCase
end
end
-
def test_proper_encoding_of_table_name
assert_equal '"table_name"', @connection.quote_table_name('table_name')
assert_equal '"table.name"', @connection.quote_table_name('"table.name"')
@@ -164,6 +183,79 @@ class SchemaTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.schema_search_path = "public"
end
+ def test_primary_key_with_schema_specified
+ [
+ %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
+ %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"),
+ %(#{SCHEMA_NAME}.#{PK_TABLE_NAME})
+ ].each do |given|
+ assert_equal 'id', @connection.primary_key(given), "primary key should be found when table referenced as #{given}"
+ end
+ end
+
+ def test_primary_key_assuming_schema_search_path
+ with_schema_search_path(SCHEMA_NAME) do
+ assert_equal 'id', @connection.primary_key(PK_TABLE_NAME), "primary key should be found"
+ end
+ end
+
+ def test_primary_key_raises_error_if_table_not_found_on_schema_search_path
+ with_schema_search_path(SCHEMA2_NAME) do
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.primary_key(PK_TABLE_NAME)
+ end
+ end
+ end
+
+ def test_pk_and_sequence_for_with_schema_specified
+ [
+ %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
+ %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"),
+ %(#{SCHEMA_NAME}.#{PK_TABLE_NAME})
+ ].each do |given|
+ pk, seq = @connection.pk_and_sequence_for(given)
+ assert_equal 'id', pk, "primary key should be found when table referenced as #{given}"
+ assert_equal "#{SCHEMA_NAME}.#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}"
+ end
+ end
+
+ def test_extract_schema_and_table
+ {
+ %(table_name) => [nil,'table_name'],
+ %("table.name") => [nil,'table.name'],
+ %(schema.table_name) => %w{schema table_name},
+ %("schema".table_name) => %w{schema table_name},
+ %(schema."table_name") => %w{schema table_name},
+ %("schema"."table_name") => %w{schema table_name},
+ %("even spaces".table) => ['even spaces','table'],
+ %(schema."table.name") => ['schema', 'table.name']
+ }.each do |given,expect|
+ assert_equal expect, @connection.send(:extract_schema_and_table, given)
+ end
+ end
+
+ def test_current_schema
+ {
+ %('$user',public) => 'public',
+ SCHEMA_NAME => SCHEMA_NAME,
+ %(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME,
+ %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => 'public'
+ }.each do |given,expect|
+ with_schema_search_path(given) { assert_equal expect, @connection.current_schema }
+ end
+ end
+
+ def test_schema_exists?
+ {
+ 'public' => true,
+ SCHEMA_NAME => true,
+ SCHEMA2_NAME => true,
+ 'darkside' => false
+ }.each do |given,expect|
+ assert_equal expect, @connection.schema_exists?(given)
+ end
+ end
+
private
def columns(table_name)
@connection.send(:column_definitions, table_name).map do |name, type, default|
diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb
new file mode 100644
index 0000000000..303ba9245a
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/view_test.rb
@@ -0,0 +1,49 @@
+require "cases/helper"
+
+class ViewTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ SCHEMA_NAME = 'test_schema'
+ TABLE_NAME = 'things'
+ VIEW_NAME = 'view_things'
+ COLUMNS = [
+ 'id integer',
+ 'name character varying(50)',
+ 'email character varying(50)',
+ 'moment timestamp without time zone'
+ ]
+
+ class ThingView < ActiveRecord::Base
+ set_table_name 'test_schema.view_things'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
+ @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})"
+ @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}"
+ end
+
+ def teardown
+ @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
+ end
+
+ def test_table_exists
+ name = ThingView.table_name
+ assert @connection.table_exists?(name), "'#{name}' table should exist"
+ end
+
+ def test_column_definitions
+ assert_nothing_raised do
+ assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}")
+ end
+ end
+
+ private
+ def columns(table_name)
+ @connection.send(:column_definitions, table_name).map do |name, type, default|
+ "#{name} #{type}" + (default ? " default #{default}" : '')
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 49999630b6..9af1f7249f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -537,6 +537,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, companies(:first_firm).clients_of_firm(true).size
end
+ def test_transactions_when_adding_to_persisted
+ good = Client.new(:name => "Good")
+ bad = Client.new(:name => "Bad", :raise_on_save => true)
+
+ begin
+ companies(:first_firm).clients_of_firm.concat(good, bad)
+ rescue Client::RaisedOnSave
+ end
+
+ assert !companies(:first_firm).clients_of_firm(true).include?(good)
+ end
+
+ def test_transactions_when_adding_to_new_record
+ assert_no_queries do
+ firm = Firm.new
+ firm.clients_of_firm.concat(Client.new("name" => "Natural Company"))
+ end
+ end
+
def test_new_aliased_to_build
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") }
@@ -778,6 +797,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
end
+ def test_transaction_when_deleting_persisted
+ good = Client.new(:name => "Good")
+ bad = Client.new(:name => "Bad", :raise_on_destroy => true)
+
+ companies(:first_firm).clients_of_firm = [good, bad]
+
+ begin
+ companies(:first_firm).clients_of_firm.destroy(good, bad)
+ rescue Client::RaisedOnDestroy
+ end
+
+ assert_equal [good, bad], companies(:first_firm).clients_of_firm(true)
+ end
+
+ def test_transaction_when_deleting_new_record
+ assert_no_queries do
+ firm = Firm.new
+ client = Client.new("name" => "New Client")
+ firm.clients_of_firm << client
+ firm.clients_of_firm.destroy(client)
+ end
+ end
+
def test_clearing_an_association_collection
firm = companies(:first_firm)
client_id = firm.clients_of_firm.first.id
@@ -1111,6 +1153,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal orig_accounts, firm.accounts
end
+ def test_transactions_when_replacing_on_persisted
+ good = Client.new(:name => "Good")
+ bad = Client.new(:name => "Bad", :raise_on_save => true)
+
+ companies(:first_firm).clients_of_firm = [good]
+
+ begin
+ companies(:first_firm).clients_of_firm = [bad]
+ rescue Client::RaisedOnSave
+ end
+
+ assert_equal [good], companies(:first_firm).clients_of_firm(true)
+ end
+
+ def test_transactions_when_replacing_on_new_record
+ assert_no_queries do
+ firm = Firm.new
+ firm.clients_of_firm = [Client.new("name" => "New Client")]
+ end
+ end
+
def test_get_ids
assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 8f55b7ebe6..4ad2cdfc7e 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -837,7 +837,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.parrots.each { |parrot| parrot.mark_for_destruction }
assert @pirate.save
- assert_no_queries do
+ assert_queries(0) do
assert @pirate.save
end
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index 85871aebdf..a1d1177289 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -77,6 +77,13 @@ module ActiveRecord
@management.call(@env)
assert ActiveRecord::Base.connection_handler.active_connections?
end
+
+ test "proxy is polite to it's body and responds to it" do
+ body = Class.new(String) { def to_path; "/path"; end }.new
+ proxy = ConnectionManagement::Proxy.new(body)
+ assert proxy.respond_to?(:to_path)
+ assert_equal proxy.to_path, "/path"
+ end
end
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index d0dc9cb03d..6735bc521b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -58,15 +58,15 @@ end
module ActiveRecord
class SQLCounter
- IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/]
+ cattr_accessor :ignored_sql
+ self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
# FIXME: this needs to be refactored so specific database can add their own
# ignored SQL. This ignored SQL is for Oracle.
- IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
+ ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
- def initialize
- $queries_executed = []
- end
+ cattr_accessor :log
+ self.log = []
def call(name, start, finish, message_id, values)
sql = values[:sql]
@@ -74,10 +74,11 @@ module ActiveRecord
# FIXME: this seems bad. we should probably have a better way to indicate
# the query was cached
unless 'CACHE' == values[:name]
- $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
+ self.class.log << sql unless self.class.ignored_sql.any? { |r| sql =~ r }
end
end
end
+
ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 33737e12a8..ef35f3341e 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -384,81 +384,81 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
# build
- def test_has_one_build_with_attr_protected_attributes
+ def test_belongs_to_build_with_attr_protected_attributes
best_friend = @person.build_best_friend_of(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_attr_accessible_attributes
+ def test_belongs_to_build_with_attr_accessible_attributes
best_friend = @person.build_best_friend_of(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_protected_attributes
+ def test_belongs_to_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_accessible_attributes
+ def test_belongs_to_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_without_protection
+ def test_belongs_to_build_without_protection
best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create
- def test_has_one_create_with_attr_protected_attributes
+ def test_belongs_to_create_with_attr_protected_attributes
best_friend = @person.create_best_friend_of(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_attr_accessible_attributes
+ def test_belongs_to_create_with_attr_accessible_attributes
best_friend = @person.create_best_friend_of(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_protected_attributes
+ def test_belongs_to_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_accessible_attributes
+ def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_without_protection
+ def test_belongs_to_create_without_protection
best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create!
- def test_has_one_create_with_bang_with_attr_protected_attributes
+ def test_belongs_to_create_with_bang_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_attr_accessible_attributes
+ def test_belongs_to_create_with_bang_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_without_protection
+ def test_belongs_to_create_with_bang_without_protection
best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
@@ -472,83 +472,328 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
# build
- def test_has_one_build_with_attr_protected_attributes
+ def test_has_many_build_with_attr_protected_attributes
best_friend = @person.best_friends.build(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_attr_accessible_attributes
+ def test_has_many_build_with_attr_accessible_attributes
best_friend = @person.best_friends.build(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_protected_attributes
+ def test_has_many_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_accessible_attributes
+ def test_has_many_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_without_protection
+ def test_has_many_build_without_protection
best_friend = @person.best_friends.build(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create
- def test_has_one_create_with_attr_protected_attributes
+ def test_has_many_create_with_attr_protected_attributes
best_friend = @person.best_friends.create(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_attr_accessible_attributes
+ def test_has_many_create_with_attr_accessible_attributes
best_friend = @person.best_friends.create(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_protected_attributes
+ def test_has_many_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_accessible_attributes
+ def test_has_many_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_without_protection
+ def test_has_many_create_without_protection
best_friend = @person.best_friends.create(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create!
- def test_has_one_create_with_bang_with_attr_protected_attributes
+ def test_has_many_create_with_bang_with_attr_protected_attributes
best_friend = @person.best_friends.create!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_attr_accessible_attributes
+ def test_has_many_create_with_bang_with_attr_accessible_attributes
best_friend = @person.best_friends.create!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
+ def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_without_protection
+ def test_has_many_create_with_bang_without_protection
best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
end
+
+
+class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+
+ def nested_attributes_hash(association, collection = false, except = [:id])
+ if collection
+ { :first_name => 'David' }.merge(:"#{association}_attributes" => [attributes_hash.except(*except)])
+ else
+ { :first_name => 'David' }.merge(:"#{association}_attributes" => attributes_hash.except(*except))
+ end
+ end
+
+ # build
+
+ def test_has_one_new_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_without_protection
+ person = LoosePerson.new(nested_attributes_hash(:best_friend, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend)
+ end
+
+ def test_belongs_to_new_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_without_protection
+ person = LoosePerson.new(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend_of)
+ end
+
+ def test_has_many_new_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_without_protection
+ person = LoosePerson.new(nested_attributes_hash(:best_friends, true, nil), :without_protection => true)
+ assert_all_attributes(person.best_friends.first)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_without_protection
+ person = LoosePerson.create(nested_attributes_hash(:best_friend, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend)
+ end
+
+ def test_belongs_to_create_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_without_protection
+ person = LoosePerson.create(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend_of)
+ end
+
+ def test_has_many_create_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_without_protection
+ person = LoosePerson.create(nested_attributes_hash(:best_friends, true, nil), :without_protection => true)
+ assert_all_attributes(person.best_friends.first)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend)
+ end
+
+ def test_belongs_to_create_with_bang_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_without_protection
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend_of)
+ end
+
+ def test_has_many_create_with_bang_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_without_protection
+ person = LoosePerson.create!(nested_attributes_hash(:best_friends, true, nil), :without_protection => true)
+ assert_all_attributes(person.best_friends.first)
+ end
+
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index bf7565a0d0..93a1249e43 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1071,6 +1071,18 @@ if ActiveRecord::Base.connection.supports_migrations?
Person.connection.drop_table :testings rescue nil
end
+ def test_column_exists_on_table_with_no_options_parameter_supplied
+ Person.connection.create_table :testings do |t|
+ t.string :foo
+ end
+ Person.connection.change_table :testings do |t|
+ assert t.column_exists?(:foo)
+ assert !(t.column_exists?(:bar))
+ end
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
def test_add_table
assert !Reminder.table_exists?
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index a61180cfaf..ad17f6f83a 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -203,3 +203,14 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
end
end
end
+
+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)
+ assert proxy.respond_to?(:to_path)
+ assert_equal proxy.to_path, "/path"
+ end
+
+end \ No newline at end of file
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index a6e08f95d0..0e158df6a0 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -498,4 +498,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
lowest_salary_dev = DeveloperOrderedBySalary.find(developers(:poor_jamis).id)
assert_equal lowest_salary_dev, DeveloperOrderedBySalary.last
end
+
+ def test_default_scope_include_with_count
+ d = DeveloperWithIncludes.create!
+ d.audit_logs.create! :message => 'foo'
+
+ assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count
+ end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index fc9df8c7a3..d88bde3860 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -145,6 +145,18 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:first).title, topics.first.title
end
+
+ def test_finding_with_arel_order
+ topics = Topic.order(Topic.arel_table[:id].asc)
+ assert_equal 4, topics.to_a.size
+ assert_equal topics(:first).title, topics.first.title
+ end
+
+ def test_finding_last_with_arel_order
+ topics = Topic.order(Topic.arel_table[:id].asc)
+ assert_equal topics(:fourth).title, topics.last.title
+ end
+
def test_finding_with_order_concatenated
topics = Topic.order('author_name').order('title')
assert_equal 4, topics.to_a.size
@@ -164,6 +176,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal entrants(:first).name, entrants.first.name
end
+ def test_finding_with_cross_table_order_and_limit
+ tags = Tag.includes(:taggings) \
+ .order("tags.name asc, taggings.taggable_id asc, REPLACE('abc', taggings.taggable_type, taggings.taggable_type)") \
+ .limit(1).to_a
+ assert_equal 1, tags.length
+ end
+
def test_finding_with_complex_order_and_limit
tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
assert_equal 1, tags.length
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index e0b30efd51..c1f7a4171a 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -124,6 +124,18 @@ class Client < Company
has_many :accounts, :through => :firm
belongs_to :account
+ class RaisedOnSave < RuntimeError; end
+ attr_accessor :raise_on_save
+ before_save do
+ raise RaisedOnSave if raise_on_save
+ end
+
+ class RaisedOnDestroy < RuntimeError; end
+ attr_accessor :raise_on_destroy
+ before_destroy do
+ raise RaisedOnDestroy if raise_on_destroy
+ end
+
# Record destruction so we can test whether firm.clients.clear has
# is calling client.destroy, deleting from the database, or setting
# foreign keys to NULL.
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 98d6aa22f7..f182a7fa97 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -91,6 +91,12 @@ class DeveloperWithSelect < ActiveRecord::Base
default_scope select('name')
end
+class DeveloperWithIncludes < ActiveRecord::Base
+ self.table_name = 'developers'
+ has_many :audit_logs, :foreign_key => :developer_id
+ default_scope includes(:audit_logs)
+end
+
class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
default_scope :order => 'salary DESC'
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index a58c9bf572..967a3625aa 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -59,8 +59,9 @@ class LoosePerson < ActiveRecord::Base
has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id
-
has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
+
+ accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends
end
class LooseDescendant < LoosePerson; end
@@ -70,11 +71,14 @@ class TightPerson < ActiveRecord::Base
attr_accessible :first_name, :gender
attr_accessible :first_name, :gender, :comments, :as => :admin
+ attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes
+ attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes, :as => :admin
has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id
belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id
-
has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id
+
+ accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends
end
class TightDescendant < TightPerson; end \ No newline at end of file
diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb
index 3b1598e368..6d123688a3 100644
--- a/activerecord/test/support/config.rb
+++ b/activerecord/test/support/config.rb
@@ -1,6 +1,7 @@
require 'yaml'
require 'erubis'
require 'fileutils'
+require 'pathname'
module ARTest
class << self
@@ -10,13 +11,16 @@ module ARTest
private
+ def config_file
+ Pathname.new(ENV['ARCONFIG'] || TEST_ROOT + '/config.yml')
+ end
+
def read_config
- unless File.exist?(TEST_ROOT + '/config.yml')
- FileUtils.cp TEST_ROOT + '/config.example.yml', TEST_ROOT + '/config.yml'
+ unless config_file.exist?
+ FileUtils.cp TEST_ROOT + '/config.example.yml', config_file
end
- raw = File.read(TEST_ROOT + '/config.yml')
- erb = Erubis::Eruby.new(raw)
+ erb = Erubis::Eruby.new(config_file.read)
expand_config(YAML.parse(erb.result(binding)).transform)
end