aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorEmilio Tagua <miloops@gmail.com>2009-08-10 18:07:33 -0300
committerEmilio Tagua <miloops@gmail.com>2009-08-10 18:07:33 -0300
commit0e2fbd80e2420329738b891240d44a056cea1de4 (patch)
tree5b16755670be58e168b5e86e2cdcb43ee5aa3918 /activerecord
parenteb3ae44ccaff1dc63eb31bf86d8db07c88ddc413 (diff)
parent600a89f2082beadf4af9fe140a1a2ae56386cd49 (diff)
downloadrails-0e2fbd80e2420329738b891240d44a056cea1de4.tar.gz
rails-0e2fbd80e2420329738b891240d44a056cea1de4.tar.bz2
rails-0e2fbd80e2420329738b891240d44a056cea1de4.zip
Merge commit 'rails/master'
Conflicts: activerecord/lib/active_record/calculations.rb activerecord/lib/active_record/connection_adapters/mysql_adapter.rb activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG2
-rw-r--r--activerecord/Rakefile4
-rw-r--r--activerecord/lib/active_record.rb1
-rwxr-xr-xactiverecord/lib/active_record/associations.rb66
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb2
-rwxr-xr-xactiverecord/lib/active_record/base.rb28
-rw-r--r--activerecord/lib/active_record/calculations.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb4
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/active_record/validator.rb68
-rw-r--r--activerecord/test/cases/adapter_test.rb12
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/habtm_join_table_test.rb56
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb58
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb46
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb10
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb8
-rwxr-xr-xactiverecord/test/cases/base_test.rb47
-rw-r--r--activerecord/test/cases/calculations_test.rb6
-rw-r--r--activerecord/test/cases/column_definition_test.rb34
-rw-r--r--activerecord/test/cases/dirty_test.rb10
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/i18n_test.rb5
-rw-r--r--activerecord/test/cases/migration_test.rb50
-rw-r--r--activerecord/test/cases/modules_test.rb42
-rw-r--r--activerecord/test/cases/named_scope_test.rb6
-rw-r--r--activerecord/test/cases/pk_test.rb18
-rw-r--r--activerecord/test/cases/reflection_test.rb4
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb20
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb17
-rw-r--r--activerecord/test/fixtures/posts.yml3
-rw-r--r--activerecord/test/models/author.rb1
-rw-r--r--activerecord/test/models/comment.rb6
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/contract.rb4
-rw-r--r--activerecord/test/models/organization.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb15
-rw-r--r--activerecord/test/schema/schema.rb4
52 files changed, 704 insertions, 101 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9adc6b887f..d40251b9ca 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
+
* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing]
* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 0d33b9d516..09dbc5ad6d 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -64,8 +64,8 @@ end
namespace :mysql do
desc 'Build the MySQL test databases'
task :build_databases do
- %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest )
- %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 )
+ %x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
+ %x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
end
desc 'Drop the MySQL test databases'
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index f768c57ace..d9310a9927 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -74,6 +74,7 @@ module ActiveRecord
autoload :TestCase, 'active_record/test_case'
autoload :Timestamp, 'active_record/timestamp'
autoload :Transactions, 'active_record/transactions'
+ autoload :Validator, 'active_record/validator'
autoload :Validations, 'active_record/validations'
module AttributeMethods
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index aeca74ef4a..baad8fc5fd 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -42,11 +42,12 @@ module ActiveRecord
end
end
- class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -416,6 +417,32 @@ module ActiveRecord
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
# @firm.invoices # selects all invoices by going through the Client join model.
#
+ # Similarly you can go through a +has_one+ association on the join model:
+ #
+ # class Group < ActiveRecord::Base
+ # has_many :users
+ # has_many :avatars, :through => :users
+ # end
+ #
+ # class User < ActiveRecord::Base
+ # belongs_to :group
+ # has_one :avatar
+ # end
+ #
+ # class Avatar < ActiveRecord::Base
+ # belongs_to :user
+ # end
+ #
+ # @group = Group.first
+ # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.avatars # selects all avatars by going through the User join model.
+ #
+ # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
+ # *read-only*. For example, the following would not work following the previous example:
+ #
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
+ # @group.avatars.delete(@group.avatars.last) # so would this
+ #
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
@@ -819,7 +846,7 @@ module ActiveRecord
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # or <tt>has_many</tt> association on the join model.
+ # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
@@ -1367,7 +1394,7 @@ module ActiveRecord
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
- send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
+ send("#{reflection.name}=", reflection.klass.find(ids))
end
end
end
@@ -1656,7 +1683,7 @@ module ActiveRecord
relation.join(joins)
relation.where(construct_conditions(options[:conditions], scope))
- relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+ relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
relation.project(column_aliases(join_dependency))
relation.group(construct_group(options[:group], options[:having], scope))
@@ -1685,18 +1712,23 @@ module ActiveRecord
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
- # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
- tables_from_conditions = conditions_tables(options)
- tables_from_order = order_tables(options)
- all_tables = tables_from_conditions + tables_from_order
- options[:joins] = all_tables.uniq.map {|table|
- join_dependency.joins_for_table_name(table)
- }.flatten.compact.uniq.collect { |assoc| assoc.association_join }.join
-
- construct_finder_sql(options.merge(
- :select => connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))
- )
- )
+ scope = scope(:find)
+
+ relation = arel_table(options[:from])
+
+ joins = join_dependency.join_associations.collect{|join| join.association_join }.join
+ joins << construct_join(options[:joins], scope)
+ relation.join(joins)
+
+ relation.where(construct_conditions(options[:conditions], scope))
+ relation.project(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(",")))
+
+ relation.group(construct_group(options[:group], options[:having], scope))
+ relation.order(construct_order(options[:order], scope))
+ relation.take(construct_limit(options[:limit], scope))
+ relation.skip(construct_limit(options[:offset], scope))
+
+ sanitize_sql(relation.to_sql)
end
def tables_in_string(string)
@@ -1870,7 +1902,7 @@ module ActiveRecord
descendant
end.flatten.compact
- remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index e67ccfb228..1b7bf42b91 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -208,6 +208,7 @@ module ActiveRecord
# Note that this method will _always_ remove records from the database
# ignoring the +:dependent+ option.
def destroy(*records)
+ records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
remove_records(records) do |records, old_records|
old_records.each { |record| record.destroy }
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 fd23e59e82..d91c555dad 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
@@ -1,6 +1,11 @@
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
+ def initialize(owner, reflection)
+ super
+ @primary_key_list = {}
+ end
+
def create(attributes = {})
create_record(attributes) { |record| insert_record(record) }
end
@@ -17,6 +22,12 @@ module ActiveRecord
@reflection.reset_column_information
end
+ def has_primary_key?
+ return @has_primary_key unless @has_primary_key.nil?
+ @has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
+ ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
+ end
+
protected
def construct_find_options!(options)
options[:joins] = @join_sql
@@ -29,6 +40,11 @@ module ActiveRecord
end
def insert_record(record, force = true, validate = true)
+ if has_primary_key?
+ raise ActiveRecord::ConfigurationError,
+ "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
+ end
+
if record.new_record?
if force
record.save!
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index e4b631bc54..73d3c23cd3 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -74,6 +74,7 @@ module ActiveRecord
"#{@reflection.primary_key_name} = NULL",
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
)
+ @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 2ed92ca1ba..6004751dc9 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -20,7 +20,11 @@ module ActiveRecord
ensure_owner_is_not_new
transaction do
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
+ object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association
+ raise_on_type_mismatch(object)
+ add_record_to_target_with_callbacks(object) do |r|
+ insert_record(object, false)
+ end
object
end
end
@@ -54,7 +58,7 @@ module ActiveRecord
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?
+ options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
end
def insert_record(record, force = true, validate = true)
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 830aa1808a..a79bf943d1 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -18,9 +18,15 @@ module ActiveRecord
current_object = @owner.send(@reflection.through_reflection.name)
if current_object
- new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
- else
- @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value
+ new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
+ elsif new_value
+ if @owner.new_record?
+ self.target = new_value
+ through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
+ through_association.build(construct_join_attributes(new_value))
+ else
+ @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index c172e7b8f9..1924156e2a 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -93,7 +93,7 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
# TODO: revist this to allow it for deletion, supposing dependent option is supported
- raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 9ec1fbeee1..911c908c8b 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -161,7 +161,7 @@ module ActiveRecord
if partial_updates?
# Serialized attributes should always be written in case they've been
# changed in place.
- update_without_dirty(changed | self.class.serialized_attributes.keys)
+ update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
else
update_without_dirty
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 2eb2699949..e1f4461965 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1397,7 +1397,7 @@ module ActiveRecord #:nodoc:
end
defaults << options[:default] if options[:default]
defaults.flatten!
- defaults << attribute_key_name.humanize
+ defaults << attribute_key_name.to_s.humanize
options[:count] ||= 1
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
end
@@ -2296,20 +2296,24 @@ 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, table_name = quoted_table_name)
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_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?('.')
- table_name, attr = attr.split('.', 2)
- table_name = connection.quote_table_name(table_name)
+ 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("#{table_name}.#{connection.quote_column_name(attr)}", value)
+ 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
@@ -3028,16 +3032,22 @@ module ActiveRecord #:nodoc:
def execute_callstack_for_multiparameter_attributes(callstack)
errors = []
- callstack.each do |name, values|
+ callstack.each do |name, values_with_empty_parameters|
begin
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ # in order to allow a date to be set without a year, we must keep the empty values.
+ # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
+ values = values_with_empty_parameters.reject(&:nil?)
+
if values.empty?
send(name + "=", nil)
else
+
value = if Time == klass
instantiate_time_object(name, values)
elsif Date == klass
begin
+ values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
Date.new(*values)
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
@@ -3065,10 +3075,8 @@ module ActiveRecord #:nodoc:
attribute_name = multiparameter_name.split("(").first
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
- unless value.empty?
- attributes[attribute_name] <<
- [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
- end
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
+ attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
end
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 43e4529575..6a5f2222a2 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -250,7 +250,6 @@ module ActiveRecord
[column_name || :all, options]
end
-
private
def validate_calculation_options(operation, options = {})
options.assert_valid_keys(CALCULATIONS_OPTIONS)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index f346e3ebc8..520f3c8c0c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -315,6 +315,20 @@ module ActiveRecord
@base = base
end
+ #Handles non supported datatypes - e.g. XML
+ def method_missing(symbol, *args)
+ if symbol.to_s == 'xml'
+ xml_column_fallback(args)
+ end
+ end
+
+ def xml_column_fallback(*args)
+ case @base.adapter_name.downcase
+ when 'sqlite', 'mysql'
+ options = args.extract_options!
+ column(args[0], :text, options)
+ end
+ end
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name)
@@ -705,3 +719,4 @@ module ActiveRecord
end
end
+
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index b2c5c78bf7..e731bc84f0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -107,7 +107,7 @@ module ActiveRecord
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
table_definition = TableDefinition.new(self)
- table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
yield table_definition if block_given?
@@ -329,7 +329,7 @@ module ActiveRecord
schema_migrations_table.column :version, :string, :null => false
end
add_index sm_table, :version, :unique => true,
- :name => 'unique_schema_migrations'
+ :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
# Backwards-compatibility: if we find schema_info, assume we've
# migrated up to that point:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c533d4cdb6..fab70f34b9 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -56,6 +56,13 @@ module ActiveRecord
false
end
+ # Can this adapter determine the primary key for tables not attached
+ # to an ActiveRecord class, such as join tables? Backend specific, as
+ # the abstract adapter always returns +false+.
+ def supports_primary_key?
+ false
+ end
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
# for all adapters except sqlite.
def supports_count_distinct?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 9173c8bca3..d3ca7c819f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -53,12 +53,7 @@ module ActiveRecord
socket = config[:socket]
username = config[:username] ? config[:username].to_s : 'root'
password = config[:password].to_s
-
- if config.has_key?(:database)
- database = config[:database]
- else
- raise ArgumentError, "No database specified. Missing argument: database."
- end
+ database = config[:database]
# Require the MySQL driver and define Mysql::Result.all_hashes
unless defined? Mysql
@@ -81,7 +76,7 @@ module ActiveRecord
module ConnectionAdapters
class MysqlColumn < Column #:nodoc:
def extract_default(default)
- if type == :binary || type == :text
+ if sql_type =~ /blob/i || type == :text
if default.blank?
return null ? nil : ''
else
@@ -95,7 +90,7 @@ module ActiveRecord
end
def has_default?
- return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
super
end
@@ -213,6 +208,10 @@ module ActiveRecord
true
end
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
def supports_savepoints? #:nodoc:
true
end
@@ -544,6 +543,12 @@ module ActiveRecord
keys.length == 1 ? [keys.first, nil] : nil
end
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
def case_sensitive_equality_operator
"= BINARY"
end
@@ -576,6 +581,10 @@ module ActiveRecord
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
end
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
+
@connection.real_connect(*@connection_options)
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index fc8ca357e5..1d52c5ec14 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -40,6 +40,12 @@ module ActiveRecord
end
module ConnectionAdapters
+ class TableDefinition
+ def xml(*args)
+ options = args.extract_options!
+ column(args[0], 'xml', options)
+ end
+ end
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
@@ -100,7 +106,7 @@ module ActiveRecord
:string
# XML type
when /^xml$/
- :string
+ :xml
# Arrays
when /^\D+\[\]$/
:string
@@ -195,7 +201,8 @@ module ActiveRecord
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "bytea" },
- :boolean => { :name => "boolean" }
+ :boolean => { :name => "boolean" },
+ :xml => { :name => "xml" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -250,6 +257,11 @@ module ActiveRecord
true
end
+ # Does PostgreSQL support finding primary key on non-ActiveRecord tables?
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
# Does PostgreSQL support standard conforming strings?
def supports_standard_conforming_strings?
# Temporarily set the client message level above error to prevent unintentional
@@ -365,7 +377,7 @@ module ActiveRecord
if value.kind_of?(String) && column && column.type == :binary
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
- "xml '#{quote_string(value)}'"
+ "xml E'#{quote_string(value)}'"
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value.to_s}'"
@@ -812,6 +824,12 @@ module ActiveRecord
nil
end
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
# Renames a table.
def rename_table(name, new_name)
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
@@ -1091,3 +1109,4 @@ module ActiveRecord
end
end
end
+
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index e939701d63..4f0b06d1bb 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -101,6 +101,10 @@ module ActiveRecord
true
end
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
def requires_reloading?
true
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0baa9654b7..db5d2b25ed 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -314,7 +314,7 @@ module ActiveRecord
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
end
- unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
+ unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
raise HasManyThroughSourceAssociationMacroError.new(self)
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 5d88012e4f..c8e1b4f53a 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -84,7 +84,6 @@ HEADER
elsif @connection.respond_to?(:primary_key)
pk = @connection.primary_key(table)
end
- pk ||= 'id'
tbl.print " create_table #{table.inspect}"
if columns.detect { |c| c.name == pk }
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index edec4e9e43..711086dc2c 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -119,7 +119,7 @@ module ActiveRecord
comparison_operator = "IS ?"
elsif column.text?
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
- value = column.limit ? value.to_s[0, column.limit] : value.to_s
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
else
comparison_operator = "= ?"
end
diff --git a/activerecord/lib/active_record/validator.rb b/activerecord/lib/active_record/validator.rb
new file mode 100644
index 0000000000..83a33f4dcd
--- /dev/null
+++ b/activerecord/lib/active_record/validator.rb
@@ -0,0 +1,68 @@
+module ActiveRecord #:nodoc:
+
+ # A simple base class that can be used along with ActiveRecord::Base.validates_with
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # if some_complex_logic
+ # record.errors[:base] = "This record is invalid"
+ # end
+ # end
+ #
+ # private
+ # def some_complex_logic
+ # # ...
+ # end
+ # end
+ #
+ # Any class that inherits from ActiveRecord::Validator will have access to <tt>record</tt>,
+ # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # record # => The person instance being validated
+ # options # => Any non-standard options passed to validates_with
+ # end
+ # end
+ #
+ # To cause a validation error, you must add to the <tt>record<tt>'s errors directly
+ # from within the validators message
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # record.errors[:base] << "This is some custom error message"
+ # record.errors[:first_name] << "This is some complex validation"
+ # # etc...
+ # end
+ # end
+ #
+ # To add behavior to the initialize method, use the following signature:
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def initialize(record, options)
+ # super
+ # @my_custom_field = options[:field_name] || :first_name
+ # end
+ # end
+ #
+ class Validator
+ attr_reader :record, :options
+
+ def initialize(record, options)
+ @record = record
+ @options = options
+ end
+
+ def validate
+ raise "You must override this method"
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 8009b1457c..c59be264a4 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -63,6 +63,18 @@ class AdapterTest < ActiveRecord::TestCase
def test_show_nonexistent_variable_returns_nil
assert_nil @connection.show_variable('foo_bar_baz')
end
+
+ def test_not_specifying_database_name_for_cross_database_selects
+ assert_nothing_raised do
+ ActiveRecord::Base.establish_connection({
+ :adapter => 'mysql',
+ :username => 'rails'
+ })
+ ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
+ end
+
+ ActiveRecord::Base.establish_connection 'arunit'
+ end
end
if current_adapter?(:PostgreSQLAdapter)
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 784c484178..2a77eed1b5 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -249,24 +249,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Topic.find(topic.id)[:replies_count]
end
- def test_belongs_to_counter_after_save
- topic = Topic.create("title" => "monday night")
- topic.replies.create("title" => "re: monday night", "content" => "football")
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
-
- topic.save
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
- end
-
- def test_belongs_to_counter_after_update_attributes
- topic = Topic.create("title" => "37s")
- topic.replies.create("title" => "re: 37s", "content" => "rails")
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
-
- topic.update_attributes("title" => "37signals")
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
- end
-
def test_assignment_before_child_saved
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb
new file mode 100644
index 0000000000..bf3e04c3eb
--- /dev/null
+++ b/activerecord/test/cases/associations/habtm_join_table_test.rb
@@ -0,0 +1,56 @@
+require 'cases/helper'
+
+class MyReader < ActiveRecord::Base
+ has_and_belongs_to_many :my_books
+end
+
+class MyBook < ActiveRecord::Base
+ has_and_belongs_to_many :my_readers
+end
+
+class HabtmJoinTableTest < ActiveRecord::TestCase
+ def setup
+ ActiveRecord::Base.connection.create_table :my_books, :force => true do |t|
+ t.string :name
+ end
+ assert ActiveRecord::Base.connection.table_exists?(:my_books)
+
+ ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t|
+ t.string :name
+ end
+ assert ActiveRecord::Base.connection.table_exists?(:my_readers)
+
+ ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t|
+ t.integer :my_book_id
+ t.integer :my_reader_id
+ end
+ assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers)
+ end
+
+ def teardown
+ ActiveRecord::Base.connection.drop_table :my_books
+ ActiveRecord::Base.connection.drop_table :my_readers
+ ActiveRecord::Base.connection.drop_table :my_books_my_readers
+ end
+
+ uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key
+ def test_should_raise_exception_when_join_table_has_a_primary_key
+ if ActiveRecord::Base.connection.supports_primary_key?
+ assert_raise ActiveRecord::ConfigurationError do
+ jaime = MyReader.create(:name=>"Jaime")
+ jaime.my_books << MyBook.create(:name=>'Great Expectations')
+ end
+ end
+ end
+
+ uses_transaction :test_should_cache_result_of_primary_key_check
+ def test_should_cache_result_of_primary_key_check
+ if ActiveRecord::Base.connection.supports_primary_key?
+ ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once
+ weaz = MyReader.create(:name=>'Weaz')
+
+ weaz.my_books << MyBook.create(:name=>'Great Expectations')
+ weaz.my_books << MyBook.create(:name=>'Greater Expectations')
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index a3d92c3bdb..f7178f2c5e 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -10,11 +10,12 @@ require 'models/author'
require 'models/comment'
require 'models/person'
require 'models/reader'
+require 'models/tagging'
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments, :author_addresses,
- :people, :posts, :readers
+ :people, :posts, :readers, :taggings
def setup
Client.destroyed_client_ids.clear
@@ -287,6 +288,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id")
end
+ def test_find_all_with_include_and_conditions
+ assert_nothing_raised do
+ Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'})
+ end
+ end
+
def test_find_in_collection
assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
@@ -510,6 +517,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, new_firm.clients_of_firm.size
end
+ def test_deleting_updates_counter_cache
+ topic = Topic.first
+ assert_equal topic.replies.to_a.size, topic.replies_count
+
+ topic.replies.delete(topic.replies.first)
+ topic.reload
+ assert_equal topic.replies.to_a.size, topic.replies_count
+ end
+
+ def test_deleting_updates_counter_cache_without_dependent_destroy
+ post = posts(:welcome)
+
+ assert_difference "post.reload.taggings_count", -1 do
+ post.taggings.delete(post.taggings.first)
+ end
+ end
+
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@@ -555,6 +579,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_clearing_updates_counter_cache
+ topic = Topic.first
+
+ topic.replies.clear
+ topic.reload
+ assert_equal 0, topic.replies_count
+ end
+
def test_clearing_a_dependent_association_collection
firm = companies(:first_firm)
client_id = firm.dependent_clients_of_firm.first.id
@@ -699,6 +731,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
end
+ def test_destroying_by_fixnum_id
+ force_signal37_to_load_all_clients_of_firm
+
+ assert_difference "Client.count", -1 do
+ companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id)
+ end
+
+ assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_destroying_by_string_id
+ force_signal37_to_load_all_clients_of_firm
+
+ assert_difference "Client.count", -1 do
+ companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s)
+ end
+
+ assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
def test_destroying_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@@ -870,7 +924,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] },
lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) },
lambda { authors(:mary).comments.delete(authors(:mary).comments.first) },
- ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) }
+ ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
def test_dynamic_find_should_respect_association_order_for_through
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index f6b4a42377..59985374d3 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -11,9 +11,12 @@ require 'models/author'
require 'models/owner'
require 'models/pet'
require 'models/toy'
+require 'models/contract'
+require 'models/company'
+require 'models/developer'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references
+ fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies
def test_associate_existing
assert_queries(2) { posts(:thinking);people(:david) }
@@ -176,6 +179,30 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") }
end
+ def test_associate_with_create_and_invalid_options
+ peeps = companies(:first_firm).developers.count
+ assert_nothing_raised { companies(:first_firm).developers.create(:name => '0') }
+ assert_equal peeps, companies(:first_firm).developers.count
+ end
+
+ def test_associate_with_create_and_valid_options
+ peeps = companies(:first_firm).developers.count
+ assert_nothing_raised { companies(:first_firm).developers.create(:name => 'developer') }
+ assert_equal peeps + 1, companies(:first_firm).developers.count
+ end
+
+ def test_associate_with_create_bang_and_invalid_options
+ peeps = companies(:first_firm).developers.count
+ assert_raises(ActiveRecord::RecordInvalid) { companies(:first_firm).developers.create!(:name => '0') }
+ assert_equal peeps, companies(:first_firm).developers.count
+ end
+
+ def test_associate_with_create_bang_and_valid_options
+ peeps = companies(:first_firm).developers.count
+ assert_nothing_raised { companies(:first_firm).developers.create!(:name => 'developer') }
+ assert_equal peeps + 1, companies(:first_firm).developers.count
+ end
+
def test_clear_associations
assert_queries(2) { posts(:welcome);posts(:welcome).people(true) }
@@ -299,4 +326,21 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys
assert_equal 1, owners(:blackbeard).toys.count
end
+
+ def test_find_on_has_many_association_collection_with_include_and_conditions
+ post_with_no_comments = people(:michael).posts_with_no_comments.first
+ assert_equal post_with_no_comments, posts(:authorless)
+ end
+
+ def test_has_many_through_has_one_reflection
+ assert_equal [comments(:eager_sti_on_associations_vs_comment)], authors(:david).very_special_comments
+ end
+
+ def test_modifying_has_many_through_has_one_reflection_should_raise
+ [
+ lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(:body => "Gorp!", :post_id => 1011), VerySpecialComment.create!(:body => "Eep!", :post_id => 1012)] },
+ lambda { authors(:david).very_special_comments << VerySpecialComment.create!(:body => "Hoohah!", :post_id => 1013) },
+ lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
+ ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index ab6e6d20fc..9aef3eb374 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -28,6 +28,16 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_not_nil new_member.current_membership
assert_not_nil new_member.club
end
+
+ def test_creating_association_builds_through_record_for_new
+ new_member = Member.new(:name => "Jane")
+ new_member.club = clubs(:moustache_club)
+ assert new_member.current_membership
+ assert_equal clubs(:moustache_club), new_member.current_membership.club
+ assert_equal clubs(:moustache_club), new_member.club
+ assert new_member.save
+ assert_equal clubs(:moustache_club), new_member.club
+ end
def test_replace_target_record
new_club = Club.create(:name => "Marx Bros")
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 9da7fc2639..c035600e69 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -319,11 +319,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_belongs_to_polymorphic_with_counter_cache
- assert_equal 0, posts(:welcome)[:taggings_count]
+ assert_equal 1, posts(:welcome)[:taggings_count]
tagging = posts(:welcome).taggings.create(:tag => tags(:general))
- assert_equal 1, posts(:welcome, :reload)[:taggings_count]
+ assert_equal 2, posts(:welcome, :reload)[:taggings_count]
tagging.destroy
- assert posts(:welcome, :reload)[:taggings_count].zero?
+ assert_equal 1, posts(:welcome, :reload)[:taggings_count]
end
def test_unavailable_through_reflection
@@ -381,7 +381,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging }
+ assert_equal Tagging.find(1,2), authors(:david).tagging
end
def test_has_many_through_polymorphic_has_many
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 8434b8efe9..26a475b964 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1070,7 +1070,25 @@ class BasicsTest < ActiveRecord::TestCase
assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
end
- def test_multiparameter_attributes_on_date_with_empty_date
+ def test_multiparameter_attributes_on_date_with_empty_year
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(1, 6, 24), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_month
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(2004, 1, 24), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_day
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
@@ -1079,6 +1097,33 @@ class BasicsTest < ActiveRecord::TestCase
assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
end
+ def test_multiparameter_attributes_on_date_with_empty_day_and_year
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(1, 6, 1), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_day_and_month
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(2004, 1, 1), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_year_and_month
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(1, 1, 24), topic.last_read.to_date
+ end
+
def test_multiparameter_attributes_on_date_with_all_empty
attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
topic = Topic.find(1)
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 855b4c60ae..004f4d0ea6 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -2,6 +2,8 @@ require "cases/helper"
require 'models/company'
require 'models/topic'
require 'models/edge'
+require 'models/club'
+require 'models/organization'
Company.has_many :accounts
@@ -223,6 +225,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 15, companies(:rails_core).companies.sum(:id)
end
+ def test_should_sum_scoped_field_with_from
+ assert_equal Club.count, Organization.clubs.count
+ end
+
def test_should_sum_scoped_field_with_conditions
assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 98abc8eac8..fc9a0ac96e 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -33,4 +33,38 @@ class ColumnDefinitionTest < ActiveRecord::TestCase
column.limit, column.precision, column.scale, column.default, column.null)
assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql
end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_should_set_default_for_mysql_binary_data_types
+ binary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "binary(1)")
+ assert_equal "a", binary_column.default
+
+ varbinary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "varbinary(1)")
+ assert_equal "a", varbinary_column.default
+ end
+
+ def test_should_not_set_default_for_blob_and_text_data_types
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "blob")
+ end
+
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "Hello", "text")
+ end
+
+ text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text")
+ assert_equal nil, text_column.default
+
+ not_null_text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text", false)
+ assert_equal "", not_null_text_column.default
+ end
+
+ def test_has_default_should_return_false_for_blog_and_test_data_types
+ blob_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "blob")
+ assert !blob_column.has_default?
+
+ text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text")
+ assert !text_column.has_default?
+ end
+ end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 1441421a80..74571d923a 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -298,6 +298,16 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present
+ with_partial_updates(Topic) do
+ Topic.create!(:author_name => 'Bill', :content => {:a => "a"})
+ topic = Topic.first(:select => 'id, author_name')
+ topic.update_attribute :author_name, 'John'
+ topic = Topic.first
+ assert_not_nil topic.content
+ end
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index b07d4f3521..eb3f03c91d 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -185,7 +185,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_binary_in_fixtures
assert_equal 1, @binaries.size
- data = File.read(ASSETS_ROOT + "/flowers.jpg")
+ data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read }
data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding)
data.freeze
assert_equal data, @flowers.data
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index b1db662eca..d59c53cec8 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -12,6 +12,11 @@ class ActiveRecordI18nTests < Test::Unit::TestCase
I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
assert_equal 'topic title attribute', Topic.human_attribute_name('title')
end
+
+ def test_translated_model_attributes_with_symbols
+ I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
+ assert_equal 'topic title attribute', Topic.human_attribute_name(:title)
+ end
def test_translated_model_attributes_with_sti
I18n.backend.store_translations 'en', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } }
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f0f21615e0..6d3f938799 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -25,6 +25,24 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ class MigrationTableAndIndexTest < ActiveRecord::TestCase
+ def test_add_schema_info_respects_prefix_and_suffix
+ conn = ActiveRecord::Base.connection
+
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ ActiveRecord::Base.table_name_prefix = 'foo_'
+ ActiveRecord::Base.table_name_suffix = '_bar'
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+
+ conn.initialize_schema_migrations_table
+
+ assert_equal "foo_unique_schema_migrations_bar", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ end
+ end
+
class MigrationTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -224,7 +242,7 @@ if ActiveRecord::Base.connection.supports_migrations?
t.column :foo, :string
end
- assert_equal %w(foo testings_id), Person.connection.columns(:testings).map { |c| c.name }.sort
+ assert_equal %w(foo testing_id), Person.connection.columns(:testings).map { |c| c.name }.sort
ensure
Person.connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
@@ -237,7 +255,7 @@ if ActiveRecord::Base.connection.supports_migrations?
t.column :foo, :string
end
- assert_equal %w(foo testingsid), Person.connection.columns(:testings).map { |c| c.name }.sort
+ assert_equal %w(foo testingid), Person.connection.columns(:testings).map { |c| c.name }.sort
ensure
Person.connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
@@ -396,7 +414,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 9, wealth_column.precision
assert_equal 7, wealth_column.scale
end
-
+
def test_native_types
Person.delete_all
Person.connection.add_column "people", "last_name", :string
@@ -975,9 +993,9 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_migrator_one_down
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
-
+
ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
-
+
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
assert !Reminder.table_exists?
@@ -1118,20 +1136,20 @@ if ActiveRecord::Base.connection.supports_migrations?
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.find(:first).content
end
-
+
def test_migrator_rollback
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
assert_equal(3, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(2, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(1, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(0, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(0, ActiveRecord::Migrator.current_version)
end
@@ -1294,7 +1312,7 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
-
+
class SexyMigrationsTest < ActiveRecord::TestCase
def test_references_column_type_adds_id
with_new_table do |t|
@@ -1350,6 +1368,15 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_xml_creates_xml_column
+ with_new_table do |t|
+ t.expects(:column).with(:data, 'xml', {})
+ t.xml :data
+ end
+ end
+ end
+
protected
def with_new_table
Person.connection.create_table :delete_me, :force => true do |t|
@@ -1567,3 +1594,4 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
end
+
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 283333fc04..4f559bcaa5 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -4,6 +4,23 @@ require 'models/company_in_module'
class ModulesTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :projects, :developers
+ def setup
+ # need to make sure Object::Firm and Object::Client are not defined,
+ # so that constantize will not be able to cheat when having to load namespaced classes
+ @undefined_consts = {}
+
+ [:Firm, :Client].each do |const|
+ @undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const)
+ end
+ end
+
+ def teardown
+ # reinstate the constants that we undefined in the setup
+ @undefined_consts.each do |constant, value|
+ Object.send :const_set, constant, value unless value.nil?
+ end
+ end
+
def test_module_spanning_associations
firm = MyApplication::Business::Firm.find(:first)
assert !firm.clients.empty?, "Firm should have clients"
@@ -36,4 +53,29 @@ class ModulesTest < ActiveRecord::TestCase
assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass'
assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model'
end
+
+ def test_assign_ids
+ firm = MyApplication::Business::Firm.first
+
+ assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ firm.client_ids = [MyApplication::Business::Client.first.id]
+ end
+ end
+
+ # need to add an eager loading condition to force the eager loading model into
+ # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640
+ def test_eager_loading_in_modules
+ clients = []
+
+ assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL')
+ clients << MyApplication::Business::Client.find(3, :include => {:firm => :account})
+ end
+
+ clients.each do |client|
+ assert_no_queries do
+ assert_not_nil(client.firm.account)
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 330ba7189f..13427daf53 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -368,6 +368,12 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
end
+
+ def test_table_names_for_chaining_scopes_with_and_without_table_name_included
+ assert_nothing_raised do
+ Comment.for_first_post.for_first_author.all
+ end
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/pk_test.rb
index 948a570b93..c121e0aa0f 100644
--- a/activerecord/test/cases/pk_test.rb
+++ b/activerecord/test/cases/pk_test.rb
@@ -98,4 +98,22 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_instance_destroy_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
end
+
+ def test_supports_primary_key
+ assert_nothing_raised NoMethodError do
+ ActiveRecord::Base.connection.supports_primary_key?
+ end
+ end
+
+ def test_primary_key_returns_value_if_it_exists
+ if ActiveRecord::Base.connection.supports_primary_key?
+ assert_equal 'id', ActiveRecord::Base.connection.primary_key('developers')
+ end
+ end
+
+ def test_primary_key_returns_nil_if_it_does_not_exist
+ if ActiveRecord::Base.connection.supports_primary_key?
+ assert_nil ActiveRecord::Base.connection.primary_key('developers_projects')
+ end
+ end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 4083b990d9..a164f5e060 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -176,8 +176,8 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 29, Firm.reflect_on_all_associations.size
- assert_equal 22, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 31, Firm.reflect_on_all_associations.size
+ assert_equal 24, Firm.reflect_on_all_associations(:has_many).size
assert_equal 7, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 4f8e20b3ba..1c43e3c5b5 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -161,7 +161,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition
end
-
+
def test_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
@@ -196,6 +196,15 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_schema_dump_includes_xml_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_xml_data_type"} =~ output
+ assert_match %r{t.xml "data"}, output
+ end
+ end
+ end
+
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
output = standard_dump
# Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
@@ -205,4 +214,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output
end
end
+
+ def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added
+ output = standard_dump
+ match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n})
+ assert_not_nil(match, "goofy_string_id table not found")
+ assert_match %r(:id => false), match[1], "no table id not preserved"
+ assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved"
+ end
end
+
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 961db51d1d..cb123d3498 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -5,6 +5,7 @@ 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
@@ -36,20 +37,20 @@ class Thaumaturgist < IneptWizard
end
class UniquenessValidationTest < ActiveRecord::TestCase
- fixtures :topics, 'warehouse-things'
+ fixtures :topics, 'warehouse-things', :developers
repair_validations(Topic)
def test_validate_uniqueness
Topic.validates_uniqueness_of(:title)
- t = Topic.new("title" => "I'm unique!")
+ t = Topic.new("title" => "I'm uniqué!")
assert t.save, "Should save t as unique"
t.content = "Remaining unique"
assert t.save, "Should still save t as unique"
- t2 = Topic.new("title" => "I'm unique!")
+ t2 = Topic.new("title" => "I'm uniqué!")
assert !t2.valid?, "Shouldn't be valid"
assert !t2.save, "Shouldn't save t2 as unique"
assert_equal ["has already been taken"], t2.errors[:title]
@@ -237,6 +238,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
end
+ def test_validate_uniqueness_with_limit_and_utf8
+ with_kcode('UTF8') do
+ # Event.title is limited to 5 characters
+ e1 = Event.create(:title => "一二三四五")
+ assert e1.valid?, "Could not create an event with a unique, 5 character title"
+ e2 = Event.create(:title => "一二三四五六七八")
+ assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
+ end
+ end
+
def test_validate_straight_inheritance_uniqueness
w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork")
assert w1.valid?, "Saving w1"
diff --git a/activerecord/test/fixtures/posts.yml b/activerecord/test/fixtures/posts.yml
index 92e5d1908f..f817493190 100644
--- a/activerecord/test/fixtures/posts.yml
+++ b/activerecord/test/fixtures/posts.yml
@@ -4,6 +4,7 @@ welcome:
title: Welcome to the weblog
body: Such a lovely day
comments_count: 2
+ taggings_count: 1
type: Post
thinking:
@@ -11,6 +12,8 @@ thinking:
author_id: 1
title: So I was thinking
body: Like I hopefully always am
+ comments_count: 1
+ taggings_count: 1
type: SpecialPost
authorless:
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index b844c7cce0..f264f980d6 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -1,5 +1,6 @@
class Author < ActiveRecord::Base
has_many :posts
+ has_many :very_special_comments, :through => :posts
has_many :posts_with_comments, :include => :comments, :class_name => "Post"
has_many :popular_grouped_posts, :include => :comments, :class_name => "Post", :group => "type", :having => "SUM(comments_count) > 1", :select => "type"
has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index f7f07c103f..399dea9f12 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -1,6 +1,10 @@
class Comment < ActiveRecord::Base
named_scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'"
-
+ named_scope :for_first_post, :conditions => { :post_id => 1 }
+ named_scope :for_first_author,
+ :joins => :post,
+ :conditions => { "posts.author_id" => 1 }
+
belongs_to :post, :counter_cache => true
def self.what_are_you
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 1c05e523e0..ab09f88a9f 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -9,6 +9,8 @@ class Company < AbstractCompany
validates_presence_of :name
has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account"
+ has_many :contracts
+ has_many :developers, :through => :contracts
def arbitrary_method
"I am Jack's profound disappointment"
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 8b84c2fb5e..cdda7a44d4 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -13,7 +13,7 @@ module MyApplication
has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}'
- has_one :account, :dependent => :destroy
+ has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy
end
class Client < Company
diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb
new file mode 100644
index 0000000000..606c99cd4e
--- /dev/null
+++ b/activerecord/test/models/contract.rb
@@ -0,0 +1,4 @@
+class Contract < ActiveRecord::Base
+ belongs_to :company
+ belongs_to :developer
+end \ No newline at end of file
diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb
index d79d5037c8..c85726169e 100644
--- a/activerecord/test/models/organization.rb
+++ b/activerecord/test/models/organization.rb
@@ -1,4 +1,6 @@
class Organization < ActiveRecord::Base
has_many :member_details
has_many :members, :through => :member_details
+
+ named_scope :clubs, { :from => 'clubs' }
end \ No newline at end of file
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 576a4d03c6..3d8911bfe9 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,7 +1,7 @@
ActiveRecord::Schema.define do
%w(postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
- postgresql_oids defaults geometrics).each do |table_name|
+ postgresql_oids postgresql_xml_data_type defaults geometrics).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -100,4 +100,15 @@ _SQL
obj_id OID
);
_SQL
-end \ No newline at end of file
+
+ begin
+ execute <<_SQL
+ CREATE TABLE postgresql_xml_data_type (
+ id SERIAL PRIMARY KEY,
+ data xml
+ );
+_SQL
+rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
+ end
+end
+
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 5f60d5e137..9ab4cf6f43 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -131,6 +131,10 @@ ActiveRecord::Schema.define do
t.integer :extendedWarranty, :null => false
end
+ create_table :contracts, :force => true do |t|
+ t.integer :developer_id
+ t.integer :company_id
+ end
create_table :customers, :force => true do |t|
t.string :name