Rails 2.1.1
Among other things, a security fix.
This commit is contained in:
parent
d2c4c8737c
commit
d4f97345db
354 changed files with 21027 additions and 3072 deletions
34
vendor/rails/activerecord/CHANGELOG
vendored
34
vendor/rails/activerecord/CHANGELOG
vendored
|
@ -1,3 +1,37 @@
|
|||
*2.1.1 (September 4th, 2008)*
|
||||
|
||||
* Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. #446. [Andrew Stone, Nik Wakelin]
|
||||
|
||||
* Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
|
||||
|
||||
* change_column_default preserves the not-null constraint. #617 [Tarmo Tänav]
|
||||
|
||||
* Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
|
||||
|
||||
# Ensure essay contains at least 100 words.
|
||||
validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
|
||||
|
||||
* Always treat integer :limit as byte length. #420 [Tarmo Tänav]
|
||||
|
||||
* Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison]
|
||||
|
||||
* Fix column collision with named_scope and :joins. #46 [Duncan Beevers, Mark Catley]
|
||||
|
||||
* db:migrate:down and :up update schema_migrations. #369 [Michael Raidel, RaceCondition]
|
||||
|
||||
* PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. [Tarmo Tänav]
|
||||
|
||||
* MySQL: rename_column preserves column defaults. #466 [Diego Algorta]
|
||||
|
||||
* Add :from option to calculations. #397 [Ben Munat]
|
||||
|
||||
* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter]
|
||||
|
||||
* PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. [Jeremy Kemper]
|
||||
|
||||
* Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
|
||||
|
||||
|
||||
*2.1.0 (May 31st, 2008)*
|
||||
|
||||
* Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. [rick]
|
||||
|
|
11
vendor/rails/activerecord/Rakefile
vendored
11
vendor/rails/activerecord/Rakefile
vendored
|
@ -5,6 +5,7 @@ require 'rake/rdoctask'
|
|||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/sshpublisher'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
|
||||
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
|
||||
|
@ -141,7 +142,7 @@ Rake::RDocTask.new { |rdoc|
|
|||
rdoc.title = "Active Record -- Object-relation mapping put on rails"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
rdoc.options << '--charset' << 'utf-8'
|
||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
||||
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
|
||||
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
|
||||
|
@ -171,7 +172,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.1.1' + PKG_BUILD)
|
||||
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
||||
|
@ -225,13 +226,13 @@ end
|
|||
|
||||
desc "Publish the beta gem"
|
||||
task :pgem => [:package] do
|
||||
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
|
||||
Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
|
||||
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
|
||||
end
|
||||
|
||||
desc "Publish the release files to RubyForge."
|
||||
|
|
18
vendor/rails/activerecord/lib/active_record.rb
vendored
18
vendor/rails/activerecord/lib/active_record.rb
vendored
|
@ -24,16 +24,14 @@
|
|||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
|
||||
unless defined? ActiveSupport
|
||||
active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
|
||||
if File.exist?(active_support_path)
|
||||
$:.unshift active_support_path
|
||||
require 'active_support'
|
||||
else
|
||||
require 'rubygems'
|
||||
gem 'activesupport'
|
||||
require 'active_support'
|
||||
end
|
||||
active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
|
||||
if File.exist?(active_support_path)
|
||||
$:.unshift active_support_path
|
||||
require 'active_support'
|
||||
else
|
||||
require 'rubygems'
|
||||
gem 'activesupport'
|
||||
require 'active_support'
|
||||
end
|
||||
|
||||
require 'active_record/base'
|
||||
|
|
|
@ -51,9 +51,7 @@ module ActiveRecord
|
|||
|
||||
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
|
||||
parent_records.each do |parent_record|
|
||||
association_proxy = parent_record.send(reflection_name)
|
||||
association_proxy.loaded
|
||||
association_proxy.target = associated_record
|
||||
parent_record.send("set_#{reflection_name}_target", associated_record)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -103,17 +101,17 @@ module ActiveRecord
|
|||
associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
|
||||
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_record_id",
|
||||
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
|
||||
:order => options[:order])
|
||||
|
||||
set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id')
|
||||
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
|
||||
end
|
||||
|
||||
def preload_has_one_association(records, reflection, preload_options={})
|
||||
id_to_record_map, ids = construct_id_map(records)
|
||||
options = reflection.options
|
||||
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
|
||||
if options[:through]
|
||||
records.each {|record| record.send(reflection.name) && record.send(reflection.name).loaded}
|
||||
through_records = preload_through_records(records, reflection, options[:through])
|
||||
through_reflection = reflections[options[:through]]
|
||||
through_primary_key = through_reflection.primary_key_name
|
||||
|
@ -126,8 +124,6 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
else
|
||||
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
|
||||
|
||||
set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
|
||||
end
|
||||
end
|
||||
|
@ -188,7 +184,6 @@ module ActiveRecord
|
|||
through_records
|
||||
end
|
||||
|
||||
# FIXME: quoting
|
||||
def preload_belongs_to_association(records, reflection, preload_options={})
|
||||
options = reflection.options
|
||||
primary_key_name = reflection.primary_key_name
|
||||
|
@ -227,9 +222,19 @@ module ActiveRecord
|
|||
|
||||
table_name = klass.quoted_table_name
|
||||
primary_key = klass.primary_key
|
||||
conditions = "#{table_name}.#{primary_key} IN (?)"
|
||||
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)"
|
||||
conditions << append_conditions(options, preload_options)
|
||||
associated_records = klass.find(:all, :conditions => [conditions, id_map.keys.uniq],
|
||||
column_type = klass.columns.detect{|c| c.name == primary_key}.type
|
||||
ids = id_map.keys.uniq.map do |id|
|
||||
if column_type == :integer
|
||||
id.to_i
|
||||
elsif column_type == :float
|
||||
id.to_f
|
||||
else
|
||||
id
|
||||
end
|
||||
end
|
||||
associated_records = klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:select => options[:select],
|
||||
:joins => options[:joins],
|
||||
|
@ -243,7 +248,7 @@ module ActiveRecord
|
|||
table_name = reflection.klass.quoted_table_name
|
||||
|
||||
if interface = reflection.options[:as]
|
||||
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.name.demodulize}'"
|
||||
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
|
||||
else
|
||||
foreign_key = reflection.primary_key_name
|
||||
conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)"
|
||||
|
|
|
@ -690,6 +690,7 @@ module ActiveRecord
|
|||
# association is a polymorphic +belongs_to+.
|
||||
# * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
|
||||
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_many :comments, :order => "posted_on"
|
||||
|
@ -710,6 +711,7 @@ module ActiveRecord
|
|||
|
||||
configure_dependency_for_has_many(reflection)
|
||||
|
||||
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
|
||||
add_multiple_associated_save_callbacks(reflection.name)
|
||||
add_association_callbacks(reflection.name, reflection.options)
|
||||
|
||||
|
@ -769,6 +771,7 @@ module ActiveRecord
|
|||
# * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
|
||||
# association is a polymorphic +belongs_to+.
|
||||
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
||||
|
@ -799,7 +802,7 @@ module ActiveRecord
|
|||
end
|
||||
after_save method_name
|
||||
|
||||
add_single_associated_save_callbacks(reflection.name)
|
||||
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
|
||||
association_accessor_methods(reflection, HasOneAssociation)
|
||||
association_constructor_method(:build, reflection, HasOneAssociation)
|
||||
association_constructor_method(:create, reflection, HasOneAssociation)
|
||||
|
@ -857,6 +860,7 @@ module ActiveRecord
|
|||
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
|
||||
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
|
||||
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default.
|
||||
#
|
||||
# Option examples:
|
||||
# belongs_to :firm, :foreign_key => "client_of"
|
||||
|
@ -937,6 +941,8 @@ module ActiveRecord
|
|||
)
|
||||
end
|
||||
|
||||
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
|
||||
|
||||
configure_dependency_for_belongs_to(reflection)
|
||||
end
|
||||
|
||||
|
@ -1025,6 +1031,7 @@ module ActiveRecord
|
|||
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
|
||||
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
|
||||
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_and_belongs_to_many :projects
|
||||
|
@ -1037,6 +1044,7 @@ module ActiveRecord
|
|||
def has_and_belongs_to_many(association_id, options = {}, &extension)
|
||||
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
||||
|
||||
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
|
||||
add_multiple_associated_save_callbacks(reflection.name)
|
||||
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
|
||||
|
||||
|
@ -1103,10 +1111,9 @@ module ActiveRecord
|
|||
association.create_through_record(new_value)
|
||||
self.send(reflection.name, new_value)
|
||||
else
|
||||
association.replace(new_value)
|
||||
association.replace(new_value)
|
||||
instance_variable_set(ivar, new_value.nil? ? nil : association)
|
||||
end
|
||||
|
||||
instance_variable_set(ivar, new_value.nil? ? nil : association)
|
||||
end
|
||||
|
||||
define_method("set_#{reflection.name}_target") do |target|
|
||||
|
@ -1157,7 +1164,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def add_single_associated_save_callbacks(association_name)
|
||||
def add_single_associated_validation_callbacks(association_name)
|
||||
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
|
@ -1169,7 +1176,7 @@ module ActiveRecord
|
|||
validate method_name
|
||||
end
|
||||
|
||||
def add_multiple_associated_save_callbacks(association_name)
|
||||
def add_multiple_associated_validation_callbacks(association_name)
|
||||
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
||||
ivar = "@#{association_name}"
|
||||
|
||||
|
@ -1190,6 +1197,10 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
validate method_name
|
||||
end
|
||||
|
||||
def add_multiple_associated_save_callbacks(association_name)
|
||||
ivar = "@#{association_name}"
|
||||
|
||||
method_name = "before_save_associated_records_for_#{association_name}".to_sym
|
||||
define_method(method_name) do
|
||||
|
@ -1211,7 +1222,6 @@ module ActiveRecord
|
|||
else
|
||||
[]
|
||||
end
|
||||
|
||||
records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
|
||||
|
||||
# reconstruct the SQL queries now that we know the owner's id
|
||||
|
@ -1343,7 +1353,8 @@ module ActiveRecord
|
|||
:uniq,
|
||||
:finder_sql, :counter_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend, :readonly
|
||||
:extend, :readonly,
|
||||
:validate
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
|
@ -1353,7 +1364,7 @@ module ActiveRecord
|
|||
|
||||
def create_has_one_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate
|
||||
)
|
||||
|
||||
create_reflection(:has_one, association_id, options, self)
|
||||
|
@ -1361,7 +1372,7 @@ module ActiveRecord
|
|||
|
||||
def create_has_one_through_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
|
||||
)
|
||||
create_reflection(:has_one, association_id, options, self)
|
||||
end
|
||||
|
@ -1369,7 +1380,7 @@ module ActiveRecord
|
|||
def create_belongs_to_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
|
||||
:counter_cache, :extend, :polymorphic, :readonly
|
||||
:counter_cache, :extend, :polymorphic, :readonly, :validate
|
||||
)
|
||||
|
||||
reflection = create_reflection(:belongs_to, association_id, options, self)
|
||||
|
@ -1388,7 +1399,8 @@ module ActiveRecord
|
|||
:uniq,
|
||||
:finder_sql, :delete_sql, :insert_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend, :readonly
|
||||
:extend, :readonly,
|
||||
:validate
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
|
@ -1465,10 +1477,15 @@ module ActiveRecord
|
|||
join_dependency.joins_for_table_name(table)
|
||||
}.flatten.compact.uniq
|
||||
|
||||
order = options[:order]
|
||||
if scoped_order = (scope && scope[:order])
|
||||
order = order ? "#{order}, #{scoped_order}" : scoped_order
|
||||
end
|
||||
|
||||
is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
|
||||
sql = "SELECT "
|
||||
if is_distinct
|
||||
sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
|
||||
sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
|
||||
else
|
||||
sql << primary_key
|
||||
end
|
||||
|
@ -1482,8 +1499,8 @@ module ActiveRecord
|
|||
add_conditions!(sql, options[:conditions], scope)
|
||||
add_group!(sql, options[:group], scope)
|
||||
|
||||
if options[:order] && is_distinct
|
||||
connection.add_order_by_for_association_limiting!(sql, options)
|
||||
if order && is_distinct
|
||||
connection.add_order_by_for_association_limiting!(sql, :order => order)
|
||||
else
|
||||
add_order!(sql, options[:order], scope)
|
||||
end
|
||||
|
@ -1502,19 +1519,19 @@ module ActiveRecord
|
|||
else all << cond
|
||||
end
|
||||
end
|
||||
conditions.join(' ').scan(/([\.\w]+).?\./).flatten
|
||||
conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
end
|
||||
|
||||
def order_tables(options)
|
||||
order = options[:order]
|
||||
order = [options[:order], scope(:find, :order) ].join(", ")
|
||||
return [] unless order && order.is_a?(String)
|
||||
order.scan(/([\.\w]+).?\./).flatten
|
||||
order.scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
end
|
||||
|
||||
def selects_tables(options)
|
||||
select = options[:select]
|
||||
return [] unless select && select.is_a?(String)
|
||||
select.scan(/"?([\.\w]+)"?.?\./).flatten
|
||||
select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
|
||||
end
|
||||
|
||||
# Checks if the conditions reference a table other than the current model table
|
||||
|
@ -1638,7 +1655,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def join_for_table_name(table_name)
|
||||
@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
|
||||
join = (@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first) rescue nil
|
||||
return join unless join.nil?
|
||||
@joins.select{|j|j.is_a?(JoinAssociation) && j.aliased_join_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
|
||||
end
|
||||
|
||||
def joins_for_table_name(table_name)
|
||||
|
@ -1714,6 +1733,7 @@ module ActiveRecord
|
|||
collection.target.push(association)
|
||||
when :has_one
|
||||
return if record.id.to_s != join.parent.record_id(row).to_s
|
||||
return if record.instance_variable_defined?("@#{join.reflection.name}")
|
||||
association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
|
||||
record.send("set_#{join.reflection.name}_target", association)
|
||||
when :belongs_to
|
||||
|
@ -1795,7 +1815,7 @@ module ActiveRecord
|
|||
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
|
||||
end
|
||||
|
||||
if reflection.macro == :has_many && reflection.options[:through]
|
||||
if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
|
||||
@aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
|
||||
end
|
||||
end
|
||||
|
@ -1819,7 +1839,7 @@ module ActiveRecord
|
|||
]
|
||||
when :has_many, :has_one
|
||||
case
|
||||
when reflection.macro == :has_many && reflection.options[:through]
|
||||
when reflection.options[:through]
|
||||
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
|
||||
|
||||
jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
|
||||
|
@ -1855,7 +1875,7 @@ module ActiveRecord
|
|||
jt_sti_extra = " AND %s.%s = %s" % [
|
||||
connection.quote_table_name(aliased_join_table_name),
|
||||
connection.quote_column_name(through_reflection.active_record.inheritance_column),
|
||||
through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
|
||||
through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
|
||||
end
|
||||
when :belongs_to
|
||||
first_key = primary_key
|
||||
|
@ -1920,10 +1940,8 @@ module ActiveRecord
|
|||
else
|
||||
""
|
||||
end || ''
|
||||
join << %(AND %s.%s = %s ) % [
|
||||
connection.quote_table_name(aliased_table_name),
|
||||
connection.quote_column_name(klass.inheritance_column),
|
||||
klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
|
||||
join << %(AND %s) % [
|
||||
klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
|
||||
|
||||
[through_reflection, reflection].each do |ref|
|
||||
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
|
||||
|
|
|
@ -78,11 +78,14 @@ module ActiveRecord
|
|||
@loaded = false
|
||||
end
|
||||
|
||||
def build(attributes = {})
|
||||
def build(attributes = {}, &block)
|
||||
if attributes.is_a?(Array)
|
||||
attributes.collect { |attr| build(attr) }
|
||||
attributes.collect { |attr| build(attr, &block) }
|
||||
else
|
||||
build_record(attributes) { |record| set_belongs_to_association_for(record) }
|
||||
build_record(attributes) do |record|
|
||||
block.call(record) if block_given?
|
||||
set_belongs_to_association_for(record)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -187,7 +190,7 @@ module ActiveRecord
|
|||
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
|
||||
@target.size
|
||||
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
|
||||
unsaved_records = Array(@target.detect { |r| r.new_record? })
|
||||
unsaved_records = @target.select { |r| r.new_record? }
|
||||
unsaved_records.size + count_records
|
||||
else
|
||||
count_records
|
||||
|
@ -335,7 +338,7 @@ module ActiveRecord
|
|||
callback(:before_add, record)
|
||||
yield(record) if block_given?
|
||||
@target ||= [] unless loaded?
|
||||
@target << record
|
||||
@target << record unless @reflection.options[:uniq] && @target.include?(record)
|
||||
callback(:after_add, record)
|
||||
record
|
||||
end
|
||||
|
|
|
@ -69,8 +69,8 @@ module ActiveRecord
|
|||
@target
|
||||
end
|
||||
|
||||
def respond_to?(symbol, include_priv = false)
|
||||
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
|
||||
def respond_to?(*args)
|
||||
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
|
||||
end
|
||||
|
||||
# Explicitly proxy === because the instance method removal above
|
||||
|
@ -131,10 +131,6 @@ module ActiveRecord
|
|||
records.map { |record| record.quoted_id }.join(',')
|
||||
end
|
||||
|
||||
def interpolate_sql_options!(options, *keys)
|
||||
keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
|
||||
end
|
||||
|
||||
def interpolate_sql(sql, record = nil)
|
||||
@owner.send(:interpolate_sql, sql, record)
|
||||
end
|
||||
|
|
|
@ -70,10 +70,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def construct_sql
|
||||
interpolate_sql_options!(@reflection.options, :finder_sql)
|
||||
|
||||
if @reflection.options[:finder_sql]
|
||||
@finder_sql = @reflection.options[:finder_sql]
|
||||
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
||||
else
|
||||
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
|
@ -87,6 +85,7 @@ module ActiveRecord
|
|||
:joins => @join_sql,
|
||||
:readonly => false,
|
||||
:order => @reflection.options[:order],
|
||||
:include => @reflection.options[:include],
|
||||
:limit => @reflection.options[:limit] } }
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,16 @@ module ActiveRecord
|
|||
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
|
||||
options[:include] ||= @reflection.options[:include]
|
||||
|
||||
@reflection.klass.count(column_name, options)
|
||||
value = @reflection.klass.count(column_name, options)
|
||||
|
||||
limit = @reflection.options[:limit]
|
||||
offset = @reflection.options[:offset]
|
||||
|
||||
if limit || offset
|
||||
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -27,8 +36,11 @@ module ActiveRecord
|
|||
else
|
||||
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
|
||||
end
|
||||
|
||||
@target = [] and loaded if count == 0
|
||||
|
||||
# If there's nothing in the database and @target has no new records
|
||||
# we are certain the current target is an empty array. This is a
|
||||
# documented side-effect of the method that may avoid an extra SELECT.
|
||||
@target ||= [] and loaded if count == 0
|
||||
|
||||
if @reflection.options[:limit]
|
||||
count = [ @reflection.options[:limit], count ].min
|
||||
|
@ -100,7 +112,7 @@ module ActiveRecord
|
|||
create_scoping = {}
|
||||
set_belongs_to_association_for(create_scoping)
|
||||
{
|
||||
:find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] },
|
||||
:find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
|
||||
:create => create_scoping
|
||||
}
|
||||
end
|
||||
|
|
|
@ -237,7 +237,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def build_sti_condition
|
||||
"#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
|
||||
@reflection.through_reflection.klass.send(:type_condition)
|
||||
end
|
||||
|
||||
alias_method :sql_conditions, :conditions
|
||||
|
|
|
@ -21,8 +21,8 @@ module ActiveRecord
|
|||
def replace(obj, dont_save = false)
|
||||
load_target
|
||||
|
||||
unless @target.nil?
|
||||
if dependent? && !dont_save && @target != obj
|
||||
unless @target.nil? || @target == obj
|
||||
if dependent? && !dont_save
|
||||
@target.destroy unless @target.new_record?
|
||||
@owner.clear_association_cache
|
||||
else
|
||||
|
|
|
@ -22,6 +22,10 @@ module ActiveRecord
|
|||
|
||||
def find_target
|
||||
super.first
|
||||
end
|
||||
|
||||
def reset_target!
|
||||
@target = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -372,7 +372,7 @@ module ActiveRecord #:nodoc:
|
|||
def self.reset_subclasses #:nodoc:
|
||||
nonreloadables = []
|
||||
subclasses.each do |klass|
|
||||
unless Dependencies.autoloaded? klass
|
||||
unless ActiveSupport::Dependencies.autoloaded? klass
|
||||
nonreloadables << klass
|
||||
next
|
||||
end
|
||||
|
@ -439,6 +439,10 @@ module ActiveRecord #:nodoc:
|
|||
cattr_accessor :schema_format , :instance_writer => false
|
||||
@@schema_format = :ruby
|
||||
|
||||
# Specify whether or not to use timestamps for migration numbers
|
||||
cattr_accessor :timestamped_migrations , :instance_writer => false
|
||||
@@timestamped_migrations = true
|
||||
|
||||
# Determine whether to store the full constant name including namespace when using STI
|
||||
superclass_delegating_accessor :store_full_sti_class
|
||||
self.store_full_sti_class = false
|
||||
|
@ -828,7 +832,7 @@ module ActiveRecord #:nodoc:
|
|||
def update_counters(id, counters)
|
||||
updates = counters.inject([]) { |list, (counter_name, increment)|
|
||||
sign = increment < 0 ? "-" : "+"
|
||||
list << "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} #{sign} #{increment.abs}"
|
||||
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
|
||||
}.join(", ")
|
||||
update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
|
||||
end
|
||||
|
@ -1465,7 +1469,7 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def construct_finder_sql(options)
|
||||
scope = scope(:find)
|
||||
sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
|
||||
sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
|
||||
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
|
||||
|
||||
add_joins!(sql, options, scope)
|
||||
|
@ -1577,10 +1581,11 @@ module ActiveRecord #:nodoc:
|
|||
sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
|
||||
end
|
||||
|
||||
def type_condition
|
||||
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_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
|
||||
condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
|
||||
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
|
||||
|
||||
" (#{type_condition}) "
|
||||
|
@ -1717,7 +1722,7 @@ module ActiveRecord #:nodoc:
|
|||
def attribute_condition(argument)
|
||||
case argument
|
||||
when nil then "IS ?"
|
||||
when Array, ActiveRecord::Associations::AssociationCollection then "IN (?)"
|
||||
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "IN (?)"
|
||||
when Range then "BETWEEN ? AND ?"
|
||||
else "= ?"
|
||||
end
|
||||
|
@ -2053,9 +2058,10 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
|
||||
statement.gsub(/:([a-zA-Z]\w*)/) do
|
||||
match = $1.to_sym
|
||||
if bind_vars.include?(match)
|
||||
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
||||
if $1 == ':' # skip postgresql casts
|
||||
$& # return the whole match
|
||||
elsif bind_vars.include?(match = $2.to_sym)
|
||||
quote_bound_value(bind_vars[match])
|
||||
else
|
||||
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
||||
|
@ -2064,13 +2070,18 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def expand_range_bind_variables(bind_vars) #:nodoc:
|
||||
bind_vars.sum do |var|
|
||||
expanded = []
|
||||
|
||||
bind_vars.each do |var|
|
||||
if var.is_a?(Range)
|
||||
[var.first, var.last]
|
||||
expanded << var.first
|
||||
expanded << var.last
|
||||
else
|
||||
[var]
|
||||
expanded << var
|
||||
end
|
||||
end
|
||||
|
||||
expanded
|
||||
end
|
||||
|
||||
def quote_bound_value(value) #:nodoc:
|
||||
|
@ -2572,8 +2583,15 @@ module ActiveRecord #:nodoc:
|
|||
quoted = {}
|
||||
connection = self.class.connection
|
||||
attribute_names.each do |name|
|
||||
if column = column_for_attribute(name)
|
||||
quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary
|
||||
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
||||
value = read_attribute(name)
|
||||
|
||||
# We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML.
|
||||
if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))
|
||||
value = value.to_yaml
|
||||
end
|
||||
|
||||
quoted[name] = connection.quote(value, column)
|
||||
end
|
||||
end
|
||||
include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module ActiveRecord
|
||||
module Calculations #:nodoc:
|
||||
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include]
|
||||
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from]
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
@ -27,6 +27,8 @@ module ActiveRecord
|
|||
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
|
||||
# include the joined columns.
|
||||
# * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
|
||||
# * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
|
||||
# of a database view).
|
||||
#
|
||||
# Examples for counting all:
|
||||
# Person.count # returns the total count of all people
|
||||
|
@ -71,7 +73,7 @@ module ActiveRecord
|
|||
#
|
||||
# Person.sum('age')
|
||||
def sum(column_name, options = {})
|
||||
calculate(:sum, column_name, options) || 0
|
||||
calculate(:sum, column_name, options)
|
||||
end
|
||||
|
||||
# This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
|
||||
|
@ -178,13 +180,23 @@ module ActiveRecord
|
|||
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
|
||||
|
||||
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
|
||||
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
|
||||
sql << " FROM #{connection.quote_table_name(table_name)} "
|
||||
if options[:from]
|
||||
sql << " FROM #{options[:from]} "
|
||||
else
|
||||
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
|
||||
sql << " FROM #{connection.quote_table_name(table_name)} "
|
||||
end
|
||||
|
||||
joins = ""
|
||||
add_joins!(joins, options, scope)
|
||||
|
||||
if merged_includes.any?
|
||||
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
|
||||
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
|
||||
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
||||
end
|
||||
add_joins!(sql, options, scope)
|
||||
|
||||
sql << joins unless joins.blank?
|
||||
|
||||
add_conditions!(sql, options[:conditions], scope)
|
||||
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
||||
|
||||
|
@ -205,7 +217,7 @@ module ActiveRecord
|
|||
|
||||
sql << " ORDER BY #{options[:order]} " if options[:order]
|
||||
add_limit!(sql, options, scope)
|
||||
sql << ')' if use_workaround
|
||||
sql << ') AS #{aggregate_alias}_subquery' if use_workaround
|
||||
sql
|
||||
end
|
||||
|
||||
|
@ -266,6 +278,7 @@ module ActiveRecord
|
|||
operation = operation.to_s.downcase
|
||||
case operation
|
||||
when 'count' then value.to_i
|
||||
when 'sum' then value =~ /\./ ? value.to_f : value.to_i
|
||||
when 'avg' then value && value.to_f
|
||||
else column ? column.type_cast(value) : value
|
||||
end
|
||||
|
|
|
@ -257,7 +257,10 @@ module ActiveRecord
|
|||
|
||||
def to_sql
|
||||
column_sql = "#{base.quote_column_name(name)} #{sql_type}"
|
||||
add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
|
||||
column_options = {}
|
||||
column_options[:null] = null unless null.nil?
|
||||
column_options[:default] = default unless default.nil?
|
||||
add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
|
||||
column_sql
|
||||
end
|
||||
alias to_s :to_sql
|
||||
|
@ -304,8 +307,7 @@ module ActiveRecord
|
|||
#
|
||||
# Available options are (none of these exists by default):
|
||||
# * <tt>:limit</tt> -
|
||||
# Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
|
||||
# <tt>:binary</tt> or <tt>:integer</tt> columns only)
|
||||
# Requests a maximum column length. This is number of characters for <tt>:string</tt> and <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
|
||||
# * <tt>:default</tt> -
|
||||
# The column's default value. Use nil for NULL.
|
||||
# * <tt>:null</tt> -
|
||||
|
@ -442,9 +444,10 @@ module ActiveRecord
|
|||
|
||||
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
|
||||
# <tt>:updated_at</tt> to the table.
|
||||
def timestamps
|
||||
column(:created_at, :datetime)
|
||||
column(:updated_at, :datetime)
|
||||
def timestamps(*args)
|
||||
options = args.extract_options!
|
||||
column(:created_at, :datetime, options)
|
||||
column(:updated_at, :datetime, options)
|
||||
end
|
||||
|
||||
def references(*args)
|
||||
|
|
|
@ -331,15 +331,26 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def assume_migrated_upto_version(version)
|
||||
version = version.to_i
|
||||
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
|
||||
|
||||
migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
|
||||
versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
|
||||
filename.split('/').last.split('_').first.to_i
|
||||
end
|
||||
|
||||
execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i)
|
||||
(versions - migrated).select { |v| v < version.to_i }.each do |v|
|
||||
execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
|
||||
unless migrated.include?(version)
|
||||
execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
|
||||
end
|
||||
|
||||
inserted = Set.new
|
||||
(versions - migrated).each do |v|
|
||||
if inserted.include?(v)
|
||||
raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
|
||||
elsif v < version
|
||||
execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
|
||||
inserted << v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -372,13 +383,9 @@ module ActiveRecord
|
|||
|
||||
def add_column_options!(sql, options) #:nodoc:
|
||||
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
|
||||
# must explcitly check for :null to allow change_column to work on migrations
|
||||
if options.has_key? :null
|
||||
if options[:null] == false
|
||||
sql << " NOT NULL"
|
||||
else
|
||||
sql << " NULL"
|
||||
end
|
||||
# must explicitly check for :null to allow change_column to work on migrations
|
||||
if options[:null] == false
|
||||
sql << " NOT NULL"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -50,10 +50,7 @@ module ActiveRecord
|
|||
rescue LoadError => cannot_require_mysql
|
||||
# Use the bundled Ruby/MySQL driver if no driver is already in place
|
||||
begin
|
||||
ActiveRecord::Base.logger.info(
|
||||
"WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. " +
|
||||
"Please install the C-based MySQL library instead (gem install mysql)."
|
||||
) if ActiveRecord::Base.logger
|
||||
ActiveSupport::Deprecation.warn "You're using the Ruby-based MySQL library that ships with Rails. This library will be REMOVED FROM RAILS 2.2. Please switch to the offical mysql gem: `gem install mysql`", caller
|
||||
|
||||
require 'active_record/vendor/mysql'
|
||||
rescue LoadError
|
||||
|
@ -113,7 +110,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def extract_limit(sql_type)
|
||||
if sql_type =~ /blob|text/i
|
||||
case sql_type
|
||||
when /blob|text/i
|
||||
case sql_type
|
||||
when /tiny/i
|
||||
255
|
||||
|
@ -124,6 +122,11 @@ module ActiveRecord
|
|||
else
|
||||
super # we could return 65535 here, but we leave it undecorated by default
|
||||
end
|
||||
when /^bigint/i; 8
|
||||
when /^int/i; 4
|
||||
when /^mediumint/i; 3
|
||||
when /^smallint/i; 2
|
||||
when /^tinyint/i; 1
|
||||
else
|
||||
super
|
||||
end
|
||||
|
@ -193,10 +196,10 @@ module ActiveRecord
|
|||
|
||||
def native_database_types #:nodoc:
|
||||
{
|
||||
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
||||
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
|
||||
:string => { :name => "varchar", :limit => 255 },
|
||||
:text => { :name => "text" },
|
||||
:integer => { :name => "int"},
|
||||
:integer => { :name => "int", :limit => 4 },
|
||||
:float => { :name => "float" },
|
||||
:decimal => { :name => "decimal" },
|
||||
:datetime => { :name => "datetime" },
|
||||
|
@ -336,10 +339,11 @@ module ActiveRecord
|
|||
|
||||
def add_limit_offset!(sql, options) #:nodoc:
|
||||
if limit = options[:limit]
|
||||
limit = sanitize_limit(limit)
|
||||
unless offset = options[:offset]
|
||||
sql << " LIMIT #{limit}"
|
||||
else
|
||||
sql << " LIMIT #{offset}, #{limit}"
|
||||
sql << " LIMIT #{offset.to_i}, #{limit}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -439,18 +443,29 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default) #:nodoc:
|
||||
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
||||
column = column_for(table_name, column_name)
|
||||
change_column table_name, column_name, column.sql_type, :default => default
|
||||
end
|
||||
|
||||
execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
|
||||
def change_column_null(table_name, column_name, null, default = nil)
|
||||
column = column_for(table_name, column_name)
|
||||
|
||||
unless null || default.nil?
|
||||
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
||||
end
|
||||
|
||||
change_column table_name, column_name, column.sql_type, :null => null
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
column = column_for(table_name, column_name)
|
||||
|
||||
unless options_include_default?(options)
|
||||
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
||||
options[:default] = column.default
|
||||
else
|
||||
raise "No such column: #{table_name}.#{column_name}"
|
||||
end
|
||||
options[:default] = column.default
|
||||
end
|
||||
|
||||
unless options.has_key?(:null)
|
||||
options[:null] = column.null
|
||||
end
|
||||
|
||||
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
||||
|
@ -459,8 +474,17 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
options = {}
|
||||
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
||||
options[:default] = column.default
|
||||
options[:null] = column.null
|
||||
else
|
||||
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
||||
end
|
||||
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
||||
execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
||||
rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
||||
add_column_options!(rename_column_sql, options)
|
||||
execute(rename_column_sql)
|
||||
end
|
||||
|
||||
# Maps logical Rails types to MySQL-specific data types.
|
||||
|
@ -468,14 +492,12 @@ module ActiveRecord
|
|||
return super unless type.to_s == 'integer'
|
||||
|
||||
case limit
|
||||
when 0..3
|
||||
"smallint(#{limit})"
|
||||
when 4..8
|
||||
"int(#{limit})"
|
||||
when 9..20
|
||||
"bigint(#{limit})"
|
||||
else
|
||||
'int(11)'
|
||||
when 1; 'tinyint'
|
||||
when 2; 'smallint'
|
||||
when 3; 'mediumint'
|
||||
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
||||
when 5..8; 'bigint'
|
||||
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -525,6 +547,13 @@ module ActiveRecord
|
|||
def version
|
||||
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
||||
end
|
||||
|
||||
def column_for(table_name, column_name)
|
||||
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
||||
raise "No such column: #{table_name}.#{column_name}"
|
||||
end
|
||||
column
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,8 +23,8 @@ module ActiveRecord
|
|||
config = config.symbolize_keys
|
||||
host = config[:host]
|
||||
port = config[:port] || 5432
|
||||
username = config[:username].to_s
|
||||
password = config[:password].to_s
|
||||
username = config[:username].to_s if config[:username]
|
||||
password = config[:password].to_s if config[:password]
|
||||
|
||||
if config.has_key?(:database)
|
||||
database = config[:database]
|
||||
|
@ -47,6 +47,14 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
private
|
||||
def extract_limit(sql_type)
|
||||
case sql_type
|
||||
when /^bigint/i; 8
|
||||
when /^smallint/i; 2
|
||||
else super
|
||||
end
|
||||
end
|
||||
|
||||
# Extracts the scale from PostgreSQL-specific data types.
|
||||
def extract_scale(sql_type)
|
||||
# Money type has a fixed scale of 2.
|
||||
|
@ -174,8 +182,8 @@ module ActiveRecord
|
|||
def self.extract_value_from_default(default)
|
||||
case default
|
||||
# Numeric types
|
||||
when /\A-?\d+(\.\d*)?\z/
|
||||
default
|
||||
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
|
||||
$1
|
||||
# Character types
|
||||
when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
|
||||
$1
|
||||
|
@ -319,6 +327,10 @@ module ActiveRecord
|
|||
has_support
|
||||
end
|
||||
|
||||
def supports_insert_with_returning?
|
||||
postgresql_version >= 80200
|
||||
end
|
||||
|
||||
# Returns the configured supported identifier length supported by PostgreSQL,
|
||||
# or report the default of 63 on PostgreSQL 7.x.
|
||||
def table_alias_length
|
||||
|
@ -360,7 +372,7 @@ module ActiveRecord
|
|||
# There are some incorrectly compiled postgres drivers out there
|
||||
# that don't define PGconn.escape.
|
||||
self.class.instance_eval do
|
||||
undef_method(:quote_string)
|
||||
remove_method(:quote_string)
|
||||
end
|
||||
end
|
||||
quote_string(s)
|
||||
|
@ -411,8 +423,34 @@ module ActiveRecord
|
|||
|
||||
# Executes an INSERT query and returns the new record's ID
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
# Extract the table from the insert sql. Yuck.
|
||||
table = sql.split(" ", 4)[2].gsub('"', '')
|
||||
super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk))
|
||||
|
||||
# Try an insert with 'returning id' if available (PG >= 8.2)
|
||||
if supports_insert_with_returning?
|
||||
pk, sequence_name = *pk_and_sequence_for(table) unless pk
|
||||
if pk
|
||||
id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
||||
clear_query_cache
|
||||
return id
|
||||
end
|
||||
end
|
||||
|
||||
# Otherwise, insert then grab last_insert_id.
|
||||
if insert_id = super
|
||||
insert_id
|
||||
else
|
||||
# If neither pk nor sequence name is given, look them up.
|
||||
unless pk || sequence_name
|
||||
pk, sequence_name = *pk_and_sequence_for(table)
|
||||
end
|
||||
|
||||
# If a pk is given, fallback to default sequence name.
|
||||
# Don't fetch last insert id for a table without a pk.
|
||||
if pk && sequence_name ||= default_sequence_name(table, pk)
|
||||
last_insert_id(table, sequence_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# create a 2D array representing the result set
|
||||
|
@ -492,13 +530,13 @@ module ActiveRecord
|
|||
option_string = options.symbolize_keys.sum do |key, value|
|
||||
case key
|
||||
when :owner
|
||||
" OWNER = '#{value}'"
|
||||
" OWNER = \"#{value}\""
|
||||
when :template
|
||||
" TEMPLATE = #{value}"
|
||||
" TEMPLATE = \"#{value}\""
|
||||
when :encoding
|
||||
" ENCODING = '#{value}'"
|
||||
when :tablespace
|
||||
" TABLESPACE = #{value}"
|
||||
" TABLESPACE = \"#{value}\""
|
||||
when :connection_limit
|
||||
" CONNECTION LIMIT = #{value}"
|
||||
else
|
||||
|
@ -506,7 +544,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
execute "CREATE DATABASE #{name}#{option_string}"
|
||||
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
|
||||
end
|
||||
|
||||
# Drops a PostgreSQL database
|
||||
|
@ -514,7 +552,15 @@ module ActiveRecord
|
|||
# Example:
|
||||
# drop_database 'matt_development'
|
||||
def drop_database(name) #:nodoc:
|
||||
execute "DROP DATABASE IF EXISTS #{name}"
|
||||
if postgresql_version >= 80200
|
||||
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
||||
else
|
||||
begin
|
||||
execute "DROP DATABASE #{quote_table_name(name)}"
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
@logger.warn "#{name} database doesn't exist." if @logger
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -676,7 +722,7 @@ module ActiveRecord
|
|||
|
||||
# Renames a table.
|
||||
def rename_table(name, new_name)
|
||||
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
||||
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
||||
end
|
||||
|
||||
# Adds a new column to the named table.
|
||||
|
@ -698,7 +744,8 @@ module ActiveRecord
|
|||
|
||||
begin
|
||||
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
rescue ActiveRecord::StatementInvalid => e
|
||||
raise e if postgresql_version > 80000
|
||||
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
|
||||
begin
|
||||
begin_db_transaction
|
||||
|
@ -743,15 +790,14 @@ module ActiveRecord
|
|||
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
||||
return super unless type.to_s == 'integer'
|
||||
|
||||
if limit.nil? || limit == 4
|
||||
'integer'
|
||||
elsif limit < 4
|
||||
'smallint'
|
||||
else
|
||||
'bigint'
|
||||
case limit
|
||||
when 1..2; 'smallint'
|
||||
when 3..4, nil; 'integer'
|
||||
when 5..8; 'bigint'
|
||||
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
||||
#
|
||||
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
||||
|
|
|
@ -238,6 +238,15 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def change_column_null(table_name, column_name, null, default = nil)
|
||||
unless null || default.nil?
|
||||
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
||||
end
|
||||
alter_table(table_name) do |definition|
|
||||
definition[column_name].null = null
|
||||
end
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
include_default = options_include_default?(options)
|
||||
|
@ -251,6 +260,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
unless columns(table_name).detect{|c| c.name == column_name.to_s }
|
||||
raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
|
||||
end
|
||||
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
|
||||
end
|
||||
|
||||
|
|
|
@ -123,7 +123,10 @@ module ActiveRecord
|
|||
attr = attr.to_s
|
||||
|
||||
# The attribute already has an unsaved change.
|
||||
unless changed_attributes.include?(attr)
|
||||
if changed_attributes.include?(attr)
|
||||
old = changed_attributes[attr]
|
||||
changed_attributes.delete(attr) unless field_changed?(attr, old, value)
|
||||
else
|
||||
old = clone_attribute_value(:read_attribute, attr)
|
||||
changed_attributes[attr] = old if field_changed?(attr, old, value)
|
||||
end
|
||||
|
@ -134,7 +137,9 @@ module ActiveRecord
|
|||
|
||||
def update_with_dirty
|
||||
if partial_updates?
|
||||
update_without_dirty(changed)
|
||||
# Serialized attributes should always be written in case they've been
|
||||
# changed in place.
|
||||
update_without_dirty(changed | self.class.serialized_attributes.keys)
|
||||
else
|
||||
update_without_dirty
|
||||
end
|
||||
|
@ -142,9 +147,11 @@ module ActiveRecord
|
|||
|
||||
def field_changed?(attr, old, value)
|
||||
if column = column_for_attribute(attr)
|
||||
if column.type == :integer && column.null && old.nil?
|
||||
if column.type == :integer && column.null && (old.nil? || old == 0)
|
||||
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
|
||||
# Hence we don't record it as a change if the value changes from nil to ''.
|
||||
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
||||
# be typecast back to 0 (''.to_i => 0)
|
||||
value = nil if value.blank?
|
||||
else
|
||||
value = column.type_cast(value)
|
||||
|
|
|
@ -68,6 +68,7 @@ module ActiveRecord
|
|||
|
||||
def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
|
||||
return update_without_lock(attribute_names) unless locking_enabled?
|
||||
return 0 if attribute_names.empty?
|
||||
|
||||
lock_col = self.class.locking_column
|
||||
previous_value = send(lock_col).to_i
|
||||
|
|
|
@ -238,6 +238,22 @@ module ActiveRecord
|
|||
# lower than the current schema version: when migrating up, those
|
||||
# never-applied "interleaved" migrations will be automatically applied, and
|
||||
# when migrating down, never-applied "interleaved" migrations will be skipped.
|
||||
#
|
||||
# == Timestamped Migrations
|
||||
#
|
||||
# By default, Rails generates migrations that look like:
|
||||
#
|
||||
# 20080717013526_your_migration_name.rb
|
||||
#
|
||||
# The prefix is a generation timestamp (in UTC).
|
||||
#
|
||||
# If you'd prefer to use numeric prefixes, you can turn timestamped migrations
|
||||
# off by setting:
|
||||
#
|
||||
# config.active_record.timestamped_migrations = false
|
||||
#
|
||||
# In environment.rb.
|
||||
#
|
||||
class Migration
|
||||
@@verbose = true
|
||||
cattr_accessor :verbose
|
||||
|
@ -369,11 +385,17 @@ module ActiveRecord
|
|||
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
|
||||
end
|
||||
|
||||
def get_all_versions
|
||||
Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort
|
||||
end
|
||||
|
||||
def current_version
|
||||
version = Base.connection.select_values(
|
||||
"SELECT version FROM #{schema_migrations_table_name}"
|
||||
).map(&:to_i).max rescue nil
|
||||
version || 0
|
||||
sm_table = schema_migrations_table_name
|
||||
if Base.connection.table_exists?(sm_table)
|
||||
get_all_versions.max || 0
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def proper_table_name(name)
|
||||
|
@ -389,7 +411,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def current_version
|
||||
self.class.current_version
|
||||
migrated.last || 0
|
||||
end
|
||||
|
||||
def current_migration
|
||||
|
@ -399,7 +421,10 @@ module ActiveRecord
|
|||
def run
|
||||
target = migrations.detect { |m| m.version == @target_version }
|
||||
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
|
||||
target.migrate(@direction)
|
||||
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
|
||||
target.migrate(@direction)
|
||||
record_version_state_after_migrating(target.version)
|
||||
end
|
||||
end
|
||||
|
||||
def migrate
|
||||
|
@ -470,17 +495,19 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def migrated
|
||||
sm_table = self.class.schema_migrations_table_name
|
||||
Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort
|
||||
@migrated_versions ||= self.class.get_all_versions
|
||||
end
|
||||
|
||||
private
|
||||
def record_version_state_after_migrating(version)
|
||||
sm_table = self.class.schema_migrations_table_name
|
||||
|
||||
@migrated_versions ||= []
|
||||
if down?
|
||||
@migrated_versions.delete(version.to_i)
|
||||
Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
|
||||
else
|
||||
@migrated_versions.push(version.to_i).sort!
|
||||
Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,6 +82,7 @@ module ActiveRecord
|
|||
# expected_options = { :conditions => { :colored => 'red' } }
|
||||
# assert_equal expected_options, Shirt.colored('red').proxy_options
|
||||
def named_scope(name, options = {}, &block)
|
||||
name = name.to_sym
|
||||
scopes[name] = lambda do |parent_scope, *args|
|
||||
Scope.new(parent_scope, case options
|
||||
when Hash
|
||||
|
@ -102,7 +103,7 @@ module ActiveRecord
|
|||
attr_reader :proxy_scope, :proxy_options
|
||||
|
||||
[].methods.each do |m|
|
||||
unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
|
||||
unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/
|
||||
delegate m, :to => :proxy_found
|
||||
end
|
||||
end
|
||||
|
@ -139,6 +140,10 @@ module ActiveRecord
|
|||
@found ? @found.empty? : count.zero?
|
||||
end
|
||||
|
||||
def respond_to?(method, include_private = false)
|
||||
super || @proxy_scope.respond_to?(method, include_private)
|
||||
end
|
||||
|
||||
protected
|
||||
def proxy_found
|
||||
@found || load_found
|
||||
|
|
|
@ -20,7 +20,7 @@ module ActiveRecord
|
|||
# ActiveRecord::Base.observers = Cacher, GarbageCollector
|
||||
#
|
||||
# Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
|
||||
# called during startup, and before each development request.
|
||||
# called during startup, and before each development request.
|
||||
def observers=(*observers)
|
||||
@observers = observers.flatten
|
||||
end
|
||||
|
@ -130,11 +130,11 @@ module ActiveRecord
|
|||
# Observers register themselves in the model class they observe, since it is the class that
|
||||
# notifies them of events when they occur. As a side-effect, when an observer is loaded its
|
||||
# corresponding model class is loaded.
|
||||
#
|
||||
#
|
||||
# Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
|
||||
# application initializers. Now observers are loaded after application initializers,
|
||||
# application initializers. Now observers are loaded after application initializers,
|
||||
# so observed models can make use of extensions.
|
||||
#
|
||||
#
|
||||
# If by any chance you are using observed models in the initialization you can still
|
||||
# load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
|
||||
# singletons and that call instantiates and registers them.
|
||||
|
@ -189,7 +189,9 @@ module ActiveRecord
|
|||
|
||||
def add_observer!(klass)
|
||||
klass.add_observer(self)
|
||||
klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
|
||||
if respond_to?(:after_find) && !klass.method_defined?(:after_find)
|
||||
klass.class_eval 'def after_find() end'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,11 +22,22 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def assert_queries(num = 1)
|
||||
$query_count = 0
|
||||
def assert_sql(*patterns_to_match)
|
||||
$queries_executed = []
|
||||
yield
|
||||
ensure
|
||||
assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
|
||||
failed_patterns = []
|
||||
patterns_to_match.each do |pattern|
|
||||
failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
|
||||
end
|
||||
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found."
|
||||
end
|
||||
|
||||
def assert_queries(num = 1)
|
||||
$queries_executed = []
|
||||
yield
|
||||
ensure
|
||||
assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed."
|
||||
end
|
||||
|
||||
def assert_no_queries(&block)
|
||||
|
|
|
@ -480,8 +480,9 @@ module ActiveRecord
|
|||
# validates_length_of :fax, :in => 7..32, :allow_nil => true
|
||||
# validates_length_of :phone, :in => 7..32, :allow_blank => true
|
||||
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
|
||||
# validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
|
||||
# validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
|
||||
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
|
||||
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
|
||||
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
|
||||
# end
|
||||
#
|
||||
# Configuration options:
|
||||
|
@ -492,7 +493,6 @@ module ActiveRecord
|
|||
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
|
||||
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
||||
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
||||
#
|
||||
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)").
|
||||
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)").
|
||||
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)").
|
||||
|
@ -504,12 +504,16 @@ module ActiveRecord
|
|||
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
|
||||
# count words as in above example.)
|
||||
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
|
||||
def validates_length_of(*attrs)
|
||||
# Merge given options with defaults.
|
||||
options = {
|
||||
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
|
||||
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
|
||||
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
|
||||
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length],
|
||||
:tokenizer => lambda {|value| value.split(//)}
|
||||
}.merge(DEFAULT_VALIDATION_OPTIONS)
|
||||
options.update(attrs.extract_options!.symbolize_keys)
|
||||
|
||||
|
@ -536,7 +540,7 @@ module ActiveRecord
|
|||
too_long = options[:too_long] % option_value.end
|
||||
|
||||
validates_each(attrs, options) do |record, attr, value|
|
||||
value = value.split(//) if value.kind_of?(String)
|
||||
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
||||
if value.nil? or value.size < option_value.begin
|
||||
record.errors.add(attr, too_short)
|
||||
elsif value.size > option_value.end
|
||||
|
@ -553,7 +557,7 @@ module ActiveRecord
|
|||
message = (options[:message] || options[message_options[option]]) % option_value
|
||||
|
||||
validates_each(attrs, options) do |record, attr, value|
|
||||
value = value.split(//) if value.kind_of?(String)
|
||||
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
||||
record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
|
||||
end
|
||||
end
|
||||
|
@ -614,14 +618,20 @@ module ActiveRecord
|
|||
# class (which has a database table to query from).
|
||||
finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
|
||||
|
||||
if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?)
|
||||
is_text_column = finder_class.columns_hash[attr_name.to_s].text?
|
||||
|
||||
if !value.nil? && is_text_column
|
||||
value = value.to_s
|
||||
end
|
||||
|
||||
if value.nil? || (configuration[:case_sensitive] || !is_text_column)
|
||||
condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"
|
||||
condition_params = [value]
|
||||
else
|
||||
# sqlite has case sensitive SELECT query, while MySQL/Postgresql don't.
|
||||
# Hence, this is needed only for sqlite.
|
||||
condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
|
||||
condition_params = [value.downcase]
|
||||
condition_params = [value.chars.downcase]
|
||||
end
|
||||
|
||||
if scope = configuration[:scope]
|
||||
|
@ -851,7 +861,7 @@ module ActiveRecord
|
|||
raw_value = raw_value.to_i
|
||||
else
|
||||
begin
|
||||
raw_value = Kernel.Float(raw_value.to_s)
|
||||
raw_value = Kernel.Float(raw_value)
|
||||
rescue ArgumentError, TypeError
|
||||
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
|
||||
next
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActiveRecord
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 1
|
||||
TINY = 0
|
||||
TINY = 1
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -13,8 +13,8 @@ class PostgresqlActiveSchemaTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_create_database_with_encoding
|
||||
assert_equal "CREATE DATABASE matt ENCODING = 'utf8'", create_database(:matt)
|
||||
assert_equal "CREATE DATABASE aimonetti ENCODING = 'latin1'", create_database(:aimonetti, :encoding => :latin1)
|
||||
assert_equal %(CREATE DATABASE "matt" ENCODING = 'utf8'), create_database(:matt)
|
||||
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -118,7 +118,7 @@ class AdapterTest < ActiveRecord::TestCase
|
|||
sql_inject = "1, 7 procedure help()"
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
|
||||
assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
|
||||
assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7)
|
||||
else
|
||||
assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
|
||||
assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
|
||||
|
|
|
@ -409,4 +409,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|||
sponsor.sponsorable = new_member
|
||||
assert_equal nil, sponsor.sponsorable_id
|
||||
end
|
||||
|
||||
def test_save_fails_for_invalid_belongs_to
|
||||
assert log = AuditLog.create(:developer_id=>0,:message=>"")
|
||||
|
||||
log.developer = Developer.new
|
||||
assert !log.developer.valid?
|
||||
assert !log.valid?
|
||||
assert !log.save
|
||||
assert_equal "is invalid", log.errors.on("developer")
|
||||
end
|
||||
|
||||
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
|
||||
assert log = AuditLog.create(:developer_id=>0,:message=>"")
|
||||
|
||||
log.unvalidated_developer = Developer.new
|
||||
assert !log.unvalidated_developer.valid?
|
||||
assert log.valid?
|
||||
assert log.save
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ require 'models/topic'
|
|||
require 'models/reply'
|
||||
|
||||
class CascadedEagerLoadingTest < ActiveRecord::TestCase
|
||||
fixtures :authors, :mixins, :companies, :posts, :topics
|
||||
fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations
|
||||
|
||||
def test_eager_association_loading_with_cascaded_two_levels
|
||||
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
|
||||
|
@ -68,6 +68,18 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_has_many_sti_and_subclasses
|
||||
silly = SillyReply.new(:title => "gaga", :content => "boo-boo", :parent_id => 1)
|
||||
silly.parent_id = 1
|
||||
assert silly.save
|
||||
|
||||
topics = Topic.find(:all, :include => :replies, :order => 'topics.id, replies_topics.id')
|
||||
assert_no_queries do
|
||||
assert_equal 2, topics[0].replies.size
|
||||
assert_equal 0, topics[1].replies.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_sti
|
||||
replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
|
||||
assert replies.include?(topics(:second))
|
||||
|
|
36
vendor/rails/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
vendored
Normal file
36
vendor/rails/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
require 'cases/helper'
|
||||
require 'models/post'
|
||||
require 'models/tagging'
|
||||
|
||||
module Namespaced
|
||||
class Post < ActiveRecord::Base
|
||||
set_table_name 'posts'
|
||||
has_one :tagging, :as => :taggable, :class_name => 'Tagging'
|
||||
end
|
||||
end
|
||||
|
||||
class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
|
||||
|
||||
def setup
|
||||
generate_test_objects
|
||||
end
|
||||
|
||||
def generate_test_objects
|
||||
post = Namespaced::Post.create( :title => 'Great stuff', :body => 'This is not', :author_id => 1 )
|
||||
tagging = Tagging.create( :taggable => post )
|
||||
end
|
||||
|
||||
def test_class_names
|
||||
old = ActiveRecord::Base.store_full_sti_class
|
||||
|
||||
ActiveRecord::Base.store_full_sti_class = false
|
||||
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
|
||||
assert_nil post.tagging
|
||||
|
||||
ActiveRecord::Base.store_full_sti_class = true
|
||||
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
|
||||
assert_equal 'Tagging', post.tagging.class.name
|
||||
ensure
|
||||
ActiveRecord::Base.store_full_sti_class = old
|
||||
end
|
||||
end
|
|
@ -14,11 +14,14 @@ require 'models/job'
|
|||
require 'models/subscriber'
|
||||
require 'models/subscription'
|
||||
require 'models/book'
|
||||
require 'models/developer'
|
||||
require 'models/project'
|
||||
|
||||
class EagerAssociationTest < ActiveRecord::TestCase
|
||||
fixtures :posts, :comments, :authors, :categories, :categories_posts,
|
||||
:companies, :accounts, :tags, :taggings, :people, :readers,
|
||||
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books
|
||||
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
|
||||
:developers, :projects, :developers_projects
|
||||
|
||||
def test_loading_with_one_association
|
||||
posts = Post.find(:all, :include => :comments)
|
||||
|
@ -35,6 +38,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
assert_equal Post.find(1).last_comment, post.last_comment
|
||||
end
|
||||
|
||||
def test_loading_with_one_association_with_non_preload
|
||||
posts = Post.find(:all, :include => :last_comment, :order => 'comments.id DESC')
|
||||
post = posts.find { |p| p.id == 1 }
|
||||
assert_equal Post.find(1).last_comment, post.last_comment
|
||||
end
|
||||
|
||||
def test_loading_conditions_with_or
|
||||
posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
|
||||
assert_nil posts.detect { |p| p.author_id != authors(:david).id },
|
||||
|
@ -556,6 +565,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
assert_nothing_raised { Post.find(:all, :include => 'comments') }
|
||||
end
|
||||
|
||||
def test_eager_with_floating_point_numbers
|
||||
assert_queries(2) do
|
||||
# Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query
|
||||
Comment.find :all, :conditions => "123.456 = 123.456", :include => :post
|
||||
end
|
||||
end
|
||||
|
||||
def test_preconfigured_includes_with_belongs_to
|
||||
author = posts(:welcome).author_with_posts
|
||||
assert_no_queries {assert_equal 5, author.posts.size}
|
||||
|
@ -609,4 +625,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
Comment.find :all, :include => :post
|
||||
end
|
||||
end
|
||||
|
||||
def test_conditions_on_join_table_with_include_and_limit
|
||||
assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size
|
||||
end
|
||||
|
||||
def test_order_on_join_table_with_include_and_limit
|
||||
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,7 +70,7 @@ end
|
|||
|
||||
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
||||
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
|
||||
:parrots, :pirates, :treasures, :price_estimates
|
||||
:parrots, :pirates, :treasures, :price_estimates, :tags, :taggings
|
||||
|
||||
def test_has_and_belongs_to_many
|
||||
david = Developer.find(1)
|
||||
|
@ -299,6 +299,17 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 3, projects(:active_record, :reload).developers.size
|
||||
end
|
||||
|
||||
def test_uniq_option_prevents_duplicate_push
|
||||
project = projects(:active_record)
|
||||
project.developers << developers(:jamis)
|
||||
project.developers << developers(:david)
|
||||
assert_equal 3, project.developers.size
|
||||
|
||||
project.developers << developers(:david)
|
||||
project.developers << developers(:jamis)
|
||||
assert_equal 3, project.developers.size
|
||||
end
|
||||
|
||||
def test_deleting
|
||||
david = Developer.find(1)
|
||||
active_record = Project.find(1)
|
||||
|
@ -439,6 +450,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find"
|
||||
end
|
||||
|
||||
def test_find_in_association_with_custom_finder_sql_and_multiple_interpolations
|
||||
# interpolate once:
|
||||
assert_equal [developers(:david), developers(:jamis), developers(:poor_jamis)], projects(:active_record).developers_with_finder_sql, "first interpolation"
|
||||
# interpolate again, for a different project id
|
||||
assert_equal [developers(:david)], projects(:action_controller).developers_with_finder_sql, "second interpolation"
|
||||
end
|
||||
|
||||
def test_find_in_association_with_custom_finder_sql_and_string_id
|
||||
assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find"
|
||||
end
|
||||
|
@ -629,8 +647,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
developer.save
|
||||
developer.reload
|
||||
assert_equal 2, developer.projects.length
|
||||
assert_equal projects(:active_record), developer.projects[0]
|
||||
assert_equal projects(:action_controller), developer.projects[1]
|
||||
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
|
||||
end
|
||||
|
||||
def test_assign_ids_ignoring_blanks
|
||||
|
@ -639,8 +656,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
developer.save
|
||||
developer.reload
|
||||
assert_equal 2, developer.projects.length
|
||||
assert_equal projects(:active_record), developer.projects[0]
|
||||
assert_equal projects(:action_controller), developer.projects[1]
|
||||
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
|
||||
end
|
||||
|
||||
def test_select_limited_ids_list
|
||||
|
@ -681,4 +697,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal developer, project.developers.find(:first)
|
||||
assert_equal project, developer.projects.find(:first)
|
||||
end
|
||||
|
||||
def test_dynamic_find_should_respect_association_include
|
||||
# SQL error in sort clause if :include is not included
|
||||
# due to Unknown column 'authors.id'
|
||||
assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ require 'models/reader'
|
|||
class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
fixtures :accounts, :categories, :companies, :developers, :projects,
|
||||
:developers_projects, :topics, :authors, :comments, :author_addresses,
|
||||
:people, :posts
|
||||
:people, :posts, :readers
|
||||
|
||||
def setup
|
||||
Client.destroyed_client_ids.clear
|
||||
|
@ -37,15 +37,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_counting_with_single_conditions
|
||||
assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
|
||||
assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => ['name=?', "Microsoft"])
|
||||
end
|
||||
|
||||
def test_counting_with_single_hash
|
||||
assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
|
||||
assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => {:name => "Microsoft"})
|
||||
end
|
||||
|
||||
def test_counting_with_column_name_and_hash
|
||||
assert_equal 2, Firm.find(:first).plain_clients.count(:all, :conditions => '1=1')
|
||||
assert_equal 2, Firm.find(:first).plain_clients.count(:name)
|
||||
end
|
||||
|
||||
def test_counting_with_association_limit
|
||||
firm = companies(:first_firm)
|
||||
assert_equal firm.limited_clients.length, firm.limited_clients.size
|
||||
assert_equal firm.limited_clients.length, firm.limited_clients.count
|
||||
end
|
||||
|
||||
def test_finding
|
||||
|
@ -342,6 +348,34 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert new_firm.new_record?
|
||||
end
|
||||
|
||||
def test_invalid_adding_with_validate_false
|
||||
firm = Firm.find(:first)
|
||||
client = Client.new
|
||||
firm.unvalidated_clients_of_firm << client
|
||||
|
||||
assert firm.valid?
|
||||
assert !client.valid?
|
||||
assert firm.save
|
||||
assert client.new_record?
|
||||
end
|
||||
|
||||
def test_valid_adding_with_validate_false
|
||||
no_of_clients = Client.count
|
||||
|
||||
firm = Firm.find(:first)
|
||||
client = Client.new("name" => "Apple")
|
||||
|
||||
assert firm.valid?
|
||||
assert client.valid?
|
||||
assert client.new_record?
|
||||
|
||||
firm.unvalidated_clients_of_firm << client
|
||||
|
||||
assert firm.save
|
||||
assert !client.new_record?
|
||||
assert_equal no_of_clients+1, Client.count
|
||||
end
|
||||
|
||||
def test_build
|
||||
company = companies(:first_firm)
|
||||
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
|
||||
|
@ -356,6 +390,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 2, company.clients_of_firm(true).size
|
||||
end
|
||||
|
||||
def test_collection_size_after_building
|
||||
company = companies(:first_firm) # company already has one client
|
||||
company.clients_of_firm.build("name" => "Another Client")
|
||||
company.clients_of_firm.build("name" => "Yet Another Client")
|
||||
assert_equal 3, company.clients_of_firm.size
|
||||
end
|
||||
|
||||
def test_collection_size_twice_for_regressions
|
||||
post = posts(:thinking)
|
||||
assert_equal 0, post.readers.size
|
||||
# This test needs a post that has no readers, we assert it to ensure it holds,
|
||||
# but need to reload the post because the very call to #size hides the bug.
|
||||
post.reload
|
||||
post.readers.build
|
||||
size1 = post.readers.size
|
||||
size2 = post.readers.size
|
||||
assert_equal size1, size2
|
||||
end
|
||||
|
||||
def test_build_many
|
||||
company = companies(:first_firm)
|
||||
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
|
||||
|
@ -386,6 +439,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 2, first_topic.replies.to_ary.size
|
||||
end
|
||||
|
||||
def test_build_via_block
|
||||
company = companies(:first_firm)
|
||||
new_client = assert_no_queries { company.clients_of_firm.build {|client| client.name = "Another Client" } }
|
||||
assert !company.clients_of_firm.loaded?
|
||||
|
||||
assert_equal "Another Client", new_client.name
|
||||
assert new_client.new_record?
|
||||
assert_equal new_client, company.clients_of_firm.last
|
||||
company.name += '-changed'
|
||||
assert_queries(2) { assert company.save }
|
||||
assert !new_client.new_record?
|
||||
assert_equal 2, company.clients_of_firm(true).size
|
||||
end
|
||||
|
||||
def test_build_many_via_block
|
||||
company = companies(:first_firm)
|
||||
new_clients = assert_no_queries do
|
||||
company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
|
||||
client.name = "changed"
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal 2, new_clients.size
|
||||
assert_equal "changed", new_clients.first.name
|
||||
assert_equal "changed", new_clients.last.name
|
||||
|
||||
company.name += '-changed'
|
||||
assert_queries(3) { assert company.save }
|
||||
assert_equal 3, company.clients_of_firm(true).size
|
||||
end
|
||||
|
||||
def test_create_without_loading_association
|
||||
first_firm = companies(:first_firm)
|
||||
Firm.column_names
|
||||
|
@ -929,4 +1013,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert firm.clients.loaded?
|
||||
end
|
||||
|
||||
def test_joins_with_namespaced_model_should_use_correct_type
|
||||
old = ActiveRecord::Base.store_full_sti_class
|
||||
ActiveRecord::Base.store_full_sti_class = true
|
||||
|
||||
firm = Namespaced::Firm.create({ :name => 'Some Company' })
|
||||
firm.clients.create({ :name => 'Some Client' })
|
||||
|
||||
stats = Namespaced::Firm.find(firm.id, {
|
||||
:select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients",
|
||||
:joins => :clients,
|
||||
:group => "#{Namespaced::Firm.table_name}.id"
|
||||
})
|
||||
assert_equal 1, stats.num_clients.to_i
|
||||
|
||||
ensure
|
||||
ActiveRecord::Base.store_full_sti_class = old
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -187,4 +187,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
|||
post.people_with_callbacks.clear
|
||||
assert_equal (%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort
|
||||
end
|
||||
|
||||
def test_dynamic_find_should_respect_association_include
|
||||
# SQL error in sort clause if :include is not included
|
||||
# due to Unknown column 'comments.id'
|
||||
assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title('Welcome to the weblog')
|
||||
end
|
||||
|
||||
def test_count_with_include_should_alias_join_table
|
||||
assert_equal 2, people(:michael).posts.count(:include => :readers)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,6 +72,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|||
assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
|
||||
end
|
||||
|
||||
def test_natural_assignment_to_already_associated_record
|
||||
company = companies(:first_firm)
|
||||
account = accounts(:signals37)
|
||||
assert_equal company.account, account
|
||||
company.account = account
|
||||
company.reload
|
||||
account.reload
|
||||
assert_equal company.account, account
|
||||
end
|
||||
|
||||
def test_assignment_without_replacement
|
||||
apple = Firm.create("name" => "Apple")
|
||||
citibank = Account.create("credit_limit" => 10)
|
||||
|
@ -275,6 +285,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal "is invalid", firm.errors.on("account")
|
||||
end
|
||||
|
||||
|
||||
def test_save_succeeds_for_invalid_has_one_with_validate_false
|
||||
firm = Firm.find(:first)
|
||||
assert firm.valid?
|
||||
|
||||
firm.unvalidated_account = Account.new
|
||||
|
||||
assert !firm.unvalidated_account.valid?
|
||||
assert firm.valid?
|
||||
assert firm.save
|
||||
end
|
||||
|
||||
def test_assignment_before_either_saved
|
||||
firm = Firm.new("name" => "GlobalMegaCorp")
|
||||
firm.account = a = Account.new("credit_limit" => 1000)
|
||||
|
|
|
@ -44,19 +44,23 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
|||
def test_has_one_through_polymorphic
|
||||
assert_equal clubs(:moustache_club), @member.sponsor_club
|
||||
end
|
||||
|
||||
|
||||
def has_one_through_to_has_many
|
||||
assert_equal 2, @member.fellow_members.size
|
||||
end
|
||||
|
||||
|
||||
def test_has_one_through_eager_loading
|
||||
members = Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
|
||||
members = assert_queries(3) do #base table, through table, clubs table
|
||||
Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
|
||||
end
|
||||
assert_equal 1, members.size
|
||||
assert_not_nil assert_no_queries {members[0].club}
|
||||
end
|
||||
|
||||
|
||||
def test_has_one_through_eager_loading_through_polymorphic
|
||||
members = Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
|
||||
members = assert_queries(3) do #base table, through table, clubs table
|
||||
Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
|
||||
end
|
||||
assert_equal 1, members.size
|
||||
assert_not_nil assert_no_queries {members[0].sponsor_club}
|
||||
end
|
||||
|
@ -71,4 +75,39 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
|||
assert_not_nil assert_no_queries {clubs[0].sponsored_member}
|
||||
end
|
||||
|
||||
def test_has_one_through_nonpreload_eagerloading
|
||||
members = assert_queries(1) do
|
||||
Member.find(:all, :include => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
|
||||
end
|
||||
assert_equal 1, members.size
|
||||
assert_not_nil assert_no_queries {members[0].club}
|
||||
end
|
||||
|
||||
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
|
||||
members = assert_queries(1) do
|
||||
Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
|
||||
end
|
||||
assert_equal 1, members.size
|
||||
assert_not_nil assert_no_queries {members[0].sponsor_club}
|
||||
end
|
||||
|
||||
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
|
||||
Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save!
|
||||
members = assert_queries(1) do
|
||||
Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback
|
||||
end
|
||||
assert_equal 1, members.size
|
||||
assert_not_nil assert_no_queries { members[0].sponsor_club }
|
||||
assert_equal clubs(:crazy_club), members[0].sponsor_club
|
||||
end
|
||||
|
||||
def test_uninitialized_has_one_through_should_return_nil_for_unsaved_record
|
||||
assert_nil Member.new.club
|
||||
end
|
||||
|
||||
def test_assigning_association_correctly_assigns_target
|
||||
new_member = Member.create(:name => "Chris")
|
||||
new_member.club = new_club = Club.create(:name => "LRUG")
|
||||
assert_equal new_club, new_member.club.target
|
||||
end
|
||||
end
|
||||
|
|
|
@ -694,6 +694,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
|
|||
assert ! david.categories.include?(category)
|
||||
end
|
||||
|
||||
def test_has_many_through_goes_through_all_sti_classes
|
||||
sub_sti_post = SubStiPost.create!(:title => 'test', :body => 'test', :author_id => 1)
|
||||
new_comment = sub_sti_post.comments.create(:body => 'test')
|
||||
|
||||
assert_equal [9, 10, new_comment.id], authors(:david).sti_post_comments.map(&:id).sort
|
||||
end
|
||||
|
||||
private
|
||||
# create dynamic Post models to allow different dependency options
|
||||
def find_post_with_dependency(post_id, association, association_name, dependency)
|
||||
|
|
|
@ -27,7 +27,7 @@ require 'models/sponsor'
|
|||
|
||||
class AssociationsTest < ActiveRecord::TestCase
|
||||
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
|
||||
:computers
|
||||
:computers, :people, :readers
|
||||
|
||||
def test_include_with_order_works
|
||||
assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
|
||||
|
@ -45,7 +45,7 @@ class AssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal [], person.readers.find(:all)
|
||||
person.save!
|
||||
reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar")
|
||||
assert_equal [reader], person.readers.find(:all)
|
||||
assert person.readers.find(reader.id)
|
||||
end
|
||||
|
||||
def test_force_reload
|
||||
|
|
|
@ -137,7 +137,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_time_attributes_are_retrieved_in_current_time_zone
|
||||
in_time_zone "Pacific Time (US & Canada)" do
|
||||
utc_time = Time.utc(2008, 1, 1)
|
||||
|
@ -145,7 +145,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
record[:written_on] = utc_time
|
||||
assert_equal utc_time, record.written_on # record.written on is equal to (i.e., simultaneous with) utc_time
|
||||
assert_kind_of ActiveSupport::TimeWithZone, record.written_on # but is a TimeWithZone
|
||||
assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone
|
||||
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone
|
||||
assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time # and represents time values adjusted accordingly
|
||||
end
|
||||
end
|
||||
|
@ -156,7 +156,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
record = @target.new
|
||||
record.written_on = utc_time
|
||||
assert_equal utc_time, record.written_on
|
||||
assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
|
||||
end
|
||||
end
|
||||
|
@ -168,7 +168,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
record = @target.new
|
||||
record.written_on = cst_time
|
||||
assert_equal utc_time, record.written_on
|
||||
assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
|
||||
end
|
||||
end
|
||||
|
@ -181,12 +181,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
record = @target.new
|
||||
record.written_on = time_string
|
||||
assert_equal Time.zone.parse(time_string), record.written_on
|
||||
assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil
|
||||
in_time_zone "Pacific Time (US & Canada)" do
|
||||
record = @target.new
|
||||
|
@ -202,7 +202,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
record = @target.new
|
||||
record.written_on = time_string
|
||||
assert_equal Time.zone.parse(time_string), record.written_on
|
||||
assert_equal TimeZone[timezone_offset], record.written_on.time_zone
|
||||
assert_equal ActiveSupport::TimeZone[timezone_offset], record.written_on.time_zone
|
||||
assert_equal Time.utc(2008, 1, 1), record.written_on.time
|
||||
end
|
||||
end
|
||||
|
@ -214,7 +214,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
record = @target.new
|
||||
record.written_on = utc_time.in_time_zone
|
||||
assert_equal utc_time, record.written_on
|
||||
assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
|
||||
assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
|
||||
end
|
||||
end
|
||||
|
@ -223,12 +223,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
def time_related_columns_on_topic
|
||||
Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name)
|
||||
end
|
||||
|
||||
|
||||
def in_time_zone(zone)
|
||||
old_zone = Time.zone
|
||||
old_tz = ActiveRecord::Base.time_zone_aware_attributes
|
||||
|
||||
Time.zone = zone ? TimeZone[zone] : nil
|
||||
Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil
|
||||
ActiveRecord::Base.time_zone_aware_attributes = !zone.nil?
|
||||
yield
|
||||
ensure
|
||||
|
|
|
@ -19,6 +19,7 @@ require 'models/warehouse_thing'
|
|||
require 'rexml/document'
|
||||
|
||||
class Category < ActiveRecord::Base; end
|
||||
class Categorization < ActiveRecord::Base; end
|
||||
class Smarts < ActiveRecord::Base; end
|
||||
class CreditCard < ActiveRecord::Base
|
||||
class PinNumber < ActiveRecord::Base
|
||||
|
@ -75,7 +76,7 @@ class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
|
|||
end
|
||||
|
||||
class BasicsTest < ActiveRecord::TestCase
|
||||
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors
|
||||
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories
|
||||
|
||||
def test_table_exists
|
||||
assert !NonExistentTable.table_exists?
|
||||
|
@ -130,22 +131,22 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
|
||||
def test_read_attributes_before_type_cast
|
||||
category = Category.new({:name=>"Test categoty", :type => nil})
|
||||
category_attrs = {"name"=>"Test categoty", "type" => nil}
|
||||
category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil}
|
||||
assert_equal category_attrs , category.attributes_before_type_cast
|
||||
end
|
||||
|
||||
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
def test_read_attributes_before_type_cast_on_boolean
|
||||
bool = Booleantest.create({ "value" => false })
|
||||
assert_equal 0, bool.attributes_before_type_cast["value"]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_read_attributes_before_type_cast_on_datetime
|
||||
developer = Developer.find(:first)
|
||||
assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
|
||||
end
|
||||
|
||||
|
||||
def test_hash_content
|
||||
topic = Topic.new
|
||||
topic.content = { "one" => 1, "two" => 2 }
|
||||
|
@ -251,7 +252,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
topic = Topic.create("title" => "New Topic")
|
||||
topicReloaded = Topic.find(topic.id)
|
||||
assert_equal(topic, topicReloaded)
|
||||
end
|
||||
end
|
||||
|
||||
def test_create_through_factory_with_block
|
||||
topic = Topic.create("title" => "New Topic") do |t|
|
||||
|
@ -576,7 +577,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
def test_destroy_all
|
||||
original_count = Topic.count
|
||||
topics_by_mary = Topic.count(:conditions => mary = "author_name = 'Mary'")
|
||||
|
||||
|
||||
Topic.destroy_all mary
|
||||
assert_equal original_count - topics_by_mary, Topic.count
|
||||
end
|
||||
|
@ -614,6 +615,22 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert_equal -2, Topic.find(2).replies_count
|
||||
end
|
||||
|
||||
def test_update_counter
|
||||
category = categories(:general)
|
||||
assert_nil category.categorizations_count
|
||||
assert_equal 2, category.categorizations.count
|
||||
|
||||
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
|
||||
category.reload
|
||||
assert_not_nil category.categorizations_count
|
||||
assert_equal 2, category.categorizations_count
|
||||
|
||||
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
|
||||
category.reload
|
||||
assert_not_nil category.categorizations_count
|
||||
assert_equal 4, category.categorizations_count
|
||||
end
|
||||
|
||||
def test_update_all
|
||||
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
|
||||
assert_equal "bulk updated!", Topic.find(1).content
|
||||
|
@ -665,7 +682,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
|
||||
def test_delete_all
|
||||
assert Topic.count > 0
|
||||
|
||||
|
||||
assert_equal Topic.count, Topic.delete_all
|
||||
end
|
||||
|
||||
|
@ -970,7 +987,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
topic.attributes = attributes
|
||||
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
|
||||
end
|
||||
|
||||
|
||||
def test_multiparameter_attributes_on_time_with_old_date
|
||||
attributes = {
|
||||
"written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
|
||||
|
@ -998,7 +1015,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
|
||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||
ActiveRecord::Base.default_timezone = :utc
|
||||
Time.zone = TimeZone[-28800]
|
||||
Time.zone = ActiveSupport::TimeZone[-28800]
|
||||
attributes = {
|
||||
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
|
||||
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
|
||||
|
@ -1016,7 +1033,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
|
||||
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
|
||||
ActiveRecord::Base.time_zone_aware_attributes = false
|
||||
Time.zone = TimeZone[-28800]
|
||||
Time.zone = ActiveSupport::TimeZone[-28800]
|
||||
attributes = {
|
||||
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
|
||||
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
|
||||
|
@ -1032,7 +1049,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
|
||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||
ActiveRecord::Base.default_timezone = :utc
|
||||
Time.zone = TimeZone[-28800]
|
||||
Time.zone = ActiveSupport::TimeZone[-28800]
|
||||
Topic.skip_time_zone_conversion_for_attributes = [:written_on]
|
||||
attributes = {
|
||||
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
|
||||
|
@ -1336,6 +1353,12 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert_equal(myobj, topic.content)
|
||||
end
|
||||
|
||||
def test_serialized_time_attribute
|
||||
myobj = Time.local(2008,1,1,1,0)
|
||||
topic = Topic.create("content" => myobj).reload
|
||||
assert_equal(myobj, topic.content)
|
||||
end
|
||||
|
||||
def test_nil_serialized_attribute_with_class_constraint
|
||||
myobj = MyObject.new('value1', 'value2')
|
||||
topic = Topic.new
|
||||
|
@ -1647,7 +1670,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
last = Developer.find :last
|
||||
assert_equal last, Developer.find(:first, :order => 'id desc')
|
||||
end
|
||||
|
||||
|
||||
def test_last
|
||||
assert_equal Developer.find(:first, :order => 'id desc'), Developer.last
|
||||
end
|
||||
|
@ -1655,7 +1678,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
def test_all_with_conditions
|
||||
assert_equal Developer.find(:all, :order => 'id desc'), Developer.all(:order => 'id desc')
|
||||
end
|
||||
|
||||
|
||||
def test_find_ordered_last
|
||||
last = Developer.find :last, :order => 'developers.salary ASC'
|
||||
assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last
|
||||
|
@ -1670,14 +1693,14 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
last = Developer.find :last, :order => 'developers.name, developers.salary DESC'
|
||||
assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last
|
||||
end
|
||||
|
||||
|
||||
def test_find_scoped_ordered_last
|
||||
last_developer = Developer.with_scope(:find => { :order => 'developers.salary ASC' }) do
|
||||
Developer.find(:last)
|
||||
end
|
||||
assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last
|
||||
end
|
||||
|
||||
|
||||
def test_abstract_class
|
||||
assert !ActiveRecord::Base.abstract_class?
|
||||
assert LoosePerson.abstract_class?
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require "cases/helper"
|
||||
require 'models/company'
|
||||
require 'models/topic'
|
||||
require 'models/edge'
|
||||
|
||||
Company.has_many :accounts
|
||||
|
||||
|
@ -99,6 +100,12 @@ class CalculationsTest < ActiveRecord::TestCase
|
|||
|
||||
def test_should_return_zero_if_sum_conditions_return_nothing
|
||||
assert_equal 0, Account.sum(:credit_limit, :conditions => '1 = 2')
|
||||
assert_equal 0, companies(:rails_core).companies.sum(:id, :conditions => '1 = 2')
|
||||
end
|
||||
|
||||
def test_sum_should_return_valid_values_for_decimals
|
||||
NumericData.create(:bank_balance => 19.83)
|
||||
assert_equal 19.83, NumericData.sum(:bank_balance)
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_field_with_conditions
|
||||
|
@ -266,6 +273,51 @@ class CalculationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_should_sum_expression
|
||||
assert_equal "636", Account.sum("2 * credit_limit")
|
||||
assert_equal 636, Account.sum("2 * credit_limit")
|
||||
end
|
||||
|
||||
def test_count_with_from_option
|
||||
assert_equal Company.count(:all), Company.count(:all, :from => 'companies')
|
||||
assert_equal Account.count(:all, :conditions => "credit_limit = 50"),
|
||||
Account.count(:all, :from => 'accounts', :conditions => "credit_limit = 50")
|
||||
assert_equal Company.count(:type, :conditions => {:type => "Firm"}),
|
||||
Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies')
|
||||
end
|
||||
|
||||
def test_sum_with_from_option
|
||||
assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts')
|
||||
assert_equal Account.sum(:credit_limit, :conditions => "credit_limit > 50"),
|
||||
Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
|
||||
end
|
||||
|
||||
def test_average_with_from_option
|
||||
assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts')
|
||||
assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"),
|
||||
Account.average(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
|
||||
end
|
||||
|
||||
def test_minimum_with_from_option
|
||||
assert_equal Account.minimum(:credit_limit), Account.minimum(:credit_limit, :from => 'accounts')
|
||||
assert_equal Account.minimum(:credit_limit, :conditions => "credit_limit > 50"),
|
||||
Account.minimum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
|
||||
end
|
||||
|
||||
def test_maximum_with_from_option
|
||||
assert_equal Account.maximum(:credit_limit), Account.maximum(:credit_limit, :from => 'accounts')
|
||||
assert_equal Account.maximum(:credit_limit, :conditions => "credit_limit > 50"),
|
||||
Account.maximum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
|
||||
end
|
||||
|
||||
def test_from_option_with_specified_index
|
||||
if Edge.connection.adapter_name == 'MySQL'
|
||||
assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)')
|
||||
assert_equal Edge.count(:all, :conditions => 'sink_id < 5'),
|
||||
Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5')
|
||||
end
|
||||
end
|
||||
|
||||
def test_from_option_with_table_different_than_class
|
||||
assert_equal Account.count(:all), Company.count(:all, :from => 'accounts')
|
||||
end
|
||||
|
||||
end
|
||||
|
|
36
vendor/rails/activerecord/test/cases/column_definition_test.rb
vendored
Normal file
36
vendor/rails/activerecord/test/cases/column_definition_test.rb
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
require "cases/helper"
|
||||
|
||||
class ColumnDefinitionTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
@adapter = ActiveRecord::ConnectionAdapters::AbstractAdapter.new(nil)
|
||||
def @adapter.native_database_types
|
||||
{:string => "varchar"}
|
||||
end
|
||||
end
|
||||
|
||||
# Avoid column definitions in create table statements like:
|
||||
# `title` varchar(255) DEFAULT NULL
|
||||
def test_should_not_include_default_clause_when_default_is_null
|
||||
column = ActiveRecord::ConnectionAdapters::Column.new("title", nil, "varchar(20)")
|
||||
column_def = ActiveRecord::ConnectionAdapters::ColumnDefinition.new(
|
||||
@adapter, column.name, "string",
|
||||
column.limit, column.precision, column.scale, column.default, column.null)
|
||||
assert_equal "title varchar(20)", column_def.to_sql
|
||||
end
|
||||
|
||||
def test_should_include_default_clause_when_default_is_present
|
||||
column = ActiveRecord::ConnectionAdapters::Column.new("title", "Hello", "varchar(20)")
|
||||
column_def = ActiveRecord::ConnectionAdapters::ColumnDefinition.new(
|
||||
@adapter, column.name, "string",
|
||||
column.limit, column.precision, column.scale, column.default, column.null)
|
||||
assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, column_def.to_sql
|
||||
end
|
||||
|
||||
def test_should_specify_not_null_if_null_option_is_false
|
||||
column = ActiveRecord::ConnectionAdapters::Column.new("title", "Hello", "varchar(20)", false)
|
||||
column_def = ActiveRecord::ConnectionAdapters::ColumnDefinition.new(
|
||||
@adapter, column.name, "string",
|
||||
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
|
||||
end
|
12
vendor/rails/activerecord/test/cases/database_statements_test.rb
vendored
Normal file
12
vendor/rails/activerecord/test/cases/database_statements_test.rb
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
require "cases/helper"
|
||||
|
||||
class DatabaseStatementsTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
def test_insert_should_return_the_inserted_id
|
||||
id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
|
||||
assert_not_nil id
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@ require 'models/entrant'
|
|||
class DefaultTest < ActiveRecord::TestCase
|
||||
def test_nil_defaults_for_not_null_columns
|
||||
column_defaults =
|
||||
if current_adapter?(:MysqlAdapter) && Mysql.client_version < 50051
|
||||
if current_adapter?(:MysqlAdapter) && (Mysql.client_version < 50051 || (50100..50122).include?(Mysql.client_version))
|
||||
{ 'id' => nil, 'name' => '', 'course_id' => nil }
|
||||
else
|
||||
{ 'id' => nil, 'name' => nil, 'course_id' => nil }
|
||||
|
|
|
@ -2,6 +2,7 @@ require 'cases/helper'
|
|||
require 'models/topic' # For booleans
|
||||
require 'models/pirate' # For timestamps
|
||||
require 'models/parrot'
|
||||
require 'models/person' # For optimistic locking
|
||||
|
||||
class Pirate # Just reopening it, not defining it
|
||||
attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected
|
||||
|
@ -54,6 +55,33 @@ class DirtyTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_zero_to_blank_marked_as_changed
|
||||
pirate = Pirate.new
|
||||
pirate.catchphrase = "Yarrrr, me hearties"
|
||||
pirate.parrot_id = 1
|
||||
pirate.save
|
||||
|
||||
# check the change from 1 to ''
|
||||
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
|
||||
pirate.parrot_id = ''
|
||||
assert pirate.parrot_id_changed?
|
||||
assert_equal([1, nil], pirate.parrot_id_change)
|
||||
pirate.save
|
||||
|
||||
# check the change from nil to 0
|
||||
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
|
||||
pirate.parrot_id = 0
|
||||
assert pirate.parrot_id_changed?
|
||||
assert_equal([nil, 0], pirate.parrot_id_change)
|
||||
pirate.save
|
||||
|
||||
# check the change from 0 to ''
|
||||
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
|
||||
pirate.parrot_id = ''
|
||||
assert pirate.parrot_id_changed?
|
||||
assert_equal([0, nil], pirate.parrot_id_change)
|
||||
end
|
||||
|
||||
def test_object_should_be_changed_if_any_attribute_is_changed
|
||||
pirate = Pirate.new
|
||||
assert !pirate.changed?
|
||||
|
@ -125,6 +153,24 @@ class DirtyTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_partial_update_with_optimistic_locking
|
||||
person = Person.new(:first_name => 'foo')
|
||||
old_lock_version = 1
|
||||
|
||||
with_partial_updates Person, false do
|
||||
assert_queries(2) { 2.times { person.save! } }
|
||||
Person.update_all({ :first_name => 'baz' }, :id => person.id)
|
||||
end
|
||||
|
||||
with_partial_updates Person, true do
|
||||
assert_queries(0) { 2.times { person.save! } }
|
||||
assert_equal old_lock_version, person.reload.lock_version
|
||||
|
||||
assert_queries(1) { person.first_name = 'bar'; person.save! }
|
||||
assert_not_equal old_lock_version, person.reload.lock_version
|
||||
end
|
||||
end
|
||||
|
||||
def test_changed_attributes_should_be_preserved_if_save_failure
|
||||
pirate = Pirate.new
|
||||
pirate.parrot_id = 1
|
||||
|
@ -145,6 +191,54 @@ class DirtyTest < ActiveRecord::TestCase
|
|||
assert !pirate.changed?
|
||||
end
|
||||
|
||||
def test_reverted_changes_are_not_dirty
|
||||
phrase = "shiver me timbers"
|
||||
pirate = Pirate.create!(:catchphrase => phrase)
|
||||
pirate.catchphrase = "*hic*"
|
||||
assert pirate.changed?
|
||||
pirate.catchphrase = phrase
|
||||
assert !pirate.changed?
|
||||
end
|
||||
|
||||
def test_reverted_changes_are_not_dirty_after_multiple_changes
|
||||
phrase = "shiver me timbers"
|
||||
pirate = Pirate.create!(:catchphrase => phrase)
|
||||
10.times do |i|
|
||||
pirate.catchphrase = "*hic*" * i
|
||||
assert pirate.changed?
|
||||
end
|
||||
assert pirate.changed?
|
||||
pirate.catchphrase = phrase
|
||||
assert !pirate.changed?
|
||||
end
|
||||
|
||||
|
||||
def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
|
||||
pirate = Pirate.create!(:catchphrase => "Yar!")
|
||||
|
||||
pirate.parrot_id = 1
|
||||
assert pirate.changed?
|
||||
assert pirate.parrot_id_changed?
|
||||
assert !pirate.catchphrase_changed?
|
||||
|
||||
pirate.parrot_id = nil
|
||||
assert !pirate.changed?
|
||||
assert !pirate.parrot_id_changed?
|
||||
assert !pirate.catchphrase_changed?
|
||||
end
|
||||
|
||||
def test_save_should_store_serialized_attributes_even_with_partial_updates
|
||||
with_partial_updates(Topic) do
|
||||
topic = Topic.create!(:content => {:a => "a"})
|
||||
topic.content[:b] = "b"
|
||||
#assert topic.changed? # Known bug, will fail
|
||||
topic.save!
|
||||
assert_equal "b", topic.content[:b]
|
||||
topic.reload
|
||||
assert_equal "b", topic.content[:b]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_partial_updates(klass, on = true)
|
||||
old = klass.partial_updates?
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require "cases/helper"
|
||||
require 'models/author'
|
||||
require 'models/categorization'
|
||||
require 'models/comment'
|
||||
require 'models/company'
|
||||
require 'models/topic'
|
||||
|
@ -394,6 +395,12 @@ class FinderTest < ActiveRecord::TestCase
|
|||
assert_equal '1,1,1', bind('?', os)
|
||||
end
|
||||
|
||||
def test_named_bind_with_postgresql_type_casts
|
||||
l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
|
||||
assert_nothing_raised(&l)
|
||||
assert_equal "#{ActiveRecord::Base.quote_value('10')}::integer '2009-01-01'::date", l.call
|
||||
end
|
||||
|
||||
def test_string_sanitation
|
||||
assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
|
||||
assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
|
||||
|
|
10
vendor/rails/activerecord/test/cases/helper.rb
vendored
10
vendor/rails/activerecord/test/cases/helper.rb
vendored
|
@ -32,13 +32,13 @@ end
|
|||
ActiveRecord::Base.connection.class.class_eval do
|
||||
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/]
|
||||
|
||||
def execute_with_counting(sql, name = nil, &block)
|
||||
$query_count ||= 0
|
||||
$query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
|
||||
execute_without_counting(sql, name, &block)
|
||||
def execute_with_query_record(sql, name = nil, &block)
|
||||
$queries_executed ||= []
|
||||
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
|
||||
execute_without_query_record(sql, name, &block)
|
||||
end
|
||||
|
||||
alias_method_chain :execute, :counting
|
||||
alias_method_chain :execute, :query_record
|
||||
end
|
||||
|
||||
# Make with_scope public for tests
|
||||
|
|
|
@ -191,6 +191,13 @@ class InheritanceTest < ActiveRecord::TestCase
|
|||
assert_not_nil account.instance_variable_get("@firm"), "nil proves eager load failed"
|
||||
end
|
||||
|
||||
def test_eager_load_belongs_to_primary_key_quoting
|
||||
con = Account.connection
|
||||
assert_sql(/\(#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)\)/) do
|
||||
Account.find(1, :include => :firm)
|
||||
end
|
||||
end
|
||||
|
||||
def test_alt_eager_loading
|
||||
switch_to_alt_inheritance_column
|
||||
test_eager_load_belongs_to_something_inherited
|
||||
|
@ -223,11 +230,11 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
|
|||
fixtures :companies
|
||||
|
||||
def setup
|
||||
Dependencies.log_activity = true
|
||||
ActiveSupport::Dependencies.log_activity = true
|
||||
end
|
||||
|
||||
def teardown
|
||||
Dependencies.log_activity = false
|
||||
ActiveSupport::Dependencies.log_activity = false
|
||||
self.class.const_remove :FirmOnTheFly rescue nil
|
||||
Firm.const_remove :FirmOnTheFly rescue nil
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ require "cases/helper"
|
|||
require 'models/topic'
|
||||
require 'models/developer'
|
||||
require 'models/reply'
|
||||
require 'models/minimalistic'
|
||||
|
||||
class Topic; def after_find() end end
|
||||
class Developer; def after_find() end end
|
||||
|
@ -44,6 +45,14 @@ class TopicObserver < ActiveRecord::Observer
|
|||
end
|
||||
end
|
||||
|
||||
class MinimalisticObserver < ActiveRecord::Observer
|
||||
attr_reader :minimalistic
|
||||
|
||||
def after_find(minimalistic)
|
||||
@minimalistic = minimalistic
|
||||
end
|
||||
end
|
||||
|
||||
class MultiObserver < ActiveRecord::Observer
|
||||
attr_reader :record
|
||||
|
||||
|
@ -65,7 +74,7 @@ class MultiObserver < ActiveRecord::Observer
|
|||
end
|
||||
|
||||
class LifecycleTest < ActiveRecord::TestCase
|
||||
fixtures :topics, :developers
|
||||
fixtures :topics, :developers, :minimalistics
|
||||
|
||||
def test_before_destroy
|
||||
original_count = Topic.count
|
||||
|
@ -134,6 +143,50 @@ class LifecycleTest < ActiveRecord::TestCase
|
|||
assert_equal developer.name, multi_observer.record.name
|
||||
end
|
||||
|
||||
def test_after_find_can_be_observed_when_its_not_defined_on_the_model
|
||||
observer = MinimalisticObserver.instance
|
||||
assert_equal Minimalistic, MinimalisticObserver.observed_class
|
||||
|
||||
minimalistic = Minimalistic.find(1)
|
||||
assert_equal minimalistic, observer.minimalistic
|
||||
end
|
||||
|
||||
def test_after_find_can_be_observed_when_its_defined_on_the_model
|
||||
observer = TopicObserver.instance
|
||||
assert_equal Topic, TopicObserver.observed_class
|
||||
|
||||
topic = Topic.find(1)
|
||||
assert_equal topic, observer.topic
|
||||
end
|
||||
|
||||
def test_after_find_is_not_created_if_its_not_used
|
||||
# use a fresh class so an observer can't have defined an
|
||||
# after_find on it
|
||||
model_class = Class.new(ActiveRecord::Base)
|
||||
observer_class = Class.new(ActiveRecord::Observer)
|
||||
observer_class.observe(model_class)
|
||||
|
||||
observer = observer_class.instance
|
||||
|
||||
assert !model_class.method_defined?(:after_find)
|
||||
end
|
||||
|
||||
def test_after_find_is_not_clobbered_if_it_already_exists
|
||||
# use a fresh observer class so we can instantiate it (Observer is
|
||||
# a Singleton)
|
||||
model_class = Class.new(ActiveRecord::Base) do
|
||||
def after_find; end
|
||||
end
|
||||
original_method = model_class.instance_method(:after_find)
|
||||
observer_class = Class.new(ActiveRecord::Observer) do
|
||||
def after_find; end
|
||||
end
|
||||
observer_class.observe(model_class)
|
||||
|
||||
observer = observer_class.instance
|
||||
assert_equal original_method, model_class.instance_method(:after_find)
|
||||
end
|
||||
|
||||
def test_invalid_observer
|
||||
assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
|
||||
end
|
||||
|
|
|
@ -29,10 +29,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
|||
assert_equal 0, p1.lock_version
|
||||
assert_equal 0, p2.lock_version
|
||||
|
||||
p1.first_name = 'stu'
|
||||
p1.save!
|
||||
assert_equal 1, p1.lock_version
|
||||
assert_equal 0, p2.lock_version
|
||||
|
||||
p2.first_name = 'sue'
|
||||
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
|
||||
end
|
||||
|
||||
|
@ -42,11 +44,14 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
|||
assert_equal 0, p1.lock_version
|
||||
assert_equal 0, p2.lock_version
|
||||
|
||||
p1.first_name = 'stu'
|
||||
p1.save!
|
||||
assert_equal 1, p1.lock_version
|
||||
assert_equal 0, p2.lock_version
|
||||
|
||||
p2.first_name = 'sue'
|
||||
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
|
||||
p2.first_name = 'sue2'
|
||||
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
|
||||
end
|
||||
|
||||
|
@ -54,15 +59,18 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
|||
p1 = Person.new(:first_name => 'anika')
|
||||
assert_equal 0, p1.lock_version
|
||||
|
||||
p1.first_name = 'anika2'
|
||||
p1.save!
|
||||
p2 = Person.find(p1.id)
|
||||
assert_equal 0, p1.lock_version
|
||||
assert_equal 0, p2.lock_version
|
||||
|
||||
p1.first_name = 'anika3'
|
||||
p1.save!
|
||||
assert_equal 1, p1.lock_version
|
||||
assert_equal 0, p2.lock_version
|
||||
|
||||
p2.first_name = 'sue'
|
||||
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
|
||||
end
|
||||
|
||||
|
@ -81,10 +89,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
|||
assert_equal 0, t1.version
|
||||
assert_equal 0, t2.version
|
||||
|
||||
t1.tps_report_number = 700
|
||||
t1.save!
|
||||
assert_equal 1, t1.version
|
||||
assert_equal 0, t2.version
|
||||
|
||||
t2.tps_report_number = 800
|
||||
assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
|
||||
end
|
||||
|
||||
|
@ -93,6 +103,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
|||
assert_equal 0, p1.lock_version
|
||||
assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
|
||||
|
||||
p1.first_name = 'bianca2'
|
||||
p1.save!
|
||||
assert_equal 1, p1.lock_version
|
||||
assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
|
||||
|
@ -146,6 +157,15 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
|||
assert ref.save
|
||||
end
|
||||
|
||||
# Useful for partial updates, don't only update the lock_version if there
|
||||
# is nothing else being updated.
|
||||
def test_update_without_attributes_does_not_only_update_lock_version
|
||||
assert_nothing_raised do
|
||||
p1 = Person.new(:first_name => 'anika')
|
||||
p1.send(:update_with_lock, [])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_counter_column_to(model)
|
||||
|
|
|
@ -6,7 +6,7 @@ require 'models/post'
|
|||
require 'models/category'
|
||||
|
||||
class MethodScopingTest < ActiveRecord::TestCase
|
||||
fixtures :developers, :projects, :comments, :posts
|
||||
fixtures :developers, :projects, :comments, :posts, :developers_projects
|
||||
|
||||
def test_set_conditions
|
||||
Developer.with_scope(:find => { :conditions => 'just a test...' }) do
|
||||
|
@ -87,6 +87,16 @@ class MethodScopingTest < ActiveRecord::TestCase
|
|||
assert_equal 1, scoped_developers.size
|
||||
end
|
||||
|
||||
def test_scoped_find_joins
|
||||
scoped_developers = Developer.with_scope(:find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do
|
||||
Developer.find(:all, :conditions => 'developers_projects.project_id = 2')
|
||||
end
|
||||
assert scoped_developers.include?(developers(:david))
|
||||
assert !scoped_developers.include?(developers(:jamis))
|
||||
assert_equal 1, scoped_developers.size
|
||||
assert_equal developers(:david).attributes, scoped_developers.first.attributes
|
||||
end
|
||||
|
||||
def test_scoped_count_include
|
||||
# with the include, will retrieve only developers for the given project
|
||||
Developer.with_scope(:find => { :include => :projects }) do
|
||||
|
|
|
@ -3,6 +3,7 @@ require 'bigdecimal/util'
|
|||
|
||||
require 'models/person'
|
||||
require 'models/topic'
|
||||
require 'models/developer'
|
||||
|
||||
require MIGRATIONS_ROOT + "/valid/1_people_have_last_names"
|
||||
require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
|
||||
|
@ -153,9 +154,10 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
|
||||
t.column :default_int, :integer
|
||||
|
||||
t.column :one_int, :integer, :limit => 1
|
||||
t.column :four_int, :integer, :limit => 4
|
||||
t.column :eight_int, :integer, :limit => 8
|
||||
t.column :one_int, :integer, :limit => 1
|
||||
t.column :four_int, :integer, :limit => 4
|
||||
t.column :eight_int, :integer, :limit => 8
|
||||
t.column :eleven_int, :integer, :limit => 11
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -167,12 +169,20 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
one = columns.detect { |c| c.name == "one_int" }
|
||||
four = columns.detect { |c| c.name == "four_int" }
|
||||
eight = columns.detect { |c| c.name == "eight_int" }
|
||||
eleven = columns.detect { |c| c.name == "eleven_int" }
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
assert_equal 'integer', default.sql_type
|
||||
assert_equal 'smallint', one.sql_type
|
||||
assert_equal 'integer', four.sql_type
|
||||
assert_equal 'bigint', eight.sql_type
|
||||
assert_equal 'integer', eleven.sql_type
|
||||
elsif current_adapter?(:MysqlAdapter)
|
||||
assert_match 'int(11)', default.sql_type
|
||||
assert_match 'tinyint', one.sql_type
|
||||
assert_match 'int', four.sql_type
|
||||
assert_match 'bigint', eight.sql_type
|
||||
assert_match 'int(11)', eleven.sql_type
|
||||
elsif current_adapter?(:OracleAdapter)
|
||||
assert_equal 'NUMBER(38)', default.sql_type
|
||||
assert_equal 'NUMBER(1)', one.sql_type
|
||||
|
@ -227,6 +237,39 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
end
|
||||
end
|
||||
|
||||
def test_create_table_with_timestamps_should_create_datetime_columns
|
||||
table_name = :testings
|
||||
|
||||
Person.connection.create_table table_name do |t|
|
||||
t.timestamps
|
||||
end
|
||||
created_columns = Person.connection.columns(table_name)
|
||||
|
||||
created_at_column = created_columns.detect {|c| c.name == 'created_at' }
|
||||
updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
|
||||
|
||||
assert created_at_column.null
|
||||
assert updated_at_column.null
|
||||
ensure
|
||||
Person.connection.drop_table table_name rescue nil
|
||||
end
|
||||
|
||||
def test_create_table_with_timestamps_should_create_datetime_columns_with_options
|
||||
table_name = :testings
|
||||
|
||||
Person.connection.create_table table_name do |t|
|
||||
t.timestamps :null => false
|
||||
end
|
||||
created_columns = Person.connection.columns(table_name)
|
||||
|
||||
created_at_column = created_columns.detect {|c| c.name == 'created_at' }
|
||||
updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
|
||||
|
||||
assert !created_at_column.null
|
||||
assert !updated_at_column.null
|
||||
ensure
|
||||
Person.connection.drop_table table_name rescue nil
|
||||
end
|
||||
|
||||
# SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL
|
||||
# column to a table without a default value.
|
||||
|
@ -399,10 +442,7 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
|
||||
ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
|
||||
Person.reset_column_information
|
||||
Person.create :intelligence_quotient => 300
|
||||
jonnyg = Person.find(:first)
|
||||
assert_equal 127, jonnyg.intelligence_quotient
|
||||
jonnyg.destroy
|
||||
assert_match /tinyint/, Person.columns_hash['intelligence_quotient'].sql_type
|
||||
ensure
|
||||
ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
|
||||
end
|
||||
|
@ -483,6 +523,37 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
end
|
||||
end
|
||||
|
||||
def test_rename_column_preserves_default_value_not_null
|
||||
begin
|
||||
default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default
|
||||
assert_equal 70000, default_before
|
||||
Developer.connection.rename_column "developers", "salary", "anual_salary"
|
||||
Developer.reset_column_information
|
||||
assert Developer.column_names.include?("anual_salary")
|
||||
default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default
|
||||
assert_equal 70000, default_after
|
||||
ensure
|
||||
Developer.connection.rename_column "developers", "anual_salary", "salary"
|
||||
Developer.reset_column_information
|
||||
end
|
||||
end
|
||||
|
||||
def test_rename_nonexistent_column
|
||||
ActiveRecord::Base.connection.create_table(:hats) do |table|
|
||||
table.column :hat_name, :string, :default => nil
|
||||
end
|
||||
exception = if current_adapter?(:PostgreSQLAdapter)
|
||||
ActiveRecord::StatementInvalid
|
||||
else
|
||||
ActiveRecord::ActiveRecordError
|
||||
end
|
||||
assert_raises(exception) do
|
||||
Person.connection.rename_column "hats", "nonexistent", "should_fail"
|
||||
end
|
||||
ensure
|
||||
ActiveRecord::Base.connection.drop_table(:hats)
|
||||
end
|
||||
|
||||
def test_rename_column_with_sql_reserved_word
|
||||
begin
|
||||
assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" }
|
||||
|
@ -662,6 +733,55 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
Person.connection.drop_table :testings rescue nil
|
||||
end
|
||||
|
||||
def test_keeping_default_and_notnull_constaint_on_change
|
||||
Person.connection.create_table :testings do |t|
|
||||
t.column :title, :string
|
||||
end
|
||||
person_klass = Class.new(Person)
|
||||
person_klass.set_table_name 'testings'
|
||||
|
||||
person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
|
||||
person_klass.reset_column_information
|
||||
assert_equal 99, person_klass.columns_hash["wealth"].default
|
||||
assert_equal false, person_klass.columns_hash["wealth"].null
|
||||
assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
|
||||
|
||||
# change column default to see that column doesn't lose its not null definition
|
||||
person_klass.connection.change_column_default "testings", "wealth", 100
|
||||
person_klass.reset_column_information
|
||||
assert_equal 100, person_klass.columns_hash["wealth"].default
|
||||
assert_equal false, person_klass.columns_hash["wealth"].null
|
||||
|
||||
# rename column to see that column doesn't lose its not null and/or default definition
|
||||
person_klass.connection.rename_column "testings", "wealth", "money"
|
||||
person_klass.reset_column_information
|
||||
assert_nil person_klass.columns_hash["wealth"]
|
||||
assert_equal 100, person_klass.columns_hash["money"].default
|
||||
assert_equal false, person_klass.columns_hash["money"].null
|
||||
|
||||
# change column
|
||||
person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
|
||||
person_klass.reset_column_information
|
||||
assert_equal 1000, person_klass.columns_hash["money"].default
|
||||
assert_equal false, person_klass.columns_hash["money"].null
|
||||
|
||||
# change column, make it nullable and clear default
|
||||
person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
|
||||
person_klass.reset_column_information
|
||||
assert_nil person_klass.columns_hash["money"].default
|
||||
assert_equal true, person_klass.columns_hash["money"].null
|
||||
|
||||
# change_column_null, make it not nullable and set null values to a default value
|
||||
person_klass.connection.execute('UPDATE testings SET money = NULL')
|
||||
person_klass.connection.change_column_null "testings", "money", false, 2000
|
||||
person_klass.reset_column_information
|
||||
assert_nil person_klass.columns_hash["money"].default
|
||||
assert_equal false, person_klass.columns_hash["money"].null
|
||||
assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort
|
||||
ensure
|
||||
Person.connection.drop_table :testings rescue nil
|
||||
end
|
||||
|
||||
def test_change_column_default_to_null
|
||||
Person.connection.change_column_default "people", "first_name", nil
|
||||
Person.reset_column_information
|
||||
|
@ -799,6 +919,21 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
assert !Reminder.table_exists?
|
||||
end
|
||||
|
||||
def test_migrator_double_up
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
|
||||
assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) }
|
||||
assert_equal(1, ActiveRecord::Migrator.current_version)
|
||||
end
|
||||
|
||||
def test_migrator_double_down
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
|
||||
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1)
|
||||
assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) }
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
end
|
||||
|
||||
def test_finds_migrations
|
||||
migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
|
||||
[['1', 'people_have_last_names'],
|
||||
|
@ -888,16 +1023,6 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
end
|
||||
|
||||
def test_migrator_run
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3)
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3)
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
end
|
||||
|
||||
def test_schema_migrations_table_name
|
||||
ActiveRecord::Base.table_name_prefix = "prefix_"
|
||||
|
@ -1077,8 +1202,8 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
|
||||
def test_timestamps_creates_updated_at_and_created_at
|
||||
with_new_table do |t|
|
||||
t.expects(:column).with(:created_at, :datetime)
|
||||
t.expects(:column).with(:updated_at, :datetime)
|
||||
t.expects(:column).with(:created_at, :datetime, kind_of(Hash))
|
||||
t.expects(:column).with(:updated_at, :datetime, kind_of(Hash))
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
@ -1206,10 +1331,10 @@ if ActiveRecord::Base.connection.supports_migrations?
|
|||
end
|
||||
|
||||
def integer_column
|
||||
if current_adapter?(:SQLite3Adapter) || current_adapter?(:SQLiteAdapter) || current_adapter?(:PostgreSQLAdapter)
|
||||
"integer"
|
||||
else
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
'int(11)'
|
||||
else
|
||||
'integer'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class MultipleDbTest < ActiveRecord::TestCase
|
|||
def test_course_connection_should_survive_dependency_reload
|
||||
assert Course.connection
|
||||
|
||||
Dependencies.clear
|
||||
ActiveSupport::Dependencies.clear
|
||||
Object.send(:remove_const, :Course)
|
||||
require_dependency 'models/course'
|
||||
|
||||
|
|
|
@ -4,9 +4,10 @@ require 'models/topic'
|
|||
require 'models/comment'
|
||||
require 'models/reply'
|
||||
require 'models/author'
|
||||
require 'models/developer'
|
||||
|
||||
class NamedScopeTest < ActiveRecord::TestCase
|
||||
fixtures :posts, :authors, :topics, :comments
|
||||
fixtures :posts, :authors, :topics, :comments, :author_addresses
|
||||
|
||||
def test_implements_enumerable
|
||||
assert !Topic.find(:all).empty?
|
||||
|
@ -45,6 +46,17 @@ class NamedScopeTest < ActiveRecord::TestCase
|
|||
assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
|
||||
end
|
||||
|
||||
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
|
||||
assert Topic.approved.respond_to?(:proxy_found)
|
||||
assert Topic.approved.respond_to?(:count)
|
||||
assert Topic.approved.respond_to?(:length)
|
||||
end
|
||||
|
||||
def test_respond_to_respects_include_private_parameter
|
||||
assert !Topic.approved.respond_to?(:load_found)
|
||||
assert Topic.approved.respond_to?(:load_found, true)
|
||||
end
|
||||
|
||||
def test_subclasses_inherit_scopes
|
||||
assert Topic.scopes.include?(:base)
|
||||
|
||||
|
@ -59,6 +71,12 @@ class NamedScopeTest < ActiveRecord::TestCase
|
|||
assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count
|
||||
end
|
||||
|
||||
def test_scopes_with_string_name_can_be_composed
|
||||
# NOTE that scopes defined with a string as a name worked on their own
|
||||
# but when called on another scope the other scope was completely replaced
|
||||
assert_equal Topic.replied.approved, Topic.replied.approved_as_string
|
||||
end
|
||||
|
||||
def test_scopes_are_composable
|
||||
assert_equal (approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved
|
||||
assert_equal (replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied
|
||||
|
@ -77,6 +95,25 @@ class NamedScopeTest < ActiveRecord::TestCase
|
|||
assert_equal topics_written_before_the_second, Topic.written_before(topics(:second).written_on)
|
||||
end
|
||||
|
||||
def test_scopes_with_joins
|
||||
address = author_addresses(:david_address)
|
||||
posts_with_authors_at_address = Post.find(
|
||||
:all, :joins => 'JOIN authors ON authors.id = posts.author_id',
|
||||
:conditions => [ 'authors.author_address_id = ?', address.id ]
|
||||
)
|
||||
assert_equal posts_with_authors_at_address, Post.with_authors_at_address(address)
|
||||
end
|
||||
|
||||
def test_scopes_with_joins_respects_custom_select
|
||||
address = author_addresses(:david_address)
|
||||
posts_with_authors_at_address_titles = Post.find(:all,
|
||||
:select => 'title',
|
||||
:joins => 'JOIN authors ON authors.id = posts.author_id',
|
||||
:conditions => [ 'authors.author_address_id = ?', address.id ]
|
||||
)
|
||||
assert_equal posts_with_authors_at_address_titles, Post.with_authors_at_address(address).find(:all, :select => 'title')
|
||||
end
|
||||
|
||||
def test_extensions
|
||||
assert_equal 1, Topic.anonymous_extension.one
|
||||
assert_equal 2, Topic.named_extension.two
|
||||
|
@ -154,4 +191,16 @@ class NamedScopeTest < ActiveRecord::TestCase
|
|||
topics.empty? # use loaded (no query)
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_all_should_behave_like_select
|
||||
assert_equal Topic.base.select(&:approved), Topic.base.find_all(&:approved)
|
||||
end
|
||||
|
||||
def test_rand_should_select_a_random_object_from_proxy
|
||||
assert Topic.approved.rand.is_a?(Topic)
|
||||
end
|
||||
|
||||
def test_should_use_where_in_query_for_named_scope
|
||||
assert_equal Developer.find_all_by_name('Jamis'), Developer.find_all_by_id(Developer.jamises)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,7 +58,7 @@ end
|
|||
uses_mocha 'QueryCacheExpiryTest' do
|
||||
|
||||
class QueryCacheExpiryTest < ActiveRecord::TestCase
|
||||
fixtures :tasks
|
||||
fixtures :tasks, :posts, :categories, :categories_posts
|
||||
|
||||
def test_find
|
||||
Task.connection.expects(:clear_query_cache).times(1)
|
||||
|
@ -116,8 +116,9 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
|
|||
def test_cache_is_expired_by_habtm_delete
|
||||
ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
|
||||
ActiveRecord::Base.cache do
|
||||
c = Category.find(:first)
|
||||
p = Post.find(:first)
|
||||
c = Category.find(1)
|
||||
p = Post.find(1)
|
||||
assert p.categories.any?
|
||||
p.categories.delete_all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -160,9 +160,9 @@ class ReflectionTest < ActiveRecord::TestCase
|
|||
|
||||
def test_reflection_of_all_associations
|
||||
# FIXME these assertions bust a lot
|
||||
assert_equal 20, Firm.reflect_on_all_associations.size
|
||||
assert_equal 16, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 4, Firm.reflect_on_all_associations(:has_one).size
|
||||
assert_equal 22, Firm.reflect_on_all_associations.size
|
||||
assert_equal 17, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 5, Firm.reflect_on_all_associations(:has_one).size
|
||||
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
|
||||
end
|
||||
|
||||
|
|
|
@ -72,6 +72,52 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
assert_match %r{:null => false}, output
|
||||
end
|
||||
|
||||
def test_schema_dump_includes_limit_constraint_for_integer_columns
|
||||
stream = StringIO.new
|
||||
|
||||
ActiveRecord::SchemaDumper.ignore_tables = [/^(?!integer_limits)/]
|
||||
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
||||
output = stream.string
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
assert_match %r{c_int_1.*:limit => 2}, output
|
||||
assert_match %r{c_int_2.*:limit => 2}, output
|
||||
|
||||
# int 3 is 4 bytes in postgresql
|
||||
assert_match %r{c_int_3.*}, output
|
||||
assert_no_match %r{c_int_3.*:limit}, output
|
||||
|
||||
assert_match %r{c_int_4.*}, output
|
||||
assert_no_match %r{c_int_4.*:limit}, output
|
||||
elsif current_adapter?(:MysqlAdapter)
|
||||
assert_match %r{c_int_1.*:limit => 1}, output
|
||||
assert_match %r{c_int_2.*:limit => 2}, output
|
||||
assert_match %r{c_int_3.*:limit => 3}, output
|
||||
|
||||
assert_match %r{c_int_4.*}, output
|
||||
assert_no_match %r{c_int_4.*:limit}, output
|
||||
elsif current_adapter?(:SQLiteAdapter)
|
||||
assert_match %r{c_int_1.*:limit => 1}, output
|
||||
assert_match %r{c_int_2.*:limit => 2}, output
|
||||
assert_match %r{c_int_3.*:limit => 3}, output
|
||||
assert_match %r{c_int_4.*:limit => 4}, output
|
||||
end
|
||||
assert_match %r{c_int_without_limit.*}, output
|
||||
assert_no_match %r{c_int_without_limit.*:limit}, output
|
||||
|
||||
if current_adapter?(:SQLiteAdapter)
|
||||
assert_match %r{c_int_5.*:limit => 5}, output
|
||||
assert_match %r{c_int_6.*:limit => 6}, output
|
||||
assert_match %r{c_int_7.*:limit => 7}, output
|
||||
assert_match %r{c_int_8.*:limit => 8}, output
|
||||
else
|
||||
assert_match %r{c_int_5.*:limit => 8}, output
|
||||
assert_match %r{c_int_6.*:limit => 8}, output
|
||||
assert_match %r{c_int_7.*:limit => 8}, output
|
||||
assert_match %r{c_int_8.*:limit => 8}, output
|
||||
end
|
||||
end
|
||||
|
||||
def test_schema_dump_with_string_ignored_table
|
||||
stream = StringIO.new
|
||||
|
||||
|
|
|
@ -451,6 +451,18 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
t2.title = nil
|
||||
assert t2.valid?, "should validate with nil"
|
||||
assert t2.save, "should save with nil"
|
||||
|
||||
with_kcode('UTF8') do
|
||||
t_utf8 = Topic.new("title" => "Я тоже уникальный!")
|
||||
assert t_utf8.save, "Should save t_utf8 as unique"
|
||||
|
||||
# If database hasn't UTF-8 character set, this test fails
|
||||
if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!"
|
||||
t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
|
||||
assert !t2_utf8.valid?, "Shouldn't be valid"
|
||||
assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_validate_case_sensitive_uniqueness
|
||||
|
@ -1059,6 +1071,18 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_validates_length_of_with_block
|
||||
Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least %d words.",
|
||||
:tokenizer => lambda {|str| str.scan(/\w+/) }
|
||||
t = Topic.create!(:content => "this content should be long enough")
|
||||
assert t.valid?
|
||||
|
||||
t.content = "not long enough"
|
||||
assert !t.valid?
|
||||
assert t.errors.on(:content)
|
||||
assert_equal "Your essay must be at least 5 words.", t.errors[:content]
|
||||
end
|
||||
|
||||
def test_validates_size_of_association_utf8
|
||||
with_kcode('UTF8') do
|
||||
assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
|
||||
|
@ -1379,6 +1403,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
|
|||
INTEGERS = [0, 10, -10] + INTEGER_STRINGS
|
||||
BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
|
||||
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
|
||||
INFINITY = [1.0/0.0]
|
||||
|
||||
def setup
|
||||
Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
|
@ -1390,27 +1415,27 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
|
|||
Topic.validates_numericality_of :approved
|
||||
|
||||
invalid!(NIL + BLANK + JUNK)
|
||||
valid!(FLOATS + INTEGERS + BIGDECIMAL)
|
||||
valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_nil_allowed
|
||||
Topic.validates_numericality_of :approved, :allow_nil => true
|
||||
|
||||
invalid!(BLANK + JUNK)
|
||||
valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
|
||||
valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_integer_only
|
||||
Topic.validates_numericality_of :approved, :only_integer => true
|
||||
|
||||
invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL)
|
||||
invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
|
||||
valid!(INTEGERS)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_integer_only_and_nil_allowed
|
||||
Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
|
||||
|
||||
invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL)
|
||||
invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
|
||||
valid!(NIL + INTEGERS)
|
||||
end
|
||||
|
||||
|
@ -1431,7 +1456,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
|
|||
def test_validates_numericality_with_equal_to
|
||||
Topic.validates_numericality_of :approved, :equal_to => 10
|
||||
|
||||
invalid!([-10, 11], 'must be equal to 10')
|
||||
invalid!([-10, 11] + INFINITY, 'must be equal to 10')
|
||||
valid!([10])
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class Author < ActiveRecord::Base
|
||||
has_many :posts
|
||||
has_many :posts_with_comments, :include => :comments, :class_name => "Post"
|
||||
has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
|
||||
has_many :posts_with_categories, :include => :categories, :class_name => "Post"
|
||||
has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post"
|
||||
has_many :posts_containing_the_letter_a, :class_name => "Post"
|
||||
|
@ -31,6 +32,9 @@ class Author < ActiveRecord::Base
|
|||
has_many :special_posts
|
||||
has_many :special_post_comments, :through => :special_posts, :source => :comments
|
||||
|
||||
has_many :sti_posts, :class_name => 'StiPost'
|
||||
has_many :sti_post_comments, :through => :sti_posts, :source => :comments
|
||||
|
||||
has_many :special_nonexistant_posts, :class_name => "SpecialPost", :conditions => "posts.body = 'nonexistant'"
|
||||
has_many :special_nonexistant_post_comments, :through => :special_nonexistant_posts, :source => :comments, :conditions => "comments.post_id = 0"
|
||||
has_many :nonexistant_comments, :through => :posts
|
||||
|
|
|
@ -2,6 +2,7 @@ class Category < ActiveRecord::Base
|
|||
has_and_belongs_to_many :posts
|
||||
has_and_belongs_to_many :special_posts, :class_name => "Post"
|
||||
has_and_belongs_to_many :other_posts, :class_name => "Post"
|
||||
has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, :class_name => "Post", :include => :authors, :order => "authors.id"
|
||||
|
||||
has_and_belongs_to_many(:select_testing_posts,
|
||||
:class_name => 'Post',
|
||||
|
|
11
vendor/rails/activerecord/test/models/company.rb
vendored
11
vendor/rails/activerecord/test/models/company.rb
vendored
|
@ -18,6 +18,13 @@ end
|
|||
module Namespaced
|
||||
class Company < ::Company
|
||||
end
|
||||
|
||||
class Firm < ::Company
|
||||
has_many :clients, :class_name => 'Namespaced::Client'
|
||||
end
|
||||
|
||||
class Client < ::Company
|
||||
end
|
||||
end
|
||||
|
||||
class Firm < Company
|
||||
|
@ -26,6 +33,7 @@ class Firm < Company
|
|||
"AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
|
||||
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
|
||||
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
|
||||
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
|
||||
has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
|
||||
has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
|
||||
has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1
|
||||
|
@ -46,7 +54,8 @@ class Firm < Company
|
|||
has_many :plain_clients, :class_name => 'Client'
|
||||
has_many :readonly_clients, :class_name => 'Client', :readonly => true
|
||||
|
||||
has_one :account, :foreign_key => "firm_id", :dependent => :destroy
|
||||
has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true
|
||||
has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false
|
||||
has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account'
|
||||
has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true
|
||||
end
|
||||
|
|
|
@ -43,6 +43,8 @@ class Developer < ActiveRecord::Base
|
|||
|
||||
has_many :audit_logs
|
||||
|
||||
named_scope :jamises, :conditions => {:name => 'Jamis'}
|
||||
|
||||
validates_inclusion_of :salary, :in => 50000..200000
|
||||
validates_length_of :name, :within => 3..20
|
||||
|
||||
|
@ -56,7 +58,8 @@ class Developer < ActiveRecord::Base
|
|||
end
|
||||
|
||||
class AuditLog < ActiveRecord::Base
|
||||
belongs_to :developer
|
||||
belongs_to :developer, :validate => true
|
||||
belongs_to :unvalidated_developer, :class_name => 'Developer'
|
||||
end
|
||||
|
||||
DeveloperSalary = Struct.new(:amount)
|
||||
|
|
|
@ -6,5 +6,5 @@ class Person < ActiveRecord::Base
|
|||
has_many :references
|
||||
has_many :jobs, :through => :references
|
||||
has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
|
||||
|
||||
has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id'
|
||||
end
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
class Post < ActiveRecord::Base
|
||||
named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'"
|
||||
|
||||
named_scope :with_authors_at_address, lambda { |address| {
|
||||
:conditions => [ 'authors.author_address_id = ?', address.id ],
|
||||
:joins => 'JOIN authors ON authors.id = posts.author_id'
|
||||
}
|
||||
}
|
||||
|
||||
belongs_to :author do
|
||||
def greeting
|
||||
"hello"
|
||||
|
|
|
@ -7,7 +7,7 @@ class Project < ActiveRecord::Base
|
|||
has_and_belongs_to_many :developers_named_david, :class_name => "Developer", :conditions => "name = 'David'", :uniq => true
|
||||
has_and_belongs_to_many :developers_named_david_with_hash_conditions, :class_name => "Developer", :conditions => { :name => 'David' }, :uniq => true
|
||||
has_and_belongs_to_many :salaried_developers, :class_name => "Developer", :conditions => "salary > 0"
|
||||
has_and_belongs_to_many :developers_with_finder_sql, :class_name => "Developer", :finder_sql => 'SELECT t.*, j.* FROM developers_projects j, developers t WHERE t.id = j.developer_id AND j.project_id = #{id}'
|
||||
has_and_belongs_to_many :developers_with_finder_sql, :class_name => "Developer", :finder_sql => 'SELECT t.*, j.* FROM developers_projects j, developers t WHERE t.id = j.developer_id AND j.project_id = #{id} ORDER BY t.id'
|
||||
has_and_belongs_to_many :developers_by_sql, :class_name => "Developer", :delete_sql => "DELETE FROM developers_projects WHERE project_id = \#{id} AND developer_id = \#{record.id}"
|
||||
has_and_belongs_to_many :developers_with_callbacks, :class_name => "Developer", :before_add => Proc.new {|o, r| o.developers_log << "before_adding#{r.id || '<new>'}"},
|
||||
:after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id || '<new>'}"},
|
||||
|
|
|
@ -4,6 +4,7 @@ class Topic < ActiveRecord::Base
|
|||
{ :conditions => ['written_on < ?', time] }
|
||||
}
|
||||
named_scope :approved, :conditions => {:approved => true}
|
||||
named_scope 'approved_as_string', :conditions => {:approved => true}
|
||||
named_scope :replied, :conditions => ['replies_count > 0']
|
||||
named_scope :anonymous_extension do
|
||||
def one
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
ActiveRecord::Schema.define do
|
||||
create_table :binary_fields, :force => true do |t|
|
||||
create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
|
||||
t.binary :tiny_blob, :limit => 255
|
||||
t.binary :normal_blob, :limit => 65535
|
||||
t.binary :medium_blob, :limit => 16777215
|
||||
|
@ -9,4 +9,4 @@ ActiveRecord::Schema.define do
|
|||
t.text :medium_text, :limit => 16777215
|
||||
t.text :long_text, :limit => 2147483647
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -66,6 +66,7 @@ ActiveRecord::Schema.define do
|
|||
create_table :categories, :force => true do |t|
|
||||
t.string :name, :null => false
|
||||
t.string :type
|
||||
t.integer :categorizations_count
|
||||
end
|
||||
|
||||
create_table :categories_posts, :force => true, :id => false do |t|
|
||||
|
@ -407,6 +408,13 @@ ActiveRecord::Schema.define do
|
|||
t.column :key, :string
|
||||
end
|
||||
|
||||
create_table :integer_limits, :force => true do |t|
|
||||
t.integer :"c_int_without_limit"
|
||||
(1..8).each do |i|
|
||||
t.integer :"c_int_#{i}", :limit => i
|
||||
end
|
||||
end
|
||||
|
||||
except 'SQLite' do
|
||||
# fk_test_has_fk should be before fk_test_has_pk
|
||||
create_table :fk_test_has_fk, :force => true do |t|
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue