aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2009-05-15 16:47:15 +0200
committerPratik Naik <pratiknaik@gmail.com>2009-05-15 16:47:15 +0200
commit59653101b8cef7915cb1fb4ad4b84f49ae0881e5 (patch)
tree9dacd7eaa05c7957b14389538a30d7ebf7f7e349 /activerecord
parent26ad104e72e2b758815a043341dd83a1b02e8c7f (diff)
parenteb021707f53be46140b55a48e5ef03ed0577a45c (diff)
downloadrails-59653101b8cef7915cb1fb4ad4b84f49ae0881e5.tar.gz
rails-59653101b8cef7915cb1fb4ad4b84f49ae0881e5.tar.bz2
rails-59653101b8cef7915cb1fb4ad4b84f49ae0881e5.zip
Merge commit 'mainstream/master'
Conflicts: actionpack/lib/action_view/helpers/form_helper.rb
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/Rakefile26
-rw-r--r--activerecord/examples/simple.rb14
-rw-r--r--activerecord/lib/active_record.rb14
-rw-r--r--activerecord/lib/active_record/aggregations.rb4
-rw-r--r--activerecord/lib/active_record/association_preload.rb8
-rwxr-xr-xactiverecord/lib/active_record/associations.rb50
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb8
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb14
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb30
-rw-r--r--activerecord/lib/active_record/autosave_association.rb13
-rwxr-xr-xactiverecord/lib/active_record/base.rb34
-rw-r--r--activerecord/lib/active_record/batches.rb4
-rw-r--r--activerecord/lib/active_record/calculations.rb5
-rw-r--r--activerecord/lib/active_record/callbacks.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb1
-rw-r--r--activerecord/lib/active_record/dirty.rb25
-rw-r--r--activerecord/lib/active_record/fixtures.rb39
-rw-r--r--activerecord/lib/active_record/i18n_interpolation_deprecation.rb26
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb16
-rw-r--r--activerecord/lib/active_record/migration.rb10
-rw-r--r--activerecord/lib/active_record/named_scope.rb16
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb23
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/reflection.rb25
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/serialization.rb17
-rw-r--r--activerecord/lib/active_record/serializers/json_serializer.rb7
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb5
-rw-r--r--activerecord/lib/active_record/timestamp.rb12
-rw-r--r--activerecord/lib/active_record/transactions.rb12
-rw-r--r--activerecord/lib/active_record/validations.rb18
-rw-r--r--activerecord/test/cases/aggregations_test.rb1
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb14
-rw-r--r--activerecord/test/cases/associations/eager_test.rb12
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb1
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb313
-rwxr-xr-xactiverecord/test/cases/base_test.rb1
-rw-r--r--activerecord/test/cases/finder_test.rb11
-rw-r--r--activerecord/test/cases/helper.rb5
-rw-r--r--activerecord/test/cases/method_scoping_test.rb6
-rw-r--r--activerecord/test/cases/named_scope_test.rb3
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb19
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb29
-rw-r--r--activerecord/test/cases/repair_helper.rb6
-rw-r--r--activerecord/test/cases/validations_i18n_test.rb34
-rw-r--r--activerecord/test/fixtures/faces.yml7
-rw-r--r--activerecord/test/fixtures/interests.yml29
-rw-r--r--activerecord/test/fixtures/men.yml5
-rw-r--r--activerecord/test/fixtures/zines.yml5
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/face.rb5
-rw-r--r--activerecord/test/models/interest.rb4
-rw-r--r--activerecord/test/models/man.rb7
-rw-r--r--activerecord/test/models/pirate.rb2
-rw-r--r--activerecord/test/models/zine.rb3
-rw-r--r--activerecord/test/schema/schema.rb21
67 files changed, 837 insertions, 270 deletions
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index b50008c971..0b3f50d17e 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -4,7 +4,6 @@ require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
-require 'rake/contrib/sshpublisher'
require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
@@ -33,21 +32,32 @@ desc 'Run mysql, sqlite, and postgresql tests'
task :test => defined?(JRUBY_VERSION) ?
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
%w(test_mysql test_sqlite3 test_postgresql)
+task :isolated_test => defined?(JRUBY_VERSION) ?
+ %w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
+ %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql)
-for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
+%w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
Rake::TestTask.new("test_#{adapter}") { |t|
- if adapter =~ /jdbc/
- t.libs << "test" << "test/connections/jdbc_#{adapter}"
- else
- t.libs << "test" << "test/connections/native_#{adapter}"
- end
+ connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ t.libs << "test" << connection_path
t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
t.verbose = true
}
+ task "isolated_test_#{adapter}" do
+ connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ puts [adapter, adapter_short, connection_path].inspect
+ ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
+ Dir["test/cases/**/*_test{,_#{adapter_short}}.rb"].all? do |file|
+ system(ruby, "-Ilib:test:#{connection_path}", file)
+ end or raise "Failures"
+ end
+
namespace adapter do
task :test => "test_#{adapter}"
+ task :isolated_test => "isolated_test_#{adapter}"
end
end
@@ -231,12 +241,14 @@ end
desc "Publish the beta gem"
task :pgem => [:package] do
+ require 'rake/contrib/sshpublisher'
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
+ require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
end
diff --git a/activerecord/examples/simple.rb b/activerecord/examples/simple.rb
new file mode 100644
index 0000000000..c12f746992
--- /dev/null
+++ b/activerecord/examples/simple.rb
@@ -0,0 +1,14 @@
+$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
+require 'active_record'
+
+class Person < ActiveRecord::Base
+ establish_connection :adapter => 'sqlite3', :database => 'foobar.db'
+ connection.create_table table_name, :force => true do |t|
+ t.string :name
+ end
+end
+
+bob = Person.create!(:name => 'bob')
+puts Person.all.inspect
+bob.destroy
+puts Person.all.inspect
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 8b6e019882..4d501d319f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -21,16 +21,9 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-begin
- require 'active_support'
-rescue LoadError
- activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
- if File.directory?(activesupport_path)
- $:.unshift activesupport_path
- require 'active_support'
- end
-end
-require 'active_support/core/all'
+activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
+$:.unshift(activesupport_path) if File.directory?(activesupport_path)
+require 'active_support'
module ActiveRecord
# TODO: Review explicit loads to see if they will automatically be handled by the initializer.
@@ -81,5 +74,4 @@ module ActiveRecord
end
end
-require 'active_record/i18n_interpolation_deprecation'
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 1eefebb3b3..359e70f5ed 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -1,8 +1,6 @@
module ActiveRecord
module Aggregations # :nodoc:
- def self.included(base)
- base.extend(ClassMethods)
- end
+ extend ActiveSupport::DependencyModule
def clear_aggregation_cache #:nodoc:
self.class.reflect_on_all_aggregations.to_a.each do |assoc|
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 8788746598..41158a66bc 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -1,9 +1,7 @@
module ActiveRecord
# See ActiveRecord::AssociationPreload::ClassMethods for documentation.
module AssociationPreload #:nodoc:
- def self.included(base)
- base.extend(ClassMethods)
- end
+ extend ActiveSupport::DependencyModule
# Implements the details of eager loading of ActiveRecord associations.
# Application developers should not use this module directly.
@@ -126,6 +124,7 @@ module ActiveRecord
association_proxy = parent_record.send(reflection_name)
association_proxy.loaded
association_proxy.target.push(*[associated_record].flatten)
+ association_proxy.__send__(:set_inverse_instance, associated_record, parent_record)
end
end
@@ -152,7 +151,8 @@ module ActiveRecord
seen_keys[associated_record[key].to_s] = true
mapped_records = id_to_record_map[associated_record[key].to_s]
mapped_records.each do |mapped_record|
- mapped_record.send("set_#{reflection_name}_target", associated_record)
+ association_proxy = mapped_record.send("set_#{reflection_name}_target", associated_record)
+ association_proxy.__send__(:set_inverse_instance, associated_record, mapped_record)
end
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index a1d952e50d..41c0394763 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,4 +1,12 @@
+require 'active_support/core_ext/module/delegation'
+
module ActiveRecord
+ class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
+ def initialize(reflection)
+ super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{reflection.class_name})")
+ end
+ end
+
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
def initialize(owner_class_name, reflection)
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
@@ -71,6 +79,8 @@ module ActiveRecord
# See ActiveRecord::Associations::ClassMethods for documentation.
module Associations # :nodoc:
+ extend ActiveSupport::DependencyModule
+
# These classes will be loaded when associations are created.
# So there is no need to eager load them.
autoload :AssociationCollection, 'active_record/associations/association_collection'
@@ -83,10 +93,6 @@ module ActiveRecord
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
- def self.included(base)
- base.extend(ClassMethods)
- end
-
# Clears out the association cache
def clear_association_cache #:nodoc:
self.class.reflect_on_all_associations.to_a.each do |assoc|
@@ -1488,7 +1494,7 @@ module ActiveRecord
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
- :validate
+ :validate, :inverse_of
]
def create_has_many_reflection(association_id, options, &extension)
@@ -1502,7 +1508,7 @@ module ActiveRecord
@@valid_keys_for_has_one_association = [
:class_name, :foreign_key, :remote, :select, :conditions, :order,
:include, :dependent, :counter_cache, :extend, :as, :readonly,
- :validate, :primary_key
+ :validate, :primary_key, :inverse_of
]
def create_has_one_reflection(association_id, options)
@@ -1521,7 +1527,7 @@ module ActiveRecord
@@valid_keys_for_belongs_to_association = [
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions,
:include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
- :validate, :touch
+ :validate, :touch, :inverse_of
]
def create_belongs_to_reflection(association_id, options)
@@ -1665,17 +1671,29 @@ module ActiveRecord
string.scan(/([\.a-zA-Z_]+).?\./).flatten
end
+ def tables_in_hash(hash)
+ return [] if hash.blank?
+ tables = hash.map do |key, value|
+ if value.is_a?(Hash)
+ key.to_s
+ else
+ tables_in_string(key) if key.is_a?(String)
+ end
+ end
+ tables.flatten.compact
+ end
+
def conditions_tables(options)
# look in both sets of conditions
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
case cond
when nil then all
- when Array then all << cond.first
- when Hash then all << cond.keys
- else all << cond
+ when Array then all << tables_in_string(cond.first)
+ when Hash then all << tables_in_hash(cond)
+ else all << tables_in_string(cond)
end
end
- tables_in_string(conditions.join(' '))
+ conditions.flatten
end
def order_tables(options)
@@ -1910,21 +1928,27 @@ module ActiveRecord
return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
association = join.instantiate(row)
collection.target.push(association)
+ collection.__send__(:set_inverse_instance, association, record)
when :has_one
return if record.id.to_s != join.parent.record_id(row).to_s
return if record.instance_variable_defined?("@#{join.reflection.name}")
association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
- record.send("set_#{join.reflection.name}_target", association)
+ set_target_and_inverse(join, association, record)
when :belongs_to
return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
association = join.instantiate(row)
- record.send("set_#{join.reflection.name}_target", association)
+ set_target_and_inverse(join, association, record)
else
raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
end
return association
end
+ def set_target_and_inverse(join, association, record)
+ association_proxy = record.send("set_#{join.reflection.name}_target", association)
+ association_proxy.__send__(:set_inverse_instance, association, record)
+ end
+
class JoinBase # :nodoc:
attr_reader :active_record, :table_joins
delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 66cfab5c2e..1e2313ab62 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -399,11 +399,14 @@ module ActiveRecord
find(:all)
end
- @reflection.options[:uniq] ? uniq(records) : records
+ records = @reflection.options[:uniq] ? uniq(records) : records
+ records.each do |record|
+ set_inverse_instance(record, @owner)
+ end
+ records
end
private
-
def create_record(attrs)
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
ensure_owner_is_not_new
@@ -433,6 +436,7 @@ module ActiveRecord
@target ||= [] unless loaded?
@target << record unless @reflection.options[:uniq] && @target.include?(record)
callback(:after_add, record)
+ set_inverse_instance(record, @owner)
record
end
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 241b9bfee0..e36b04ea95 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -53,6 +53,7 @@ module ActiveRecord
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
+ reflection.check_validity!
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
reset
end
@@ -274,6 +275,19 @@ module ActiveRecord
def owner_quoted_id
@owner.quoted_id
end
+
+ def set_inverse_instance(record, instance)
+ return if record.nil? || !we_can_set_the_inverse_on_this?(record)
+ inverse_relationship = @reflection.inverse_of
+ unless inverse_relationship.nil?
+ record.send(:"set_#{inverse_relationship.name}_target", instance)
+ end
+ end
+
+ # Override in subclasses
+ def we_can_set_the_inverse_on_this?(record)
+ false
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index f05c6be075..c88575048a 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -31,6 +31,8 @@ module ActiveRecord
@updated = true
end
+ set_inverse_instance(record, @owner)
+
loaded
record
end
@@ -41,18 +43,26 @@ module ActiveRecord
private
def find_target
- @reflection.klass.find(
+ the_target = @reflection.klass.find(
@owner[@reflection.primary_key_name],
:select => @reflection.options[:select],
:conditions => conditions,
:include => @reflection.options[:include],
:readonly => @reflection.options[:readonly]
)
+ set_inverse_instance(the_target, @owner)
+ the_target
end
def foreign_key_present
!@owner[@reflection.primary_key_name].nil?
end
+
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
+ # has_one associations.
+ def we_can_set_the_inverse_on_this?(record)
+ @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index a2cbabfe0c..73dd50dd07 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -116,6 +116,11 @@ module ActiveRecord
:create => create_scoping
}
end
+
+ def we_can_set_the_inverse_on_this?(record)
+ inverse = @reflection.inverse_of
+ return !inverse.nil?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 1c091e7d5a..2dca84b911 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,11 +1,6 @@
module ActiveRecord
module Associations
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
- def initialize(owner, reflection)
- reflection.check_validity!
- super
- end
-
alias_method :new, :build
def create!(attrs = nil)
@@ -251,6 +246,11 @@ module ActiveRecord
def cached_counter_attribute_name
"#{@reflection.name}_count"
end
+
+ # NOTE - not sure that we can actually cope with inverses here
+ def we_can_set_the_inverse_on_this?(record)
+ false
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index b92cbbdeab..b72b84343b 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -74,13 +74,15 @@ module ActiveRecord
private
def find_target
- @reflection.klass.find(:first,
+ the_target = @reflection.klass.find(:first,
:conditions => @finder_sql,
:select => @reflection.options[:select],
:order => @reflection.options[:order],
:include => @reflection.options[:include],
:readonly => @reflection.options[:readonly]
)
+ set_inverse_instance(the_target, @owner)
+ the_target
end
def construct_sql
@@ -117,8 +119,15 @@ module ActiveRecord
self.target = record
end
+ set_inverse_instance(record, @owner)
+
record
end
+
+ def we_can_set_the_inverse_on_this?(record)
+ inverse = @reflection.inverse_of
+ return !inverse.nil?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 3ffc48941c..d5e215af9d 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,17 +1,23 @@
+require 'active_support/core_ext/enumerable'
+
module ActiveRecord
module AttributeMethods #:nodoc:
+ extend ActiveSupport::DependencyModule
+
DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
- def self.included(base)
- base.extend ClassMethods
- base.attribute_method_suffix(*DEFAULT_SUFFIXES)
- base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
- base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
- base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
- base.time_zone_aware_attributes = false
- base.class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
- base.skip_time_zone_conversion_for_attributes = []
+ included do
+ attribute_method_suffix(*DEFAULT_SUFFIXES)
+
+ cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+
+ cattr_accessor :time_zone_aware_attributes, :instance_writer => false
+ self.time_zone_aware_attributes = false
+
+ class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
+ self.skip_time_zone_conversion_for_attributes = []
end
# Declare and check for suffixed attribute methods.
@@ -99,8 +105,8 @@ module ActiveRecord
def instance_method_already_implemented?(method_name)
method_name = method_name.to_s
return true if method_name =~ /^id(=$|\?$|$)/
- @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map(&:to_s).to_set
- @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map(&:to_s).to_set
+ @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set
+ @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
@_defined_class_methods.include?(method_name)
end
@@ -118,7 +124,7 @@ module ActiveRecord
# with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
def cached_attributes
@cached_attributes ||=
- columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
end
# Returns +true+ if the provided attribute is being cached.
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 9717ca3d8b..4ab2818282 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -125,16 +125,15 @@ module ActiveRecord
# post.author.name = ''
# post.save(false) # => true
module AutosaveAssociation
+ extend ActiveSupport::DependencyModule
+
ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
- def self.included(base)
- base.class_eval do
- base.extend(ClassMethods)
- alias_method_chain :reload, :autosave_associations
+ included do
+ alias_method_chain :reload, :autosave_associations
- ASSOCIATION_TYPES.each do |type|
- base.send("valid_keys_for_#{type}_association") << :autosave
- end
+ ASSOCIATION_TYPES.each do |type|
+ send("valid_keys_for_#{type}_association") << :autosave
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 32bd903aed..9014bfdfa6 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1,6 +1,15 @@
require 'yaml'
require 'set'
require 'active_support/dependencies'
+require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/class/delegating_attributes'
+require 'active_support/core_ext/class/inheritable_attributes'
+require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/hash/deep_merge'
+require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/string/behavior'
+require 'active_support/core/time'
module ActiveRecord #:nodoc:
# Generic Active Record exception class.
@@ -688,14 +697,9 @@ module ActiveRecord #:nodoc:
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
def exists?(id_or_conditions = {})
- connection.select_all(
- construct_finder_sql(
- :select => "#{quoted_table_name}.#{primary_key}",
- :conditions => expand_id_conditions(id_or_conditions),
- :limit => 1
- ),
- "#{name} Exists"
- ).size > 0
+ find_initial(
+ :select => "#{quoted_table_name}.#{primary_key}",
+ :conditions => expand_id_conditions(id_or_conditions)) ? true : false
end
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -1031,7 +1035,7 @@ module ActiveRecord #:nodoc:
# To start from an all-closed default and enable attributes as needed,
# have a look at +attr_accessible+.
def attr_protected(*attributes)
- write_inheritable_attribute(:attr_protected, Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
+ write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || []))
end
# Returns an array of all the attributes that have been protected from mass-assignment.
@@ -1888,7 +1892,7 @@ module ActiveRecord #:nodoc:
else
find(:#{finder}, options.merge(finder_options))
end
- #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
+ #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect { |pair| pair.join(\' = \') }.join(\', \')}")' if bang}
end
}, __FILE__, __LINE__
send(method_id, *arguments)
@@ -2610,11 +2614,11 @@ module ActiveRecord #:nodoc:
# Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
# instance will affect the other.
def becomes(klass)
- returning klass.new do |became|
- became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@attributes_cache", @attributes_cache)
- became.instance_variable_set("@new_record", new_record?)
- end
+ became = klass.new
+ became.instance_variable_set("@attributes", @attributes)
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
+ became.instance_variable_set("@new_record", new_record?)
+ became
end
# Updates a single attribute and saves the record without going through the normal validation procedure.
diff --git a/activerecord/lib/active_record/batches.rb b/activerecord/lib/active_record/batches.rb
index 5a6cecd4ad..4836601297 100644
--- a/activerecord/lib/active_record/batches.rb
+++ b/activerecord/lib/active_record/batches.rb
@@ -1,8 +1,6 @@
module ActiveRecord
module Batches # :nodoc:
- def self.included(base)
- base.extend(ClassMethods)
- end
+ extend ActiveSupport::DependencyModule
# When processing large numbers of records, it's often a good idea to do
# so in batches to prevent memory ballooning.
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index f077818d3b..7afa7c49bd 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -1,9 +1,8 @@
module ActiveRecord
module Calculations #:nodoc:
+ extend ActiveSupport::DependencyModule
+
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from]
- def self.included(base)
- base.extend(ClassMethods)
- end
module ClassMethods
# Count operates using three different approaches.
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index e375037b5b..a77fdb1c13 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -211,21 +211,23 @@ module ActiveRecord
# needs to be aware of it because an ordinary +save+ will raise such exception
# instead of quietly returning +false+.
module Callbacks
+ extend ActiveSupport::DependencyModule
+
CALLBACKS = %w(
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
after_validation before_validation_on_create after_validation_on_create before_validation_on_update
after_validation_on_update before_destroy after_destroy
)
- def self.included(base) #:nodoc:
- base.extend Observable
+ included do
+ extend Observable
[:create_or_update, :valid?, :create, :update, :destroy].each do |method|
- base.send :alias_method_chain, method, :callbacks
+ alias_method_chain method, :callbacks
end
- base.send :include, ActiveSupport::Callbacks
- base.define_callbacks *CALLBACKS
+ include ActiveSupport::Callbacks
+ define_callbacks *CALLBACKS
end
# Is called when the object was instantiated by one of the finders, like <tt>Base.find</tt>.
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 01fdd034cd..12253eac3f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,5 +1,6 @@
require 'monitor'
require 'set'
+require 'active_support/core_ext/module/synchronization'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -107,13 +108,14 @@ module ActiveRecord
checkin conn if conn
end
- # Reserve a connection, and yield it to a block. Ensure the connection is
- # checked back in when finished.
+ # If a connection already exists yield it to the block. If no connection
+ # exists checkout a connection, yield it to the block, and checkin the
+ # connection when finished.
def with_connection
- conn = checkout
- yield conn
+ fresh_connection = true unless @reserved_connections[current_connection_id]
+ yield connection
ensure
- checkin conn
+ release_connection if fresh_connection
end
# Returns true if a connection has already been opened.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 3a7bf35248..720fba29e9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/big_decimal/conversions'
+
module ActiveRecord
module ConnectionAdapters # :nodoc:
module Quoting
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 c29c1562b4..ff63ea3a2e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -316,7 +316,7 @@ module ActiveRecord
def initialize_schema_migrations_table
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
- unless tables.detect { |t| t == sm_table }
+ unless table_exists?(sm_table)
create_table(sm_table, :id => false) do |schema_migrations_table|
schema_migrations_table.column :version, :string, :null => false
end
@@ -327,7 +327,7 @@ module ActiveRecord
# migrated up to that point:
si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
- if tables.detect { |t| t == si_table }
+ if table_exists?(si_table)
old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
assume_migrated_upto_version(old_version)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index a8cd9f033b..91b111ab55 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -12,6 +12,8 @@ require 'active_record/connection_adapters/abstract/connection_pool'
require 'active_record/connection_adapters/abstract/connection_specification'
require 'active_record/connection_adapters/abstract/query_cache'
+require 'active_support/core_ext/benchmark'
+
module ActiveRecord
module ConnectionAdapters # :nodoc:
# ActiveRecord supports multiple database systems. AbstractAdapter and
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 9300df28ee..d5536e4d67 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -1,4 +1,5 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_support/core_ext/kernel/requires'
require 'set'
module MysqlCompat #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 4961793866..002696d2c4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,4 +1,5 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_support/core_ext/kernel/requires'
begin
require_library_or_gem 'pg'
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 75420f69aa..5eef692d05 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -25,9 +25,9 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc:
def table_structure(table_name)
- returning structure = @connection.table_info(quote_table_name(table_name)) do
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- end
+ structure = @connection.table_info(quote_table_name(table_name))
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
+ structure
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 05334a830a..c9d0c9574f 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -1,4 +1,5 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_support/core_ext/kernel/requires'
module ActiveRecord
class Base
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index 4a2510aa63..ac84f6b209 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -34,20 +34,21 @@ module ActiveRecord
# person.name << 'by'
# person.name_change # => ['uncle bob', 'uncle bobby']
module Dirty
+ extend ActiveSupport::DependencyModule
+
DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
- def self.included(base)
- base.attribute_method_suffix *DIRTY_SUFFIXES
- base.alias_method_chain :write_attribute, :dirty
- base.alias_method_chain :save, :dirty
- base.alias_method_chain :save!, :dirty
- base.alias_method_chain :update, :dirty
- base.alias_method_chain :reload, :dirty
+ included do
+ attribute_method_suffix *DIRTY_SUFFIXES
- base.superclass_delegating_accessor :partial_updates
- base.partial_updates = true
+ alias_method_chain :write_attribute, :dirty
+ alias_method_chain :save, :dirty
+ alias_method_chain :save!, :dirty
+ alias_method_chain :update, :dirty
+ alias_method_chain :reload, :dirty
- base.send(:extend, ClassMethods)
+ superclass_delegating_accessor :partial_updates
+ self.partial_updates = true
end
# Do any attributes have unsaved changes?
@@ -167,7 +168,9 @@ module ActiveRecord
module ClassMethods
def self.extended(base)
- base.metaclass.alias_method_chain(:alias_attribute, :dirty)
+ class << base
+ alias_method_chain :alias_attribute, :dirty
+ end
end
def alias_attribute_with_dirty(new_name, old_name)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 41200bc622..cc3c4ece47 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -3,6 +3,7 @@ require 'yaml'
require 'csv'
require 'active_support/dependencies'
require 'active_support/test_case'
+require 'active_support/core_ext/logger'
if RUBY_VERSION < '1.9'
module YAML #:nodoc:
@@ -805,27 +806,25 @@ end
module ActiveRecord
module TestFixtures
- def self.included(base)
- base.class_eval do
- setup :setup_fixtures
- teardown :teardown_fixtures
-
- superclass_delegating_accessor :fixture_path
- superclass_delegating_accessor :fixture_table_names
- superclass_delegating_accessor :fixture_class_names
- superclass_delegating_accessor :use_transactional_fixtures
- superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
- superclass_delegating_accessor :pre_loaded_fixtures
-
- self.fixture_table_names = []
- self.use_transactional_fixtures = false
- self.use_instantiated_fixtures = true
- self.pre_loaded_fixtures = false
-
- self.fixture_class_names = {}
- end
+ extend ActiveSupport::DependencyModule
+
+ included do
+ setup :setup_fixtures
+ teardown :teardown_fixtures
+
+ superclass_delegating_accessor :fixture_path
+ superclass_delegating_accessor :fixture_table_names
+ superclass_delegating_accessor :fixture_class_names
+ superclass_delegating_accessor :use_transactional_fixtures
+ superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
+ superclass_delegating_accessor :pre_loaded_fixtures
+
+ self.fixture_table_names = []
+ self.use_transactional_fixtures = false
+ self.use_instantiated_fixtures = true
+ self.pre_loaded_fixtures = false
- base.extend ClassMethods
+ self.fixture_class_names = {}
end
module ClassMethods
diff --git a/activerecord/lib/active_record/i18n_interpolation_deprecation.rb b/activerecord/lib/active_record/i18n_interpolation_deprecation.rb
deleted file mode 100644
index cd634e1b8d..0000000000
--- a/activerecord/lib/active_record/i18n_interpolation_deprecation.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# Deprecates the use of the former message interpolation syntax in activerecord
-# as in "must have %d characters". The new syntax uses explicit variable names
-# as in "{{value}} must have {{count}} characters".
-
-require 'i18n/backend/simple'
-module I18n
- module Backend
- class Simple
- DEPRECATED_INTERPOLATORS = { '%d' => '{{count}}', '%s' => '{{value}}' }
-
- protected
- def interpolate_with_deprecated_syntax(locale, string, values = {})
- return string unless string.is_a?(String)
-
- string = string.gsub(/%d|%s/) do |s|
- instead = DEPRECATED_INTERPOLATORS[s]
- ActiveSupport::Deprecation.warn "using #{s} in messages is deprecated; use #{instead} instead."
- instead
- end
-
- interpolate_without_deprecated_syntax(locale, string, values)
- end
- alias_method_chain :interpolate, :deprecated_syntax
- end
- end
-end \ No newline at end of file
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 7fa7e267d8..cf4f8864c6 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -42,17 +42,17 @@ module ActiveRecord
# To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
# This method uses the same syntax as <tt>set_table_name</tt>
module Optimistic
- def self.included(base) #:nodoc:
- base.extend ClassMethods
+ extend ActiveSupport::DependencyModule
- base.cattr_accessor :lock_optimistically, :instance_writer => false
- base.lock_optimistically = true
+ included do
+ cattr_accessor :lock_optimistically, :instance_writer => false
+ self.lock_optimistically = true
- base.alias_method_chain :update, :lock
- base.alias_method_chain :destroy, :lock
- base.alias_method_chain :attributes_from_column_definition, :lock
+ alias_method_chain :update, :lock
+ alias_method_chain :destroy, :lock
+ alias_method_chain :attributes_from_column_definition, :lock
- class << base
+ class << self
alias_method :locking_column=, :set_locking_column
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 657acd6dc0..a7be3539d5 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -511,11 +511,11 @@ module ActiveRecord
raise DuplicateMigrationNameError.new(name.camelize)
end
- klasses << returning(MigrationProxy.new) do |migration|
- migration.name = name.camelize
- migration.version = version
- migration.filename = file
- end
+ migration = MigrationProxy.new
+ migration.name = name.camelize
+ migration.version = version
+ migration.filename = file
+ klasses << migration
end
migrations = migrations.sort_by(&:version)
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 1f3ef300f2..07f98dc743 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -1,5 +1,10 @@
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/hash/except'
+
module ActiveRecord
module NamedScope
+ extend ActiveSupport::DependencyModule
+
# All subclasses of ActiveRecord::Base have one named scope:
# * <tt>scoped</tt> - which allows for the creation of anonymous \scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
#
@@ -7,11 +12,8 @@ module ActiveRecord
# intermediate values (scopes) around as first-class objects is convenient.
#
# You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
- def self.included(base)
- base.class_eval do
- extend ClassMethods
- named_scope :scoped, lambda { |scope| scope }
- end
+ included do
+ named_scope :scoped, lambda { |scope| scope }
end
module ClassMethods
@@ -114,7 +116,7 @@ module ActiveRecord
end
end
- delegate :scopes, :with_scope, :to => :proxy_scope
+ delegate :scopes, :with_scope, :scoped_methods, :to => :proxy_scope
def initialize(proxy_scope, options, &block)
options ||= {}
@@ -178,7 +180,7 @@ module ActiveRecord
else
with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do
method = :new if method == :build
- if current_scoped_methods_when_defined
+ if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined)
with_scope current_scoped_methods_when_defined do
proxy_scope.send(method, *args, &block)
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index f48ad8818b..500b9b8ae0 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -1,9 +1,13 @@
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/object/try'
+
module ActiveRecord
module NestedAttributes #:nodoc:
- def self.included(base)
- base.extend(ClassMethods)
- base.class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false
- base.reject_new_nested_attributes_procs = {}
+ extend ActiveSupport::DependencyModule
+
+ included do
+ class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false
+ self.reject_new_nested_attributes_procs = {}
end
# == Nested Attributes
@@ -180,10 +184,14 @@ module ActiveRecord
# and the Proc should return either +true+ or +false+. When no Proc
# is specified a record will be built for all attribute hashes that
# do not have a <tt>_delete</tt> 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.
#
# Examples:
# # creates avatar_attributes=
# accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? }
+ # # creates avatar_attributes=
+ # accepts_nested_attributes_for :avatar, :reject_if => :all_blank
# # creates avatar_attributes= and posts_attributes=
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true
def accepts_nested_attributes_for(*attr_names)
@@ -201,7 +209,12 @@ module ActiveRecord
end
reflection.options[:autosave] = true
- self.reject_new_nested_attributes_procs[association_name.to_sym] = options[:reject_if]
+
+ self.reject_new_nested_attributes_procs[association_name.to_sym] = if options[:reject_if] == :all_blank
+ proc { |attributes| attributes.all? {|k,v| v.blank?} }
+ else
+ options[:reject_if]
+ end
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, false)
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index b35e407cc1..1ca76c7b2f 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -3,9 +3,7 @@ require 'set'
module ActiveRecord
module Observing # :nodoc:
- def self.included(base)
- base.extend ClassMethods
- end
+ extend ActiveSupport::DependencyModule
module ClassMethods
# Activates the observers assigned. Examples:
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 2d4c1d5507..3747ba449d 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,8 +1,6 @@
module ActiveRecord
module Reflection # :nodoc:
- def self.included(base)
- base.extend(ClassMethods)
- end
+ extend ActiveSupport::DependencyModule
# Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
# This information can, for example, be used in a form builder that took an Active Record object and created input
@@ -212,6 +210,13 @@ module ActiveRecord
end
def check_validity!
+ check_validity_of_inverse!
+ end
+
+ def check_validity_of_inverse!
+ if has_inverse? && inverse_of.nil?
+ raise InverseOfAssociationNotFoundError.new(self)
+ end
end
def through_reflection
@@ -225,6 +230,18 @@ module ActiveRecord
nil
end
+ def has_inverse?
+ !@options[:inverse_of].nil?
+ end
+
+ def inverse_of
+ if has_inverse?
+ @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
+ else
+ nil
+ end
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
@@ -300,6 +317,8 @@ module ActiveRecord
unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
raise HasManyThroughSourceAssociationMacroError.new(self)
end
+
+ check_validity_of_inverse!
end
def through_reflection_primary_key
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 557a554966..de530a3456 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -1,5 +1,5 @@
require 'stringio'
-require 'bigdecimal'
+require 'active_support/core_ext/big_decimal'
module ActiveRecord
# This class is used to dump the database schema for some connection to some
@@ -176,4 +176,4 @@ HEADER
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 78f66c3a73..7959f2b510 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,5 +1,3 @@
-require 'active_support/json'
-
module ActiveRecord #:nodoc:
module Serialization
class Serializer #:nodoc:
@@ -73,16 +71,19 @@ module ActiveRecord #:nodoc:
end
def serializable_record
- returning(serializable_record = {}) do
- serializable_names.each { |name| serializable_record[name] = @record.send(name) }
- add_includes do |association, records, opts|
+ record = {}
+ serializable_names.each { |name| record[name] = @record.send(name) }
+
+ add_includes do |association, records, opts|
+ record[association] =
if records.is_a?(Enumerable)
- serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record }
+ records.collect { |r| self.class.new(r, opts).serializable_record }
else
- serializable_record[association] = self.class.new(records, opts).serializable_record
+ self.class.new(records, opts).serializable_record
end
- end
end
+
+ record
end
def serialize
diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb
index 48df15d2c0..d376fd5e1b 100644
--- a/activerecord/lib/active_record/serializers/json_serializer.rb
+++ b/activerecord/lib/active_record/serializers/json_serializer.rb
@@ -2,9 +2,10 @@ require 'active_support/json'
module ActiveRecord #:nodoc:
module Serialization
- def self.included(base)
- base.cattr_accessor :include_root_in_json, :instance_writer => false
- base.extend ClassMethods
+ extend ActiveSupport::DependencyModule
+
+ included do
+ cattr_accessor :include_root_in_json, :instance_writer => false
end
# Returns a JSON string representing the model. Some configuration is
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index fa75874603..4eaf9531e2 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/conversions'
+
module ActiveRecord #:nodoc:
module Serialization
# Builds an XML document to represent the model. Some configuration is
@@ -165,8 +167,9 @@ module ActiveRecord #:nodoc:
class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
def builder
@builder ||= begin
+ require 'builder' unless defined? ::Builder
options[:indent] ||= 2
- builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
unless options[:skip_instruct]
builder.instruct!
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index d9e1ef351f..3734e170af 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -8,12 +8,14 @@ module ActiveRecord
# Timestamps are in the local timezone by default but you can use UTC by setting
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
module Timestamp
- def self.included(base) #:nodoc:
- base.alias_method_chain :create, :timestamps
- base.alias_method_chain :update, :timestamps
+ extend ActiveSupport::DependencyModule
- base.class_inheritable_accessor :record_timestamps, :instance_writer => false
- base.record_timestamps = true
+ included do
+ alias_method_chain :create, :timestamps
+ alias_method_chain :update, :timestamps
+
+ class_inheritable_accessor :record_timestamps, :instance_writer => false
+ self.record_timestamps = true
end
# Saves the record with the updated_at/on attributes set to the current time.
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index b059eb7f6f..471a81dfb5 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -3,16 +3,14 @@ require 'thread'
module ActiveRecord
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
+ extend ActiveSupport::DependencyModule
+
class TransactionError < ActiveRecordError # :nodoc:
end
- def self.included(base)
- base.extend(ClassMethods)
-
- base.class_eval do
- [:destroy, :save, :save!].each do |method|
- alias_method_chain method, :transactions
- end
+ included do
+ [:destroy, :save, :save!].each do |method|
+ alias_method_chain method, :transactions
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 1c7ccc18c0..236c9f36de 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -245,9 +245,10 @@ module ActiveRecord
# # <error>Address can't be blank</error>
# # </errors>
def to_xml(options={})
+ require 'builder' unless defined? ::Builder
options[:root] ||= "errors"
options[:indent] ||= 2
- options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
options[:builder].instruct! unless options.delete(:skip_instruct)
options[:builder].errors do |e|
@@ -299,17 +300,16 @@ module ActiveRecord
#
# An Errors object is automatically created for every Active Record.
module Validations
+ extend ActiveSupport::DependencyModule
+
VALIDATIONS = %w( validate validate_on_create validate_on_update )
- def self.included(base) # :nodoc:
- base.extend ClassMethods
- base.class_eval do
- alias_method_chain :save, :validation
- alias_method_chain :save!, :validation
- end
+ included do
+ alias_method_chain :save, :validation
+ alias_method_chain :save!, :validation
- base.send :include, ActiveSupport::Callbacks
- base.define_callbacks *VALIDATIONS
+ include ActiveSupport::Callbacks
+ define_callbacks *VALIDATIONS
end
# Active Record classes can implement validations in several ways. The highest level, easiest to read,
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 4e0e1c7f15..8b6ec04018 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/customer'
+require 'active_support/core_ext/exception'
class AggregationsTest < ActiveRecord::TestCase
fixtures :customers
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 677226ec89..cb7fe9698b 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -4,15 +4,15 @@ require 'models/author'
require 'models/comment'
require 'models/category'
require 'models/categorization'
+require 'active_support/core_ext/array/random_access'
module Remembered
- def self.included(base)
- base.extend ClassMethods
- base.class_eval do
- after_create :remember
- protected
- def remember; self.class.remembered << self; end
- end
+ extend ActiveSupport::DependencyModule
+
+ included do
+ after_create :remember
+ protected
+ def remember; self.class.remembered << self; end
end
module ClassMethods
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 40723814c5..d23f86b700 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -223,6 +223,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_eager_association_loading_with_belongs_to_and_conditions_hash
+ comments = []
+ assert_nothing_raised do
+ comments = Comment.find(:all, :include => :post, :conditions => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id')
+ end
+ assert_equal 3, comments.length
+ assert_equal [5,6,7], comments.collect { |c| c.id }
+ assert_no_queries do
+ comments.first.post
+ end
+ end
+
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 5e8b2cadfc..8dc95806b9 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -24,6 +24,7 @@ require 'models/club'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
+require 'active_support/core_ext/string/conversions'
class ProjectWithAfterCreateHook < ActiveRecord::Base
set_table_name 'projects'
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
new file mode 100644
index 0000000000..47f83db112
--- /dev/null
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -0,0 +1,313 @@
+require "cases/helper"
+require 'models/man'
+require 'models/face'
+require 'models/interest'
+require 'models/zine'
+require 'models/club'
+require 'models/sponsor'
+
+class InverseAssociationTests < ActiveRecord::TestCase
+ def test_should_allow_for_inverse_of_options_in_associations
+ assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_many') do
+ Class.new(ActiveRecord::Base).has_many(:wheels, :inverse_of => :car)
+ end
+
+ assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_one') do
+ Class.new(ActiveRecord::Base).has_one(:engine, :inverse_of => :car)
+ end
+
+ assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on belongs_to') do
+ Class.new(ActiveRecord::Base).belongs_to(:car, :inverse_of => :driver)
+ end
+ end
+
+ def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse
+ has_one_with_inverse_ref = Man.reflect_on_association(:face)
+ assert has_one_with_inverse_ref.respond_to?(:has_inverse?)
+ assert has_one_with_inverse_ref.has_inverse?
+
+ has_many_with_inverse_ref = Man.reflect_on_association(:interests)
+ assert has_many_with_inverse_ref.respond_to?(:has_inverse?)
+ assert has_many_with_inverse_ref.has_inverse?
+
+ belongs_to_with_inverse_ref = Face.reflect_on_association(:man)
+ assert belongs_to_with_inverse_ref.respond_to?(:has_inverse?)
+ assert belongs_to_with_inverse_ref.has_inverse?
+
+ has_one_without_inverse_ref = Club.reflect_on_association(:sponsor)
+ assert has_one_without_inverse_ref.respond_to?(:has_inverse?)
+ assert !has_one_without_inverse_ref.has_inverse?
+
+ has_many_without_inverse_ref = Club.reflect_on_association(:memberships)
+ assert has_many_without_inverse_ref.respond_to?(:has_inverse?)
+ assert !has_many_without_inverse_ref.has_inverse?
+
+ belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club)
+ assert belongs_to_without_inverse_ref.respond_to?(:has_inverse?)
+ assert !belongs_to_without_inverse_ref.has_inverse?
+ end
+
+ def test_should_be_able_to_ask_a_reflection_what_it_is_the_inverse_of
+ has_one_ref = Man.reflect_on_association(:face)
+ assert has_one_ref.respond_to?(:inverse_of)
+
+ has_many_ref = Man.reflect_on_association(:interests)
+ assert has_many_ref.respond_to?(:inverse_of)
+
+ belongs_to_ref = Face.reflect_on_association(:man)
+ assert belongs_to_ref.respond_to?(:inverse_of)
+ end
+
+ def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of
+ has_one_ref = Man.reflect_on_association(:face)
+ assert_equal Face.reflect_on_association(:man), has_one_ref.inverse_of
+
+ has_many_ref = Man.reflect_on_association(:interests)
+ assert_equal Interest.reflect_on_association(:man), has_many_ref.inverse_of
+
+ belongs_to_ref = Face.reflect_on_association(:man)
+ assert_equal Man.reflect_on_association(:face), belongs_to_ref.inverse_of
+ end
+
+ def test_associations_with_no_inverse_of_should_return_nil
+ has_one_ref = Club.reflect_on_association(:sponsor)
+ assert_nil has_one_ref.inverse_of
+
+ has_many_ref = Club.reflect_on_association(:memberships)
+ assert_nil has_many_ref.inverse_of
+
+ belongs_to_ref = Sponsor.reflect_on_association(:sponsor_club)
+ assert_nil belongs_to_ref.inverse_of
+ end
+end
+
+class InverseHasOneTests < ActiveRecord::TestCase
+ fixtures :men, :faces
+
+ def test_parent_instance_should_be_shared_with_child_on_find
+ m = Man.find(:first)
+ f = m.face
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
+ f.man.name = 'Mungo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+
+ def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find
+ m = Man.find(:first, :include => :face)
+ f = m.face
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
+ f.man.name = 'Mungo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
+
+ m = Man.find(:first, :include => :face, :order => 'faces.id')
+ f = m.face
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
+ f.man.name = 'Mungo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_built_child
+ m = Man.find(:first)
+ f = m.build_face(:description => 'haunted')
+ assert_not_nil f.man
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
+ f.man.name = 'Mungo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_created_child
+ m = Man.find(:first)
+ f = m.create_face(:description => 'haunted')
+ assert_not_nil f.man
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
+ f.man.name = 'Mungo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
+ end
+
+ def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face }
+ end
+end
+
+class InverseHasManyTests < ActiveRecord::TestCase
+ fixtures :men, :interests
+
+ def test_parent_instance_should_be_shared_with_every_child_on_find
+ m = Man.find(:first)
+ is = m.interests
+ is.each do |i|
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+ end
+
+ def test_parent_instance_should_be_shared_with_eager_loaded_children
+ m = Man.find(:first, :include => :interests)
+ is = m.interests
+ is.each do |i|
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+ m = Man.find(:first, :include => :interests, :order => 'interests.id')
+ is = m.interests
+ is.each do |i|
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_built_child
+ m = Man.find(:first)
+ i = m.interests.build(:topic => 'Industrial Revolution Re-enactment')
+ assert_not_nil i.man
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_created_child
+ m = Man.find(:first)
+ i = m.interests.create(:topic => 'Industrial Revolution Re-enactment')
+ assert_not_nil i.man
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_poked_in_child
+ m = Man.find(:first)
+ i = Interest.create(:topic => 'Industrial Revolution Re-enactment')
+ m.interests << i
+ assert_not_nil i.man
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
+ end
+
+ def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests }
+ end
+end
+
+class InverseBelongsToTests < ActiveRecord::TestCase
+ fixtures :men, :faces, :interests
+
+ def test_child_instance_should_be_shared_with_parent_on_find
+ f = Face.find(:first)
+ m = f.man
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
+ f.description = 'gormless'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
+ m.face.description = 'pleasing'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
+ end
+
+ def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
+ f = Face.find(:first, :include => :man)
+ m = f.man
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
+ f.description = 'gormless'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
+ m.face.description = 'pleasing'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
+
+
+ f = Face.find(:first, :include => :man, :order => 'men.id')
+ m = f.man
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
+ f.description = 'gormless'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
+ m.face.description = 'pleasing'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
+ end
+
+ def test_child_instance_should_be_shared_with_newly_built_parent
+ f = Face.find(:first)
+ m = f.build_man(:name => 'Charles')
+ assert_not_nil m.face
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
+ f.description = 'gormless'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
+ m.face.description = 'pleasing'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to just-built-parent-owned instance"
+ end
+
+ def test_child_instance_should_be_shared_with_newly_created_parent
+ f = Face.find(:first)
+ m = f.create_man(:name => 'Charles')
+ assert_not_nil m.face
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
+ f.description = 'gormless'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
+ m.face.description = 'pleasing'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to newly-created-parent-owned instance"
+ end
+
+ def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
+ i = Interest.find(:first)
+ m = i.man
+ assert_not_nil m.interests
+ iz = m.interests.detect {|iz| iz.id == i.id}
+ assert_not_nil iz
+ assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
+ i.topic = 'Eating cheese with a spoon'
+ assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child"
+ iz.topic = 'Cow tipping'
+ assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance"
+ end
+
+ def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man }
+ end
+end
+
+# NOTE - these tests might not be meaningful, ripped as they were from the parental_control plugin
+# which would guess the inverse rather than look for an explicit configuration option.
+class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
+ fixtures :men, :interests, :zines
+
+ def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
+ assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
+ i = Interest.find(:first)
+ z = i.zine
+ m = i.man
+ end
+ end
+
+ def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models
+ assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
+ i = Interest.find(:first)
+ i.build_zine(:title => 'Get Some in Winter! 2008')
+ i.build_man(:name => 'Gordon')
+ i.save!
+ end
+ end
+end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 7ca2807f7e..59aa6953e3 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -18,6 +18,7 @@ require 'models/minimalistic'
require 'models/warehouse_thing'
require 'models/parrot'
require 'rexml/document'
+require 'active_support/core_ext/exception'
class Category < ActiveRecord::Base; end
class Categorization < ActiveRecord::Base; end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 28eb311618..d0d7094e30 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -119,6 +119,12 @@ class FinderTest < ActiveRecord::TestCase
Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
+ def test_exists_with_scoped_include
+ Developer.with_scope(:find => { :include => :projects, :order => "projects.name" }) do
+ assert Developer.exists?
+ end
+ end
+
def test_find_by_array_of_one_id
assert_kind_of(Array, Topic.find([ 1 ]))
assert_equal(1, Topic.find([ 1 ]).length)
@@ -485,8 +491,9 @@ class FinderTest < ActiveRecord::TestCase
assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
end
- def test_bind_string
- assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '')
+ def test_bind_empty_string
+ quoted_empty = ActiveRecord::Base.connection.quote('')
+ assert_equal quoted_empty, bind('?', '')
end
def test_bind_chars
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 1ec52ac24d..05e92433cd 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -15,6 +15,11 @@ require 'connection'
require 'cases/repair_helper'
+begin
+ require 'ruby-debug'
+rescue LoadError
+end
+
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 2165d2f7e8..c479859ee0 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -628,9 +628,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_named_scope
- expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.salary }
+ def test_named_scope_overwrites_default
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
assert_equal expected, received
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index ae6a54a5bd..7dcea6d42e 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/array/random_access'
require 'models/post'
require 'models/topic'
require 'models/comment'
@@ -265,7 +266,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_rand_should_select_a_random_object_from_proxy
- assert Topic.approved.rand.is_a?(Topic)
+ assert_kind_of Topic, Topic.approved.rand
end
def test_should_use_where_in_query_for_named_scope
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index cd6277c24b..f31275163d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -4,6 +4,7 @@ require "models/ship"
require "models/bird"
require "models/parrot"
require "models/treasure"
+require 'active_support/hash_with_indifferent_access'
module AssertRaiseWithMessage
def assert_raise_with_message(expected_exception, expected_message)
@@ -31,11 +32,27 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_should_add_a_proc_to_reject_new_nested_attributes_procs
- [:parrots, :birds].each do |name|
+ [:parrots, :birds, :birds_with_reject_all_blank].each do |name|
assert_instance_of Proc, Pirate.reject_new_nested_attributes_procs[name]
end
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 => ''}]
+ pirate.save!
+
+ assert pirate.birds_with_reject_all_blank.empty?
+ end
+
+ def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
+ pirate.birds_with_reject_all_blank_attributes = [{:name => 'Tweetie', :color => ''}]
+ pirate.save!
+
+ assert_equal 1, pirate.birds_with_reject_all_blank.count
+ end
+
def test_should_raise_an_ArgumentError_for_non_existing_associations
assert_raise_with_message ArgumentError, "No association found for name `honesty'. Has it been defined yet?" do
Pirate.accepts_nested_attributes_for :honesty
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 2649a9358a..bb9013c2a1 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -1,4 +1,6 @@
require "cases/helper"
+require "models/project"
+require "timeout"
class PooledConnectionsTest < ActiveRecord::TestCase
def setup
@@ -89,6 +91,33 @@ class PooledConnectionsTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.connection_handler = old_handler
end
+
+ def test_with_connection_nesting_safety
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1, :wait_timeout => 0.1}))
+
+ before_count = Project.count
+
+ add_record('one')
+
+ ActiveRecord::Base.connection.transaction do
+ add_record('two')
+ # Have another thread try to screw up the transaction
+ Thread.new do
+ ActiveRecord::Base.connection.rollback_db_transaction
+ ActiveRecord::Base.connection_pool.release_connection
+ end.join rescue nil
+ add_record('three')
+ end
+
+ after_count = Project.count
+ assert_equal 3, after_count - before_count
+ end
+
+ private
+
+ def add_record(name)
+ ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name }
+ end
end unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
class AllowConcurrencyDeprecatedTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/repair_helper.rb b/activerecord/test/cases/repair_helper.rb
index 0155668811..686bfee46d 100644
--- a/activerecord/test/cases/repair_helper.rb
+++ b/activerecord/test/cases/repair_helper.rb
@@ -1,11 +1,7 @@
module ActiveRecord
module Testing
module RepairHelper
- def self.included(base)
- base.class_eval do
- extend ClassMethods
- end
- end
+ extend ActiveSupport::DependencyModule
module Toolbox
def self.record_validations(*model_classes)
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 66982346e9..20d1369a64 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -38,35 +38,17 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
end
end
- def test_default_error_messages_is_deprecated
- assert_deprecated('ActiveRecord::Errors.default_error_messages') do
- ActiveRecord::Errors.default_error_messages
+ def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated
+ assert_not_deprecated do
+ default = "%s interpolation syntax was deprecated"
+ assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this')
end
end
- def test_percent_s_interpolation_syntax_in_error_messages_still_works
- ActiveSupport::Deprecation.silence do
- result = I18n.t :does_not_exist, :default => "%s interpolation syntax is deprecated", :value => 'this'
- assert_equal result, "this interpolation syntax is deprecated"
- end
- end
-
- def test_percent_s_interpolation_syntax_in_error_messages_is_deprecated
- assert_deprecated('using %s in messages') do
- I18n.t :does_not_exist, :default => "%s interpolation syntax is deprected", :value => 'this'
- end
- end
-
- def test_percent_d_interpolation_syntax_in_error_messages_still_works
- ActiveSupport::Deprecation.silence do
- result = I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprecated", :count => 2
- assert_equal result, "2 interpolation syntaxes are deprecated"
- end
- end
-
- def test_percent_d_interpolation_syntax_in_error_messages_is_deprecated
- assert_deprecated('using %d in messages') do
- I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprected", :count => 2
+ def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated
+ assert_not_deprecated do
+ default = "%d interpolation syntaxes are deprecated"
+ assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2)
end
end
diff --git a/activerecord/test/fixtures/faces.yml b/activerecord/test/fixtures/faces.yml
new file mode 100644
index 0000000000..1dd2907cf7
--- /dev/null
+++ b/activerecord/test/fixtures/faces.yml
@@ -0,0 +1,7 @@
+trusting:
+ description: trusting
+ man: gordon
+
+weather_beaten:
+ description: weather beaten
+ man: steve
diff --git a/activerecord/test/fixtures/interests.yml b/activerecord/test/fixtures/interests.yml
new file mode 100644
index 0000000000..ec71890ab6
--- /dev/null
+++ b/activerecord/test/fixtures/interests.yml
@@ -0,0 +1,29 @@
+trainspotting:
+ topic: Trainspotting
+ zine: staying_in
+ man: gordon
+
+birdwatching:
+ topic: Birdwatching
+ zine: staying_in
+ man: gordon
+
+stamp_collecting:
+ topic: Stamp Collecting
+ zine: staying_in
+ man: gordon
+
+hunting:
+ topic: Hunting
+ zine: going_out
+ man: steve
+
+woodsmanship:
+ topic: Woodsmanship
+ zine: going_out
+ man: steve
+
+survial:
+ topic: Survival
+ zine: going_out
+ man: steve
diff --git a/activerecord/test/fixtures/men.yml b/activerecord/test/fixtures/men.yml
new file mode 100644
index 0000000000..c67429f925
--- /dev/null
+++ b/activerecord/test/fixtures/men.yml
@@ -0,0 +1,5 @@
+gordon:
+ name: Gordon
+
+steve:
+ name: Steve
diff --git a/activerecord/test/fixtures/zines.yml b/activerecord/test/fixtures/zines.yml
new file mode 100644
index 0000000000..07dce4db7e
--- /dev/null
+++ b/activerecord/test/fixtures/zines.yml
@@ -0,0 +1,5 @@
+staying_in:
+ title: Staying in '08
+
+going_out:
+ title: Outdoor Pursuits 2k+8
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 7f02403d5a..3c34efbe16 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/misc'
+
module MyApplication
module Business
class Company < ActiveRecord::Base
diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb
new file mode 100644
index 0000000000..1540dbf741
--- /dev/null
+++ b/activerecord/test/models/face.rb
@@ -0,0 +1,5 @@
+class Face < ActiveRecord::Base
+ belongs_to :man, :inverse_of => :face
+ # This is a "broken" inverse_of for the purposes of testing
+ belongs_to :horrible_man, :class_name => 'Man', :inverse_of => :horrible_face
+end
diff --git a/activerecord/test/models/interest.rb b/activerecord/test/models/interest.rb
new file mode 100644
index 0000000000..d8291d00cc
--- /dev/null
+++ b/activerecord/test/models/interest.rb
@@ -0,0 +1,4 @@
+class Interest < ActiveRecord::Base
+ belongs_to :man, :inverse_of => :interests
+ belongs_to :zine, :inverse_of => :interests
+end
diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb
new file mode 100644
index 0000000000..f40bc9d0fc
--- /dev/null
+++ b/activerecord/test/models/man.rb
@@ -0,0 +1,7 @@
+class Man < ActiveRecord::Base
+ has_one :face, :inverse_of => :man
+ has_many :interests, :inverse_of => :man
+ # These are "broken" inverse_of associations for the purposes of testing
+ has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man
+ has_many :secret_interests, :class_name => 'Interest', :inverse_of => :secret_man
+end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 238917bf30..acf53fce8b 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -28,11 +28,13 @@ class Pirate < ActiveRecord::Base
:after_add => proc {|p,b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}"},
:before_remove => proc {|p,b| p.ship_log << "before_removing_proc_bird_#{b.id}"},
:after_remove => proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"}
+ has_many :birds_with_reject_all_blank, :class_name => "Bird"
accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks,
:birds_with_method_callbacks, :birds_with_proc_callbacks, :allow_destroy => true
+ accepts_nested_attributes_for :birds_with_reject_all_blank, :reject_if => :all_blank
validates_presence_of :catchphrase
diff --git a/activerecord/test/models/zine.rb b/activerecord/test/models/zine.rb
new file mode 100644
index 0000000000..c2d0fdaf25
--- /dev/null
+++ b/activerecord/test/models/zine.rb
@@ -0,0 +1,3 @@
+class Zine < ActiveRecord::Base
+ has_many :interests, :inverse_of => :zine
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 98e6d192a5..a776cd974b 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -57,6 +57,7 @@ ActiveRecord::Schema.define do
create_table :birds, :force => true do |t|
t.string :name
+ t.string :color
t.integer :pirate_id
end
@@ -467,6 +468,26 @@ ActiveRecord::Schema.define do
end
end
+ # NOTE - the following 4 tables are used by models that have :inverse_of options on the associations
+ create_table :men, :force => true do |t|
+ t.string :name
+ end
+
+ create_table :faces, :force => true do |t|
+ t.string :description
+ t.integer :man_id
+ end
+
+ create_table :interests, :force => true do |t|
+ t.string :topic
+ t.integer :man_id
+ t.integer :zine_id
+ end
+
+ create_table :zines, :force => true do |t|
+ t.string :title
+ end
+
except 'SQLite' do
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, :force => true do |t|