aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-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
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb6
-rw-r--r--activerecord/test/cases/autosave_association_test.rb31
-rwxr-xr-xactiverecord/test/cases/base_test.rb5
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb65
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb5
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb4
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb11
-rw-r--r--activerecord/test/cases/validations_repair_helper.rb20
-rw-r--r--activerecord/test/cases/validations_test.rb28
-rw-r--r--activerecord/test/models/reply.rb6
30 files changed, 353 insertions, 264 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
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 289c89d1e2..d359ad48c5 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -327,10 +327,4 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert !account.new_record?
assert_equal 500, account.credit_limit
end
-
- def test_create!_respects_hash_condition
- account = companies(:first_firm).create_account_limit_500_with_hash_conditions!
- assert !account.new_record?
- assert_equal 500, account.credit_limit
- end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 803e5b25b1..cf763d730a 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -786,14 +786,14 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@pirate.ship.name = ''
assert @pirate.invalid?
- assert @pirate.errors[:ship_name].any?
+ assert @pirate.errors[:"ship.name"].any?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.ship.name = nil
@pirate.catchphrase = nil
assert @pirate.invalid?
- assert @pirate.errors[:ship_name].any?
+ assert @pirate.errors[:"ship.name"].any?
assert @pirate.errors[:catchphrase].any?
end
@@ -886,7 +886,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@ship.pirate.catchphrase = ''
assert @ship.invalid?
- assert @ship.errors[:pirate_catchphrase].any?
+ assert @ship.errors[:"pirate.catchphrase"].any?
end
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
@@ -894,7 +894,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
@ship.pirate.catchphrase = nil
assert @ship.invalid?
assert @ship.errors[:name].any?
- assert @ship.errors[:pirate_catchphrase].any?
+ assert @ship.errors[:"pirate.catchphrase"].any?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@@ -961,7 +961,7 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each { |child| child.name = '' }
assert !@pirate.valid?
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert @pirate.errors[@association_name].empty?
end
@@ -969,16 +969,33 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).build(:name => '')
assert !@pirate.valid?
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert @pirate.errors[@association_name].empty?
end
+ def test_should_default_invalid_error_from_i18n
+ I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
+ { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } }
+ }})
+
+ @pirate.send(@association_name).build(:name => '')
+
+ assert !@pirate.valid?
+ assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"]
+ assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages
+ assert @pirate.errors[@association_name].empty?
+ ensure
+ I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
+ { @association_name.to_s.singularize.to_sym => nil }
+ }})
+ end
+
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.send(@association_name).each { |child| child.name = '' }
@pirate.catchphrase = nil
assert !@pirate.valid?
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert @pirate.errors[:catchphrase].any?
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ebb717812d..730d9d8df7 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -201,7 +201,7 @@ class BasicsTest < ActiveRecord::TestCase
topic = Topic.new(:title => "New Topic")
assert topic.save!
- reply = Reply.new
+ reply = WrongReply.new
assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
end
@@ -959,6 +959,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_update_attributes!
+ Reply.validates_presence_of(:title)
reply = Reply.find(2)
assert_equal "The Second Topic of the day", reply.title
assert_equal "Have a nice day", reply.content
@@ -974,6 +975,8 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "Have a nice day", reply.content
assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
+ ensure
+ Reply.reset_callbacks(:validate)
end
def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 60c5bad225..8891282915 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -592,7 +592,7 @@ module NestedAttributesOnACollectionAssociationTests
assert_no_difference ['Man.count', 'Interest.count'] do
man = Man.create(:name => 'John',
:interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
- assert !man.errors[:interests_man].empty?
+ assert !man.errors[:"interests.man"].empty?
end
end
# restore :inverse_of
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 6605c8bf34..18f6152cc0 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -10,6 +10,7 @@ require 'models/comment'
require 'models/entrant'
require 'models/developer'
require 'models/company'
+require 'models/bird'
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@@ -177,7 +178,7 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_find_with_included_associations
+ def test_find_with_preloaded_associations
assert_queries(2) do
posts = Post.preload(:comments)
assert posts.first.comments.first
@@ -205,6 +206,29 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_find_with_included_associations
+ assert_queries(2) do
+ posts = Post.includes(:comments)
+ assert posts.first.comments.first
+ end
+
+ assert_queries(2) do
+ posts = Post.scoped.includes(:comments)
+ assert posts.first.comments.first
+ end
+
+ assert_queries(2) do
+ posts = Post.includes(:author)
+ assert posts.first.author
+ end
+
+ assert_queries(3) do
+ posts = Post.includes(:author, :comments).to_a
+ assert posts.first.author
+ assert posts.first.comments.first
+ end
+ end
+
def test_default_scope_with_conditions_string
assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
assert_equal nil, DeveloperCalledDavid.create!.name
@@ -477,4 +501,43 @@ class RelationTest < ActiveRecord::TestCase
assert posts.many?
assert ! posts.limit(1).many?
end
+
+ def test_build
+ posts = Post.scoped
+
+ post = posts.new
+ assert_kind_of Post, post
+ end
+
+ def test_scoped_build
+ posts = Post.where(:title => 'You told a lie')
+
+ post = posts.new
+ assert_kind_of Post, post
+ assert_equal 'You told a lie', post.title
+ end
+
+ def test_create
+ birds = Bird.scoped
+
+ sparrow = birds.create
+ assert_kind_of Bird, sparrow
+ assert sparrow.new_record?
+
+ hen = birds.where(:name => 'hen').create
+ assert ! hen.new_record?
+ assert_equal 'hen', hen.name
+ end
+
+ def test_create_bang
+ birds = Bird.scoped
+
+ assert_raises(ActiveRecord::RecordInvalid) { birds.create! }
+
+ hen = birds.where(:name => 'hen').create!
+ assert_kind_of Bird, hen
+ assert ! hen.new_record?
+ assert_equal 'hen', hen.name
+ end
+
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 278a7a6a06..5ed997356b 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -10,7 +10,7 @@ require 'models/interest'
class AssociationValidationTest < ActiveRecord::TestCase
fixtures :topics, :owners
- repair_validations(Topic)
+ repair_validations(Topic, Reply)
def test_validates_size_of_association
repair_validations(Owner) do
@@ -40,7 +40,8 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_many
- Topic.validates_associated( :replies )
+ Topic.validates_associated(:replies)
+ Reply.validates_presence_of(:content)
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
assert !t.valid?
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 532de67d99..f017f24048 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -3,8 +3,9 @@ require 'models/topic'
require 'models/reply'
class I18nValidationTest < ActiveRecord::TestCase
+ repair_validations(Topic, Reply)
def setup
- Topic.reset_callbacks(:validate)
+ Reply.validates_presence_of(:title)
@topic = Topic.new
@old_load_path, @old_backend = I18n.load_path, I18n.backend
I18n.load_path.clear
@@ -13,7 +14,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
def teardown
- Topic.reset_callbacks(:validate)
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index db633339f3..8f84841fe6 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -5,7 +5,6 @@ require 'models/reply'
require 'models/warehouse_thing'
require 'models/guid'
require 'models/event'
-require 'models/developer'
# The following methods in Topic are used in test_conditional_validation_*
class Topic
@@ -276,14 +275,4 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert w6.errors[:city].any?, "Should have errors for city"
assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city"
end
-
- def test_validates_uniqueness_of_with_custom_message_using_quotes
- repair_validations(Developer) do
- Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "David"
- assert !d.valid?
- assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
- end
- end
end
diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb
index e04738d209..11912ca1cc 100644
--- a/activerecord/test/cases/validations_repair_helper.rb
+++ b/activerecord/test/cases/validations_repair_helper.rb
@@ -4,31 +4,19 @@ module ActiveRecord
module ClassMethods
def repair_validations(*model_classes)
- setup do
- @_stored_callbacks = {}
- model_classes.each do |k|
- @_stored_callbacks[k] = k._validate_callbacks.dup
- end
- end
teardown do
model_classes.each do |k|
- k._validate_callbacks = @_stored_callbacks[k]
- k.__update_callbacks(:validate)
+ k.reset_callbacks(:validate)
end
end
end
end
- def repair_validations(*model_classes, &block)
- @__stored_callbacks = {}
- model_classes.each do |k|
- @__stored_callbacks[k] = k._validate_callbacks.dup
- end
- return block.call
+ def repair_validations(*model_classes)
+ yield
ensure
model_classes.each do |k|
- k._validate_callbacks = @__stored_callbacks[k]
- k.__update_callbacks(:validate)
+ k.reset_callbacks(:validate)
end
end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 7462d944e0..3a1d5ae212 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -42,7 +42,7 @@ class ValidationsTest < ActiveRecord::TestCase
repair_validations(Topic)
def test_error_on_create
- r = Reply.new
+ r = WrongReply.new
r.title = "Wrong Create"
assert !r.valid?
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
@@ -50,7 +50,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_error_on_update
- r = Reply.new
+ r = WrongReply.new
r.title = "Bad"
r.content = "Good"
assert r.save, "First save should be successful"
@@ -63,11 +63,11 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_invalid_record_exception
- assert_raise(ActiveRecord::RecordInvalid) { Reply.create! }
- assert_raise(ActiveRecord::RecordInvalid) { Reply.new.save! }
+ assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
+ assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! }
begin
- r = Reply.new
+ r = WrongReply.new
r.save!
flunk
rescue ActiveRecord::RecordInvalid => invalid
@@ -77,13 +77,13 @@ class ValidationsTest < ActiveRecord::TestCase
def test_exception_on_create_bang_many
assert_raise(ActiveRecord::RecordInvalid) do
- Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
+ WrongReply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
end
end
def test_exception_on_create_bang_with_block
assert_raise(ActiveRecord::RecordInvalid) do
- Reply.create!({ "title" => "OK" }) do |r|
+ WrongReply.create!({ "title" => "OK" }) do |r|
r.content = nil
end
end
@@ -91,15 +91,15 @@ class ValidationsTest < ActiveRecord::TestCase
def test_exception_on_create_bang_many_with_block
assert_raise(ActiveRecord::RecordInvalid) do
- Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
+ WrongReply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
r.content = nil
end
end
end
def test_scoped_create_without_attributes
- Reply.send(:with_scope, :create => {}) do
- assert_raise(ActiveRecord::RecordInvalid) { Reply.create! }
+ WrongReply.send(:with_scope, :create => {}) do
+ assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
end
end
@@ -122,15 +122,15 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_create_without_validation
- reply = Reply.new
+ reply = WrongReply.new
assert !reply.save
assert reply.save(false)
end
def test_create_without_validation_bang
- count = Reply.count
- assert_nothing_raised { Reply.new.save_without_validation! }
- assert count+1, Reply.count
+ count = WrongReply.count
+ assert_nothing_raised { WrongReply.new.save_without_validation! }
+ assert count+1, WrongReply.count
end
def test_validates_acceptance_of_with_non_existant_table
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index ba5a1d1d01..f1ba45b528 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -7,11 +7,13 @@ class Reply < Topic
belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count"
has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id"
+ attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title
+end
+
+class WrongReply < Reply
validate :errors_on_empty_content
validate :title_is_wrong_create, :on => :create
- attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title
-
validate :check_empty_title
validate :check_content_mismatch, :on => :create
validate :check_wrong_update, :on => :update