aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb1
-rwxr-xr-xactiverecord/lib/active_record/associations.rb88
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rwxr-xr-xactiverecord/lib/active_record/base.rb103
-rw-r--r--activerecord/lib/active_record/calculations.rb4
-rw-r--r--activerecord/lib/active_record/locale/en.yml3
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/named_scope.rb4
-rw-r--r--activerecord/lib/active_record/railtie.rb (renamed from activerecord/lib/active_record/rails.rb)6
-rw-r--r--activerecord/lib/active_record/relation.rb110
-rw-r--r--activerecord/lib/active_record/relation/calculation_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb45
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb19
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb17
19 files changed, 233 insertions, 201 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index a524dc50a1..cf439b0dc0 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -54,6 +54,7 @@ module ActiveRecord
autoload :QueryMethods
autoload :FinderMethods
autoload :CalculationMethods
+ autoload :PredicateBuilder
end
autoload :Base
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f0bad6c3ba..d74b21b690 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1465,8 +1465,7 @@ module ActiveRecord
after_destroy(method_name)
end
- def find_with_associations(options = {}, join_dependency = nil)
- join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
+ def find_with_associations(options = {}, join_dependency)
rows = select_all_rows(options, join_dependency)
join_dependency.instantiate(rows)
rescue ThrowResult
@@ -1488,7 +1487,7 @@ module ActiveRecord
dependent_conditions = []
dependent_conditions << "#{reflection.primary_key_name} = \#{record.#{reflection.name}.send(:owner_quoted_id)}"
dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
- dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions]
+ dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.table_name) if reflection.options[:conditions]
dependent_conditions << extra_conditions if extra_conditions
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
dependent_conditions = dependent_conditions.gsub('@', '\@')
@@ -1706,7 +1705,7 @@ module ActiveRecord
def construct_finder_arel_with_included_associations(options, join_dependency)
scope = scope(:find)
- relation = arel_table
+ relation = active_relation
for association in join_dependency.join_associations
relation = association.join_relation(relation)
@@ -1751,7 +1750,7 @@ module ActiveRecord
def construct_finder_sql_for_association_limiting(options, join_dependency)
scope = scope(:find)
- relation = arel_table(options[:from])
+ relation = active_relation
for association in join_dependency.join_associations
relation = association.join_relation(relation)
@@ -1764,89 +1763,12 @@ module ActiveRecord
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.to_sql
end
- def tables_in_string(string)
- return [] if string.blank?
- string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten
- end
-
- def tables_in_hash(hash)
- return [] if hash.blank?
- tables = hash.map do |key, value|
- if value.is_a?(Hash)
- key.to_s
- else
- tables_in_string(key) if key.is_a?(String)
- end
- end
- tables.flatten.compact
- end
-
- def conditions_tables(options)
- # look in both sets of conditions
- conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
- case cond
- when nil then all
- when Array then all << tables_in_string(cond.first)
- when Hash then all << tables_in_hash(cond)
- else all << tables_in_string(cond)
- end
- end
- conditions.flatten
- end
-
- def order_tables(options)
- order = [options[:order], scope(:find, :order) ].join(", ")
- return [] unless order && order.is_a?(String)
- tables_in_string(order)
- end
-
- def selects_tables(options)
- select = options[:select]
- return [] unless select && select.is_a?(String)
- tables_in_string(select)
- end
-
- def joined_tables(options)
- scope = scope(:find)
- joins = options[:joins]
- merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
- [table_name] + case merged_joins
- when Symbol, Hash, Array
- if array_of_strings?(merged_joins)
- tables_in_string(merged_joins.join(' '))
- else
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_joins, nil)
- join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
- end
- else
- tables_in_string(merged_joins)
- end
- end
-
- # Checks if the conditions reference a table other than the current model table
- def include_eager_conditions?(options, tables = nil, joined_tables = nil)
- ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
- end
-
- # Checks if the query order references a table other than the current model's table.
- def include_eager_order?(options, tables = nil, joined_tables = nil)
- ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
- end
-
- def include_eager_select?(options, joined_tables = nil)
- (selects_tables(options) - (joined_tables || joined_tables(options))).any?
- end
-
- def references_eager_loaded_tables?(options)
- joined_tables = joined_tables(options)
- include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
- end
-
def using_limitable_reflections?(reflections)
reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 1ceb0dbf96..358db6df1d 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -21,7 +21,7 @@ module ActiveRecord
construct_sql
end
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
def select(select = nil, &block)
if block_given?
@@ -58,7 +58,7 @@ module ActiveRecord
find_scope = construct_scope[:find].slice(:conditions, :order)
with_scope(:find => find_scope) do
- relation = @reflection.klass.send(:construct_finder_arel_with_includes, options)
+ relation = @reflection.klass.send(:construct_finder_arel, options)
case args.first
when :first, :last, :all
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 7d8f4670fa..022dd2ae9b 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -161,7 +161,7 @@ module ActiveRecord
end
# Forwards the call to the reflection class.
- def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
+ def sanitize_sql(sql, table_name = @reflection.klass.table_name)
@reflection.klass.send(:sanitize_sql, sql, table_name)
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 9569b0c6f9..bd05d1014c 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -44,7 +44,7 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
- relation = arel_table(@reflection.options[:join_table])
+ relation = Arel::Table.new(@reflection.options[:join_table])
attributes = columns.inject({}) do |attrs, column|
case column.name.to_s
when @reflection.primary_key_name.to_s
@@ -70,7 +70,7 @@ module ActiveRecord
if sql = @reflection.options[:delete_sql]
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
else
- relation = arel_table(@reflection.options[:join_table])
+ relation = Arel::Table.new(@reflection.options[:join_table])
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id)))
).delete
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index be74ddfcf0..d3336cf2d2 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -69,7 +69,7 @@ module ActiveRecord
when :delete_all
@reflection.klass.delete(records.map { |record| record.id })
else
- relation = arel_table(@reflection.table_name)
+ relation = Arel::Table.new(@reflection.table_name)
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id)))
).update(relation[@reflection.primary_key_name] => nil)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 44c668b619..98ab64537e 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -267,7 +267,7 @@ module ActiveRecord
unless valid = association.valid?
if reflection.options[:autosave]
association.errors.each do |attribute, message|
- attribute = "#{reflection.name}_#{attribute}"
+ attribute = "#{reflection.name}.#{attribute}"
errors[attribute] << message if errors[attribute].empty?
end
else
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c4bdbdad08..70776c7aa2 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -645,7 +645,7 @@ module ActiveRecord #:nodoc:
options = args.extract_options!
set_readonly_option!(options)
- relation = construct_finder_arel_with_includes(options)
+ relation = construct_finder_arel(options)
case args.first
when :first, :last, :all
@@ -655,7 +655,7 @@ module ActiveRecord #:nodoc:
end
end
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
# A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:first)</tt>.
@@ -815,8 +815,8 @@ module ActiveRecord #:nodoc:
#
# # Delete multiple rows
# Todo.delete([2,3,4])
- def delete(id)
- delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
+ def delete(id_or_array)
+ active_relation.where(construct_conditions(nil, scope(:find))).delete(id_or_array)
end
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
@@ -873,7 +873,7 @@ module ActiveRecord #:nodoc:
def update_all(updates, conditions = nil, options = {})
scope = scope(:find)
- relation = arel_table
+ relation = active_relation
if conditions = construct_conditions(conditions, scope)
relation = relation.where(Arel::SqlLiteral.new(conditions))
@@ -938,7 +938,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)
- arel_table.where(construct_conditions(conditions, scope(:find))).delete_all
+ active_relation.where(construct_conditions(conditions, scope(:find))).delete_all
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -1391,7 +1391,8 @@ module ActiveRecord #:nodoc:
# end
def reset_column_information
undefine_attribute_methods
- @arel_table = @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
+ @active_relation = @active_relation_engine = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -1504,8 +1505,16 @@ module ActiveRecord #:nodoc:
"(#{segments.join(') AND (')})" unless segments.empty?
end
- def arel_table(table = nil)
- Relation.new(self, Arel::Table.new(table || table_name))
+ def active_relation
+ @active_relation ||= Relation.new(self, active_relation_table)
+ end
+
+ def active_relation_table(table_name_alias = nil)
+ Arel::Table.new(table_name, :as => table_name_alias)
+ end
+
+ def active_relation_engine
+ @active_relation_engine ||= Arel::Sql::Engine.new(self)
end
private
@@ -1561,7 +1570,7 @@ module ActiveRecord #:nodoc:
def construct_finder_arel(options = {}, scope = scope(:find))
validate_find_options(options)
- relation = arel_table.
+ 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]))).
@@ -1570,7 +1579,8 @@ module ActiveRecord #:nodoc:
order(construct_order(options[:order], scope)).
limit(construct_limit(options[:limit], scope)).
offset(construct_offset(options[:offset], scope)).
- from(options[:from])
+ from(options[:from]).
+ includes( merge_includes(scope && scope[:include], options[:include]))
lock = (scope && scope[:lock]) || options[:lock]
relation = relation.lock if lock.present?
@@ -1580,21 +1590,6 @@ module ActiveRecord #:nodoc:
relation
end
- def construct_finder_arel_with_includes(options = {})
- relation = construct_finder_arel(options)
- include_associations = merge_includes(scope(:find, :include), options[:include])
-
- if include_associations.any?
- if references_eager_loaded_tables?(options)
- relation = relation.eager_load(include_associations)
- else
- relation = relation.preload(include_associations)
- end
- end
-
- 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
@@ -1662,7 +1657,7 @@ module ActiveRecord #:nodoc:
def build_association_joins(joins)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil)
- relation = arel_table.relation
+ relation = active_relation.relation
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),
@@ -1677,14 +1672,14 @@ module ActiveRecord #:nodoc:
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
end
- def type_condition(table_alias=nil)
- quoted_table_alias = self.connection.quote_table_name(table_alias || table_name)
- quoted_inheritance_column = connection.quote_column_name(inheritance_column)
- type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' " ) do |condition, subclass|
- condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
- 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]
+ condition = sti_column.eq(sti_name)
+ subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
- " (#{type_condition}) "
+ condition.to_sql
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -1713,7 +1708,7 @@ module ActiveRecord #:nodoc:
super unless all_attributes_exists?(attribute_names)
if match.finder?
options = arguments.extract_options!
- relation = options.any? ? construct_finder_arel_with_includes(options) : scoped
+ relation = options.any? ? construct_finder_arel(options) : 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
@@ -1964,7 +1959,7 @@ module ActiveRecord #:nodoc:
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition, table_name = quoted_table_name)
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
return nil if condition.blank?
case condition
@@ -2035,30 +2030,12 @@ module ActiveRecord #:nodoc:
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
- conditions = attrs.map do |attr, value|
- table_name = default_table_name
-
- unless value.is_a?(Hash)
- attr = attr.to_s
-
- # Extract table name from qualified attribute names.
- if attr.include?('.')
- attr_table_name, attr = attr.split('.', 2)
- attr_table_name = connection.quote_table_name(attr_table_name)
- else
- attr_table_name = table_name
- end
-
- attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
- else
- sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
- end
- end.join(' AND ')
-
- replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
+ table = Arel::Table.new(default_table_name, active_relation_engine)
+ builder = PredicateBuilder.new(active_relation_engine)
+ builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -2323,7 +2300,7 @@ module ActiveRecord #:nodoc:
# be made (since they can't be persisted).
def destroy
unless new_record?
- self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete
+ self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).delete_all
end
@destroyed = true
@@ -2610,7 +2587,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.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
+ self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
@@ -2623,9 +2600,9 @@ module ActiveRecord #:nodoc:
attributes_values = arel_attributes_values
new_id = if attributes_values.empty?
- self.class.arel_table.insert connection.empty_insert_statement_value
+ self.class.active_relation.insert connection.empty_insert_statement_value
else
- self.class.arel_table.insert attributes_values
+ self.class.active_relation.insert attributes_values
end
self.id ||= new_id
@@ -2720,7 +2697,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.arel_table[name]] = value
+ attrs[self.class.active_relation[name]] = value
end
end
end
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index d51d9f2159..20d287faeb 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -162,7 +162,7 @@ module ActiveRecord
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope))
construct_calculation_arel_with_included_associations(options, join_dependency)
else
- arel_table.
+ active_relation.
joins(construct_join(options[:joins], scope)).
from((scope && scope[:from]) || options[:from]).
where(construct_conditions(options[:conditions], scope)).
@@ -178,7 +178,7 @@ module ActiveRecord
def construct_calculation_arel_with_included_associations(options, join_dependency)
scope = scope(:find)
- relation = arel_table
+ relation = active_relation
for association in join_dependency.join_associations
relation = association.join_relation(relation)
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 092f5f0023..e33d389f8c 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -1,6 +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:
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 986bc7009b..f9e538c586 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -78,11 +78,11 @@ module ActiveRecord
attribute_names.uniq!
begin
- arel_table = self.class.arel_table(self.class.table_name)
+ relation = self.class.active_relation
- affected_rows = arel_table.where(
- arel_table[self.class.primary_key].eq(quoted_id).and(
- arel_table[self.class.locking_column].eq(quote_value(previous_value))
+ affected_rows = relation.where(
+ relation[self.class.primary_key].eq(quoted_id).and(
+ relation[self.class.locking_column].eq(quote_value(previous_value))
)
).update(arel_attributes_values(false, false, attribute_names))
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index a6336e762a..f63b249241 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -27,9 +27,9 @@ module ActiveRecord
Scope.new(self, options, &block)
else
unless scoped?(:find)
- finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table
+ finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn
else
- construct_finder_arel_with_includes
+ construct_finder_arel
end
end
end
diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/railtie.rb
index a13bd2a5da..657ee738c0 100644
--- a/activerecord/lib/active_record/rails.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -2,10 +2,12 @@
# rails, so let's make sure that it gets required before
# here. This is needed for correctly setting up the middleware.
# In the future, this might become an optional require.
-require "action_controller/rails"
+require "active_record"
+require "action_controller/railtie"
+require "rails"
module ActiveRecord
- class Plugin < Rails::Plugin
+ class Railtie < Rails::Railtie
plugin_name :active_record
rake_tasks do
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ae03e1d7e9..114095b7ef 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -2,32 +2,60 @@ module ActiveRecord
class Relation
include QueryMethods, FinderMethods, CalculationMethods
- delegate :to_sql, :to => :relation
delegate :length, :collect, :map, :each, :all?, :to => :to_a
- attr_reader :relation, :klass, :preload_associations, :eager_load_associations
- attr_writer :readonly, :preload_associations, :eager_load_associations
+ attr_reader :relation, :klass
+ attr_writer :readonly, :table
+ attr_accessor :preload_associations, :eager_load_associations, :include_associations
def initialize(klass, relation)
@klass, @relation = klass, relation
@preload_associations = []
@eager_load_associations = []
+ @include_associations = []
@loaded, @readonly = false
end
+ def new(*args, &block)
+ with_create_scope { @klass.new(*args, &block) }
+ end
+
+ def create(*args, &block)
+ with_create_scope { @klass.create(*args, &block) }
+ end
+
+ def create!(*args, &block)
+ with_create_scope { @klass.create!(*args, &block) }
+ end
+
def merge(r)
raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
- joins(r.relation.joins(r.relation)).
- group(r.send(:group_clauses).join(', ')).
- order(r.send(:order_clauses).join(', ')).
- where(r.send(:where_clause)).
- limit(r.taken).
- offset(r.skipped).
- select(r.send(:select_clauses).join(', ')).
- eager_load(r.eager_load_associations).
- preload(r.preload_associations).
- from(r.send(:sources).present? ? r.send(:from_clauses) : nil)
+ merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations)
+ merged_relation.readonly = r.readonly
+
+ [self.relation, r.relation].each do |arel|
+ merged_relation = merged_relation.
+ joins(arel.joins(arel)).
+ group(arel.groupings).
+ order(arel.send(:order_clauses).join(', ')).
+ limit(arel.taken).
+ offset(arel.skipped).
+ select(arel.send(:select_clauses)).
+ from(arel.sources)
+ end
+
+ merged_wheres = @relation.wheres
+
+ r.wheres.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
+ end
+
+ merged_relation.where(*merged_wheres)
end
alias :& :merge
@@ -47,7 +75,9 @@ module ActiveRecord
def to_a
return @records if loaded?
- @records = if @eager_load_associations.any?
+ find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables?
+
+ @records = if find_with_associations
begin
@klass.send(:find_with_associations, {
:select => @relation.send(:select_clauses).join(', '),
@@ -59,7 +89,7 @@ module ActiveRecord
:offset => @relation.skipped,
:from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
},
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil))
+ ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil))
rescue ThrowResult
[]
end
@@ -67,7 +97,10 @@ module ActiveRecord
@klass.find_by_sql(@relation.to_sql)
end
- @preload_associations.each {|associations| @klass.send(:preload_associations, @records, associations) }
+ preload = @preload_associations
+ preload += @include_associations unless find_with_associations
+ preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
+
@records.each { |record| record.readonly! } if @readonly
@loaded = true
@@ -109,6 +142,10 @@ module ActiveRecord
@relation.delete.tap { reset }
end
+ def delete(id_or_array)
+ where(@klass.primary_key => id_or_array).delete_all
+ end
+
def loaded?
@loaded
end
@@ -119,19 +156,33 @@ module ActiveRecord
end
def reset
- @first = @last = nil
+ @first = @last = @create_scope = @to_sql = nil
@records = []
self
end
def spawn(relation = @relation)
- relation = self.class.new(@klass, relation)
+ relation = Relation.new(@klass, relation)
relation.readonly = @readonly
relation.preload_associations = @preload_associations
relation.eager_load_associations = @eager_load_associations
+ relation.include_associations = @include_associations
+ relation.table = table
relation
end
+ def table
+ @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass))
+ end
+
+ def primary_key
+ @primary_key ||= table[@klass.primary_key]
+ end
+
+ def to_sql
+ @to_sql ||= @relation.to_sql
+ end
+
protected
def method_missing(method, *args, &block)
@@ -153,9 +204,30 @@ module ActiveRecord
end
end
- def where_clause(join_string = "\n\tAND ")
+ 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
+ end
+
+ def where_clause(join_string = " AND ")
@relation.send(:where_clauses).join(join_string)
end
+ def references_eager_loaded_tables?
+ joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
+ (tables_in_string(to_sql) - joined_tables).any?
+ end
+
+ def tables_in_string(string)
+ return [] if string.blank?
+ string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq
+ end
+
end
end
diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb
index a925a99464..5246c7bc5d 100644
--- a/activerecord/lib/active_record/relation/calculation_methods.rb
+++ b/activerecord/lib/active_record/relation/calculation_methods.rb
@@ -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.arel_table, column_name)
+ Arel::Attribute.new(@klass.active_relation, column_name)
else
Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
end
@@ -77,7 +77,7 @@ module ActiveRecord
select_statement = if operation == 'count' && column_name == :all
"COUNT(*) AS count_all"
else
- Arel::Attribute.new(@klass.arel_table, column_name).send(operation).as(aggregate_alias).to_sql
+ Arel::Attribute.new(@klass.active_relation, column_name).send(operation).as(aggregate_alias).to_sql
end
select_statement << ", #{group_field} AS #{group_alias}"
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7a1d6fc538..c3e5f27838 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -21,8 +21,8 @@ module ActiveRecord
end
def exists?(id = nil)
- relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1)
- relation = relation.where(@klass.primary_key => id) if id
+ relation = select(primary_key).limit(1)
+ relation = relation.where(primary_key.eq(id)) if id
relation.first ? true : false
end
@@ -78,7 +78,7 @@ module ActiveRecord
end
def find_one(id)
- record = where(@klass.primary_key => id).first
+ record = where(primary_key.eq(id)).first
unless record
conditions = where_clause(', ')
@@ -90,7 +90,7 @@ module ActiveRecord
end
def find_some(ids)
- result = where(@klass.primary_key => ids).all
+ result = where(primary_key.in(ids)).all
expected_size =
if @relation.taken && ids.size > @relation.taken
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
new file mode 100644
index 0000000000..6b7d941350
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -0,0 +1,45 @@
+module ActiveRecord
+ class PredicateBuilder
+
+ def initialize(engine)
+ @engine = engine
+ end
+
+ def build_from_hash(attributes, default_table)
+ predicates = attributes.map do |column, value|
+ table = default_table
+
+ if value.is_a?(Hash)
+ table = Arel::Table.new(column, :engine => @engine)
+ build_from_hash(value, table)
+ else
+ column = column.to_s
+
+ if column.include?('.')
+ table_name, column = column.split('.', 2)
+ table = Arel::Table.new(table_name, :engine => @engine)
+ end
+
+ attribute = table[column] || Arel::Attribute.new(table, column.to_sym)
+
+ case value
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
+ attribute.in(value)
+ when Range
+ # TODO : Arel should handle ranges with excluded end.
+ if value.exclude_end?
+ [attribute.gteq(value.begin), attribute.lt(value.end)]
+ else
+ attribute.in(value)
+ end
+ else
+ attribute.eq(value)
+ end
+ end
+ end
+
+ predicates.flatten
+ end
+
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 631c80da25..cf2cc7ba70 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,6 +5,10 @@ module ActiveRecord
spawn.tap {|r| r.preload_associations += Array.wrap(associations) }
end
+ def includes(*associations)
+ spawn.tap {|r| r.include_associations += Array.wrap(associations) }
+ end
+
def eager_load(*associations)
spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) }
end
@@ -104,14 +108,19 @@ module ActiveRecord
def where(*args)
return spawn if args.blank?
- if [String, Hash, Array].include?(args.first.class)
- conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
- conditions = Arel::SqlLiteral.new(conditions) if conditions
+ builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass))
+
+ conditions = if [String, Array].include?(args.first.class)
+ merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
+ Arel::SqlLiteral.new(merged) if merged
+ elsif args.first.is_a?(Hash)
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first)
+ builder.build_from_hash(attributes, table)
else
- conditions = args.first
+ args.first
end
- spawn(@relation.where(conditions))
+ conditions.is_a?(String) ? spawn(@relation.where(conditions)) : spawn(@relation.where(*conditions))
end
private
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index ffbe1b5c40..7efd312357 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -8,24 +8,25 @@ module ActiveRecord
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
+ table = finder_class.active_relation
+
table_name = record.class.quoted_table_name
sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
+ relation = table.where(sql, *params)
+
Array(options[:scope]).each do |scope_item|
scope_value = record.send(scope_item)
- sql << " AND " << record.class.send(:attribute_condition, "#{table_name}.#{scope_item}", scope_value)
- params << scope_value
+ relation = relation.where(scope_item => scope_value)
end
unless record.new_record?
- sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
- params << record.send(:id)
+ # TODO : This should be in Arel
+ relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
end
- finder_class.send(:with_exclusive_scope) do
- if finder_class.exists?([sql, *params])
- record.errors.add(attribute, :taken, :default => options[:message], :value => value)
- end
+ if relation.exists?
+ record.errors.add(attribute, :taken, :default => options[:message], :value => value)
end
end