aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/association_preload.rb33
-rwxr-xr-xactiverecord/lib/active_record/associations.rb247
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb5
-rw-r--r--activerecord/lib/active_record/autosave_association.rb67
-rwxr-xr-xactiverecord/lib/active_record/base.rb305
-rw-r--r--activerecord/lib/active_record/calculations.rb75
-rw-r--r--activerecord/lib/active_record/callbacks.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb7
-rw-r--r--activerecord/lib/active_record/locale/en.yml44
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb10
-rw-r--r--activerecord/lib/active_record/named_scope.rb8
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb53
-rw-r--r--activerecord/lib/active_record/railtie.rb30
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb15
-rw-r--r--activerecord/lib/active_record/railties/databases.rake65
-rw-r--r--activerecord/lib/active_record/railties/subscriber.rb27
-rw-r--r--activerecord/lib/active_record/reflection.rb25
-rw-r--r--activerecord/lib/active_record/relation.rb95
-rw-r--r--activerecord/lib/active_record/relation/calculation_methods.rb19
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb208
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb130
-rw-r--r--activerecord/lib/active_record/schema.rb6
-rw-r--r--activerecord/lib/active_record/test_case.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/validations/associated.rb3
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb11
38 files changed, 809 insertions, 802 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 728dec8925..d5b6d40514 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -134,6 +134,9 @@ module ActiveRecord
autoload :AbstractAdapter
end
end
+
+ autoload :TestCase
+ autoload :TestFixtures, 'active_record/fixtures'
end
Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 9f7b2a60b2..a43c4d09d6 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -188,11 +188,11 @@ module ActiveRecord
conditions << append_conditions(reflection, preload_options)
associated_records = reflection.klass.with_exclusive_scope do
- reflection.klass.find(:all, :conditions => [conditions, ids],
- :include => options[:include],
- :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
- :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
- :order => options[:order])
+ reflection.klass.where([conditions, ids]).
+ includes(options[:include]).
+ joins("INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}").
+ select("#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id").
+ order(options[:order]).to_a
end
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
end
@@ -327,6 +327,7 @@ module ActiveRecord
table_name = klass.quoted_table_name
primary_key = klass.primary_key
column_type = klass.columns.detect{|c| c.name == primary_key}.type
+
ids = id_map.keys.map do |id|
if column_type == :integer
id.to_i
@@ -336,15 +337,14 @@ module ActiveRecord
id
end
end
+
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
conditions << append_conditions(reflection, preload_options)
+
associated_records = klass.with_exclusive_scope do
- klass.find(:all, :conditions => [conditions, ids],
- :include => options[:include],
- :select => options[:select],
- :joins => options[:joins],
- :order => options[:order])
+ klass.where([conditions, ids]).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
end
+
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
end
end
@@ -363,13 +363,12 @@ module ActiveRecord
conditions << append_conditions(reflection, preload_options)
reflection.klass.with_exclusive_scope do
- reflection.klass.find(:all,
- :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
- :include => preload_options[:include] || options[:include],
- :conditions => [conditions, ids],
- :joins => options[:joins],
- :group => preload_options[:group] || options[:group],
- :order => preload_options[:order] || options[:order])
+ reflection.klass.select(preload_options[:select] || options[:select] || "#{table_name}.*").
+ includes(preload_options[:include] || options[:include]).
+ where([conditions, ids]).
+ joins(options[:joins]).
+ group(preload_options[:group] || options[:group]).
+ order(preload_options[:order] || options[:order])
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 149a11eb47..468a6cd9f8 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1701,24 +1701,30 @@ module ActiveRecord
end
def construct_finder_arel_with_included_associations(options, join_dependency)
- scope = scope(:find)
-
- relation = active_relation
+ relation = unscoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(construct_join(options[:joins], scope)).
+ relation = relation.joins(options[:joins]).
select(column_aliases(join_dependency)).
- group(options[:group] || (scope && scope[:group])).
- having(options[:having] || (scope && scope[:having])).
- order(construct_order(options[:order], scope)).
- where(construct_conditions(options[:conditions], scope)).
- from((scope && scope[:from]) || options[:from])
+ group(options[:group]).
+ having(options[:having]).
+ order(options[:order]).
+ where(options[:conditions]).
+ from(options[:from])
+
+ scoped_relation = current_scoped_methods
+ scoped_relation_limit = scoped_relation.taken if scoped_relation
+
+ relation = current_scoped_methods.except(:limit).merge(relation) if current_scoped_methods
- relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
- relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections)
+ if !using_limitable_reflections?(join_dependency.reflections) && ((scoped_relation && scoped_relation.taken) || options[:limit])
+ relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency))
+ end
+
+ relation = relation.limit(options[:limit] || scoped_relation_limit) if using_limitable_reflections?(join_dependency.reflections)
relation
end
@@ -1746,29 +1752,29 @@ module ActiveRecord
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
- scope = scope(:find)
-
- relation = active_relation
+ relation = unscoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(construct_join(options[:joins], scope)).
- where(construct_conditions(options[:conditions], scope)).
- group(options[:group] || (scope && scope[:group])).
- having(options[:having] || (scope && scope[:having])).
- order(construct_order(options[:order], scope)).
- limit(construct_limit(options[:limit], scope)).
- offset(construct_limit(options[:offset], scope)).
- from(options[:from]).
- select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(",")))
+ relation = relation.joins(options[:joins]).
+ where(options[:conditions]).
+ group(options[:group]).
+ having(options[:having]).
+ order(options[:order]).
+ limit(options[:limit]).
+ offset(options[:offset]).
+ from(options[:from])
+
+ relation = current_scoped_methods.except(:select, :includes, :eager_load).merge(relation) if current_scoped_methods
+ relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order]))
relation.to_sql
end
def using_limitable_reflections?(reflections)
- reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
+ reflections.collect(&:collection?).length.zero?
end
def column_aliases(join_dependency)
@@ -1841,7 +1847,7 @@ module ActiveRecord
case associations
when Symbol, String
reflection = base.reflections[associations]
- if reflection && [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
+ if reflection && reflection.collection?
records.each { |record| record.send(reflection.name).target.uniq! }
end
when Array
@@ -1851,12 +1857,11 @@ module ActiveRecord
when Hash
associations.keys.each do |name|
reflection = base.reflections[name]
- is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
parent_records = []
records.each do |record|
if descendant = record.send(reflection.name)
- if is_collection
+ if reflection.collection?
parent_records.concat descendant.target.uniq
else
parent_records << descendant
@@ -1954,7 +1959,7 @@ module ActiveRecord
class JoinBase # :nodoc:
attr_reader :active_record, :table_joins
- delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
+ delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
def initialize(active_record, joins = nil)
@active_record = active_record
@@ -2029,140 +2034,108 @@ module ActiveRecord
def association_join
return @join if @join
- connection = reflection.active_record.connection
+
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
+
@join = case reflection.macro
- when :has_and_belongs_to_many
- ["%s.%s = %s.%s " % [
- connection.quote_table_name(aliased_join_table_name),
- options[:foreign_key] || reflection.active_record.to_s.foreign_key,
- connection.quote_table_name(parent.aliased_table_name),
- reflection.active_record.primary_key],
- "%s.%s = %s.%s " % [
- connection.quote_table_name(aliased_table_name),
- klass.primary_key,
- connection.quote_table_name(aliased_join_table_name),
- options[:association_foreign_key] || klass.to_s.foreign_key
- ]
- ]
- when :has_many, :has_one
- if reflection.options[:through]
- jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
- first_key = second_key = as_extra = nil
-
- if through_reflection.options[:as] # has_many :through against a polymorphic join
- jt_foreign_key = through_reflection.options[:as].to_s + '_id'
- jt_as_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
- klass.quote_value(parent.active_record.base_class.name)
- ]
+ when :has_and_belongs_to_many
+ join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
+ fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
+ klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
+
+ [
+ join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
+ aliased_table[klass.primary_key].eq(join_table[klass_fk])
+ ]
+ when :has_many, :has_one
+ if reflection.options[:through]
+ join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
+ first_key = second_key = as_extra = nil
+
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
+ jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
+ else
+ jt_foreign_key = through_reflection.primary_key_name
+ end
+
+ case source_reflection.macro
+ when :has_many
+ if source_reflection.options[:as]
+ first_key = "#{source_reflection.options[:as]}_id"
+ second_key = options[:foreign_key] || primary_key
+ as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
else
- jt_foreign_key = through_reflection.primary_key_name
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
+ second_key = options[:foreign_key] || primary_key
end
- case source_reflection.macro
- when :has_many
- if source_reflection.options[:as]
- first_key = "#{source_reflection.options[:as]}_id"
- second_key = options[:foreign_key] || primary_key
- as_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_table_name),
- connection.quote_column_name("#{source_reflection.options[:as]}_type"),
- klass.quote_value(source_reflection.active_record.base_class.name)
- ]
- else
- first_key = through_reflection.klass.base_class.to_s.foreign_key
- second_key = options[:foreign_key] || primary_key
- end
-
- unless through_reflection.klass.descends_from_active_record?
- jt_sti_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(through_reflection.active_record.inheritance_column),
- through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
- end
- when :belongs_to
- first_key = primary_key
- if reflection.options[:source_type]
- second_key = source_reflection.association_foreign_key
- jt_source_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
- klass.quote_value(reflection.options[:source_type])
- ]
- else
- second_key = source_reflection.primary_key_name
- end
+ unless through_reflection.klass.descends_from_active_record?
+ jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
+ end
+ when :belongs_to
+ first_key = primary_key
+ if reflection.options[:source_type]
+ second_key = source_reflection.association_foreign_key
+ jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
+ else
+ second_key = source_reflection.primary_key_name
end
-
- ["(%s.%s = %s.%s%s%s%s) " % [
- connection.quote_table_name(parent.aliased_table_name),
- connection.quote_column_name(parent.primary_key),
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(jt_foreign_key),
- jt_as_extra, jt_source_extra, jt_sti_extra],
- "(%s.%s = %s.%s%s) " % [
- connection.quote_table_name(aliased_table_name),
- connection.quote_column_name(first_key),
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(second_key),
- as_extra]
- ]
-
- elsif reflection.options[:as]
- "%s.%s = %s.%s AND %s.%s = %s" % [
- connection.quote_table_name(aliased_table_name),
- "#{reflection.options[:as]}_id",
- connection.quote_table_name(parent.aliased_table_name),
- parent.primary_key,
- connection.quote_table_name(aliased_table_name),
- "#{reflection.options[:as]}_type",
- klass.quote_value(parent.active_record.base_class.name)
- ]
- else
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
- "%s.%s = %s.%s " % [
- aliased_table_name,
- foreign_key,
- parent.aliased_table_name,
- reflection.options[:primary_key] || parent.primary_key
- ]
end
- when :belongs_to
- "%s.%s = %s.%s " % [
- connection.quote_table_name(aliased_table_name),
- reflection.klass.primary_key,
- connection.quote_table_name(parent.aliased_table_name),
- options[:foreign_key] || reflection.primary_key_name
+
+ [
+ [parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
+ aliased_table[first_key].eq(join_table[second_key])
]
+ elsif reflection.options[:as]
+ id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
+ type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
+ [id_rel, type_rel]
+ else
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
+ [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
+ end
+ when :belongs_to
+ [aliased_table[reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
+ end
+
+ unless klass.descends_from_active_record?
+ sti_column = aliased_table[klass.inheritance_column]
+ sti_condition = sti_column.eq(klass.sti_name)
+ klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
+
+ @join << sti_condition
end
- @join << %(AND %s) % [
- klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|
- @join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions]
+ if ref && ref.options[:conditions]
+ @join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))
+ end
end
@join
end
def relation
+ aliased = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
+
if reflection.macro == :has_and_belongs_to_many
- [Arel::Table.new(table_alias_for(options[:join_table], aliased_join_table_name)), Arel::Table.new(table_name_and_alias)]
+ [Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased]
elsif reflection.options[:through]
- [Arel::Table.new(table_alias_for(through_reflection.klass.table_name, aliased_join_table_name)), Arel::Table.new(table_name_and_alias)]
+ [Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine), aliased]
else
- Arel::Table.new(table_name_and_alias)
+ aliased
end
end
def join_relation(joining_relation, join = nil)
if (relations = relation).is_a?(Array)
- joining_relation.
- joins(relations.first, Arel::OuterJoin).on(association_join.first).
- joins(relations.last, Arel::OuterJoin).on(association_join.last)
+ joining_relation.joins(Relation::JoinOperation.new(relations.first, Arel::OuterJoin, association_join.first)).
+ joins(Relation::JoinOperation.new(relations.last, Arel::OuterJoin, association_join.last))
else
- joining_relation.joins(relations, Arel::OuterJoin).on(association_join)
+ joining_relation.joins(Relation::JoinOperation.new(relations, Arel::OuterJoin, association_join))
end
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 358db6df1d..64dd5cf629 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -58,11 +58,14 @@ module ActiveRecord
find_scope = construct_scope[:find].slice(:conditions, :order)
with_scope(:find => find_scope) do
- relation = @reflection.klass.send(:construct_finder_arel, options)
+ relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods))
case args.first
- when :first, :last, :all
+ when :first, :last
relation.send(args.first)
+ when :all
+ records = relation.all
+ @reflection.options[:uniq] ? uniq(records) : records
else
relation.find(*args)
end
@@ -402,7 +405,7 @@ module ActiveRecord
end
elsif @reflection.klass.scopes.include?(method)
@reflection.klass.scopes[method].call(self, *args)
- else
+ else
with_scope(construct_scope) do
if block_given?
@reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
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 214ce5959a..387b85aacd 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -51,8 +51,6 @@ module ActiveRecord
end
def construct_find_options!(options)
- options[:select] = construct_select(options[:select])
- options[:from] ||= construct_from
options[:joins] = construct_joins(options[:joins])
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 1924156e2a..6f0f698f1e 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -6,8 +6,7 @@ module ActiveRecord
def construct_scope
{ :create => construct_owner_attributes(@reflection),
- :find => { :from => construct_from,
- :conditions => construct_conditions,
+ :find => { :conditions => construct_conditions,
:joins => construct_joins,
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
:select => construct_select,
@@ -145,7 +144,7 @@ module ActiveRecord
end
def build_sti_condition
- @reflection.through_reflection.klass.send(:type_condition)
+ @reflection.through_reflection.klass.send(:type_condition).to_sql
end
alias_method :sql_conditions, :conditions
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 98ab64537e..e178cb4ef2 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -158,47 +158,39 @@ module ActiveRecord
#
# For performance reasons, we don't check whether to validate at runtime,
# but instead only define the method and callback when needed. However,
- # this can change, for instance, when using nested attributes. Since we
- # don't want the callbacks to get defined multiple times, there are
- # guards that check if the save or validation methods have already been
- # defined before actually defining them.
+ # this can change, for instance, when using nested attributes, which is
+ # called _after_ the association has been defined. Since we don't want
+ # the callbacks to get defined multiple times, there are guards that
+ # check if the save or validation methods have already been defined
+ # before actually defining them.
def add_autosave_association_callbacks(reflection)
- save_method = "autosave_associated_records_for_#{reflection.name}"
- validation_method = "validate_associated_records_for_#{reflection.name}"
- force_validation = (reflection.options[:validate] == true || reflection.options[:autosave] == true)
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
+ collection = reflection.collection?
- case reflection.macro
- when :has_many, :has_and_belongs_to_many
- unless method_defined?(save_method)
+ unless method_defined?(save_method)
+ if collection
before_save :before_save_collection_association
define_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
- end
-
- if !method_defined?(validation_method) &&
- (force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false))
- define_method(validation_method) { validate_collection_association(reflection) }
- validate validation_method
- end
- else
- unless method_defined?(save_method)
- case reflection.macro
- when :has_one
+ else
+ if reflection.macro == :has_one
define_method(save_method) { save_has_one_association(reflection) }
after_save save_method
- when :belongs_to
+ else
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method
end
end
+ end
- if !method_defined?(validation_method) && force_validation
- define_method(validation_method) { validate_single_association(reflection) }
- validate validation_method
- end
+ if reflection.validate? && !method_defined?(validation_method)
+ method = (collection ? :validate_collection_association : :validate_single_association)
+ define_method(validation_method) { send(method, reflection) }
+ validate validation_method
end
end
end
@@ -232,10 +224,10 @@ module ActiveRecord
def associated_records_to_validate_or_save(association, new_record, autosave)
if new_record
association
- elsif association.loaded?
- autosave ? association : association.find_all { |record| record.new_record? }
+ elsif autosave
+ association.target.find_all { |record| record.new_record? || record.changed? || record.marked_for_destruction? }
else
- autosave ? association.target : association.target.find_all { |record| record.new_record? }
+ association.target.find_all { |record| record.new_record? }
end
end
@@ -268,7 +260,8 @@ module ActiveRecord
if reflection.options[:autosave]
association.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
- errors[attribute] << message if errors[attribute].empty?
+ errors[attribute] << message
+ errors[attribute].uniq!
end
else
errors.add(reflection.name)
@@ -304,13 +297,15 @@ module ActiveRecord
association.destroy(record)
elsif autosave != false && (@new_record_before_save || record.new_record?)
if autosave
- association.send(:insert_record, record, false, false)
+ saved = association.send(:insert_record, record, false, false)
else
association.send(:insert_record, record)
end
elsif autosave
- record.save(false)
+ saved = record.save(false)
end
+
+ raise ActiveRecord::Rollback if saved == false
end
end
@@ -337,7 +332,9 @@ module ActiveRecord
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
association[reflection.primary_key_name] = key
- association.save(!autosave)
+ saved = association.save(!autosave)
+ raise ActiveRecord::Rollback if !saved && autosave
+ saved
end
end
end
@@ -358,7 +355,7 @@ module ActiveRecord
if autosave && association.marked_for_destruction?
association.destroy
elsif autosave != false
- association.save(!autosave) if association.new_record? || autosave
+ saved = association.save(!autosave) if association.new_record? || autosave
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
@@ -368,6 +365,8 @@ module ActiveRecord
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
end
+
+ saved if autosave
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 0ebcf4a3cc..4ee9887186 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -518,14 +518,6 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
- # Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
- # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
- # may complicate matters if you use software like syslog. This is true, by default.
- cattr_accessor :colorize_logging, :instance_writer => false
- @@colorize_logging = true
-
- ##
- # :singleton-method:
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
# This is set to :local by default.
cattr_accessor :default_timezone, :instance_writer => false
@@ -550,13 +542,20 @@ module ActiveRecord #:nodoc:
# Determine whether to store the full constant name including namespace when using STI
superclass_delegating_accessor :store_full_sti_class
- self.store_full_sti_class = false
+ self.store_full_sti_class = true
# Stores the default scope for the class
class_inheritable_accessor :default_scoping, :instance_writer => false
self.default_scoping = []
class << self # Class methods
+ def colorize_logging(*args)
+ ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " <<
+ "config.active_record.colorize_logging are deprecated. Please use " <<
+ "Rails::Subscriber.colorize_logging or config.colorize_logging instead", caller
+ end
+ alias :colorize_logging= :colorize_logging
+
# Find operates with four different retrieval approaches:
#
# * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
@@ -643,9 +642,8 @@ module ActiveRecord #:nodoc:
# end
def find(*args)
options = args.extract_options!
- set_readonly_option!(options)
- relation = construct_finder_arel(options)
+ relation = construct_finder_arel(options, current_scoped_methods)
case args.first
when :first, :last, :all
@@ -816,7 +814,7 @@ module ActiveRecord #:nodoc:
# # Delete multiple rows
# Todo.delete([2,3,4])
def delete(id_or_array)
- active_relation.where(construct_conditions(nil, scope(:find))).delete(id_or_array)
+ scoped.delete(id_or_array)
end
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
@@ -871,20 +869,18 @@ module ActiveRecord #:nodoc:
# # Update all books that match our conditions, but limit it to 5 ordered by date
# Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
def update_all(updates, conditions = nil, options = {})
- scope = scope(:find)
-
- relation = active_relation
+ relation = unscoped
- if conditions = construct_conditions(conditions, scope)
- relation = relation.where(Arel::SqlLiteral.new(conditions))
- end
+ relation = relation.where(conditions) if conditions
+ relation = relation.limit(options[:limit]) if options[:limit].present?
+ relation = relation.order(options[:order]) if options[:order].present?
- relation = if options.has_key?(:limit) || (scope && scope[:limit])
+ if current_scoped_methods && current_scoped_methods.limit_value.present? && current_scoped_methods.order_values.present?
# Only take order from scope if limit is also provided by scope, this
# is useful for updating a has_many association with a limit.
- relation.order(construct_order(options[:order], scope)).limit(construct_limit(options[:limit], scope))
+ relation = current_scoped_methods.merge(relation) if current_scoped_methods
else
- relation.order(options[:order])
+ relation = current_scoped_methods.except(:limit, :order).merge(relation) if current_scoped_methods
end
relation.update(sanitize_sql_for_assignment(updates))
@@ -938,7 +934,7 @@ module ActiveRecord #:nodoc:
# Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
- active_relation.where(construct_conditions(conditions, scope(:find))).delete_all
+ where(conditions).delete_all
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -1392,7 +1388,7 @@ module ActiveRecord #:nodoc:
def reset_column_information
undefine_attribute_methods
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
- @active_relation = @active_relation_engine = nil
+ @arel_engine = @unscoped = @arel_table = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -1505,20 +1501,21 @@ module ActiveRecord #:nodoc:
"(#{segments.join(') AND (')})" unless segments.empty?
end
- def active_relation
- @active_relation ||= Relation.new(self, active_relation_table)
+ def unscoped
+ @unscoped ||= Relation.new(self, arel_table)
+ finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
end
- def active_relation_table(table_name_alias = nil)
- Arel::Table.new(table_name, :as => table_name_alias, :engine => active_relation_engine)
+ def arel_table
+ @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
end
- def active_relation_engine
- @active_relation_engine ||= begin
+ def arel_engine
+ @arel_engine ||= begin
if self == ActiveRecord::Base
Arel::Table.engine
else
- connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.active_relation_engine
+ connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.arel_engine
end
end
end
@@ -1565,111 +1562,36 @@ module ActiveRecord #:nodoc:
end
end
- def default_select(qualified)
- if qualified
- quoted_table_name + '.*'
- else
- '*'
- end
- end
-
- def construct_finder_arel(options = {}, scope = scope(:find))
- validate_find_options(options)
-
- relation = active_relation.
- joins(construct_join(options[:joins], scope)).
- where(construct_conditions(options[:conditions], scope)).
- select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))).
- group(options[:group] || (scope && scope[:group])).
- having(options[:having] || (scope && scope[:having])).
- order(construct_order(options[:order], scope)).
- limit(construct_limit(options[:limit], scope)).
- offset(construct_offset(options[:offset], scope)).
- from(options[:from]).
- includes( merge_includes(scope && scope[:include], options[:include]))
-
- lock = (scope && scope[:lock]) || options[:lock]
- relation = relation.lock if lock.present?
-
- relation = relation.readonly if options[:readonly]
-
+ def construct_finder_arel(options = {}, scope = nil)
+ relation = unscoped.apply_finder_options(options)
+ relation = scope.merge(relation) if scope
relation
end
- def construct_join(joins, scope)
- merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
- case merged_joins
+ def construct_join(joins)
+ case joins
when Symbol, Hash, Array
- if array_of_strings?(merged_joins)
- merged_joins.join(' ') + " "
+ if array_of_strings?(joins)
+ joins.join(' ') + " "
else
- build_association_joins(merged_joins)
+ build_association_joins(joins)
end
when String
- " #{merged_joins} "
+ " #{joins} "
else
""
end
end
- def construct_order(order, scope)
- orders = []
-
- scoped_order = scope[:order] if scope
- if order
- orders << order
- orders << scoped_order if scoped_order && scoped_order != order
- elsif scoped_order
- orders << scoped_order
- end
-
- orders.reject {|o| o.blank?}
- end
-
- def construct_limit(limit, scope)
- limit ||= scope[:limit] if scope
- limit
- end
-
- def construct_offset(offset, scope)
- offset ||= scope[:offset] if scope
- offset
- end
-
- def construct_conditions(conditions, scope)
- conditions = [conditions]
- conditions << scope[:conditions] if scope
- conditions << type_condition if finder_needs_type_condition?
- merge_conditions(*conditions)
- end
-
- # Merges includes so that the result is a valid +include+
- def merge_includes(first, second)
- (Array.wrap(first) + Array.wrap(second)).uniq
- end
-
- def merge_joins(*joins)
- if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
- joins = joins.collect do |join|
- join = [join] if join.is_a?(String)
- join = build_association_joins(join) unless array_of_strings?(join)
- join
- end
- joins.flatten.map{|j| j.strip}.uniq
- else
- joins.collect{|j| Array.wrap(j)}.flatten.uniq
- end
- end
-
def build_association_joins(joins)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil)
- relation = active_relation.relation
+ relation = unscoped.table
join_dependency.join_associations.map { |association|
if (association_relation = association.relation).is_a?(Array)
- [Arel::InnerJoin.new(relation, association_relation.first, association.association_join.first).joins(relation),
- Arel::InnerJoin.new(relation, association_relation.last, association.association_join.last).joins(relation)].join()
+ [Arel::InnerJoin.new(relation, association_relation.first, *association.association_join.first).joins(relation),
+ Arel::InnerJoin.new(relation, association_relation.last, *association.association_join.last).joins(relation)].join()
else
- Arel::InnerJoin.new(relation, association_relation, association.association_join).joins(relation)
+ Arel::InnerJoin.new(relation, association_relation, *association.association_join).joins(relation)
end
}.join(" ")
end
@@ -1678,14 +1600,12 @@ module ActiveRecord #:nodoc:
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
end
- def type_condition(table_alias = nil)
- table = Arel::Table.new(table_name, :engine => active_relation_engine, :as => table_alias)
-
- sti_column = table[inheritance_column]
+ def type_condition
+ sti_column = arel_table[inheritance_column]
condition = sti_column.eq(sti_name)
subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
- condition.to_sql
+ condition
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -1714,7 +1634,7 @@ module ActiveRecord #:nodoc:
super unless all_attributes_exists?(attribute_names)
if match.finder?
options = arguments.extract_options!
- relation = options.any? ? construct_finder_arel(options) : scoped
+ relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
relation.send :find_by_attributes, match, attribute_names, *arguments
elsif match.instantiator?
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
@@ -1831,52 +1751,43 @@ module ActiveRecord #:nodoc:
def with_scope(method_scoping = {}, action = :merge, &block)
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
- # Dup first and second level of hash (method and params).
- method_scoping = method_scoping.inject({}) do |hash, (method, params)|
- hash[method] = (params == true) ? params : params.dup
- hash
- end
+ if method_scoping.is_a?(Hash)
+ # Dup first and second level of hash (method and params).
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
+ hash[method] = (params == true) ? params : params.dup
+ hash
+ end
+
+ method_scoping.assert_valid_keys([ :find, :create ])
+ relation = construct_finder_arel(method_scoping[:find] || {})
+
+ if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
+ scope_for_create = case action
+ when :merge
+ current_scoped_methods.create_with_value.merge(method_scoping[:create])
+ when :reverse_merge
+ method_scoping[:create].merge(current_scoped_methods.create_with_value)
+ else
+ method_scoping[:create]
+ end
- method_scoping.assert_valid_keys([ :find, :create ])
+ relation = relation.create_with(scope_for_create)
+ else
+ scope_for_create = method_scoping[:create]
+ scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods
+ relation = relation.create_with(scope_for_create) if scope_for_create
+ end
- if f = method_scoping[:find]
- f.assert_valid_keys(VALID_FIND_OPTIONS)
- set_readonly_option! f
+ method_scoping = relation
end
- # Merge scopings
- if [:merge, :reverse_merge].include?(action) && current_scoped_methods
- method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
- case hash[method]
- when Hash
- if method == :find
- (hash[method].keys + params.keys).uniq.each do |key|
- merge = hash[method][key] && params[key] # merge if both scopes have the same key
- if key == :conditions && merge
- if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
- hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
- else
- hash[method][key] = merge_conditions(params[key], hash[method][key])
- end
- elsif key == :include && merge
- hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
- elsif key == :joins && merge
- hash[method][key] = merge_joins(params[key], hash[method][key])
- else
- hash[method][key] = hash[method][key] || params[key]
- end
- end
- else
- if action == :reverse_merge
- hash[method] = hash[method].merge(params)
- else
- hash[method] = params.merge(hash[method])
- end
- end
- else
- hash[method] = params
- end
- hash
+ if current_scoped_methods
+ case action
+ when :merge
+ method_scoping = current_scoped_methods.merge(method_scoping)
+ when :reverse_merge
+ method_scoping = current_scoped_methods.except(:where).merge(method_scoping)
+ method_scoping = method_scoping.merge(current_scoped_methods.only(:where))
end
end
@@ -1905,21 +1816,7 @@ module ActiveRecord #:nodoc:
# default_scope :order => 'last_name, first_name'
# end
def default_scope(options = {})
- self.default_scoping << { :find => options, :create => options[:conditions].is_a?(Hash) ? options[:conditions] : {} }
- end
-
- # Test whether the given method and optional key are scoped.
- def scoped?(method, key = nil) #:nodoc:
- if current_scoped_methods && (scope = current_scoped_methods[method])
- !key || !scope[key].nil?
- end
- end
-
- # Retrieve the scope for the given method and optional key.
- def scope(method, key = nil) #:nodoc:
- if current_scoped_methods && (scope = current_scoped_methods[method])
- key ? scope[key] : scope
- end
+ self.default_scoping << construct_finder_arel(options)
end
def scoped_methods #:nodoc:
@@ -2039,8 +1936,8 @@ module ActiveRecord #:nodoc:
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
- table = Arel::Table.new(default_table_name, active_relation_engine)
- builder = PredicateBuilder.new(active_relation_engine)
+ table = Arel::Table.new(self.table_name, :engine => arel_engine, :as => default_table_name)
+ builder = PredicateBuilder.new(arel_engine)
builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -2123,25 +2020,6 @@ module ActiveRecord #:nodoc:
end
end
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
- :order, :select, :readonly, :group, :having, :from, :lock ]
-
- def validate_find_options(options) #:nodoc:
- options.assert_valid_keys(VALID_FIND_OPTIONS)
- end
-
- def set_readonly_option!(options) #:nodoc:
- # Inherit :readonly from finder scope if set. Otherwise,
- # if :joins is not blank then :readonly defaults to true.
- unless options.has_key?(:readonly)
- if scoped_readonly = scope(:find, :readonly)
- options[:readonly] = scoped_readonly
- elsif !options[:joins].blank? && !options[:select]
- options[:readonly] = true
- end
- end
- end
-
def encode_quoted_value(value) #:nodoc:
quoted_value = connection.quote(value)
quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
@@ -2160,7 +2038,12 @@ module ActiveRecord #:nodoc:
@new_record = true
ensure_proper_type
self.attributes = attributes unless attributes.nil?
- self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
+
+ if scope = self.class.send(:current_scoped_methods)
+ create_with = scope.scope_for_create
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
+ end
+
result = yield self if block_given?
_run_initialize_callbacks
result
@@ -2186,7 +2069,11 @@ module ActiveRecord #:nodoc:
@attributes_cache = {}
@new_record = true
ensure_proper_type
- self.class.send(:scope, :create).each { |att, value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
+
+ if scope = self.class.send(:current_scoped_methods)
+ create_with = scope.scope_for_create
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
+ end
end
# Returns a String, which Action Pack uses for constructing an URL to this
@@ -2306,7 +2193,7 @@ module ActiveRecord #:nodoc:
# be made (since they can't be persisted).
def destroy
unless new_record?
- self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).delete_all
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
end
@destroyed = true
@@ -2593,7 +2480,7 @@ module ActiveRecord #:nodoc:
def update(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return 0 if attributes_with_values.empty?
- self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).update(attributes_with_values)
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
@@ -2606,9 +2493,9 @@ module ActiveRecord #:nodoc:
attributes_values = arel_attributes_values
new_id = if attributes_values.empty?
- self.class.active_relation.insert connection.empty_insert_statement_value
+ self.class.unscoped.insert connection.empty_insert_statement_value
else
- self.class.active_relation.insert attributes_values
+ self.class.unscoped.insert attributes_values
end
self.id ||= new_id
@@ -2703,7 +2590,7 @@ module ActiveRecord #:nodoc:
if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
value = value.to_yaml
end
- attrs[self.class.active_relation[name]] = value
+ attrs[self.class.arel_table[name]] = value
end
end
end
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 20d287faeb..e4b3caab4e 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -46,19 +46,19 @@ module ActiveRecord
def count(*args)
case args.size
when 0
- construct_calculation_arel.count
+ construct_calculation_arel({}, current_scoped_methods).count
when 1
if args[0].is_a?(Hash)
options = args[0]
distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false
- construct_calculation_arel(options).count(options[:select], :distinct => distinct)
+ construct_calculation_arel(options, current_scoped_methods).count(options[:select], :distinct => distinct)
else
- construct_calculation_arel.count(args[0])
+ construct_calculation_arel({}, current_scoped_methods).count(args[0])
end
when 2
column_name, options = args
distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false
- construct_calculation_arel(options).count(column_name, :distinct => distinct)
+ construct_calculation_arel(options, current_scoped_methods).count(column_name, :distinct => distinct)
else
raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
end
@@ -141,7 +141,7 @@ module ActiveRecord
# Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
- construct_calculation_arel(options).calculate(operation, column_name, options.slice(:distinct))
+ construct_calculation_arel(options, current_scoped_methods).calculate(operation, column_name, options.slice(:distinct))
rescue ThrowResult
0
end
@@ -151,49 +151,58 @@ module ActiveRecord
options.assert_valid_keys(CALCULATIONS_OPTIONS)
end
- def construct_calculation_arel(options = {})
+ def construct_calculation_arel(options = {}, merge_with_relation = nil)
validate_calculation_options(options)
options = options.except(:distinct)
- scope = scope(:find)
- includes = merge_includes(scope ? scope[:include] : [], options[:include])
+ merge_with_includes = merge_with_relation ? merge_with_relation.includes_values : []
+ includes = (merge_with_includes + Array.wrap(options[:include])).uniq
if includes.any?
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope))
- construct_calculation_arel_with_included_associations(options, join_dependency)
+ merge_with_joins = merge_with_relation ? merge_with_relation.joins_values : []
+ joins = (merge_with_joins + Array.wrap(options[:joins])).uniq
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins))
+ construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation)
else
- active_relation.
- joins(construct_join(options[:joins], scope)).
- from((scope && scope[:from]) || options[:from]).
- where(construct_conditions(options[:conditions], scope)).
- order(options[:order]).
- limit(options[:limit]).
- offset(options[:offset]).
- group(options[:group]).
- having(options[:having]).
- select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins])))
+ relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having))
+
+ if merge_with_relation
+ relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation)
+ end
+
+ from = merge_with_relation.from_value if merge_with_relation && merge_with_relation.from_value.present?
+ from = options[:from] if from.blank? && options[:from].present?
+ relation = relation.from(from)
+
+ select = options[:select].presence || (merge_with_relation ? merge_with_relation.select_values.join(", ") : nil)
+ relation = relation.select(select)
+
+ relation
end
end
- def construct_calculation_arel_with_included_associations(options, join_dependency)
- scope = scope(:find)
-
- relation = active_relation
+ def construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation = nil)
+ relation = unscoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(construct_join(options[:joins], scope)).
- select(column_aliases(join_dependency)).
- group(options[:group]).
- having(options[:having]).
- order(options[:order]).
- where(construct_conditions(options[:conditions], scope)).
- from((scope && scope[:from]) || options[:from])
+ if merge_with_relation
+ relation.joins_values = (merge_with_relation.joins_values + relation.joins_values).uniq
+ relation.where_values = merge_with_relation.where_values
+
+ merge_limit = merge_with_relation.taken
+ end
+
+ relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)).
+ select(column_aliases(join_dependency))
+
+ if !using_limitable_reflections?(join_dependency.reflections) && (merge_limit || options[:limit])
+ relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency))
+ end
- relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
- relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections)
+ relation = relation.limit(options[:limit] || merge_limit) if using_limitable_reflections?(join_dependency.reflections)
relation
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index e2a8f03c8f..aecde5848c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -9,7 +9,6 @@ module ActiveRecord
# * (-) <tt>valid</tt>
# * (1) <tt>before_validation</tt>
# * (-) <tt>validate</tt>
- # * (-) <tt>validate_on_create</tt>
# * (2) <tt>after_validation</tt>
# * (3) <tt>before_save</tt>
# * (4) <tt>before_create</tt>
@@ -223,9 +222,10 @@ module ActiveRecord
extend ActiveModel::Callbacks
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
- define_model_callbacks :validation, :only => [:before, :after]
end
module ClassMethods
@@ -236,6 +236,24 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
+
+ def before_validation(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array(options[:if])
+ options[:if] << "@_on_validate == :#{options[:on]}"
+ end
+ set_callback(:validation, :before, *args, &block)
+ end
+
+ def after_validation(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array(options[:if])
+ options[:if] << "!halted && value != false"
+ options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
+ set_callback(:validation, :after, *(args << options), &block)
+ end
end
def create_or_update_with_callbacks #:nodoc:
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 377f2a44c5..bf8c546d2e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -56,7 +56,7 @@ module ActiveRecord
# * +wait_timeout+: number of seconds to block and wait for a connection
# before giving up and raising a timeout error (default 5 seconds).
class ConnectionPool
- attr_reader :spec
+ attr_reader :spec, :connections
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
# object which describes database connection information (e.g. adapter,
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index bbc290f721..2f36bec764 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -51,8 +51,8 @@ module ActiveRecord
def self.establish_connection(spec = nil)
case spec
when nil
- raise AdapterNotSpecified unless defined? RAILS_ENV
- establish_connection(RAILS_ENV)
+ raise AdapterNotSpecified unless defined?(Rails.env)
+ establish_connection(Rails.env)
when ConnectionSpecification
@@connection_handler.establish_connection(name, spec)
when Symbol, String
@@ -88,26 +88,6 @@ module ActiveRecord
end
class << self
- # Deprecated and no longer has any effect.
- def allow_concurrency
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency has been deprecated and no longer has any effect. Please remove all references to allow_concurrency.")
- end
-
- # Deprecated and no longer has any effect.
- def allow_concurrency=(flag)
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency= has been deprecated and no longer has any effect. Please remove all references to allow_concurrency=.")
- end
-
- # Deprecated and no longer has any effect.
- def verification_timeout
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.verification_timeout has been deprecated and no longer has any effect. Please remove all references to verification_timeout.")
- end
-
- # Deprecated and no longer has any effect.
- def verification_timeout=(flag)
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.verification_timeout= has been deprecated and no longer has any effect. Please remove all references to verification_timeout=.")
- end
-
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work unrelated
# to any of the specific Active Records.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 00c71090f3..020acbbe5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -75,7 +75,8 @@ module ActiveRecord
def cache_sql(sql)
result =
if @query_cache.has_key?(sql)
- log_info(sql, "CACHE", 0.0)
+ ActiveSupport::Notifications.instrument("active_record.sql",
+ :sql => sql, :name => "CACHE", :connection_id => self.object_id)
@query_cache[sql]
else
@query_cache[sql] = yield
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 e731bc84f0..86ba7d72c3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -344,12 +344,12 @@ module ActiveRecord
end
end
- def assume_migrated_upto_version(version)
+ def assume_migrated_upto_version(version, migrations_path = ActiveRecord::Migrator.migrations_path)
version = version.to_i
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
- versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
+ versions = Dir["#{migrations_path}/[0-9]*_*.rb"].map do |filename|
filename.split('/').last.split('_').first.to_i
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d09aa3c4d2..7e80347f75 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -39,7 +39,6 @@ module ActiveRecord
def initialize(connection, logger = nil) #:nodoc:
@connection, @logger = connection, logger
@runtime = 0
- @last_verification = 0
@query_cache_enabled = false
end
@@ -191,27 +190,19 @@ module ActiveRecord
"active_record_#{open_transactions}"
end
- def log_info(sql, name, ms)
- if @logger && @logger.debug?
- name = '%s (%.1fms)' % [name || 'SQL', ms]
- @logger.debug(format_log_entry(name, sql.squeeze(' ')))
- end
- end
-
protected
+
def log(sql, name)
+ name ||= "SQL"
result = nil
- ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name) do
+ ActiveSupport::Notifications.instrument("active_record.sql",
+ :sql => sql, :name => name, :connection_id => self.object_id) do
@runtime += Benchmark.ms { result = yield }
end
result
rescue Exception => e
- # Log message and raise exception.
- # Set last_verification to 0, so that connection gets verified
- # upon reentering the request loop
- @last_verification = 0
message = "#{e.class.name}: #{e.message}: #{sql}"
- log_info(message, name, 0)
+ @logger.debug message if @logger
raise translate_exception(e, message)
end
@@ -220,23 +211,6 @@ module ActiveRecord
ActiveRecord::StatementInvalid.new(message)
end
- def format_log_entry(message, dump = nil)
- if ActiveRecord::Base.colorize_logging
- if @@row_even
- @@row_even = false
- message_color, dump_color = "4;36;1", "0;1"
- else
- @@row_even = true
- message_color, dump_color = "4;35;1", "0"
- end
-
- log_entry = " \e[#{message_color}m#{message}\e[0m "
- log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
- log_entry
- else
- "%s %s" % [message, dump]
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index fa28bc64df..8c0bf6396a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -579,6 +579,8 @@ module ActiveRecord
protected
def translate_exception(exception, message)
+ return super unless exception.respond_to?(:errno)
+
case exception.errno
when 1062
RecordNotUnique.new(message, exception)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 78b897add6..0a52f3a6a2 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# Allow database path relative to Rails.root, but only if
# the database path is not the special path that tells
# Sqlite to build a database only in memory.
- if Object.const_defined?(:Rails) && ':memory:' != config[:database]
+ if defined?(Rails.root) && ':memory:' != config[:database]
config[:database] = File.expand_path(config[:database], Rails.root)
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index c94b31a856..79f70f07cd 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -3,7 +3,6 @@ require 'yaml'
require 'csv'
require 'zlib'
require 'active_support/dependencies'
-require 'active_support/test_case'
require 'active_support/core_ext/logger'
if RUBY_VERSION < '1.9'
@@ -434,7 +433,7 @@ end
# Any fixture labeled "DEFAULTS" is safely ignored.
class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
- MAX_ID = 2 ** 31 - 1
+ MAX_ID = 2 ** 30 - 1
DEFAULT_FILTER_RE = /\.ya?ml$/
@@all_cached_fixtures = {}
@@ -822,8 +821,8 @@ module ActiveRecord
superclass_delegating_accessor :pre_loaded_fixtures
self.fixture_table_names = []
- self.use_transactional_fixtures = false
- self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = true
+ self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
self.fixture_class_names = {}
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index e33d389f8c..4115cc8e17 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -1,33 +1,9 @@
en:
- activerecord:
- errors:
- # model.errors.full_messages format.
- format: "{{attribute}} {{message}}"
-
- # The values :model, :attribute and :value are always available for interpolation
- # The value :count is available when applicable. Can be used for pluralization.
- messages:
- inclusion: "is not included in the list"
- exclusion: "is reserved"
- invalid: "is invalid"
- confirmation: "doesn't match confirmation"
- accepted: "must be accepted"
- empty: "can't be empty"
- blank: "can't be blank"
- too_long: "is too long (maximum is {{count}} characters)"
- too_short: "is too short (minimum is {{count}} characters)"
- wrong_length: "is the wrong length (should be {{count}} characters)"
- taken: "has already been taken"
- not_a_number: "is not a number"
- greater_than: "must be greater than {{count}}"
- greater_than_or_equal_to: "must be greater than or equal to {{count}}"
- equal_to: "must be equal to {{count}}"
- less_than: "must be less than {{count}}"
- less_than_or_equal_to: "must be less than or equal to {{count}}"
- odd: "must be odd"
- even: "must be even"
- record_invalid: "Validation failed: {{errors}}"
- # Append your own errors here or at the model/attributes scope.
+ errors:
+ messages:
+ taken: "has already been taken"
+ record_invalid: "Validation failed: {{errors}}"
+ # Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
@@ -42,7 +18,14 @@ en:
# Will define custom blank validation message for User model and
# custom blank validation message for login attribute of User model.
#models:
-
+
+ # Attributes names common to most models
+ #attributes:
+ #created_at: "Created at"
+ #updated_at: "Updated at"
+
+ # ActiveRecord models configuration
+ #activerecord:
# Translate model names. Used in Model.human_name().
#models:
# For example,
@@ -55,4 +38,3 @@ en:
# user:
# login: "Handle"
# will translate User attribute "login" as "Handle"
-
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index f9e538c586..9fcdabdb44 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -78,7 +78,7 @@ module ActiveRecord
attribute_names.uniq!
begin
- relation = self.class.active_relation
+ relation = self.class.unscoped
affected_rows = relation.where(
relation[self.class.primary_key].eq(quoted_id).and(
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c218c5bfd1..c059b2d18b 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -340,6 +340,10 @@ module ActiveRecord
self.verbose = save
end
+ def connection
+ ActiveRecord::Base.connection
+ end
+
def method_missing(method, *arguments, &block)
arg_list = arguments.map(&:inspect) * ', '
@@ -347,7 +351,7 @@ module ActiveRecord
unless arguments.empty? || method == :execute
arguments[0] = Migrator.proper_table_name(arguments.first)
end
- Base.connection.send(method, *arguments, &block)
+ connection.send(method, *arguments, &block)
end
end
end
@@ -404,6 +408,10 @@ module ActiveRecord
self.new(direction, migrations_path, target_version).run
end
+ def migrations_path
+ 'db/migrate'
+ end
+
def schema_migrations_table_name
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index f63b249241..90fd700437 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -26,10 +26,12 @@ module ActiveRecord
if options.present?
Scope.new(self, options, &block)
else
- unless scoped?(:find)
- finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn
+ current_scope = current_scoped_methods
+
+ unless current_scope
+ unscoped.spawn
else
- construct_finder_arel
+ construct_finder_arel({}, current_scoped_methods)
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index dbdeba6c24..d8fae495e7 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -188,6 +188,8 @@ module ActiveRecord
# the parent model is saved. This happens inside the transaction initiated
# by the parents save method. See ActiveRecord::AutosaveAssociation.
module ClassMethods
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
+
# Defines an attributes writer for the specified association(s). If you
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
# will need to add the attribute writer to the allowed list.
@@ -229,23 +231,14 @@ module ActiveRecord
options = { :allow_destroy => false, :update_only => false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
+ options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
- type = case reflection.macro
- when :has_one, :belongs_to
- :one_to_one
- when :has_many, :has_and_belongs_to_many
- :collection
- end
-
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
- self.nested_attributes_options[association_name.to_sym] = options
-
- if options[:reject_if] == :all_blank
- self.nested_attributes_options[association_name.to_sym][:reject_if] = proc { |attributes| attributes.all? {|k,v| v.blank?} }
- end
+ nested_attributes_options[association_name.to_sym] = options
+ type = (reflection.collection? ? :collection : :one_to_one)
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
@@ -271,21 +264,11 @@ module ActiveRecord
marked_for_destruction?
end
- # Deal with deprecated _delete.
- #
- def _delete #:nodoc:
- ActiveSupport::Deprecation.warn "_delete is deprecated in nested attributes. Use _destroy instead."
- _destroy
- end
-
private
# Attribute hash keys that should not be assigned as normal attributes.
# These hash keys are nested attributes implementation details.
- #
- # TODO Remove _delete from UNASSIGNABLE_KEYS when deprecation warning are
- # removed.
- UNASSIGNABLE_KEYS = %w( id _destroy _delete )
+ UNASSIGNABLE_KEYS = %w( id _destroy )
# Assigns the given attributes to the association.
#
@@ -298,13 +281,17 @@ module ActiveRecord
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
check_existing_record = (options[:update_only] || !attributes['id'].blank?)
if check_existing_record && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
+
+ elsif attributes['id']
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
+
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
@@ -343,7 +330,7 @@ module ActiveRecord
# { :id => '2', :_destroy => true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -366,6 +353,8 @@ module ActiveRecord
end
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ else
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
end
end
@@ -382,8 +371,7 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) ||
- ConnectionAdapters::Column.value_to_boolean(hash['_delete']) # TODO Remove after deprecation.
+ ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
# Determines if a new record should be build by checking for
@@ -394,14 +382,17 @@ module ActiveRecord
end
def call_reject_if(association_name, attributes)
- callback = self.nested_attributes_options[association_name][:reject_if]
-
- case callback
+ case callback = nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
- callback.try(:call, attributes)
+ callback.call(attributes)
end
end
+
+ def raise_nested_attributes_record_not_found(association_name, record_id)
+ reflection = self.class.reflect_on_association(association_name)
+ raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 657ee738c0..bc06333f1c 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -14,6 +14,10 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
+ # TODO If we require the wrong file, the error never comes up.
+ require "active_record/railties/subscriber"
+ subscriber ActiveRecord::Railties::Subscriber.new
+
initializer "active_record.set_configs" do |app|
app.config.active_record.each do |k,v|
ActiveRecord::Base.send "#{k}=", v
@@ -52,6 +56,19 @@ module ActiveRecord
initializer "active_record.load_observers" do
ActiveRecord::Base.instantiate_observers
+
+ ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
+ ActiveRecord::Base.instantiate_observers
+ end
+ end
+
+ initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
+ unless app.config.cache_classes
+ ActionDispatch::Callbacks.after do
+ ActiveRecord::Base.reset_subclasses
+ ActiveRecord::Base.clear_reloadable_connections!
+ end
+ end
end
# TODO: ActiveRecord::Base.logger should delegate to its own config.logger
@@ -59,13 +76,16 @@ module ActiveRecord
ActiveRecord::Base.logger ||= ::Rails.logger
end
- initializer "active_record.notifications" do
- require 'active_support/notifications'
+ initializer "active_record.i18n_deprecation" do
+ require 'active_support/i18n'
- ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload|
- ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000)
+ begin
+ I18n.t(:"activerecord.errors", :raise => true)
+ warn "[DEPRECATION] \"activerecord.errors\" namespace is deprecated in I18n " <<
+ "yml files, please use just \"errors\" instead."
+ rescue Exception => e
+ # No message then.
end
end
-
end
end
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index 535e967ec3..aed1c59b00 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -5,6 +5,8 @@ module ActiveRecord
module ControllerRuntime
extend ActiveSupport::Concern
+ protected
+
attr_internal :db_runtime
def cleanup_view_runtime
@@ -19,11 +21,16 @@ module ActiveRecord
end
end
+ def append_info_to_payload(payload)
+ super
+ payload[:db_runtime] = db_runtime
+ end
+
module ClassMethods
- def log_process_action(controller)
- super
- db_runtime = controller.send :db_runtime
- logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime
+ def log_process_action(payload)
+ messages, db_runtime = super, payload[:db_runtime]
+ messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime
+ messages
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index a35a6c156b..b39e064e45 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -26,9 +26,9 @@ namespace :db do
end
end
- desc 'Create the database defined in config/database.yml for the current RAILS_ENV'
+ desc 'Create the database defined in config/database.yml for the current Rails.env'
task :create => :load_config do
- create_database(ActiveRecord::Base.configurations[RAILS_ENV])
+ create_database(ActiveRecord::Base.configurations[Rails.env])
end
def create_database(config)
@@ -46,7 +46,7 @@ namespace :db do
$stderr.puts "Couldn't create database for #{config.inspect}"
end
end
- return # Skip the else clause of begin/rescue
+ return # Skip the else clause of begin/rescue
else
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection
@@ -111,9 +111,9 @@ namespace :db do
end
end
- desc 'Drops the database for the current RAILS_ENV'
+ desc 'Drops the database for the current Rails.env'
task :drop => :load_config do
- config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
begin
drop_database(config)
rescue Exception => e
@@ -188,7 +188,7 @@ namespace :db do
desc "Retrieves the charset for the current environment's database"
task :charset => :environment do
- config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when 'mysql'
ActiveRecord::Base.establish_connection(config)
@@ -203,7 +203,7 @@ namespace :db do
desc "Retrieves the collation for the current environment's database"
task :collation => :environment do
- config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when 'mysql'
ActiveRecord::Base.establish_connection(config)
@@ -246,6 +246,7 @@ namespace :db do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :load => :environment do
require 'active_record/fixtures'
+
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
@@ -257,7 +258,7 @@ namespace :db do
desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :identify => :environment do
- require "active_record/fixtures"
+ require 'active_record/fixtures'
label, id = ENV["LABEL"], ENV["ID"]
raise "LABEL or ID required" if label.blank? && id.blank?
@@ -295,7 +296,7 @@ namespace :db do
if File.exists?(file)
load(file)
else
- abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
+ abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/boot.rb to limit the frameworks that will be loaded}
end
end
end
@@ -304,36 +305,36 @@ namespace :db do
desc "Dump the database structure to a SQL file"
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
- case abcs[RAILS_ENV]["adapter"]
+ case abcs[Rails.env]["adapter"]
when "mysql", "oci", "oracle"
- ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
- File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ ActiveRecord::Base.establish_connection(abcs[Rails.env])
+ File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
- ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"]
- ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
- ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"]
- search_path = abcs[RAILS_ENV]["schema_search_path"]
+ ENV['PGHOST'] = abcs[Rails.env]["host"] if abcs[Rails.env]["host"]
+ ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]["port"]
+ ENV['PGPASSWORD'] = abcs[Rails.env]["password"].to_s if abcs[Rails.env]["password"]
+ search_path = abcs[Rails.env]["schema_search_path"]
unless search_path.blank?
search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ")
end
- `pg_dump -i -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{search_path} #{abcs[RAILS_ENV]["database"]}`
+ `pg_dump -i -U "#{abcs[Rails.env]["username"]}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]["database"]}`
raise "Error dumping database" if $?.exitstatus == 1
when "sqlite", "sqlite3"
- dbfile = abcs[RAILS_ENV]["database"] || abcs[RAILS_ENV]["dbfile"]
- `#{abcs[RAILS_ENV]["adapter"]} #{dbfile} .schema > db/#{RAILS_ENV}_structure.sql`
+ dbfile = abcs[Rails.env]["database"] || abcs[Rails.env]["dbfile"]
+ `#{abcs[Rails.env]["adapter"]} #{dbfile} .schema > db/#{Rails.env}_structure.sql`
when "sqlserver"
- `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r`
- `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r`
+ `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /f db\\#{Rails.env}_structure.sql /q /A /r`
+ `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /F db\ /q /A /r`
when "firebird"
- set_firebird_env(abcs[RAILS_ENV])
- db_string = firebird_db_string(abcs[RAILS_ENV])
- sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql"
+ set_firebird_env(abcs[Rails.env])
+ db_string = firebird_db_string(abcs[Rails.env])
+ sh "isql -a #{db_string} > #{Rails.root}/db/#{Rails.env}_structure.sql"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
if ActiveRecord::Base.connection.supports_migrations?
- File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
+ File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
end
end
end
@@ -356,28 +357,28 @@ namespace :db do
when "mysql"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
+ IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table|
ActiveRecord::Base.connection.execute(table)
end
when "postgresql"
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
- `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
+ `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]}`
when "sqlite", "sqlite3"
dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
- `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql`
+ `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
when "sqlserver"
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
+ `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
- IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl|
+ IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
when "firebird"
set_firebird_env(abcs["test"])
db_string = firebird_db_string(abcs["test"])
- sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}"
+ sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
@@ -400,7 +401,7 @@ namespace :db do
when "sqlserver"
dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-')
`osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}`
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
+ `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
diff --git a/activerecord/lib/active_record/railties/subscriber.rb b/activerecord/lib/active_record/railties/subscriber.rb
new file mode 100644
index 0000000000..7c2a10cf0f
--- /dev/null
+++ b/activerecord/lib/active_record/railties/subscriber.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ module Railties
+ class Subscriber < Rails::Subscriber
+ def sql(event)
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
+ sql = event.payload[:sql].squeeze(' ')
+
+ if odd?
+ name = color(name, :cyan, true)
+ sql = color(sql, nil, true)
+ else
+ name = color(name, :magenta, true)
+ end
+
+ debug "#{name} #{sql}"
+ end
+
+ def odd?
+ @odd_or_even = !@odd_or_even
+ end
+
+ def logger
+ ActiveRecord::Base.logger
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index b751c9ad68..32b9a2aa87 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -252,10 +252,33 @@ module ActiveRecord
end
end
+ # Returns whether or not this association reflection is for a collection
+ # association. Returns +true+ if the +macro+ is one of +has_many+ or
+ # +has_and_belongs_to_many+, +false+ otherwise.
+ def collection?
+ if @collection.nil?
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
+ end
+ @collection
+ end
+
+ # Returns whether or not the association should be validated as part of
+ # the parent's validation.
+ #
+ # Unless you explicitely disable validation with
+ # <tt>:validate => false</tt>, it will take place when:
+ #
+ # * you explicitely enable validation; <tt>:validate => true</tt>
+ # * you use autosave; <tt>:autosave => true</tt>
+ # * the association is a +has_many+ association
+ def validate?
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
- class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
+ class_name = class_name.singularize if collection?
class_name
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 6b9925d4e7..85bf878416 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,19 +1,19 @@
module ActiveRecord
class Relation
- include QueryMethods, FinderMethods, CalculationMethods, SpawnMethods
+ JoinOperation = Struct.new(:relation, :join_class, :on)
+ ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
+ MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
+
+ include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods
delegate :length, :collect, :map, :each, :all?, :to => :to_a
- attr_reader :relation, :klass
- attr_writer :readonly, :table
- attr_accessor :preload_associations, :eager_load_associations, :includes_associations
+ attr_reader :table, :klass
- def initialize(klass, relation)
- @klass, @relation = klass, relation
- @preload_associations = []
- @eager_load_associations = []
- @includes_associations = []
- @loaded, @readonly = false
+ def initialize(klass, table)
+ @klass, @table = klass, table
+ (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
end
def new(*args, &block)
@@ -29,7 +29,7 @@ module ActiveRecord
end
def respond_to?(method, include_private = false)
- return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method)
+ return true if arel.respond_to?(method, include_private) || Array.method_defined?(method)
if match = DynamicFinderMatch.match(method)
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
@@ -43,33 +43,38 @@ module ActiveRecord
def to_a
return @records if loaded?
- find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables?
+ find_with_associations = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)
@records = if find_with_associations
begin
- @klass.send(:find_with_associations, {
- :select => @relation.send(:select_clauses).join(', '),
- :joins => @relation.joins(relation),
- :group => @relation.send(:group_clauses).join(', '),
+ options = {
+ :select => @select_values.any? ? @select_values.join(", ") : nil,
+ :joins => arel.joins(arel),
+ :group => @group_values.any? ? @group_values.join(", ") : nil,
:order => order_clause,
:conditions => where_clause,
- :limit => @relation.taken,
- :offset => @relation.skipped,
- :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
- },
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @includes_associations, nil))
+ :limit => arel.taken,
+ :offset => arel.skipped,
+ :from => (arel.send(:from_clauses) if arel.send(:sources).present?)
+ }
+
+ including = (@eager_load_values + @includes_values).uniq
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
+ @klass.send(:find_with_associations, options, join_dependency)
rescue ThrowResult
[]
end
else
- @klass.find_by_sql(@relation.to_sql)
+ @klass.find_by_sql(arel.to_sql)
end
- preload = @preload_associations
- preload += @includes_associations unless find_with_associations
+ preload = @preload_values
+ preload += @includes_values unless find_with_associations
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
- @records.each { |record| record.readonly! } if @readonly
+ # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
+ readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
+ @records.each { |record| record.readonly! } if readonly
@loaded = true
@records
@@ -97,7 +102,7 @@ module ActiveRecord
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
- @relation.send(:taken).present? ? to_a.many? : size > 1
+ arel.send(:taken).present? ? to_a.many? : size > 1
end
end
@@ -107,7 +112,7 @@ module ActiveRecord
end
def delete_all
- @relation.delete.tap { reset }
+ arel.delete.tap { reset }
end
def delete(id_or_array)
@@ -124,28 +129,33 @@ module ActiveRecord
end
def reset
- @first = @last = @create_scope = @to_sql = @order_clause = nil
+ @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = nil
@records = []
self
end
- def table
- @table ||= Arel::Table.new(@klass.table_name, :engine => @klass.active_relation_engine)
- end
-
def primary_key
@primary_key ||= table[@klass.primary_key]
end
def to_sql
- @to_sql ||= @relation.to_sql
+ @to_sql ||= arel.to_sql
+ end
+
+ def scope_for_create
+ @scope_for_create ||= begin
+ @create_with_value || wheres.inject({}) do |hash, where|
+ hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
+ hash
+ end
+ end
end
protected
def method_missing(method, *args, &block)
- if @relation.respond_to?(method)
- @relation.send(method, *args, &block)
+ if arel.respond_to?(method)
+ arel.send(method, *args, &block)
elsif Array.method_defined?(method)
to_a.send(method, *args, &block)
elsif match = DynamicFinderMatch.match(method)
@@ -163,26 +173,19 @@ module ActiveRecord
end
def with_create_scope
- @klass.send(:with_scope, :create => create_scope) { yield }
- end
-
- def create_scope
- @create_scope ||= wheres.inject({}) do |hash, where|
- hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
- hash
- end
+ @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
end
def where_clause(join_string = " AND ")
- @relation.send(:where_clauses).join(join_string)
+ arel.send(:where_clauses).join(join_string)
end
def order_clause
- @order_clause ||= @relation.send(:order_clauses).join(', ')
+ @order_clause ||= arel.send(:order_clauses).join(', ')
end
def references_eager_loaded_tables?
- joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
+ joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq
(tables_in_string(to_sql) - joined_tables).any?
end
diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb
index 5246c7bc5d..91de89e607 100644
--- a/activerecord/lib/active_record/relation/calculation_methods.rb
+++ b/activerecord/lib/active_record/relation/calculation_methods.rb
@@ -25,7 +25,7 @@ module ActiveRecord
operation = operation.to_s.downcase
if operation == "count"
- joins = @relation.joins(relation)
+ joins = arel.joins(arel)
if joins.present? && joins =~ /LEFT OUTER/i
distinct = true
column_name = @klass.primary_key if column_name == :all
@@ -40,7 +40,7 @@ module ActiveRecord
distinct = options[:distinct] || distinct
column_name = :all if column_name.blank? && operation == "count"
- if @relation.send(:groupings).any?
+ if @group_values.any?
return execute_grouped_calculation(operation, column_name)
else
return execute_simple_calculation(operation, column_name, distinct)
@@ -53,7 +53,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
column = if @klass.column_names.include?(column_name.to_s)
- Arel::Attribute.new(@klass.active_relation, column_name)
+ Arel::Attribute.new(@klass.unscoped, column_name)
else
Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
end
@@ -63,7 +63,7 @@ module ActiveRecord
end
def execute_grouped_calculation(operation, column_name) #:nodoc:
- group_attr = @relation.send(:groupings).first.value
+ group_attr = @group_values.first
association = @klass.reflect_on_association(group_attr.to_sym)
associated = association && association.macro == :belongs_to # only count belongs_to associations
group_field = associated ? association.primary_key_name : group_attr
@@ -77,7 +77,7 @@ module ActiveRecord
select_statement = if operation == 'count' && column_name == :all
"COUNT(*) AS count_all"
else
- Arel::Attribute.new(@klass.active_relation, column_name).send(operation).as(aggregate_alias).to_sql
+ Arel::Attribute.new(@klass.unscoped, column_name).send(operation).as(aggregate_alias).to_sql
end
select_statement << ", #{group_field} AS #{group_alias}"
@@ -106,7 +106,6 @@ module ActiveRecord
column_name = :all
# Handles count(), count(:column), count(:distinct => true), count(:column, :distinct => true)
- # TODO : relation.projections only works when .select() was last in the chain. Fix it!
case args.size
when 0
select = get_projection_name_from_chained_relations
@@ -165,12 +164,8 @@ module ActiveRecord
column ? column.type_cast(value) : value
end
- def get_projection_name_from_chained_relations(relation = @relation)
- if relation.respond_to?(:projections) && relation.projections.present?
- relation.send(:select_clauses).join(', ')
- elsif relation.respond_to?(:relation)
- get_projection_name_from_chained_relations(relation.relation)
- end
+ def get_projection_name_from_chained_relations
+ @select_values.join(", ") if @select_values.present?
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index c3e5f27838..3668b0997f 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -93,15 +93,15 @@ module ActiveRecord
result = where(primary_key.in(ids)).all
expected_size =
- if @relation.taken && ids.size > @relation.taken
- @relation.taken
+ if arel.taken && ids.size > arel.taken
+ arel.taken
else
ids.size
end
# 11 ids with limit 3, offset 9 should give 2 results.
- if @relation.skipped && (ids.size - @relation.skipped < expected_size)
- expected_size = ids.size - @relation.skipped
+ if arel.skipped && (ids.size - arel.skipped < expected_size)
+ expected_size = ids.size - arel.skipped
end
if result.size == expected_size
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 525a9cb365..a3ac58bc81 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,72 +1,61 @@
module ActiveRecord
module QueryMethods
-
- def preload(*associations)
- spawn.tap {|r| r.preload_associations += Array.wrap(associations) }
- end
-
- def includes(*associations)
- spawn.tap {|r| r.includes_associations += Array.wrap(associations) }
- end
-
- def eager_load(*associations)
- spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) }
- end
-
- def readonly(status = true)
- spawn.tap {|r| r.readonly = status }
- end
-
- def select(selects)
- if selects.present?
- relation = spawn(@relation.project(selects))
- relation.readonly = @relation.joins(relation).present? ? false : @readonly
- relation
- else
- spawn
+ extend ActiveSupport::Concern
+
+ included do
+ (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
+ attr_accessor :"#{query_method}_values"
+
+ class_eval <<-CEVAL
+ def #{query_method}(*args)
+ spawn.tap do |new_relation|
+ new_relation.#{query_method}_values ||= []
+ value = Array.wrap(args.flatten).reject {|x| x.blank? }
+ new_relation.#{query_method}_values += value if value.present?
+ end
+ end
+ CEVAL
end
- end
-
- def from(from)
- from.present? ? spawn(@relation.from(from)) : spawn
- end
- def having(*args)
- return spawn if args.blank?
-
- if [String, Hash, Array].include?(args.first.class)
- havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
- else
- havings = args.first
+ [:where, :having].each do |query_method|
+ class_eval <<-CEVAL
+ def #{query_method}(*args)
+ spawn.tap do |new_relation|
+ new_relation.#{query_method}_values ||= []
+ value = build_where(*args)
+ new_relation.#{query_method}_values += [*value] if value.present?
+ end
+ end
+ CEVAL
end
- spawn(@relation.having(havings))
- end
+ ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
+ attr_accessor :"#{query_method}_value"
- def group(groups)
- groups.present? ? spawn(@relation.group(groups)) : spawn
- end
-
- def order(orders)
- orders.present? ? spawn(@relation.order(orders)) : spawn
+ class_eval <<-CEVAL
+ def #{query_method}(value = true)
+ spawn.tap do |new_relation|
+ new_relation.#{query_method}_value = value
+ end
+ end
+ CEVAL
+ end
end
def lock(locks = true)
+ relation = spawn
case locks
- when String
- spawn(@relation.lock(locks))
- when TrueClass, NilClass
- spawn(@relation.lock)
+ when String, TrueClass, NilClass
+ spawn.tap {|new_relation| new_relation.lock_value = locks || true }
else
- spawn
+ spawn.tap {|new_relation| new_relation.lock_value = false }
end
end
def reverse_order
- relation = spawn
- relation.instance_variable_set(:@orders, nil)
+ order_clause = arel.send(:order_clauses).join(', ')
+ relation = except(:order)
- order_clause = @relation.send(:order_clauses).join(', ')
if order_clause.present?
relation.order(reverse_sql_order(order_clause))
else
@@ -74,41 +63,108 @@ module ActiveRecord
end
end
- def limit(limits)
- limits.present? ? spawn(@relation.take(limits)) : spawn
+ def arel
+ @arel ||= build_arel
end
- def offset(offsets)
- offsets.present? ? spawn(@relation.skip(offsets)) : spawn
- end
+ def build_arel
+ arel = table
- def on(join)
- spawn(@relation.on(join))
- end
+ joined_associations = []
+ association_joins = []
- def joins(join, join_type = nil)
- return spawn if join.blank?
+ joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
- join_relation = case join
- when String
- @relation.join(join)
- when Hash, Array, Symbol
- if @klass.send(:array_of_strings?, join)
- @relation.join(join.join(' '))
+ # Build association joins first
+ joins.each do |join|
+ association_joins << join if [Hash, Array, Symbol].include?(join.class) && !@klass.send(:array_of_strings?, join)
+ end
+
+ if association_joins.any?
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins.uniq, nil)
+ to_join = []
+
+ join_dependency.join_associations.each do |association|
+ if (association_relation = association.relation).is_a?(Array)
+ to_join << [association_relation.first, association.association_join.first]
+ to_join << [association_relation.last, association.association_join.last]
+ else
+ to_join << [association_relation, association.association_join]
+ end
+ end
+
+ to_join.each do |tj|
+ unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] }
+ joined_associations << tj
+ arel = arel.join(tj[0]).on(*tj[1])
+ end
+ end
+ end
+
+ joins.each do |join|
+ next if join.blank?
+
+ @implicit_readonly = true
+
+ case join
+ when Relation::JoinOperation
+ arel = arel.join(join.relation, join.join_class).on(*join.on)
+ when Hash, Array, Symbol
+ if @klass.send(:array_of_strings?, join)
+ join_string = join.join(' ')
+ arel = arel.join(join_string)
+ end
else
- @relation.join(@klass.send(:build_association_joins, join))
+ arel = arel.join(join)
end
- else
- @relation.join(join, join_type)
end
- spawn(join_relation).tap { |r| r.readonly = true }
+ @where_values.uniq.each do |w|
+ arel = w.is_a?(String) ? arel.where(w) : arel.where(*w)
+ end
+
+ @having_values.uniq.each do |h|
+ arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
+ end
+
+ arel = arel.take(@limit_value) if @limit_value.present?
+ arel = arel.skip(@offset_value) if @offset_value.present?
+
+ @group_values.uniq.each do |g|
+ arel = arel.group(g) if g.present?
+ end
+
+ @order_values.uniq.each do |o|
+ arel = arel.order(o) if o.present?
+ end
+
+ selects = @select_values.uniq
+
+ if selects.present?
+ selects.each do |s|
+ @implicit_readonly = false
+ arel = arel.project(s) if s.present?
+ end
+ elsif joins.present?
+ arel = arel.project(@klass.quoted_table_name + '.*')
+ end
+
+ arel = arel.from(@from_value) if @from_value.present?
+
+ case @lock_value
+ when TrueClass
+ arel = arel.lock
+ when String
+ arel = arel.lock(@lock_value)
+ end
+
+ arel
end
- def where(*args)
- return spawn if args.blank?
+ def build_where(*args)
+ return if args.blank?
- builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass))
+ builder = PredicateBuilder.new(table.engine)
conditions = if [String, Array].include?(args.first.class)
merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
@@ -120,7 +176,7 @@ module ActiveRecord
args.first
end
- conditions.is_a?(String) ? spawn(@relation.where(conditions)) : spawn(@relation.where(*conditions))
+ conditions
end
private
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index a637e97155..f4abaae43e 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,75 +1,119 @@
module ActiveRecord
module SpawnMethods
- def spawn(relation = @relation)
- relation = Relation.new(@klass, relation)
- relation.readonly = @readonly
- relation.preload_associations = @preload_associations
- relation.eager_load_associations = @eager_load_associations
- relation.includes_associations = @includes_associations
- relation.table = table
+ def spawn(arel_table = self.table)
+ relation = Relation.new(@klass, arel_table)
+
+ (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method|
+ relation.send(:"#{query_method}_values=", send(:"#{query_method}_values"))
+ end
+
+ Relation::SINGLE_VALUE_METHODS.each do |query_method|
+ relation.send(:"#{query_method}_value=", send(:"#{query_method}_value"))
+ end
+
relation
end
def merge(r)
- raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
-
- merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.includes_associations)
- merged_relation.readonly = r.readonly
-
- [self.relation, r.relation].each do |arel|
- merged_relation = merged_relation.
- joins(arel.joins(arel)).
- group(arel.groupings).
- limit(arel.taken).
- offset(arel.skipped).
- select(arel.send(:select_clauses)).
- from(arel.sources).
- having(arel.havings).
- lock(arel.locked)
+ if r.klass != @klass
+ raise ArgumentError, "Cannot merge a #{r.klass.name}(##{r.klass.object_id}) relation with #{@klass.name}(##{@klass.object_id}) relation"
end
- relation_order = r.send(:order_clause)
- merged_order = relation_order.present? ? relation_order : order_clause
- merged_relation = merged_relation.order(merged_order)
+ merged_relation = spawn.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values)
+
+ merged_relation.readonly_value = r.readonly_value unless r.readonly_value.nil?
+ merged_relation.limit_value = r.limit_value if r.limit_value.present?
+ merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
+ merged_relation.offset_value = r.offset_value if r.offset_value.present?
+
+ merged_relation = merged_relation.
+ joins(r.joins_values).
+ group(r.group_values).
+ select(r.select_values).
+ from(r.from_value).
+ having(r.having_values)
- merged_wheres = @relation.wheres
+ merged_relation.order_values = Array.wrap(order_values) + Array.wrap(r.order_values)
- r.wheres.each do |w|
+ merged_relation.create_with_value = @create_with_value
+
+ if @create_with_value && r.create_with_value
+ merged_relation.create_with_value = @create_with_value.merge(r.create_with_value)
+ else
+ merged_relation.create_with_value = r.create_with_value || @create_with_value
+ end
+
+ merged_wheres = @where_values
+
+ r.where_values.each do |w|
if w.is_a?(Arel::Predicates::Equality)
merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
end
- merged_wheres << w
+ merged_wheres += [w]
end
- merged_relation.where(*merged_wheres)
+ merged_relation.where_values = merged_wheres
+
+ merged_relation
end
alias :& :merge
def except(*skips)
result = Relation.new(@klass, table)
- result.table = table
- [:eager_load, :preload, :includes].each do |load_method|
- result = result.send(load_method, send(:"#{load_method}_associations"))
+ (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method|
+ result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method)
+ end
+
+ Relation::SINGLE_VALUE_METHODS.each do |method|
+ result.send(:"#{method}_value=", send(:"#{method}_value")) unless skips.include?(method)
end
- result.readonly = self.readonly unless skips.include?(:readonly)
+ result
+ end
- result = result.joins(@relation.joins(@relation)) unless skips.include?(:joins)
- result = result.group(@relation.groupings) unless skips.include?(:group)
- result = result.limit(@relation.taken) unless skips.include?(:limit)
- result = result.offset(@relation.skipped) unless skips.include?(:offset)
- result = result.select(@relation.send(:select_clauses)) unless skips.include?(:select)
- result = result.from(@relation.sources) unless skips.include?(:from)
- result = result.order(order_clause) unless skips.include?(:order)
- result = result.where(*@relation.wheres) unless skips.include?(:where)
- result = result.having(*@relation.havings) unless skips.include?(:having)
- result = result.lock(@relation.locked) unless skips.include?(:lock)
+ def only(*onlies)
+ result = Relation.new(@klass, table)
+
+ onlies.each do |only|
+ if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only)
+ result.send(:"#{only}_values=", send(:"#{only}_values"))
+ elsif Relation::SINGLE_VALUE_METHODS.include?(only)
+ result.send(:"#{only}_value=", send(:"#{only}_value"))
+ else
+ raise "Invalid argument : #{only}"
+ end
+ end
result
end
+ VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
+ :order, :select, :readonly, :group, :having, :from, :lock ]
+
+ def apply_finder_options(options)
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
+
+ relation = spawn
+
+ relation = relation.joins(options[:joins]).
+ where(options[:conditions]).
+ select(options[:select]).
+ group(options[:group]).
+ having(options[:having]).
+ order(options[:order]).
+ limit(options[:limit]).
+ offset(options[:offset]).
+ from(options[:from]).
+ includes(options[:include])
+
+ relation = relation.lock(options[:lock]) if options[:lock].present?
+ relation = relation.readonly(options[:readonly]) if options.has_key?(:readonly)
+
+ relation
+ end
+
end
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 8a32cf1ca2..a996a0ebac 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -28,6 +28,10 @@ module ActiveRecord
class Schema < Migration
private_class_method :new
+ def self.migrations_path
+ ActiveRecord::Migrator.migrations_path
+ end
+
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
@@ -44,7 +48,7 @@ module ActiveRecord
unless info[:version].blank?
initialize_schema_migrations_table
- assume_migrated_upto_version info[:version]
+ assume_migrated_upto_version(info[:version], migrations_path)
end
end
end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 2dfe2c09ea..0a77ad5fd7 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,5 +1,3 @@
-require "active_support/test_case"
-
module ActiveRecord
class TestCase < ActiveSupport::TestCase #:nodoc:
def assert_date_from_db(expected, actual, message = nil)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 12c1f23763..d5adcba3ba 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -11,7 +11,7 @@ module ActiveRecord
def initialize(record)
@record = record
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
- super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
+ super(I18n.t('errors.messages.record_invalid', :errors => errors))
end
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 66b78682ad..e41635134c 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -40,8 +40,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_associated(*attr_names)
- options = attr_names.extract_options!
- validates_with AssociatedValidator, options.merge(:attributes => attr_names)
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 7efd312357..e28808df98 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -2,13 +2,17 @@ module ActiveRecord
module Validations
class UniquenessValidator < ActiveModel::EachValidator
def initialize(options)
- @klass = options.delete(:klass)
super(options.reverse_merge(:case_sensitive => true))
end
+ # Unfortunately, we have to tie Uniqueness validators to a class.
+ def setup(klass)
+ @klass = klass
+ end
+
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
- table = finder_class.active_relation
+ table = finder_class.unscoped
table_name = record.class.quoted_table_name
sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
@@ -170,8 +174,7 @@ module ActiveRecord
# such a case.
#
def validates_uniqueness_of(*attr_names)
- options = attr_names.extract_options!
- validates_with UniquenessValidator, options.merge(:attributes => attr_names, :klass => self)
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
end
end
end