aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2009-07-03 13:01:39 +0100
committerPratik Naik <pratiknaik@gmail.com>2009-07-03 13:01:39 +0100
commit2fe263bb328c20539f2970057f31e567ec4ab7c8 (patch)
tree63e85164fb09aca6beb78e1a5c52424fa49ed098 /activerecord/lib
parentbf5ac9965f12840d469ef2a4a16e8205dbbe5253 (diff)
parenta4bdc00fec623f72592e663e6d7830eea0bc6ea4 (diff)
downloadrails-2fe263bb328c20539f2970057f31e567ec4ab7c8.tar.gz
rails-2fe263bb328c20539f2970057f31e567ec4ab7c8.tar.bz2
rails-2fe263bb328c20539f2970057f31e567ec4ab7c8.zip
Merge commit 'mainstream/master'
Conflicts: actionpack/lib/action_controller.rb actionpack/lib/action_controller/base/base.rb actionpack/lib/action_view/template/path.rb activesupport/lib/active_support/json/encoders/hash.rb
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb2
-rwxr-xr-xactiverecord/lib/active_record/associations.rb9
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb14
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb165
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb22
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb154
-rwxr-xr-xactiverecord/lib/active_record/base.rb54
-rw-r--r--activerecord/lib/active_record/callbacks.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb12
-rw-r--r--activerecord/lib/active_record/migration.rb5
-rw-r--r--activerecord/lib/active_record/named_scope.rb3
-rw-r--r--activerecord/lib/active_record/observer.rb95
-rw-r--r--activerecord/lib/active_record/serialization.rb5
-rw-r--r--activerecord/lib/active_record/serializers/json_serializer.rb32
-rw-r--r--activerecord/lib/active_record/validations.rb5
-rw-r--r--activerecord/lib/active_record/version.rb6
23 files changed, 315 insertions, 352 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index ee6f99edb5..63eb5c3eeb 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -59,7 +59,7 @@ module ActiveRecord
autoload :Migrator, 'active_record/migration'
autoload :NamedScope, 'active_record/named_scope'
autoload :NestedAttributes, 'active_record/nested_attributes'
- autoload :Observing, 'active_record/observer'
+ autoload :Observer, 'active_record/observer'
autoload :QueryCache, 'active_record/query_cache'
autoload :Reflection, 'active_record/reflection'
autoload :Schema, 'active_record/schema'
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 2134266f72..d3c859ccf4 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1308,13 +1308,8 @@ module ActiveRecord
association = association_proxy_class.new(self, reflection)
end
- if association_proxy_class == HasOneThroughAssociation
- association.create_through_record(new_value)
- self.send(reflection.name, new_value)
- else
- association.replace(new_value)
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
- end
+ association.replace(new_value)
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
end
define_method("set_#{reflection.name}_target") do |target|
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index c3e5bd8575..e67ccfb228 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -351,7 +351,19 @@ module ActiveRecord
protected
def construct_find_options!(options)
end
-
+
+ def construct_counter_sql
+ if @reflection.options[:counter_sql]
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ elsif @reflection.options[:finder_sql]
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ else
+ @counter_sql = @finder_sql
+ end
+ end
+
def load_target
if !@owner.new_record? || foreign_key_present
begin
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index af9ce3dfb2..fd23e59e82 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -85,15 +85,7 @@ module ActiveRecord
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
- if @reflection.options[:counter_sql]
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
- elsif @reflection.options[:finder_sql]
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
- else
- @counter_sql = @finder_sql
- end
+ construct_counter_sql
end
def construct_scope
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 73dd50dd07..e4b631bc54 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -97,15 +97,7 @@ module ActiveRecord
@finder_sql << " AND (#{conditions})" if conditions
end
- if @reflection.options[:counter_sql]
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
- elsif @reflection.options[:finder_sql]
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
- else
- @counter_sql = @finder_sql
- end
+ construct_counter_sql
end
def construct_scope
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 e8dbae9011..e21ef90391 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,6 +1,10 @@
+require "active_record/associations/through_association_scope"
+
module ActiveRecord
module Associations
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
+ include ThroughAssociationScope
+
alias_method :new, :build
def create!(attrs = nil)
@@ -72,114 +76,7 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- @reflection.klass.find(:all,
- :select => construct_select,
- :conditions => construct_conditions,
- :from => construct_from,
- :joins => construct_joins,
- :order => @reflection.options[:order],
- :limit => @reflection.options[:limit],
- :group => @reflection.options[:group],
- :readonly => @reflection.options[:readonly],
- :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
- )
- end
-
- # Construct attributes for associate pointing to owner.
- def construct_owner_attributes(reflection)
- if as = reflection.options[:as]
- { "#{as}_id" => @owner.id,
- "#{as}_type" => @owner.class.base_class.name.to_s }
- else
- { reflection.primary_key_name => @owner.id }
- end
- end
-
- # Construct attributes for :through pointing to owner and associate.
- def construct_join_attributes(associate)
- # TODO: revist this to allow it for deletion, supposing dependent option is supported
- raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
- join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
- if @reflection.options[:source_type]
- join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
- end
- join_attributes
- end
-
- # Associate attributes pointing to owner, quoted.
- def construct_quoted_owner_attributes(reflection)
- if as = reflection.options[:as]
- { "#{as}_id" => owner_quoted_id,
- "#{as}_type" => reflection.klass.quote_value(
- @owner.class.base_class.name.to_s,
- reflection.klass.columns_hash["#{as}_type"]) }
- elsif reflection.macro == :belongs_to
- { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
- else
- { reflection.primary_key_name => owner_quoted_id }
- end
- end
-
- # Build SQL conditions from attributes, qualified by table name.
- def construct_conditions
- table_name = @reflection.through_reflection.quoted_table_name
- conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
- "#{table_name}.#{attr} = #{value}"
- end
- conditions << sql_conditions if sql_conditions
- "(" + conditions.join(') AND (') + ")"
- end
-
- def construct_from
- @reflection.quoted_table_name
- end
-
- def construct_select(custom_select = nil)
- distinct = "DISTINCT " if @reflection.options[:uniq]
- selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
- end
-
- def construct_joins(custom_joins = nil)
- polymorphic_join = nil
- if @reflection.source_reflection.macro == :belongs_to
- reflection_primary_key = @reflection.klass.primary_key
- source_primary_key = @reflection.source_reflection.primary_key_name
- if @reflection.options[:source_type]
- polymorphic_join = "AND %s.%s = %s" % [
- @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
- @owner.class.quote_value(@reflection.options[:source_type])
- ]
- end
- else
- reflection_primary_key = @reflection.source_reflection.primary_key_name
- source_primary_key = @reflection.through_reflection.klass.primary_key
- if @reflection.source_reflection.options[:as]
- polymorphic_join = "AND %s.%s = %s" % [
- @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
- @owner.class.quote_value(@reflection.through_reflection.klass.name)
- ]
- end
- end
-
- "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
- @reflection.through_reflection.quoted_table_name,
- @reflection.quoted_table_name, reflection_primary_key,
- @reflection.through_reflection.quoted_table_name, source_primary_key,
- polymorphic_join
- ]
- end
-
- def construct_scope
- { :create => construct_owner_attributes(@reflection),
- :find => { :from => construct_from,
- :conditions => construct_conditions,
- :joins => construct_joins,
- :include => @reflection.options[:include],
- :select => construct_select,
- :order => @reflection.options[:order],
- :limit => @reflection.options[:limit],
- :readonly => @reflection.options[:readonly],
- } }
+ with_scope(construct_scope) { @reflection.klass.find(:all) }
end
def construct_sql
@@ -193,59 +90,9 @@ module ActiveRecord
@finder_sql = construct_conditions
end
- if @reflection.options[:counter_sql]
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
- elsif @reflection.options[:finder_sql]
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
- else
- @counter_sql = @finder_sql
- end
- end
-
- def conditions
- @conditions = build_conditions unless defined?(@conditions)
- @conditions
+ construct_counter_sql
end
- def build_conditions
- association_conditions = @reflection.options[:conditions]
- through_conditions = build_through_conditions
- source_conditions = @reflection.source_reflection.options[:conditions]
- uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
-
- if association_conditions || through_conditions || source_conditions || uses_sti
- all = []
-
- [association_conditions, source_conditions].each do |conditions|
- all << interpolate_sql(sanitize_sql(conditions)) if conditions
- end
-
- all << through_conditions if through_conditions
- all << build_sti_condition if uses_sti
-
- all.map { |sql| "(#{sql})" } * ' AND '
- end
- end
-
- def build_through_conditions
- conditions = @reflection.through_reflection.options[:conditions]
- if conditions.is_a?(Hash)
- interpolate_sql(sanitize_sql(conditions)).gsub(
- @reflection.quoted_table_name,
- @reflection.through_reflection.quoted_table_name)
- elsif conditions
- interpolate_sql(sanitize_sql(conditions))
- end
- end
-
- def build_sti_condition
- @reflection.through_reflection.klass.send(:type_condition)
- end
-
- alias_method :sql_conditions, :conditions
-
def has_cached_counter?
@owner.attribute_present?(cached_counter_attribute_name)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index b72b84343b..c2568d0c0c 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Associations
- class HasOneAssociation < BelongsToAssociation #:nodoc:
+ class HasOneAssociation < AssociationProxy #:nodoc:
def initialize(owner, reflection)
super
construct_sql
@@ -77,7 +77,7 @@ module ActiveRecord
the_target = @reflection.klass.find(:first,
:conditions => @finder_sql,
:select => @reflection.options[:select],
- :order => @reflection.options[:order],
+ :order => @reflection.options[:order],
:include => @reflection.options[:include],
:readonly => @reflection.options[:readonly]
)
@@ -88,7 +88,7 @@ module ActiveRecord
def construct_sql
case
when @reflection.options[:as]
- @finder_sql =
+ @finder_sql =
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
else
@@ -96,7 +96,7 @@ module ActiveRecord
end
@finder_sql << " AND (#{conditions})" if conditions
end
-
+
def construct_scope
create_scoping = {}
set_belongs_to_association_for(create_scoping)
@@ -113,7 +113,7 @@ module ActiveRecord
end
if replace_existing
- replace(record, true)
+ replace(record, true)
else
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
self.target = record
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index d93c8e7852..830aa1808a 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -1,6 +1,16 @@
+require "active_record/associations/through_association_scope"
+
module ActiveRecord
module Associations
- class HasOneThroughAssociation < HasManyThroughAssociation
+ class HasOneThroughAssociation < HasOneAssociation
+ include ThroughAssociationScope
+
+ def replace(new_value)
+ create_through_record(new_value)
+ @target = new_value
+ end
+
+ private
def create_through_record(new_value) #nodoc:
klass = @reflection.through_reflection.klass
@@ -15,16 +25,8 @@ module ActiveRecord
end
private
- def find(*args)
- super(args.merge(:limit => 1))
- end
-
def find_target
- super.first
- end
-
- def reset_target!
- @target = nil
+ with_scope(construct_scope) { @reflection.klass.find(:first) }
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
new file mode 100644
index 0000000000..8e7ce33814
--- /dev/null
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -0,0 +1,154 @@
+module ActiveRecord
+ module Associations
+ module ThroughAssociationScope
+
+ protected
+
+ def construct_scope
+ { :create => construct_owner_attributes(@reflection),
+ :find => { :from => construct_from,
+ :conditions => construct_conditions,
+ :joins => construct_joins,
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
+ :select => construct_select,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :readonly => @reflection.options[:readonly],
+ } }
+ end
+
+ # Build SQL conditions from attributes, qualified by table name.
+ def construct_conditions
+ table_name = @reflection.through_reflection.quoted_table_name
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
+ "#{table_name}.#{attr} = #{value}"
+ end
+ conditions << sql_conditions if sql_conditions
+ "(" + conditions.join(') AND (') + ")"
+ end
+
+ # Associate attributes pointing to owner, quoted.
+ def construct_quoted_owner_attributes(reflection)
+ if as = reflection.options[:as]
+ { "#{as}_id" => owner_quoted_id,
+ "#{as}_type" => reflection.klass.quote_value(
+ @owner.class.base_class.name.to_s,
+ reflection.klass.columns_hash["#{as}_type"]) }
+ elsif reflection.macro == :belongs_to
+ { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
+ else
+ { reflection.primary_key_name => owner_quoted_id }
+ end
+ end
+
+ def construct_from
+ @reflection.quoted_table_name
+ end
+
+ def construct_select(custom_select = nil)
+ distinct = "DISTINCT " if @reflection.options[:uniq]
+ selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
+ end
+
+ def construct_joins(custom_joins = nil)
+ polymorphic_join = nil
+ if @reflection.source_reflection.macro == :belongs_to
+ reflection_primary_key = @reflection.klass.primary_key
+ source_primary_key = @reflection.source_reflection.primary_key_name
+ if @reflection.options[:source_type]
+ polymorphic_join = "AND %s.%s = %s" % [
+ @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
+ @owner.class.quote_value(@reflection.options[:source_type])
+ ]
+ end
+ else
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
+ source_primary_key = @reflection.through_reflection.klass.primary_key
+ if @reflection.source_reflection.options[:as]
+ polymorphic_join = "AND %s.%s = %s" % [
+ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
+ @owner.class.quote_value(@reflection.through_reflection.klass.name)
+ ]
+ end
+ end
+
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
+ @reflection.through_reflection.quoted_table_name,
+ @reflection.quoted_table_name, reflection_primary_key,
+ @reflection.through_reflection.quoted_table_name, source_primary_key,
+ polymorphic_join
+ ]
+ end
+
+ # Construct attributes for associate pointing to owner.
+ def construct_owner_attributes(reflection)
+ if as = reflection.options[:as]
+ { "#{as}_id" => @owner.id,
+ "#{as}_type" => @owner.class.base_class.name.to_s }
+ else
+ { reflection.primary_key_name => @owner.id }
+ end
+ end
+
+ # Construct attributes for :through pointing to owner and associate.
+ def construct_join_attributes(associate)
+ # TODO: revist this to allow it for deletion, supposing dependent option is supported
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
+
+ join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
+
+ if @reflection.options[:source_type]
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
+ end
+
+ if @reflection.through_reflection.options[:conditions].is_a?(Hash)
+ join_attributes.merge!(@reflection.through_reflection.options[:conditions])
+ end
+
+ join_attributes
+ end
+
+ def conditions
+ @conditions = build_conditions unless defined?(@conditions)
+ @conditions
+ end
+
+ def build_conditions
+ association_conditions = @reflection.options[:conditions]
+ through_conditions = build_through_conditions
+ source_conditions = @reflection.source_reflection.options[:conditions]
+ uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
+
+ if association_conditions || through_conditions || source_conditions || uses_sti
+ all = []
+
+ [association_conditions, source_conditions].each do |conditions|
+ all << interpolate_sql(sanitize_sql(conditions)) if conditions
+ end
+
+ all << through_conditions if through_conditions
+ all << build_sti_condition if uses_sti
+
+ all.map { |sql| "(#{sql})" } * ' AND '
+ end
+ end
+
+ def build_through_conditions
+ conditions = @reflection.through_reflection.options[:conditions]
+ if conditions.is_a?(Hash)
+ interpolate_sql(sanitize_sql(conditions)).gsub(
+ @reflection.quoted_table_name,
+ @reflection.through_reflection.quoted_table_name)
+ elsif conditions
+ interpolate_sql(sanitize_sql(conditions))
+ end
+ end
+
+ def build_sti_condition
+ @reflection.through_reflection.klass.send(:type_condition)
+ end
+
+ alias_method :sql_conditions, :conditions
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 26ac2e0de4..bb7342ca6e 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -11,6 +11,7 @@ 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_ext/symbol'
+require 'active_support/core_ext/object/metaclass'
module ActiveRecord #:nodoc:
# Generic Active Record exception class.
@@ -66,6 +67,25 @@ module ActiveRecord #:nodoc:
class StatementInvalid < ActiveRecordError
end
+ # Parent class for all specific exceptions which wrap database driver exceptions
+ # provides access to the original exception also.
+ class WrappedDatabaseException < StatementInvalid
+ attr_reader :original_exception
+
+ def initialize(message, original_exception)
+ super(message)
+ @original_exception, = original_exception
+ end
+ end
+
+ # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
+ class RecordNotUnique < WrappedDatabaseException
+ end
+
+ # Raised when a record cannot be inserted or updated because it references a non-existent record.
+ class InvalidForeignKey < WrappedDatabaseException
+ end
+
# Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
# does not match number of expected variables.
#
@@ -1420,14 +1440,14 @@ module ActiveRecord #:nodoc:
end
# Transform the modelname into a more humane format, using I18n.
- # Defaults to the basic humanize method.
+ # By default, it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post").
# Default scope of the translation is activerecord.models
# Specify +options+ with additional translating options.
def human_name(options = {})
defaults = self_and_descendants_from_active_record.map do |klass|
:"#{klass.name.underscore}"
- end
- defaults << self.name.humanize
+ end
+ defaults << self.name.underscore.humanize
I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
end
@@ -2076,7 +2096,7 @@ module ActiveRecord #:nodoc:
# end
# end
def define_attr_method(name, value=nil, &block)
- sing = class << self; self; end
+ sing = metaclass
sing.send :alias_method, "original_#{name}", name
if block_given?
sing.send :define_method, name, &block
@@ -2886,6 +2906,13 @@ module ActiveRecord #:nodoc:
@attributes.frozen?
end
+ # Returns duplicated record with unfreezed attributes.
+ def dup
+ obj = super
+ obj.instance_variable_set('@attributes', instance_variable_get('@attributes').dup)
+ obj
+ end
+
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
# attributes will be marked as read only since they cannot be saved.
def readonly?
@@ -3079,11 +3106,11 @@ module ActiveRecord #:nodoc:
def execute_callstack_for_multiparameter_attributes(callstack)
errors = []
callstack.each do |name, values|
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- if values.empty?
- send(name + "=", nil)
- else
- begin
+ begin
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values.empty?
+ send(name + "=", nil)
+ else
value = if Time == klass
instantiate_time_object(name, values)
elsif Date == klass
@@ -3097,9 +3124,9 @@ module ActiveRecord #:nodoc:
end
send(name + "=", value)
- rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
end
+ rescue => ex
+ errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
end
end
unless errors.empty?
@@ -3125,7 +3152,7 @@ module ActiveRecord #:nodoc:
end
def type_cast_attribute_value(multiparameter_name, value)
- multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
end
def find_parameter_position(multiparameter_name)
@@ -3180,12 +3207,13 @@ module ActiveRecord #:nodoc:
end
Base.class_eval do
+ extend ActiveModel::Naming
extend QueryCache::ClassMethods
include Validations
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
include Dirty
- include Callbacks, Observing, Timestamp
+ include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
# AutosaveAssociation needs to be included before Transactions, because we want
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 36f5f2ce47..01e41c04df 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -353,10 +353,5 @@ module ActiveRecord
return result
end
-
- def notify(method) #:nodoc:
- self.class.changed
- self.class.notify_observers(method, self)
- end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index a41912a703..2473c772e3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -109,7 +109,7 @@ module ActiveRecord
table_definition = TableDefinition.new(self)
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
- yield table_definition
+ yield table_definition if block_given?
if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 91b111ab55..c533d4cdb6 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -211,9 +211,14 @@ module ActiveRecord
@last_verification = 0
message = "#{e.class.name}: #{e.message}: #{sql}"
log_info(message, name, 0)
- raise ActiveRecord::StatementInvalid, message
+ raise translate_exception(e, message)
end
+ def translate_exception(e, message)
+ # override in derived class
+ ActiveRecord::StatementInvalid.new(message)
+ end
+
def format_log_entry(message, dump = nil)
if ActiveRecord::Base.colorize_logging
if @@row_even
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index d5536e4d67..83cb9cff15 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -563,6 +563,19 @@ module ActiveRecord
where_sql
end
+ protected
+
+ def translate_exception(exception, message)
+ case exception.errno
+ when 1062
+ RecordNotUnique.new(message, exception)
+ when 1452
+ InvalidForeignKey.new(message, exception)
+ else
+ super
+ end
+ end
+
private
def connect
encoding = @config[:encoding]
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 002696d2c4..e77ae93c21 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -288,7 +288,13 @@ module ActiveRecord
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
- if PGconn.respond_to?(:escape_bytea)
+ if @connection.respond_to?(:escape_bytea)
+ self.class.instance_eval do
+ define_method(:escape_bytea) do |value|
+ @connection.escape_bytea(value) if value
+ end
+ end
+ elsif PGconn.respond_to?(:escape_bytea)
self.class.instance_eval do
define_method(:escape_bytea) do |value|
PGconn.escape_bytea(value) if value
@@ -377,7 +383,13 @@ module ActiveRecord
# Quotes strings for use in SQL input in the postgres driver for better performance.
def quote_string(s) #:nodoc:
- if PGconn.respond_to?(:escape)
+ if @connection.respond_to?(:escape)
+ self.class.instance_eval do
+ define_method(:quote_string) do |s|
+ @connection.escape(s)
+ end
+ end
+ elsif PGconn.respond_to?(:escape)
self.class.instance_eval do
define_method(:quote_string) do |s|
PGconn.escape(s)
@@ -929,6 +941,17 @@ module ActiveRecord
end
end
+ def translate_exception(exception, message)
+ case exception.message
+ when /duplicate key value violates unique constraint/
+ RecordNotUnique.new(message, exception)
+ when /violates foreign key constraint/
+ InvalidForeignKey.new(message, exception)
+ else
+ super
+ end
+ end
+
private
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index c9d0c9574f..5e5e30776a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -245,7 +245,7 @@ module ActiveRecord
end
def rename_table(name, new_name)
- execute "ALTER TABLE #{name} RENAME TO #{new_name}"
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
# See: http://www.sqlite.org/lang_altertable.html
@@ -431,6 +431,16 @@ module ActiveRecord
'INTEGER PRIMARY KEY NOT NULL'.freeze
end
end
+
+ def translate_exception(exception, message)
+ case exception.message
+ when /column(s)? .* (is|are) not unique/
+ RecordNotUnique.new(message, exception)
+ else
+ super
+ end
+ end
+
end
class SQLite2Adapter < SQLiteAdapter # :nodoc:
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a7be3539d5..467d955a49 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/metaclass'
+
module ActiveRecord
class IrreversibleMigration < ActiveRecordError#:nodoc:
end
@@ -300,8 +302,7 @@ module ActiveRecord
case sym
when :up, :down
- klass = (class << self; self; end)
- klass.send(:alias_method_chain, sym, "benchmarks")
+ metaclass.send(:alias_method_chain, sym, "benchmarks")
end
ensure
@ignore_new_methods = false
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 1b22fa5e24..dd2a90b8e5 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/array'
require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/object/metaclass'
module ActiveRecord
module NamedScope
@@ -99,7 +100,7 @@ module ActiveRecord
end
end, &block)
end
- (class << self; self end).instance_eval do
+ metaclass.instance_eval do
define_method name do |*args|
scopes[name].call(self, *args)
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 89ec0962bf..a34ff4a47a 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -2,56 +2,6 @@ require 'singleton'
require 'set'
module ActiveRecord
- module Observing # :nodoc:
- extend ActiveSupport::Concern
-
- module ClassMethods
- # Activates the observers assigned. Examples:
- #
- # # Calls PersonObserver.instance
- # ActiveRecord::Base.observers = :person_observer
- #
- # # Calls Cacher.instance and GarbageCollector.instance
- # ActiveRecord::Base.observers = :cacher, :garbage_collector
- #
- # # Same as above, just using explicit class references
- # ActiveRecord::Base.observers = Cacher, GarbageCollector
- #
- # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
- # called during startup, and before each development request.
- def observers=(*observers)
- @observers = observers.flatten
- end
-
- # Gets the current observers.
- def observers
- @observers ||= []
- end
-
- # Instantiate the global Active Record observers.
- def instantiate_observers
- return if @observers.blank?
- @observers.each do |observer|
- if observer.respond_to?(:to_sym) # Symbol or String
- observer.to_s.camelize.constantize.instance
- elsif observer.respond_to?(:instance)
- observer.instance
- else
- raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
- end
- end
- end
-
- protected
- # Notify observers when the observed class is subclassed.
- def inherited(subclass)
- super
- changed
- notify_observers :observed_class_inherited, subclass
- end
- end
- end
-
# Observer classes respond to lifecycle callbacks to implement trigger-like
# behavior outside the original class. This is a great way to reduce the
# clutter that normally comes when the model class is burdened with
@@ -137,56 +87,19 @@ module ActiveRecord
# load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
# singletons and that call instantiates and registers them.
#
- class Observer
- include Singleton
-
- class << self
- # Attaches the observer to the supplied model classes.
- def observe(*models)
- models.flatten!
- models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
- define_method(:observed_classes) { Set.new(models) }
- end
-
- # The class observed by default is inferred from the observer's class name:
- # assert_equal Person, PersonObserver.observed_class
- def observed_class
- if observed_class_name = name[/(.*)Observer/, 1]
- observed_class_name.constantize
- else
- nil
- end
- end
- end
-
- # Start observing the declared classes and their subclasses.
+ class Observer < ActiveModel::Observer
def initialize
- Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
- end
-
- # Send observed_method(object) if the method exists.
- def update(observed_method, object) #:nodoc:
- send(observed_method, object) if respond_to?(observed_method)
- end
-
- # Special method sent by the observed class when it is inherited.
- # Passes the new subclass.
- def observed_class_inherited(subclass) #:nodoc:
- self.class.observe(observed_classes + [subclass])
- add_observer!(subclass)
+ super
+ observed_subclasses.each { |klass| add_observer!(klass) }
end
protected
- def observed_classes
- Set.new([self.class.observed_class].compact.flatten)
- end
-
def observed_subclasses
observed_classes.sum([]) { |klass| klass.send(:subclasses) }
end
def add_observer!(klass)
- klass.add_observer(self)
+ super
if respond_to?(:after_find) && !klass.method_defined?(:after_find)
klass.class_eval 'def after_find() end'
end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 7959f2b510..23d085bea9 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -3,8 +3,9 @@ module ActiveRecord #:nodoc:
class Serializer #:nodoc:
attr_reader :options
- def initialize(record, options = {})
- @record, @options = record, options.dup
+ def initialize(record, options = nil)
+ @record = record
+ @options = options ? options.dup : {}
end
# To replicate the behavior in ActiveRecord#attributes,
diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb
index 67e2b2abb3..21afcd6e5c 100644
--- a/activerecord/lib/active_record/serializers/json_serializer.rb
+++ b/activerecord/lib/active_record/serializers/json_serializer.rb
@@ -1,4 +1,5 @@
require 'active_support/json'
+require 'active_model/naming'
module ActiveRecord #:nodoc:
module Serialization
@@ -74,36 +75,17 @@ module ActiveRecord #:nodoc:
# "title": "Welcome to the weblog"},
# {"comments": [{"body": "Don't think too hard"}],
# "title": "So I was thinking"}]}
- def to_json(options = {})
- json = JsonSerializer.new(self, options).to_s
- if include_root_in_json
- "{#{self.class.json_class_name}:#{json}}"
- else
- json
- end
+ def encode_json(encoder)
+ hash = Serializer.new(self, encoder.options).serializable_record
+ hash = { self.class.model_name.element => hash } if include_root_in_json
+ ActiveSupport::JSON.encode(hash)
end
+ def as_json(options = nil) self end #:nodoc:
+
def from_json(json)
self.attributes = ActiveSupport::JSON.decode(json)
self
end
-
- private
- # For compatibility with ActiveSupport::JSON.encode
- def rails_to_json(options, *args)
- to_json(options)
- end
-
- class JsonSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
- def serialize
- ActiveSupport::JSON.encode(serializable_record)
- end
- end
-
- module ClassMethods
- def json_class_name
- @json_class_name ||= name.demodulize.underscore.inspect
- end
- end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 14c7699705..a7fa98756e 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -40,6 +40,7 @@ module ActiveRecord
full_messages = []
each do |attribute, messages|
+ messages = Array.wrap(messages)
next if messages.empty?
if attribute == :base
@@ -193,10 +194,6 @@ module ActiveRecord
def errors
@errors ||= Errors.new(self)
end
-
- def get_attribute_value(attribute)
- respond_to?(attribute.to_sym) ? send(attribute.to_sym) : self[attribute.to_sym]
- end
end
end
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 852807b4c5..10ef9d7594 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module VERSION #:nodoc:
- MAJOR = 2
- MINOR = 3
- TINY = 2
+ MAJOR = 3
+ MINOR = 0
+ TINY = "pre"
STRING = [MAJOR, MINOR, TINY].join('.')
end