mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
merge forward to current rails/master
This commit is contained in:
@@ -1,5 +1,49 @@
|
||||
*Edge*
|
||||
|
||||
* change_column_default preserves the not-null constraint. #617 [Tarmo Tänav]
|
||||
|
||||
* Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
|
||||
|
||||
* Add :accessible option to associations for allowing (opt-in) mass assignment. #474. [David Dollar] Example :
|
||||
|
||||
class Post < ActiveRecord::Base
|
||||
belongs_to :author, :accessible => true
|
||||
has_many :comments, :accessible => true
|
||||
end
|
||||
|
||||
post = Post.create({
|
||||
:title => 'Accessible Attributes',
|
||||
:author => { :name => 'David Dollar' },
|
||||
:comments => [
|
||||
{ :body => 'First Post!' },
|
||||
{ :body => 'Nested Hashes are great!' }
|
||||
]
|
||||
})
|
||||
|
||||
post.comments << { :body => 'Another Comment' }
|
||||
|
||||
* 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+/) }
|
||||
|
||||
* Allow conditions on multiple tables to be specified using hash. [Pratik Naik]. Example:
|
||||
|
||||
User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 'black' } }
|
||||
Item.first :conditions => { :items => { :color => 'red' } }
|
||||
|
||||
* 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]
|
||||
|
||||
@@ -141,7 +141,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/*')
|
||||
@@ -225,13 +225,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("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh wrath.rubyonrails.org './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."
|
||||
|
||||
@@ -188,7 +188,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 +226,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],
|
||||
|
||||
@@ -663,6 +663,7 @@ module ActiveRecord
|
||||
# * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
|
||||
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
|
||||
# as the default <tt>:foreign_key</tt>.
|
||||
# * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+.
|
||||
# * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed
|
||||
# alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
|
||||
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
|
||||
@@ -678,7 +679,7 @@ module ActiveRecord
|
||||
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
|
||||
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
|
||||
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
|
||||
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
|
||||
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
|
||||
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
|
||||
# * <tt>:through</tt> - Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
|
||||
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
|
||||
@@ -691,6 +692,7 @@ module ActiveRecord
|
||||
# * <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.
|
||||
# * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
|
||||
#
|
||||
# Option examples:
|
||||
# has_many :comments, :order => "posted_on"
|
||||
@@ -711,7 +713,8 @@ module ActiveRecord
|
||||
|
||||
configure_dependency_for_has_many(reflection)
|
||||
|
||||
add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false
|
||||
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)
|
||||
|
||||
if options[:through]
|
||||
@@ -757,6 +760,7 @@ module ActiveRecord
|
||||
# * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
|
||||
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
|
||||
# as the default <tt>:foreign_key</tt>.
|
||||
# * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+.
|
||||
# * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
|
||||
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
|
||||
# * <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
|
||||
@@ -771,6 +775,7 @@ module ActiveRecord
|
||||
# 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.
|
||||
# * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
|
||||
#
|
||||
# Option examples:
|
||||
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
||||
@@ -801,7 +806,7 @@ module ActiveRecord
|
||||
end
|
||||
after_save method_name
|
||||
|
||||
add_single_associated_save_callbacks(reflection.name) if options[:validate] == true
|
||||
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)
|
||||
@@ -860,6 +865,7 @@ module ActiveRecord
|
||||
# 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.
|
||||
# * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
|
||||
#
|
||||
# Option examples:
|
||||
# belongs_to :firm, :foreign_key => "client_of"
|
||||
@@ -940,7 +946,7 @@ module ActiveRecord
|
||||
)
|
||||
end
|
||||
|
||||
add_single_associated_save_callbacks(reflection.name) if options[:validate] == true
|
||||
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
|
||||
|
||||
configure_dependency_for_belongs_to(reflection)
|
||||
end
|
||||
@@ -1031,6 +1037,7 @@ module ActiveRecord
|
||||
# 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.
|
||||
# * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
|
||||
#
|
||||
# Option examples:
|
||||
# has_and_belongs_to_many :projects
|
||||
@@ -1043,7 +1050,8 @@ 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_save_callbacks(reflection.name) unless options[:validate] == false
|
||||
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
|
||||
add_multiple_associated_save_callbacks(reflection.name)
|
||||
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
|
||||
|
||||
# Don't use a before_destroy callback since users' before_destroy
|
||||
@@ -1105,6 +1113,8 @@ module ActiveRecord
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
end
|
||||
|
||||
new_value = reflection.klass.new(new_value) if reflection.options[:accessible] && new_value.is_a?(Hash)
|
||||
|
||||
if association_proxy_class == HasOneThroughAssociation
|
||||
association.create_through_record(new_value)
|
||||
self.send(reflection.name, new_value)
|
||||
@@ -1141,7 +1151,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
define_method("#{reflection.name.to_s.singularize}_ids") do
|
||||
send(reflection.name).map(&:id)
|
||||
send(reflection.name).map { |record| record.id }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1163,7 +1173,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}")
|
||||
@@ -1175,7 +1185,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}"
|
||||
|
||||
@@ -1196,6 +1206,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
|
||||
@@ -1217,7 +1231,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
|
||||
@@ -1342,7 +1355,7 @@ module ActiveRecord
|
||||
|
||||
def create_has_many_reflection(association_id, options, &extension)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :table_name, :foreign_key,
|
||||
:class_name, :table_name, :foreign_key, :primary_key,
|
||||
:dependent,
|
||||
:select, :conditions, :include, :order, :group, :limit, :offset,
|
||||
:as, :through, :source, :source_type,
|
||||
@@ -1350,7 +1363,7 @@ module ActiveRecord
|
||||
:finder_sql, :counter_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend, :readonly,
|
||||
:validate
|
||||
:validate, :accessible
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
@@ -1360,7 +1373,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, :validate
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key, :accessible
|
||||
)
|
||||
|
||||
create_reflection(:has_one, association_id, options, self)
|
||||
@@ -1376,7 +1389,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, :validate
|
||||
:counter_cache, :extend, :polymorphic, :readonly, :validate, :accessible
|
||||
)
|
||||
|
||||
reflection = create_reflection(:belongs_to, association_id, options, self)
|
||||
@@ -1396,7 +1409,7 @@ module ActiveRecord
|
||||
:finder_sql, :delete_sql, :insert_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend, :readonly,
|
||||
:validate
|
||||
:validate, :accessible
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
@@ -1473,25 +1486,30 @@ 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
|
||||
sql << " FROM #{connection.quote_table_name table_name} "
|
||||
|
||||
if is_distinct
|
||||
sql << distinct_join_associations.collect(&:association_join).join
|
||||
sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
|
||||
add_joins!(sql, options, scope)
|
||||
end
|
||||
|
||||
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
|
||||
@@ -1514,7 +1532,7 @@ module ActiveRecord
|
||||
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
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ module ActiveRecord
|
||||
# If using a custom finder_sql, scan the entire collection.
|
||||
if @reflection.options[:finder_sql]
|
||||
expects_array = args.first.kind_of?(Array)
|
||||
ids = args.flatten.compact.uniq.map(&:to_i)
|
||||
ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
|
||||
|
||||
if ids.size == 1
|
||||
id = ids.first
|
||||
@@ -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
|
||||
|
||||
@@ -94,6 +97,8 @@ module ActiveRecord
|
||||
|
||||
@owner.transaction do
|
||||
flatten_deeper(records).each do |record|
|
||||
record = @reflection.klass.new(record) if @reflection.options[:accessible] && record.is_a?(Hash)
|
||||
|
||||
raise_on_type_mismatch(record)
|
||||
add_record_to_target_with_callbacks(record) do |r|
|
||||
result &&= insert_record(record) unless @owner.new_record?
|
||||
@@ -187,7 +192,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
|
||||
@@ -226,6 +231,10 @@ module ActiveRecord
|
||||
# Replace this collection with +other_array+
|
||||
# This will perform a diff and delete/add only records that have changed.
|
||||
def replace(other_array)
|
||||
other_array.map! do |val|
|
||||
val.is_a?(Hash) ? @reflection.klass.new(val) : val
|
||||
end if @reflection.options[:accessible]
|
||||
|
||||
other_array.each { |val| raise_on_type_mismatch(val) }
|
||||
|
||||
load_target
|
||||
|
||||
@@ -85,7 +85,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def conditions
|
||||
@conditions ||= interpolate_sql(sanitize_sql(@reflection.options[:conditions])) if @reflection.options[:conditions]
|
||||
@conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
|
||||
end
|
||||
alias :sql_conditions :conditions
|
||||
|
||||
@@ -219,6 +219,10 @@ module ActiveRecord
|
||||
def flatten_deeper(array)
|
||||
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
|
||||
end
|
||||
|
||||
def owner_quoted_id
|
||||
@owner.quoted_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ module ActiveRecord
|
||||
attributes = columns.inject({}) do |attrs, column|
|
||||
case column.name.to_s
|
||||
when @reflection.primary_key_name.to_s
|
||||
attrs[column.name] = @owner.quoted_id
|
||||
attrs[column.name] = owner_quoted_id
|
||||
when @reflection.association_foreign_key.to_s
|
||||
attrs[column.name] = record.quoted_id
|
||||
else
|
||||
@@ -64,7 +64,7 @@ module ActiveRecord
|
||||
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
|
||||
else
|
||||
ids = quoted_record_ids(records)
|
||||
sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
|
||||
sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
|
||||
@owner.connection.delete(sql)
|
||||
end
|
||||
end
|
||||
@@ -75,7 +75,7 @@ module ActiveRecord
|
||||
if @reflection.options[:finder_sql]
|
||||
@finder_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 = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
end
|
||||
|
||||
@@ -87,6 +87,7 @@ module ActiveRecord
|
||||
:joins => @join_sql,
|
||||
:readonly => false,
|
||||
:order => @reflection.options[:order],
|
||||
:include => @reflection.options[:include],
|
||||
:limit => @reflection.options[:limit] } }
|
||||
end
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
protected
|
||||
def owner_quoted_id
|
||||
if @reflection.options[:primary_key]
|
||||
quote_value(@owner.send(@reflection.options[:primary_key]))
|
||||
else
|
||||
@owner.quoted_id
|
||||
end
|
||||
end
|
||||
|
||||
def count_records
|
||||
count = if has_cached_counter?
|
||||
@owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
@@ -53,14 +61,14 @@ module ActiveRecord
|
||||
def delete_records(records)
|
||||
case @reflection.options[:dependent]
|
||||
when :destroy
|
||||
records.each(&:destroy)
|
||||
records.each { |r| r.destroy }
|
||||
when :delete_all
|
||||
@reflection.klass.delete(records.map(&:id))
|
||||
@reflection.klass.delete(records.map { |record| record.id })
|
||||
else
|
||||
ids = quoted_record_ids(records)
|
||||
@reflection.klass.update_all(
|
||||
"#{@reflection.primary_key_name} = NULL",
|
||||
"#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
||||
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -76,12 +84,12 @@ module ActiveRecord
|
||||
|
||||
when @reflection.options[:as]
|
||||
@finder_sql =
|
||||
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
||||
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
||||
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
|
||||
else
|
||||
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
end
|
||||
|
||||
@@ -100,7 +108,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
|
||||
|
||||
@@ -107,12 +107,12 @@ module ActiveRecord
|
||||
# Associate attributes pointing to owner, quoted.
|
||||
def construct_quoted_owner_attributes(reflection)
|
||||
if as = reflection.options[:as]
|
||||
{ "#{as}_id" => @owner.quoted_id,
|
||||
{ "#{as}_id" => owner_quoted_id,
|
||||
"#{as}_type" => reflection.klass.quote_value(
|
||||
@owner.class.base_class.name.to_s,
|
||||
reflection.klass.columns_hash["#{as}_type"]) }
|
||||
else
|
||||
{ reflection.primary_key_name => @owner.quoted_id }
|
||||
{ reflection.primary_key_name => owner_quoted_id }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -183,7 +183,7 @@ module ActiveRecord
|
||||
when @reflection.options[:finder_sql]
|
||||
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
||||
|
||||
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
else
|
||||
@finder_sql = construct_conditions
|
||||
|
||||
@@ -47,7 +47,16 @@ module ActiveRecord
|
||||
return (obj.nil? ? nil : self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
def owner_quoted_id
|
||||
if @reflection.options[:primary_key]
|
||||
quote_value(@owner.send(@reflection.options[:primary_key]))
|
||||
else
|
||||
@owner.quoted_id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def find_target
|
||||
@reflection.klass.find(:first,
|
||||
@@ -63,10 +72,10 @@ module ActiveRecord
|
||||
case
|
||||
when @reflection.options[:as]
|
||||
@finder_sql =
|
||||
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
||||
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
||||
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
||||
else
|
||||
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
||||
end
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
end
|
||||
|
||||
@@ -828,7 +828,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
|
||||
@@ -1479,7 +1479,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)
|
||||
@@ -1999,24 +1999,28 @@ module ActiveRecord #:nodoc:
|
||||
# # => "age BETWEEN 13 AND 18"
|
||||
# { 'other_records.id' => 7 }
|
||||
# # => "`other_records`.`id` = 7"
|
||||
# { :other_records => { :id => 7 } }
|
||||
# # => "`other_records`.`id` = 7"
|
||||
# And for value objects on a composed_of relationship:
|
||||
# { :address => Address.new("123 abc st.", "chicago") }
|
||||
# # => "address_street='123 abc st.' and address_city='chicago'"
|
||||
def sanitize_sql_hash_for_conditions(attrs)
|
||||
def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
|
||||
attrs = expand_hash_conditions_for_aggregates(attrs)
|
||||
|
||||
conditions = attrs.map do |attr, value|
|
||||
attr = attr.to_s
|
||||
unless value.is_a?(Hash)
|
||||
attr = attr.to_s
|
||||
|
||||
# Extract table name from qualified attribute names.
|
||||
if attr.include?('.')
|
||||
table_name, attr = attr.split('.', 2)
|
||||
table_name = connection.quote_table_name(table_name)
|
||||
# Extract table name from qualified attribute names.
|
||||
if attr.include?('.')
|
||||
table_name, attr = attr.split('.', 2)
|
||||
table_name = connection.quote_table_name(table_name)
|
||||
end
|
||||
|
||||
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
|
||||
else
|
||||
table_name = quoted_table_name
|
||||
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
|
||||
end
|
||||
|
||||
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
|
||||
end.join(' AND ')
|
||||
|
||||
replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
|
||||
@@ -2055,9 +2059,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}"
|
||||
@@ -2069,6 +2074,8 @@ module ActiveRecord #:nodoc:
|
||||
expanded = []
|
||||
|
||||
bind_vars.each do |var|
|
||||
next if var.is_a?(Hash)
|
||||
|
||||
if var.is_a?(Range)
|
||||
expanded << var.first
|
||||
expanded << var.last
|
||||
@@ -2169,11 +2176,11 @@ module ActiveRecord #:nodoc:
|
||||
def cache_key
|
||||
case
|
||||
when new_record?
|
||||
"#{self.class.name.tableize}/new"
|
||||
when self[:updated_at]
|
||||
"#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}"
|
||||
"#{self.class.model_name.cache_key}/new"
|
||||
when timestamp = self[:updated_at]
|
||||
"#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
|
||||
else
|
||||
"#{self.class.name.tableize}/#{id}"
|
||||
"#{self.class.model_name.cache_key}/#{id}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ module ActiveRecord
|
||||
def valid_with_callbacks? #:nodoc:
|
||||
return false if callback(:before_validation) == false
|
||||
if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
|
||||
return false if result == false
|
||||
return false if false == result
|
||||
|
||||
result = valid_without_callbacks?
|
||||
|
||||
@@ -293,7 +293,7 @@ module ActiveRecord
|
||||
|
||||
private
|
||||
def callback(method)
|
||||
result = run_callbacks(method) { |result, object| result == false }
|
||||
result = run_callbacks(method) { |result, object| false == result }
|
||||
|
||||
if result != false && respond_to_without_attributes?(method)
|
||||
result = send(method)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'date'
|
||||
require 'set'
|
||||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
|
||||
@@ -6,6 +7,8 @@ module ActiveRecord
|
||||
module ConnectionAdapters #:nodoc:
|
||||
# An abstract definition of a column in a table.
|
||||
class Column
|
||||
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
|
||||
|
||||
module Format
|
||||
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
||||
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
||||
@@ -30,11 +33,11 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def text?
|
||||
[:string, :text].include? type
|
||||
type == :string || type == :text
|
||||
end
|
||||
|
||||
def number?
|
||||
[:float, :integer, :decimal].include? type
|
||||
type == :integer || type == :float || type == :decimal
|
||||
end
|
||||
|
||||
# Returns the Ruby class that corresponds to the abstract data type.
|
||||
@@ -135,11 +138,7 @@ module ActiveRecord
|
||||
|
||||
# convert something to a boolean
|
||||
def value_to_boolean(value)
|
||||
if value == true || value == false
|
||||
value
|
||||
else
|
||||
%w(true t 1).include?(value.to_s.downcase)
|
||||
end
|
||||
TRUE_VALUES.include?(value)
|
||||
end
|
||||
|
||||
# convert something to a BigDecimal
|
||||
@@ -257,7 +256,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 +306,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> -
|
||||
|
||||
@@ -118,6 +118,19 @@ module ActiveRecord
|
||||
@connection
|
||||
end
|
||||
|
||||
def open_transactions
|
||||
@open_transactions ||= 0
|
||||
end
|
||||
|
||||
def increment_open_transactions
|
||||
@open_transactions ||= 0
|
||||
@open_transactions += 1
|
||||
end
|
||||
|
||||
def decrement_open_transactions
|
||||
@open_transactions -= 1
|
||||
end
|
||||
|
||||
def log_info(sql, name, runtime)
|
||||
if @logger && @logger.debug?
|
||||
name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
|
||||
|
||||
@@ -69,7 +69,7 @@ module ActiveRecord
|
||||
MysqlCompat.define_all_hashes_method!
|
||||
|
||||
mysql = Mysql.init
|
||||
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
|
||||
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
|
||||
|
||||
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
|
||||
end
|
||||
@@ -99,7 +99,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
|
||||
@@ -110,6 +111,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
|
||||
@@ -139,6 +145,7 @@ module ActiveRecord
|
||||
# * <tt>:password</tt> - Defaults to nothing.
|
||||
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
||||
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
|
||||
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
|
||||
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
|
||||
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
|
||||
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
|
||||
@@ -168,7 +175,7 @@ module ActiveRecord
|
||||
: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" },
|
||||
@@ -430,18 +437,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])}"
|
||||
@@ -450,8 +468,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.
|
||||
@@ -459,14 +486,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
|
||||
|
||||
@@ -495,7 +520,9 @@ module ActiveRecord
|
||||
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
||||
end
|
||||
|
||||
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
|
||||
if @config[:sslca] || @config[:sslkey]
|
||||
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
|
||||
end
|
||||
|
||||
@connection.real_connect(*@connection_options)
|
||||
|
||||
@@ -521,6 +548,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
|
||||
|
||||
@@ -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.
|
||||
@@ -324,12 +332,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def supports_insert_with_returning?
|
||||
unless defined? @supports_insert_with_returning
|
||||
@supports_insert_with_returning =
|
||||
@connection.respond_to?(:server_version) &&
|
||||
@connection.server_version >= 80200
|
||||
end
|
||||
@supports_insert_with_returning
|
||||
postgresql_version >= 80200
|
||||
end
|
||||
|
||||
# Returns the configured supported identifier length supported by PostgreSQL,
|
||||
@@ -553,7 +556,15 @@ module ActiveRecord
|
||||
# Example:
|
||||
# drop_database 'matt_development'
|
||||
def drop_database(name) #:nodoc:
|
||||
execute "DROP DATABASE IF EXISTS #{quote_table_name(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
|
||||
|
||||
|
||||
@@ -611,6 +622,19 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the current database name.
|
||||
def current_database
|
||||
query('select current_database()')[0][0]
|
||||
end
|
||||
|
||||
# Returns the current database encoding format.
|
||||
def encoding
|
||||
query(<<-end_sql)[0][0]
|
||||
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
||||
WHERE pg_database.datname LIKE '#{current_database}'
|
||||
end_sql
|
||||
end
|
||||
|
||||
# Sets the schema search path to a string of comma-separated schema names.
|
||||
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
||||
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
|
||||
@@ -782,15 +806,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
|
||||
|
||||
|
||||
@@ -142,9 +142,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)
|
||||
|
||||
@@ -515,7 +515,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
||||
|
||||
all_loaded_fixtures.update(fixtures_map)
|
||||
|
||||
connection.transaction(Thread.current['open_transactions'].to_i == 0) do
|
||||
connection.transaction(connection.open_transactions.zero?) do
|
||||
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
|
||||
fixtures.each { |fixture| fixture.insert_fixtures }
|
||||
|
||||
@@ -541,10 +541,11 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
||||
label.to_s.hash.abs
|
||||
end
|
||||
|
||||
attr_reader :table_name
|
||||
attr_reader :table_name, :name
|
||||
|
||||
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
|
||||
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
|
||||
@name = table_name # preserve fixture base name
|
||||
@class_name = class_name ||
|
||||
(ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
|
||||
@table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
|
||||
@@ -929,7 +930,7 @@ module Test #:nodoc:
|
||||
load_fixtures
|
||||
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
||||
end
|
||||
ActiveRecord::Base.send :increment_open_transactions
|
||||
ActiveRecord::Base.connection.increment_open_transactions
|
||||
ActiveRecord::Base.connection.begin_db_transaction
|
||||
# Load fixtures for every test.
|
||||
else
|
||||
@@ -950,9 +951,9 @@ module Test #:nodoc:
|
||||
end
|
||||
|
||||
# Rollback changes if a transaction is active.
|
||||
if use_transactional_fixtures? && Thread.current['open_transactions'] != 0
|
||||
if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
|
||||
ActiveRecord::Base.connection.rollback_db_transaction
|
||||
Thread.current['open_transactions'] = 0
|
||||
ActiveRecord::Base.connection.decrement_open_transactions
|
||||
end
|
||||
ActiveRecord::Base.verify_active_connections!
|
||||
end
|
||||
@@ -963,9 +964,9 @@ module Test #:nodoc:
|
||||
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|
||||
unless fixtures.nil?
|
||||
if fixtures.instance_of?(Fixtures)
|
||||
@loaded_fixtures[fixtures.table_name] = fixtures
|
||||
@loaded_fixtures[fixtures.name] = fixtures
|
||||
else
|
||||
fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
|
||||
fixtures.each { |f| @loaded_fixtures[f.name] = f }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -399,7 +399,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
|
||||
|
||||
@@ -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
|
||||
@@ -149,7 +150,8 @@ module ActiveRecord
|
||||
if scopes.include?(method)
|
||||
scopes[method].call(self, *args)
|
||||
else
|
||||
with_scope :find => proxy_options do
|
||||
with_scope :find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {} do
|
||||
method = :new if method == :build
|
||||
proxy_scope.send(method, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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,6 @@ module ActiveRecord
|
||||
|
||||
def add_observer!(klass)
|
||||
klass.add_observer(self)
|
||||
klass.class_eval 'def after_find() end' unless klass.method_defined?(:after_find)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,6 +112,10 @@ module ActiveRecord
|
||||
name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
|
||||
end
|
||||
|
||||
def sanitized_conditions #:nodoc:
|
||||
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
||||
end
|
||||
|
||||
private
|
||||
def derive_class_name
|
||||
name.to_s.camelize
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -73,25 +73,14 @@ module ActiveRecord
|
||||
# trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
|
||||
module ClassMethods
|
||||
def transaction(&block)
|
||||
increment_open_transactions
|
||||
connection.increment_open_transactions
|
||||
|
||||
begin
|
||||
connection.transaction(Thread.current['start_db_transaction'], &block)
|
||||
connection.transaction(connection.open_transactions == 1, &block)
|
||||
ensure
|
||||
decrement_open_transactions
|
||||
connection.decrement_open_transactions
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def increment_open_transactions #:nodoc:
|
||||
open = Thread.current['open_transactions'] ||= 0
|
||||
Thread.current['start_db_transaction'] = open.zero?
|
||||
Thread.current['open_transactions'] = open + 1
|
||||
end
|
||||
|
||||
def decrement_open_transactions #:nodoc:
|
||||
Thread.current['open_transactions'] -= 1
|
||||
end
|
||||
end
|
||||
|
||||
def transaction(&block)
|
||||
|
||||
@@ -402,7 +402,7 @@ module ActiveRecord
|
||||
record.errors.add(attr_name, message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
|
||||
#
|
||||
@@ -488,8 +488,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:
|
||||
@@ -500,7 +501,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)").
|
||||
@@ -512,9 +512,14 @@ 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 = {}.merge(DEFAULT_VALIDATION_OPTIONS)
|
||||
options = {
|
||||
:tokenizer => lambda {|value| value.split(//)}
|
||||
}.merge(DEFAULT_VALIDATION_OPTIONS)
|
||||
options.update(attrs.extract_options!.symbolize_keys)
|
||||
|
||||
# Ensure that one and only one range option is specified.
|
||||
@@ -537,7 +542,7 @@ module ActiveRecord
|
||||
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
|
||||
|
||||
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
|
||||
message = record.errors.generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
|
||||
record.errors.add(attr, message)
|
||||
@@ -554,7 +559,7 @@ module ActiveRecord
|
||||
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
|
||||
|
||||
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)
|
||||
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
|
||||
key = message_options[option]
|
||||
custom_message = options[:message] || options[key]
|
||||
@@ -728,7 +733,7 @@ module ActiveRecord
|
||||
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
def validates_inclusion_of(*attr_names)
|
||||
configuration = { :on => :save, :with => nil }
|
||||
configuration = { :on => :save }
|
||||
configuration.update(attr_names.extract_options!)
|
||||
|
||||
enum = configuration[:in] || configuration[:within]
|
||||
@@ -763,7 +768,7 @@ module ActiveRecord
|
||||
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
def validates_exclusion_of(*attr_names)
|
||||
configuration = { :on => :save, :with => nil }
|
||||
configuration = { :on => :save }
|
||||
configuration.update(attr_names.extract_options!)
|
||||
|
||||
enum = configuration[:in] || configuration[:within]
|
||||
|
||||
@@ -65,6 +65,12 @@ class AdapterTest < ActiveRecord::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
def test_encoding
|
||||
assert_not_nil @connection.encoding
|
||||
end
|
||||
end
|
||||
|
||||
def test_table_alias
|
||||
def @connection.test_table_alias_length() 10; end
|
||||
class << @connection
|
||||
|
||||
@@ -681,4 +681,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
|
||||
|
||||
@@ -129,6 +129,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name
|
||||
end
|
||||
|
||||
def test_finding_using_primary_key
|
||||
assert_equal "Summit", Firm.find(:first).clients_using_primary_key.first.name
|
||||
end
|
||||
|
||||
def test_finding_using_sql
|
||||
firm = Firm.find(:first)
|
||||
first_client = firm.clients_using_sql.first
|
||||
@@ -345,7 +349,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
def test_invalid_adding_with_validate_false
|
||||
firm = Firm.find(:first)
|
||||
client = Client.new
|
||||
firm.unvalidated_clients_of_firm << Client.new
|
||||
firm.unvalidated_clients_of_firm << client
|
||||
|
||||
assert firm.valid?
|
||||
assert !client.valid?
|
||||
@@ -353,6 +357,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
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") }
|
||||
@@ -367,6 +388,13 @@ 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_build_many
|
||||
company = companies(:first_firm)
|
||||
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
|
||||
@@ -397,6 +425,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
|
||||
|
||||
@@ -187,4 +187,10 @@ 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
|
||||
end
|
||||
|
||||
@@ -29,6 +29,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
||||
assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2
|
||||
end
|
||||
|
||||
def test_finding_using_primary_key
|
||||
firm = companies(:first_firm)
|
||||
assert_equal Account.find_by_firm_id(firm.id), firm.account
|
||||
firm.firm_id = companies(:rails_core).id
|
||||
assert_equal accounts(:rails_core_account), firm.account_using_primary_key
|
||||
end
|
||||
|
||||
def test_can_marshal_has_one_association_with_nil_target
|
||||
firm = Firm.new
|
||||
assert_nothing_raised do
|
||||
|
||||
@@ -189,6 +189,114 @@ class AssociationProxyTest < ActiveRecord::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_belongs_to_mass_assignment
|
||||
post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
|
||||
author_attributes = { :name => 'David Dollar' }
|
||||
|
||||
assert_no_difference 'Author.count' do
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) do
|
||||
Post.create(post_attributes.merge({:author => author_attributes}))
|
||||
end
|
||||
end
|
||||
|
||||
assert_difference 'Author.count' do
|
||||
post = Post.create(post_attributes.merge({:creatable_author => author_attributes}))
|
||||
assert_equal post.creatable_author.name, author_attributes[:name]
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_one_mass_assignment
|
||||
post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
|
||||
comment_attributes = { :body => 'Setter Takes Hash' }
|
||||
|
||||
assert_no_difference 'Comment.count' do
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) do
|
||||
Post.create(post_attributes.merge({:uncreatable_comment => comment_attributes}))
|
||||
end
|
||||
end
|
||||
|
||||
assert_difference 'Comment.count' do
|
||||
post = Post.create(post_attributes.merge({:creatable_comment => comment_attributes}))
|
||||
assert_equal post.creatable_comment.body, comment_attributes[:body]
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_many_mass_assignment
|
||||
post = posts(:welcome)
|
||||
post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
|
||||
comment_attributes = { :body => 'Setter Takes Hash' }
|
||||
|
||||
assert_no_difference 'Comment.count' do
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) do
|
||||
Post.create(post_attributes.merge({:comments => [comment_attributes]}))
|
||||
end
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) do
|
||||
post.comments << comment_attributes
|
||||
end
|
||||
end
|
||||
|
||||
assert_difference 'Comment.count' do
|
||||
post = Post.create(post_attributes.merge({:creatable_comments => [comment_attributes]}))
|
||||
assert_equal post.creatable_comments.last.body, comment_attributes[:body]
|
||||
end
|
||||
|
||||
assert_difference 'Comment.count' do
|
||||
post.creatable_comments << comment_attributes
|
||||
assert_equal post.comments.last.body, comment_attributes[:body]
|
||||
end
|
||||
|
||||
post.creatable_comments = [comment_attributes, comment_attributes]
|
||||
assert_equal post.creatable_comments.count, 2
|
||||
end
|
||||
|
||||
def test_has_and_belongs_to_many_mass_assignment
|
||||
post = posts(:welcome)
|
||||
post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
|
||||
category_attributes = { :name => 'Accessible Association', :type => 'Category' }
|
||||
|
||||
assert_no_difference 'Category.count' do
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) do
|
||||
Post.create(post_attributes.merge({:categories => [category_attributes]}))
|
||||
end
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) do
|
||||
post.categories << category_attributes
|
||||
end
|
||||
end
|
||||
|
||||
assert_difference 'Category.count' do
|
||||
post = Post.create(post_attributes.merge({:creatable_categories => [category_attributes]}))
|
||||
assert_equal post.creatable_categories.last.name, category_attributes[:name]
|
||||
end
|
||||
|
||||
assert_difference 'Category.count' do
|
||||
post.creatable_categories << category_attributes
|
||||
assert_equal post.creatable_categories.last.name, category_attributes[:name]
|
||||
end
|
||||
|
||||
post.creatable_categories = [category_attributes, category_attributes]
|
||||
assert_equal post.creatable_categories.count, 2
|
||||
end
|
||||
|
||||
def test_association_proxy_setter_can_take_hash
|
||||
special_comment_attributes = { :body => 'Setter Takes Hash' }
|
||||
|
||||
post = posts(:welcome)
|
||||
post.creatable_comment = { :body => 'Setter Takes Hash' }
|
||||
|
||||
assert_equal post.creatable_comment.body, special_comment_attributes[:body]
|
||||
end
|
||||
|
||||
def test_association_collection_can_take_hash
|
||||
post_attributes = { :title => 'Setter Takes', :body => 'Hash' }
|
||||
david = authors(:david)
|
||||
|
||||
post = (david.posts << post_attributes).last
|
||||
assert_equal post.title, post_attributes[:title]
|
||||
|
||||
david.posts = [post_attributes, post_attributes]
|
||||
assert_equal david.posts.count, 2
|
||||
end
|
||||
|
||||
def setup_dangling_association
|
||||
josh = Author.create(:name => "Josh")
|
||||
p = Post.create(:title => "New on Edge", :body => "More cool stuff!", :author => josh)
|
||||
|
||||
@@ -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
|
||||
|
||||
def test_table_exists
|
||||
assert !NonExistentTable.table_exists?
|
||||
@@ -130,7 +131,7 @@ 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
|
||||
|
||||
@@ -614,6 +615,22 @@ class BasicsTest < ActiveRecord::TestCase
|
||||
assert_equal -2, Topic.find(2).replies_count
|
||||
end
|
||||
|
||||
def test_update_counter
|
||||
category = Category.first
|
||||
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
|
||||
|
||||
36
activerecord/test/cases/column_definition_test.rb
Normal file
36
activerecord/test/cases/column_definition_test.rb
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 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) NULL", 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' NULL}, 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
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require "cases/helper"
|
||||
require 'models/author'
|
||||
require 'models/categorization'
|
||||
require 'models/comment'
|
||||
require 'models/company'
|
||||
require 'models/topic'
|
||||
@@ -199,6 +200,23 @@ class FinderTest < ActiveRecord::TestCase
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
|
||||
end
|
||||
|
||||
def test_find_on_hash_conditions_with_hashed_table_name
|
||||
assert Topic.find(1, :conditions => {:topics => { :approved => false }})
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) }
|
||||
end
|
||||
|
||||
def test_find_with_hash_conditions_on_joined_table
|
||||
firms = Firm.all :joins => :account, :conditions => {:accounts => { :credit_limit => 50 }}
|
||||
assert_equal 1, firms.size
|
||||
assert_equal companies(:first_firm), firms.first
|
||||
end
|
||||
|
||||
def test_find_with_hash_conditions_on_joined_table_and_with_range
|
||||
firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
|
||||
assert_equal 1, firms.size
|
||||
assert_equal companies(:rails_core), firms.first
|
||||
end
|
||||
|
||||
def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
|
||||
david = customers(:david)
|
||||
assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })
|
||||
@@ -394,6 +412,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")
|
||||
|
||||
@@ -15,6 +15,7 @@ require 'models/pirate'
|
||||
require 'models/treasure'
|
||||
require 'models/matey'
|
||||
require 'models/ship'
|
||||
require 'models/book'
|
||||
|
||||
class FixturesTest < ActiveRecord::TestCase
|
||||
self.use_instantiated_fixtures = true
|
||||
@@ -373,6 +374,34 @@ class CheckSetTableNameFixturesTest < ActiveRecord::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
class FixtureNameIsNotTableNameFixturesTest < ActiveRecord::TestCase
|
||||
set_fixture_class :items => Book
|
||||
fixtures :items
|
||||
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
|
||||
# and thus takes into account our set_fixture_class
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def test_named_accessor
|
||||
assert_kind_of Book, items(:dvd)
|
||||
end
|
||||
end
|
||||
|
||||
class FixtureNameIsNotTableNameMultipleFixturesTest < ActiveRecord::TestCase
|
||||
set_fixture_class :items => Book, :funny_jokes => Joke
|
||||
fixtures :items, :funny_jokes
|
||||
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
|
||||
# and thus takes into account our set_fixture_class
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def test_named_accessor_of_differently_named_fixture
|
||||
assert_kind_of Book, items(:dvd)
|
||||
end
|
||||
|
||||
def test_named_accessor_of_same_named_fixture
|
||||
assert_kind_of Joke, funny_jokes(:a_joke)
|
||||
end
|
||||
end
|
||||
|
||||
class CustomConnectionFixturesTest < ActiveRecord::TestCase
|
||||
set_fixture_class :courses => Course
|
||||
fixtures :courses
|
||||
@@ -432,11 +461,11 @@ class FixturesBrokenRollbackTest < ActiveRecord::TestCase
|
||||
alias_method :teardown, :blank_teardown
|
||||
|
||||
def test_no_rollback_in_teardown_unless_transaction_active
|
||||
assert_equal 0, Thread.current['open_transactions']
|
||||
assert_equal 0, ActiveRecord::Base.connection.open_transactions
|
||||
assert_raise(RuntimeError) { ar_setup_fixtures }
|
||||
assert_equal 0, Thread.current['open_transactions']
|
||||
assert_equal 0, ActiveRecord::Base.connection.open_transactions
|
||||
assert_nothing_raised { ar_teardown_fixtures }
|
||||
assert_equal 0, Thread.current['open_transactions']
|
||||
assert_equal 0, ActiveRecord::Base.connection.open_transactions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -143,12 +143,20 @@ class LifecycleTest < ActiveRecord::TestCase
|
||||
assert_equal developer.name, multi_observer.record.name
|
||||
end
|
||||
|
||||
def test_observing_after_find_when_not_defined_on_the_model
|
||||
def test_after_find_cannot_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
|
||||
assert_nil 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_invalid_observer
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -483,6 +493,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 +703,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 +889,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 +993,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_"
|
||||
@@ -1206,10 +1301,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
|
||||
|
||||
|
||||
@@ -57,4 +57,29 @@ class MultipleDbTest < ActiveRecord::TestCase
|
||||
|
||||
assert Course.connection
|
||||
end
|
||||
|
||||
def test_transactions_across_databases
|
||||
c1 = Course.find(1)
|
||||
e1 = Entrant.find(1)
|
||||
|
||||
begin
|
||||
Course.transaction do
|
||||
Entrant.transaction do
|
||||
c1.name = "Typo"
|
||||
e1.name = "Typo"
|
||||
c1.save
|
||||
e1.save
|
||||
raise "No I messed up."
|
||||
end
|
||||
end
|
||||
rescue
|
||||
# Yup caught it
|
||||
end
|
||||
|
||||
assert_equal "Typo", c1.name
|
||||
assert_equal "Typo", e1.name
|
||||
|
||||
assert_equal "Ruby Development", Course.find(1).name
|
||||
assert_equal "Ruby Developer", Entrant.find(1).name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ require 'models/reply'
|
||||
require 'models/author'
|
||||
|
||||
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?
|
||||
@@ -59,6 +59,16 @@ 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_can_be_specified_with_deep_hash_conditions
|
||||
assert_equal Topic.replied.approved, Topic.replied.approved_as_hash_condition
|
||||
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 +87,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 +183,30 @@ class NamedScopeTest < ActiveRecord::TestCase
|
||||
topics.empty? # use loaded (no query)
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_build_with_proxy_options
|
||||
topic = Topic.approved.build({})
|
||||
assert topic.approved
|
||||
end
|
||||
|
||||
def test_should_build_new_with_proxy_options
|
||||
topic = Topic.approved.new
|
||||
assert topic.approved
|
||||
end
|
||||
|
||||
def test_should_create_with_proxy_options
|
||||
topic = Topic.approved.create({})
|
||||
assert topic.approved
|
||||
end
|
||||
|
||||
def test_should_create_with_bang_with_proxy_options
|
||||
topic = Topic.approved.create!({})
|
||||
assert topic.approved
|
||||
end
|
||||
|
||||
def test_should_build_with_proxy_options_chained
|
||||
topic = Topic.approved.by_lifo.build({})
|
||||
assert topic.approved
|
||||
assert_equal 'lifo', topic.author_name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -160,9 +160,9 @@ class ReflectionTest < ActiveRecord::TestCase
|
||||
|
||||
def test_reflection_of_all_associations
|
||||
# FIXME these assertions bust a lot
|
||||
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 24, Firm.reflect_on_all_associations.size
|
||||
assert_equal 18, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 6, 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
|
||||
|
||||
|
||||
@@ -1061,6 +1061,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 }
|
||||
@@ -1381,6 +1393,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)
|
||||
@@ -1392,27 +1405,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
|
||||
|
||||
@@ -1433,7 +1446,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
activerecord/test/fixtures/companies.yml
vendored
1
activerecord/test/fixtures/companies.yml
vendored
@@ -5,6 +5,7 @@ first_client:
|
||||
client_of: 2
|
||||
name: Summit
|
||||
ruby_type: Client
|
||||
firm_name: 37signals
|
||||
|
||||
first_firm:
|
||||
id: 1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Author < ActiveRecord::Base
|
||||
has_many :posts
|
||||
has_many :posts, :accessible => true
|
||||
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"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -46,11 +46,14 @@ class Firm < Company
|
||||
has_many :clients_using_finder_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE 1=1'
|
||||
has_many :plain_clients, :class_name => 'Client'
|
||||
has_many :readonly_clients, :class_name => 'Client', :readonly => true
|
||||
has_many :clients_using_primary_key, :class_name => 'Client',
|
||||
:primary_key => 'name', :foreign_key => 'firm_name'
|
||||
|
||||
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
|
||||
has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account"
|
||||
end
|
||||
|
||||
class DependentFirm < Company
|
||||
|
||||
@@ -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"
|
||||
@@ -28,6 +33,12 @@ class Post < ActiveRecord::Base
|
||||
has_and_belongs_to_many :categories
|
||||
has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id'
|
||||
|
||||
belongs_to :creatable_author, :class_name => 'Author', :accessible => true
|
||||
has_one :uncreatable_comment, :class_name => 'Comment', :accessible => false, :order => 'id desc'
|
||||
has_one :creatable_comment, :class_name => 'Comment', :accessible => true, :order => 'id desc'
|
||||
has_many :creatable_comments, :class_name => 'Comment', :accessible => true, :dependent => :destroy
|
||||
has_and_belongs_to_many :creatable_categories, :class_name => 'Category', :accessible => true
|
||||
|
||||
has_many :taggings, :as => :taggable
|
||||
has_many :tags, :through => :taggings do
|
||||
def add_joins_and_select
|
||||
|
||||
@@ -4,6 +4,10 @@ class Topic < ActiveRecord::Base
|
||||
{ :conditions => ['written_on < ?', time] }
|
||||
}
|
||||
named_scope :approved, :conditions => {:approved => true}
|
||||
named_scope :by_lifo, :conditions => {:author_name => 'lifo'}
|
||||
|
||||
named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
|
||||
named_scope 'approved_as_string', :conditions => {:approved => true}
|
||||
named_scope :replied, :conditions => ['replies_count > 0']
|
||||
named_scope :anonymous_extension do
|
||||
def one
|
||||
|
||||
@@ -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|
|
||||
@@ -102,6 +103,7 @@ ActiveRecord::Schema.define do
|
||||
t.string :type
|
||||
t.string :ruby_type
|
||||
t.integer :firm_id
|
||||
t.string :firm_name
|
||||
t.string :name
|
||||
t.integer :client_of
|
||||
t.integer :rating, :default => 1
|
||||
@@ -407,6 +409,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|
|
||||
|
||||
Reference in New Issue
Block a user