Instiki 0.16.3: Rails 2.3.0
Instiki now runs on the Rails 2.3.0 Candidate Release. Among other improvements, this means that it now automagically selects between WEBrick and Mongrel. Just run ./instiki --daemon
This commit is contained in:
parent
43aadecc99
commit
4e14ccc74d
893 changed files with 71965 additions and 28511 deletions
830
vendor/rails/activerecord/CHANGELOG
vendored
830
vendor/rails/activerecord/CHANGELOG
vendored
File diff suppressed because it is too large
Load diff
2
vendor/rails/activerecord/MIT-LICENSE
vendored
2
vendor/rails/activerecord/MIT-LICENSE
vendored
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
Copyright (c) 2004-2009 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
10
vendor/rails/activerecord/Rakefile
vendored
10
vendor/rails/activerecord/Rakefile
vendored
|
@ -32,9 +32,13 @@ task :default => :test
|
|||
desc 'Run mysql, sqlite, and postgresql tests'
|
||||
task :test => %w(test_mysql test_sqlite3 test_postgresql)
|
||||
|
||||
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
|
||||
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
|
||||
Rake::TestTask.new("test_#{adapter}") { |t|
|
||||
t.libs << "test" << "test/connections/native_#{adapter}"
|
||||
if adapter =~ /jdbc/
|
||||
t.libs << "test" << "test/connections/jdbc_#{adapter}"
|
||||
else
|
||||
t.libs << "test" << "test/connections/native_#{adapter}"
|
||||
end
|
||||
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
|
||||
t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
|
||||
t.verbose = true
|
||||
|
@ -171,7 +175,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
s.add_dependency('activesupport', '= 2.2.2' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD)
|
||||
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
||||
|
|
90
vendor/rails/activerecord/lib/active_record.rb
vendored
90
vendor/rails/activerecord/lib/active_record.rb
vendored
|
@ -1,5 +1,5 @@
|
|||
#--
|
||||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2009 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
@ -31,51 +31,53 @@ rescue LoadError
|
|||
end
|
||||
end
|
||||
|
||||
require 'active_record/base'
|
||||
require 'active_record/named_scope'
|
||||
require 'active_record/observer'
|
||||
require 'active_record/query_cache'
|
||||
require 'active_record/validations'
|
||||
require 'active_record/callbacks'
|
||||
require 'active_record/reflection'
|
||||
require 'active_record/associations'
|
||||
require 'active_record/association_preload'
|
||||
require 'active_record/aggregations'
|
||||
require 'active_record/transactions'
|
||||
require 'active_record/timestamp'
|
||||
require 'active_record/locking/optimistic'
|
||||
require 'active_record/locking/pessimistic'
|
||||
require 'active_record/migration'
|
||||
require 'active_record/schema'
|
||||
require 'active_record/calculations'
|
||||
require 'active_record/serialization'
|
||||
require 'active_record/attribute_methods'
|
||||
require 'active_record/dirty'
|
||||
require 'active_record/dynamic_finder_match'
|
||||
module ActiveRecord
|
||||
# TODO: Review explicit loads to see if they will automatically be handled by the initilizer.
|
||||
def self.load_all!
|
||||
[Base, DynamicFinderMatch, ConnectionAdapters::AbstractAdapter]
|
||||
end
|
||||
|
||||
ActiveRecord::Base.class_eval do
|
||||
extend ActiveRecord::QueryCache
|
||||
include ActiveRecord::Validations
|
||||
include ActiveRecord::Locking::Optimistic
|
||||
include ActiveRecord::Locking::Pessimistic
|
||||
include ActiveRecord::AttributeMethods
|
||||
include ActiveRecord::Dirty
|
||||
include ActiveRecord::Callbacks
|
||||
include ActiveRecord::Observing
|
||||
include ActiveRecord::Timestamp
|
||||
include ActiveRecord::Associations
|
||||
include ActiveRecord::NamedScope
|
||||
include ActiveRecord::AssociationPreload
|
||||
include ActiveRecord::Aggregations
|
||||
include ActiveRecord::Transactions
|
||||
include ActiveRecord::Reflection
|
||||
include ActiveRecord::Calculations
|
||||
include ActiveRecord::Serialization
|
||||
autoload :VERSION, 'active_record/version'
|
||||
|
||||
autoload :ActiveRecordError, 'active_record/base'
|
||||
autoload :ConnectionNotEstablished, 'active_record/base'
|
||||
|
||||
autoload :Aggregations, 'active_record/aggregations'
|
||||
autoload :AssociationPreload, 'active_record/association_preload'
|
||||
autoload :Associations, 'active_record/associations'
|
||||
autoload :AttributeMethods, 'active_record/attribute_methods'
|
||||
autoload :AutosaveAssociation, 'active_record/autosave_association'
|
||||
autoload :Base, 'active_record/base'
|
||||
autoload :Calculations, 'active_record/calculations'
|
||||
autoload :Callbacks, 'active_record/callbacks'
|
||||
autoload :Dirty, 'active_record/dirty'
|
||||
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
|
||||
autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
|
||||
autoload :Migration, 'active_record/migration'
|
||||
autoload :Migrator, 'active_record/migration'
|
||||
autoload :NamedScope, 'active_record/named_scope'
|
||||
autoload :NestedAttributes, 'active_record/nested_attributes'
|
||||
autoload :Observing, 'active_record/observer'
|
||||
autoload :QueryCache, 'active_record/query_cache'
|
||||
autoload :Reflection, 'active_record/reflection'
|
||||
autoload :Schema, 'active_record/schema'
|
||||
autoload :SchemaDumper, 'active_record/schema_dumper'
|
||||
autoload :Serialization, 'active_record/serialization'
|
||||
autoload :SessionStore, 'active_record/session_store'
|
||||
autoload :TestCase, 'active_record/test_case'
|
||||
autoload :Timestamp, 'active_record/timestamp'
|
||||
autoload :Transactions, 'active_record/transactions'
|
||||
autoload :Validations, 'active_record/validations'
|
||||
|
||||
module Locking
|
||||
autoload :Optimistic, 'active_record/locking/optimistic'
|
||||
autoload :Pessimistic, 'active_record/locking/pessimistic'
|
||||
end
|
||||
|
||||
module ConnectionAdapters
|
||||
autoload :AbstractAdapter, 'active_record/connection_adapters/abstract_adapter'
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
||||
require 'active_record/schema_dumper'
|
||||
|
||||
require 'active_record/i18n_interpolation_deprecation'
|
||||
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
|
||||
|
|
|
@ -43,7 +43,7 @@ module ActiveRecord
|
|||
# loading in a more high-level (application developer-friendly) manner.
|
||||
module ClassMethods
|
||||
protected
|
||||
|
||||
|
||||
# Eager loads the named associations for the given ActiveRecord record(s).
|
||||
#
|
||||
# In this description, 'association name' shall refer to the name passed
|
||||
|
@ -94,8 +94,8 @@ module ActiveRecord
|
|||
raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
|
||||
preload_associations(records, parent, preload_options)
|
||||
reflection = reflections[parent]
|
||||
parents = records.map {|record| record.send(reflection.name)}.flatten
|
||||
unless parents.empty? || parents.first.nil?
|
||||
parents = records.map {|record| record.send(reflection.name)}.flatten.compact
|
||||
unless parents.empty?
|
||||
parents.first.class.preload_associations(parents, child)
|
||||
end
|
||||
end
|
||||
|
@ -113,7 +113,7 @@ module ActiveRecord
|
|||
# unnecessarily
|
||||
records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
|
||||
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
|
||||
|
||||
|
||||
# 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
|
||||
# the following could call 'preload_belongs_to_association',
|
||||
# 'preload_has_many_association', etc.
|
||||
|
@ -128,7 +128,7 @@ module ActiveRecord
|
|||
association_proxy.target.push(*[associated_record].flatten)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
|
||||
parent_records.each do |parent_record|
|
||||
parent_record.send("set_#{reflection_name}_target", associated_record)
|
||||
|
@ -183,18 +183,19 @@ module ActiveRecord
|
|||
conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
|
||||
conditions << append_conditions(reflection, preload_options)
|
||||
|
||||
associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
|
||||
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
|
||||
:order => options[:order])
|
||||
|
||||
associated_records = reflection.klass.with_exclusive_scope do
|
||||
reflection.klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
|
||||
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
|
||||
:order => options[:order])
|
||||
end
|
||||
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
|
||||
end
|
||||
|
||||
def preload_has_one_association(records, reflection, preload_options={})
|
||||
return if records.first.send("loaded_#{reflection.name}?")
|
||||
id_to_record_map, ids = construct_id_map(records)
|
||||
id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
|
||||
options = reflection.options
|
||||
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
|
||||
if options[:through]
|
||||
|
@ -204,9 +205,18 @@ module ActiveRecord
|
|||
unless through_records.empty?
|
||||
source = reflection.source_reflection.name
|
||||
through_records.first.class.preload_associations(through_records, source)
|
||||
through_records.each do |through_record|
|
||||
add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
|
||||
reflection.name, through_record.send(source))
|
||||
if through_reflection.macro == :belongs_to
|
||||
rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
|
||||
rev_primary_key = through_reflection.klass.primary_key
|
||||
through_records.each do |through_record|
|
||||
add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
|
||||
reflection.name, through_record.send(source))
|
||||
end
|
||||
else
|
||||
through_records.each do |through_record|
|
||||
add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
|
||||
reflection.name, through_record.send(source))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -219,7 +229,7 @@ module ActiveRecord
|
|||
options = reflection.options
|
||||
|
||||
primary_key_name = reflection.through_reflection_primary_key_name
|
||||
id_to_record_map, ids = construct_id_map(records, primary_key_name)
|
||||
id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
|
||||
records.each {|record| record.send(reflection.name).loaded}
|
||||
|
||||
if options[:through]
|
||||
|
@ -239,7 +249,7 @@ module ActiveRecord
|
|||
reflection.primary_key_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def preload_through_records(records, reflection, through_association)
|
||||
through_reflection = reflections[through_association]
|
||||
through_primary_key = through_reflection.primary_key_name
|
||||
|
@ -307,6 +317,7 @@ module ActiveRecord
|
|||
|
||||
klasses_and_ids.each do |klass_and_id|
|
||||
klass_name, id_map = *klass_and_id
|
||||
next if id_map.empty?
|
||||
klass = klass_name.constantize
|
||||
|
||||
table_name = klass.quoted_table_name
|
||||
|
@ -323,11 +334,13 @@ module ActiveRecord
|
|||
end
|
||||
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
|
||||
conditions << append_conditions(reflection, preload_options)
|
||||
associated_records = klass.find(:all, :conditions => [conditions, ids],
|
||||
associated_records = klass.with_exclusive_scope do
|
||||
klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:select => options[:select],
|
||||
:joins => options[:joins],
|
||||
:order => options[:order])
|
||||
end
|
||||
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
|
||||
end
|
||||
end
|
||||
|
@ -345,13 +358,15 @@ module ActiveRecord
|
|||
|
||||
conditions << append_conditions(reflection, preload_options)
|
||||
|
||||
reflection.klass.find(:all,
|
||||
reflection.klass.with_exclusive_scope do
|
||||
reflection.klass.find(:all,
|
||||
:select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
|
||||
:include => preload_options[:include] || options[:include],
|
||||
:conditions => [conditions, ids],
|
||||
:joins => options[:joins],
|
||||
:group => preload_options[:group] || options[:group],
|
||||
:order => preload_options[:order] || options[:order])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
require 'active_record/associations/association_proxy'
|
||||
require 'active_record/associations/association_collection'
|
||||
require 'active_record/associations/belongs_to_association'
|
||||
require 'active_record/associations/belongs_to_polymorphic_association'
|
||||
require 'active_record/associations/has_one_association'
|
||||
require 'active_record/associations/has_many_association'
|
||||
require 'active_record/associations/has_many_through_association'
|
||||
require 'active_record/associations/has_and_belongs_to_many_association'
|
||||
require 'active_record/associations/has_one_through_association'
|
||||
|
||||
module ActiveRecord
|
||||
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
|
||||
def initialize(owner_class_name, reflection)
|
||||
|
@ -32,7 +22,7 @@ module ActiveRecord
|
|||
through_reflection = reflection.through_reflection
|
||||
source_reflection_names = reflection.source_reflection_names
|
||||
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
|
||||
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
|
||||
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '}?")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -75,6 +65,18 @@ module ActiveRecord
|
|||
|
||||
# See ActiveRecord::Associations::ClassMethods for documentation.
|
||||
module Associations # :nodoc:
|
||||
# These classes will be loaded when associatoins are created.
|
||||
# So there is no need to eager load them.
|
||||
autoload :AssociationCollection, 'active_record/associations/association_collection'
|
||||
autoload :AssociationProxy, 'active_record/associations/association_proxy'
|
||||
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
|
||||
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
|
||||
autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
|
||||
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
|
||||
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
|
||||
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
|
||||
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
@ -86,6 +88,18 @@ module ActiveRecord
|
|||
end unless self.new_record?
|
||||
end
|
||||
|
||||
private
|
||||
# Gets the specified association instance if it responds to :loaded?, nil otherwise.
|
||||
def association_instance_get(name)
|
||||
association = instance_variable_get("@#{name}")
|
||||
association if association.respond_to?(:loaded?)
|
||||
end
|
||||
|
||||
# Set the specified association instance.
|
||||
def association_instance_set(name, association)
|
||||
instance_variable_set("@#{name}", association)
|
||||
end
|
||||
|
||||
# Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
|
||||
# "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
|
||||
# specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
|
||||
|
@ -151,7 +165,7 @@ module ActiveRecord
|
|||
# #others.destroy_all | X | X | X
|
||||
# #others.find(*args) | X | X | X
|
||||
# #others.find_first | X | |
|
||||
# #others.exist? | X | X | X
|
||||
# #others.exists? | X | X | X
|
||||
# #others.uniq | X | X | X
|
||||
# #others.reset | X | X | X
|
||||
#
|
||||
|
@ -254,6 +268,10 @@ module ActiveRecord
|
|||
# You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
|
||||
# aware of, mostly involving the saving of associated objects.
|
||||
#
|
||||
# Unless you enable the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
|
||||
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association,
|
||||
# in which case the members are always saved.
|
||||
#
|
||||
# === One-to-one associations
|
||||
#
|
||||
# * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
|
||||
|
@ -650,7 +668,7 @@ module ActiveRecord
|
|||
# Returns the number of associated objects.
|
||||
# [collection.find(...)]
|
||||
# Finds an associated object according to the same rules as ActiveRecord::Base.find.
|
||||
# [collection.exist?(...)]
|
||||
# [collection.exists?(...)]
|
||||
# Checks whether an associated object with the given conditions exists.
|
||||
# Uses the same rules as ActiveRecord::Base.exists?.
|
||||
# [collection.build(attributes = {}, ...)]
|
||||
|
@ -680,7 +698,7 @@ module ActiveRecord
|
|||
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
|
||||
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
|
||||
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
|
||||
# * <tt>Firm#clients.exist?(:name => 'ACME')</tt> (similar to <tt>Client.exist?(:name => 'ACME', :firm_id => firm.id)</tt>)
|
||||
# * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>)
|
||||
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
|
||||
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
|
||||
# The declaration can also include an options hash to specialize the behavior of the association.
|
||||
|
@ -722,6 +740,8 @@ module ActiveRecord
|
|||
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
||||
# [:group]
|
||||
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
||||
# [:having]
|
||||
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
|
||||
# [:limit]
|
||||
# An integer determining the limit on the number of rows that should be returned.
|
||||
# [:offset]
|
||||
|
@ -748,6 +768,9 @@ module ActiveRecord
|
|||
# If true, all the associated objects are readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated objects when saving the parent object. true by default.
|
||||
# [:autosave]
|
||||
# If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_many :comments, :order => "posted_on"
|
||||
# has_many :comments, :include => :author
|
||||
|
@ -861,6 +884,8 @@ module ActiveRecord
|
|||
# If true, the associated object is readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated object when saving the parent object. +false+ by default.
|
||||
# [:autosave]
|
||||
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
||||
|
@ -878,13 +903,10 @@ module ActiveRecord
|
|||
else
|
||||
reflection = create_has_one_reflection(association_id, options)
|
||||
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
method_name = "has_one_after_save_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
|
||||
if !association.nil? && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
|
||||
association = association_instance_get(reflection.name)
|
||||
if association && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
|
||||
association[reflection.primary_key_name] = id
|
||||
association.save(true)
|
||||
end
|
||||
|
@ -975,6 +997,8 @@ module ActiveRecord
|
|||
# If true, the associated object is readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
|
||||
# [:autosave]
|
||||
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# belongs_to :firm, :foreign_key => "client_of"
|
||||
|
@ -987,15 +1011,12 @@ module ActiveRecord
|
|||
def belongs_to(association_id, options = {})
|
||||
reflection = create_belongs_to_reflection(association_id, options)
|
||||
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
if reflection.options[:polymorphic]
|
||||
association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
|
||||
|
||||
method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
|
||||
association = association_instance_get(reflection.name)
|
||||
if association && association.target
|
||||
if association.new_record?
|
||||
association.save(true)
|
||||
|
@ -1015,9 +1036,7 @@ module ActiveRecord
|
|||
|
||||
method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
|
||||
if !association.nil?
|
||||
if association = association_instance_get(reflection.name)
|
||||
if association.new_record?
|
||||
association.save(true)
|
||||
end
|
||||
|
@ -1103,7 +1122,7 @@ module ActiveRecord
|
|||
# Finds an associated object responding to the +id+ and that
|
||||
# meets the condition that it has to be associated with this object.
|
||||
# Uses the same rules as ActiveRecord::Base.find.
|
||||
# [collection.exist?(...)]
|
||||
# [collection.exists?(...)]
|
||||
# Checks whether an associated object with the given conditions exists.
|
||||
# Uses the same rules as ActiveRecord::Base.exists?.
|
||||
# [collection.build(attributes = {})]
|
||||
|
@ -1129,7 +1148,7 @@ module ActiveRecord
|
|||
# * <tt>Developer#projects.empty?</tt>
|
||||
# * <tt>Developer#projects.size</tt>
|
||||
# * <tt>Developer#projects.find(id)</tt>
|
||||
# * <tt>Developer#clients.exist?(...)</tt>
|
||||
# * <tt>Developer#clients.exists?(...)</tt>
|
||||
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
|
||||
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
|
||||
# The declaration may include an options hash to specialize the behavior of the association.
|
||||
|
@ -1147,11 +1166,12 @@ module ActiveRecord
|
|||
# [:foreign_key]
|
||||
# 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_and_belongs_to_many+ association
|
||||
# will use "person_id" as the default <tt>:foreign_key</tt>.
|
||||
# to Project will use "person_id" as the default <tt>:foreign_key</tt>.
|
||||
# [:association_foreign_key]
|
||||
# Specify the association foreign key used for the association. By default this is
|
||||
# guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project,
|
||||
# the +has_and_belongs_to_many+ association will use "project_id" as the default <tt>:association_foreign_key</tt>.
|
||||
# Specify the foreign key used for the association on the receiving side of the association.
|
||||
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
|
||||
# So if a Person class makes a +has_and_belongs_to_many+ association to Project,
|
||||
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
|
||||
# [:conditions]
|
||||
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
||||
# SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
|
||||
|
@ -1179,6 +1199,8 @@ module ActiveRecord
|
|||
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
||||
# [:group]
|
||||
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
||||
# [:having]
|
||||
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
|
||||
# [:limit]
|
||||
# An integer determining the limit on the number of rows that should be returned.
|
||||
# [:offset]
|
||||
|
@ -1190,6 +1212,8 @@ module ActiveRecord
|
|||
# If true, all the associated objects are readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
|
||||
# [:autosave]
|
||||
# If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_and_belongs_to_many :projects
|
||||
|
@ -1210,11 +1234,11 @@ module ActiveRecord
|
|||
# callbacks will be executed after the association is wiped out.
|
||||
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
|
||||
class_eval <<-end_eval unless method_defined?(old_method)
|
||||
alias_method :#{old_method}, :destroy_without_callbacks
|
||||
def destroy_without_callbacks
|
||||
#{reflection.name}.clear
|
||||
#{old_method}
|
||||
end
|
||||
alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
|
||||
def destroy_without_callbacks # def destroy_without_callbacks
|
||||
#{reflection.name}.clear # posts.clear
|
||||
#{old_method} # destroy_without_habtm_shim_for_posts
|
||||
end # end
|
||||
end_eval
|
||||
|
||||
add_association_callbacks(reflection.name, options)
|
||||
|
@ -1237,33 +1261,30 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def association_accessor_methods(reflection, association_proxy_class)
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
define_method(reflection.name) do |*params|
|
||||
force_reload = params.first unless params.empty?
|
||||
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
if association.nil? || force_reload
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
retval = association.reload
|
||||
if retval.nil? and association_proxy_class == BelongsToAssociation
|
||||
instance_variable_set(ivar, nil)
|
||||
association_instance_set(reflection.name, nil)
|
||||
return nil
|
||||
end
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
|
||||
association.target.nil? ? nil : association
|
||||
end
|
||||
|
||||
define_method("loaded_#{reflection.name}?") do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
association && association.loaded?
|
||||
end
|
||||
|
||||
define_method("#{reflection.name}=") do |new_value|
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
if association.nil? || association.target != new_value
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
|
@ -1274,7 +1295,7 @@ module ActiveRecord
|
|||
self.send(reflection.name, new_value)
|
||||
else
|
||||
association.replace(new_value)
|
||||
instance_variable_set(ivar, new_value.nil? ? nil : association)
|
||||
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1282,20 +1303,18 @@ module ActiveRecord
|
|||
return if target.nil? and association_proxy_class == BelongsToAssociation
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
association.target = target
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
end
|
||||
|
||||
def collection_reader_method(reflection, association_proxy_class)
|
||||
define_method(reflection.name) do |*params|
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
force_reload = params.first unless params.empty?
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
unless association.respond_to?(:loaded?)
|
||||
unless association
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
|
||||
association.reload if force_reload
|
||||
|
@ -1333,8 +1352,7 @@ module ActiveRecord
|
|||
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}")
|
||||
if !association.nil?
|
||||
if association = association_instance_get(association_name)
|
||||
errors.add association_name unless association.target.nil? || association.valid?
|
||||
end
|
||||
end
|
||||
|
@ -1344,12 +1362,10 @@ module ActiveRecord
|
|||
|
||||
def add_multiple_associated_validation_callbacks(association_name)
|
||||
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
||||
ivar = "@#{association_name}"
|
||||
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(association_name)
|
||||
|
||||
if association.respond_to?(:loaded?)
|
||||
if association
|
||||
if new_record?
|
||||
association
|
||||
elsif association.loaded?
|
||||
|
@ -1366,8 +1382,6 @@ module ActiveRecord
|
|||
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
|
||||
@new_record_before_save = new_record?
|
||||
|
@ -1377,13 +1391,13 @@ module ActiveRecord
|
|||
|
||||
method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(association_name)
|
||||
|
||||
records_to_save = if @new_record_before_save
|
||||
association
|
||||
elsif association.respond_to?(:loaded?) && association.loaded?
|
||||
elsif association && association.loaded?
|
||||
association.select { |record| record.new_record? }
|
||||
elsif association.respond_to?(:loaded?) && !association.loaded?
|
||||
elsif association && !association.loaded?
|
||||
association.target.select { |record| record.new_record? }
|
||||
else
|
||||
[]
|
||||
|
@ -1401,15 +1415,13 @@ module ActiveRecord
|
|||
|
||||
def association_constructor_method(constructor, reflection, association_proxy_class)
|
||||
define_method("#{constructor}_#{reflection.name}") do |*params|
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
attributees = params.first unless params.empty?
|
||||
replace_existing = params[1].nil? ? true : params[1]
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
if association.nil?
|
||||
unless association
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
|
||||
if association_proxy_class == HasOneAssociation
|
||||
|
@ -1447,7 +1459,7 @@ module ActiveRecord
|
|||
dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
|
||||
dependent_conditions << extra_conditions if extra_conditions
|
||||
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
||||
|
||||
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
||||
case reflection.options[:dependent]
|
||||
when :destroy
|
||||
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
|
||||
|
@ -1457,22 +1469,22 @@ module ActiveRecord
|
|||
before_destroy method_name
|
||||
when :delete_all
|
||||
module_eval %Q{
|
||||
before_destroy do |record|
|
||||
delete_all_has_many_dependencies(record,
|
||||
"#{reflection.name}",
|
||||
#{reflection.class_name},
|
||||
"#{dependent_conditions}")
|
||||
end
|
||||
before_destroy do |record| # before_destroy do |record|
|
||||
delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record,
|
||||
"#{reflection.name}", # "posts",
|
||||
#{reflection.class_name}, # Post,
|
||||
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
||||
end # end
|
||||
}
|
||||
when :nullify
|
||||
module_eval %Q{
|
||||
before_destroy do |record|
|
||||
nullify_has_many_dependencies(record,
|
||||
"#{reflection.name}",
|
||||
#{reflection.class_name},
|
||||
"#{reflection.primary_key_name}",
|
||||
"#{dependent_conditions}")
|
||||
end
|
||||
before_destroy do |record| # before_destroy do |record|
|
||||
nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record,
|
||||
"#{reflection.name}", # "posts",
|
||||
#{reflection.class_name}, # Post,
|
||||
"#{reflection.primary_key_name}", # "user_id",
|
||||
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
||||
end # end
|
||||
}
|
||||
else
|
||||
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
|
||||
|
@ -1525,14 +1537,14 @@ module ActiveRecord
|
|||
association = send(reflection.name)
|
||||
association.destroy unless association.nil?
|
||||
end
|
||||
before_destroy method_name
|
||||
after_destroy method_name
|
||||
when :delete
|
||||
method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = send(reflection.name)
|
||||
association.delete unless association.nil?
|
||||
end
|
||||
before_destroy method_name
|
||||
after_destroy method_name
|
||||
else
|
||||
raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
|
||||
end
|
||||
|
@ -1551,7 +1563,7 @@ module ActiveRecord
|
|||
@@valid_keys_for_has_many_association = [
|
||||
:class_name, :table_name, :foreign_key, :primary_key,
|
||||
:dependent,
|
||||
:select, :conditions, :include, :order, :group, :limit, :offset,
|
||||
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
||||
:as, :through, :source, :source_type,
|
||||
:uniq,
|
||||
:finder_sql, :counter_sql,
|
||||
|
@ -1607,7 +1619,7 @@ module ActiveRecord
|
|||
mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
|
||||
@@valid_keys_for_has_and_belongs_to_many_association = [
|
||||
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
|
||||
:select, :conditions, :include, :order, :group, :limit, :offset,
|
||||
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
||||
:uniq,
|
||||
:finder_sql, :counter_sql, :delete_sql, :insert_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
|
@ -1656,7 +1668,7 @@ module ActiveRecord
|
|||
add_conditions!(sql, options[:conditions], scope)
|
||||
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
||||
|
||||
add_group!(sql, options[:group], scope)
|
||||
add_group!(sql, options[:group], options[:having], scope)
|
||||
add_order!(sql, options[:order], scope)
|
||||
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
||||
add_lock!(sql, options, scope)
|
||||
|
@ -1712,7 +1724,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
add_conditions!(sql, options[:conditions], scope)
|
||||
add_group!(sql, options[:group], scope)
|
||||
add_group!(sql, options[:group], options[:having], scope)
|
||||
|
||||
if order && is_distinct
|
||||
connection.add_order_by_for_association_limiting!(sql, :order => order)
|
||||
|
@ -1725,46 +1737,70 @@ module ActiveRecord
|
|||
return sanitize_sql(sql)
|
||||
end
|
||||
|
||||
def tables_in_string(string)
|
||||
return [] if string.blank?
|
||||
string.scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
end
|
||||
|
||||
def conditions_tables(options)
|
||||
# look in both sets of conditions
|
||||
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
|
||||
case cond
|
||||
when nil then all
|
||||
when Array then all << cond.first
|
||||
when Hash then all << cond.keys
|
||||
else all << cond
|
||||
end
|
||||
end
|
||||
conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
tables_in_string(conditions.join(' '))
|
||||
end
|
||||
|
||||
def order_tables(options)
|
||||
order = [options[:order], scope(:find, :order) ].join(", ")
|
||||
return [] unless order && order.is_a?(String)
|
||||
order.scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
tables_in_string(order)
|
||||
end
|
||||
|
||||
def selects_tables(options)
|
||||
select = options[:select]
|
||||
return [] unless select && select.is_a?(String)
|
||||
select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
|
||||
tables_in_string(select)
|
||||
end
|
||||
|
||||
def joined_tables(options)
|
||||
scope = scope(:find)
|
||||
joins = options[:joins]
|
||||
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
|
||||
[table_name] + case merged_joins
|
||||
when Symbol, Hash, Array
|
||||
if array_of_strings?(merged_joins)
|
||||
tables_in_string(merged_joins.join(' '))
|
||||
else
|
||||
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
|
||||
join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
|
||||
end
|
||||
else
|
||||
tables_in_string(merged_joins)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the conditions reference a table other than the current model table
|
||||
def include_eager_conditions?(options, tables = nil)
|
||||
((tables || conditions_tables(options)) - [table_name]).any?
|
||||
def include_eager_conditions?(options, tables = nil, joined_tables = nil)
|
||||
((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
|
||||
end
|
||||
|
||||
# Checks if the query order references a table other than the current model's table.
|
||||
def include_eager_order?(options, tables = nil)
|
||||
((tables || order_tables(options)) - [table_name]).any?
|
||||
def include_eager_order?(options, tables = nil, joined_tables = nil)
|
||||
((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
|
||||
end
|
||||
|
||||
def include_eager_select?(options)
|
||||
(selects_tables(options) - [table_name]).any?
|
||||
def include_eager_select?(options, joined_tables = nil)
|
||||
(selects_tables(options) - (joined_tables || joined_tables(options))).any?
|
||||
end
|
||||
|
||||
def references_eager_loaded_tables?(options)
|
||||
include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
|
||||
joined_tables = joined_tables(options)
|
||||
include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
|
||||
end
|
||||
|
||||
def using_limitable_reflections?(reflections)
|
||||
|
@ -2141,7 +2177,7 @@ module ActiveRecord
|
|||
aliased_table_name,
|
||||
foreign_key,
|
||||
parent.aliased_table_name,
|
||||
parent.primary_key
|
||||
reflection.options[:primary_key] || parent.primary_key
|
||||
]
|
||||
end
|
||||
when :belongs_to
|
||||
|
@ -2168,7 +2204,7 @@ module ActiveRecord
|
|||
protected
|
||||
|
||||
def aliased_table_name_for(name, suffix = nil)
|
||||
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
|
||||
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{active_record.connection.quote_table_name name.downcase}\son}
|
||||
@join_dependency.table_aliases[name] += 1
|
||||
end
|
||||
|
||||
|
|
|
@ -83,7 +83,11 @@ module ActiveRecord
|
|||
|
||||
def to_ary
|
||||
load_target
|
||||
@target.to_ary
|
||||
if @target.is_a?(Array)
|
||||
@target.to_ary
|
||||
else
|
||||
Array(@target)
|
||||
end
|
||||
end
|
||||
|
||||
def reset
|
||||
|
|
|
@ -180,7 +180,10 @@ module ActiveRecord
|
|||
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
||||
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
||||
else
|
||||
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
||||
unless @owner.new_record?
|
||||
primary_key = @reflection.options[:primary_key] || :id
|
||||
record[@reflection.primary_key_name] = @owner.send(primary_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -188,6 +191,7 @@ module ActiveRecord
|
|||
def merge_options_from_reflection!(options)
|
||||
options.reverse_merge!(
|
||||
:group => @reflection.options[:group],
|
||||
:having => @reflection.options[:having],
|
||||
:limit => @reflection.options[:limit],
|
||||
:offset => @reflection.options[:offset],
|
||||
:joins => @reflection.options[:joins],
|
||||
|
@ -206,7 +210,10 @@ module ActiveRecord
|
|||
# Forwards any missing method call to the \target.
|
||||
def method_missing(method, *args)
|
||||
if load_target
|
||||
raise NoMethodError unless @target.respond_to?(method)
|
||||
unless @target.respond_to?(method)
|
||||
message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
|
||||
raise NoMethodError, message
|
||||
end
|
||||
|
||||
if block_given?
|
||||
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
||||
|
|
|
@ -9,6 +9,14 @@ module ActiveRecord
|
|||
create_record(attributes) { |record| insert_record(record, true) }
|
||||
end
|
||||
|
||||
def columns
|
||||
@reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
||||
end
|
||||
|
||||
def reset_column_information
|
||||
@reflection.reset_column_information
|
||||
end
|
||||
|
||||
protected
|
||||
def construct_find_options!(options)
|
||||
options[:joins] = @join_sql
|
||||
|
@ -32,8 +40,6 @@ module ActiveRecord
|
|||
if @reflection.options[:insert_sql]
|
||||
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
|
||||
else
|
||||
columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
||||
|
||||
attributes = columns.inject({}) do |attrs, column|
|
||||
case column.name.to_s
|
||||
when @reflection.primary_key_name.to_s
|
||||
|
@ -103,7 +109,7 @@ module ActiveRecord
|
|||
# clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
|
||||
# an id column. This will then overwrite the id column of the records coming back.
|
||||
def finding_with_ambiguous_select?(select_clause)
|
||||
!select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
|
||||
!select_clause && columns.size != 2
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -160,9 +160,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
"INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
|
||||
@reflection.through_reflection.table_name,
|
||||
@reflection.table_name, reflection_primary_key,
|
||||
@reflection.through_reflection.table_name, source_primary_key,
|
||||
@reflection.through_reflection.quoted_table_name,
|
||||
@reflection.quoted_table_name, reflection_primary_key,
|
||||
@reflection.through_reflection.quoted_table_name, source_primary_key,
|
||||
polymorphic_join
|
||||
]
|
||||
end
|
||||
|
|
213
vendor/rails/activerecord/lib/active_record/autosave_association.rb
vendored
Normal file
213
vendor/rails/activerecord/lib/active_record/autosave_association.rb
vendored
Normal file
|
@ -0,0 +1,213 @@
|
|||
module ActiveRecord
|
||||
# AutosaveAssociation is a module that takes care of automatically saving
|
||||
# your associations when the parent is saved. In addition to saving, it
|
||||
# also destroys any associations that were marked for destruction.
|
||||
# (See mark_for_destruction and marked_for_destruction?)
|
||||
#
|
||||
# Saving of the parent, its associations, and the destruction of marked
|
||||
# associations, all happen inside 1 transaction. This should never leave the
|
||||
# database in an inconsistent state after, for instance, mass assigning
|
||||
# attributes and saving them.
|
||||
#
|
||||
# If validations for any of the associations fail, their error messages will
|
||||
# be applied to the parent.
|
||||
#
|
||||
# Note that it also means that associations marked for destruction won't
|
||||
# be destroyed directly. They will however still be marked for destruction.
|
||||
#
|
||||
# === One-to-one Example
|
||||
#
|
||||
# Consider a Post model with one Author:
|
||||
#
|
||||
# class Post
|
||||
# has_one :author, :autosave => true
|
||||
# end
|
||||
#
|
||||
# Saving changes to the parent and its associated model can now be performed
|
||||
# automatically _and_ atomically:
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.title # => "The current global position of migrating ducks"
|
||||
# post.author.name # => "alloy"
|
||||
#
|
||||
# post.title = "On the migration of ducks"
|
||||
# post.author.name = "Eloy Duran"
|
||||
#
|
||||
# post.save
|
||||
# post.reload
|
||||
# post.title # => "On the migration of ducks"
|
||||
# post.author.name # => "Eloy Duran"
|
||||
#
|
||||
# Destroying an associated model, as part of the parent's save action, is as
|
||||
# simple as marking it for destruction:
|
||||
#
|
||||
# post.author.mark_for_destruction
|
||||
# post.author.marked_for_destruction? # => true
|
||||
#
|
||||
# Note that the model is _not_ yet removed from the database:
|
||||
# id = post.author.id
|
||||
# Author.find_by_id(id).nil? # => false
|
||||
#
|
||||
# post.save
|
||||
# post.reload.author # => nil
|
||||
#
|
||||
# Now it _is_ removed from the database:
|
||||
# Author.find_by_id(id).nil? # => true
|
||||
#
|
||||
# === One-to-many Example
|
||||
#
|
||||
# Consider a Post model with many Comments:
|
||||
#
|
||||
# class Post
|
||||
# has_many :comments, :autosave => true
|
||||
# end
|
||||
#
|
||||
# Saving changes to the parent and its associated model can now be performed
|
||||
# automatically _and_ atomically:
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.title # => "The current global position of migrating ducks"
|
||||
# post.comments.first.body # => "Wow, awesome info thanks!"
|
||||
# post.comments.last.body # => "Actually, your article should be named differently."
|
||||
#
|
||||
# post.title = "On the migration of ducks"
|
||||
# post.comments.last.body = "Actually, your article should be named differently. [UPDATED]: You are right, thanks."
|
||||
#
|
||||
# post.save
|
||||
# post.reload
|
||||
# post.title # => "On the migration of ducks"
|
||||
# post.comments.last.body # => "Actually, your article should be named differently. [UPDATED]: You are right, thanks."
|
||||
#
|
||||
# Destroying one of the associated models members, as part of the parent's
|
||||
# save action, is as simple as marking it for destruction:
|
||||
#
|
||||
# post.comments.last.mark_for_destruction
|
||||
# post.comments.last.marked_for_destruction? # => true
|
||||
# post.comments.length # => 2
|
||||
#
|
||||
# Note that the model is _not_ yet removed from the database:
|
||||
# id = post.comments.last.id
|
||||
# Comment.find_by_id(id).nil? # => false
|
||||
#
|
||||
# post.save
|
||||
# post.reload.comments.length # => 1
|
||||
#
|
||||
# Now it _is_ removed from the database:
|
||||
# Comment.find_by_id(id).nil? # => true
|
||||
#
|
||||
# === Validation
|
||||
#
|
||||
# Validation is performed on the parent as usual, but also on all autosave
|
||||
# enabled associations. If any of the associations fail validation, its
|
||||
# error messages will be applied on the parents errors object and validation
|
||||
# of the parent will fail.
|
||||
#
|
||||
# Consider a Post model with Author which validates the presence of its name
|
||||
# attribute:
|
||||
#
|
||||
# class Post
|
||||
# has_one :author, :autosave => true
|
||||
# end
|
||||
#
|
||||
# class Author
|
||||
# validates_presence_of :name
|
||||
# end
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.author.name = ''
|
||||
# post.save # => false
|
||||
# post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author_name"=>["can't be blank"]}, @base=#<Post ...>>
|
||||
#
|
||||
# No validations will be performed on the associated models when validations
|
||||
# are skipped for the parent:
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.author.name = ''
|
||||
# post.save(false) # => true
|
||||
module AutosaveAssociation
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
alias_method_chain :reload, :autosave_associations
|
||||
alias_method_chain :save, :autosave_associations
|
||||
alias_method_chain :valid?, :autosave_associations
|
||||
|
||||
%w{ has_one belongs_to has_many has_and_belongs_to_many }.each do |type|
|
||||
base.send("valid_keys_for_#{type}_association") << :autosave
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Saves the parent, <tt>self</tt>, and any loaded autosave associations.
|
||||
# In addition, it destroys all children that were marked for destruction
|
||||
# with mark_for_destruction.
|
||||
#
|
||||
# This all happens inside a transaction, _if_ the Transactions module is included into
|
||||
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
||||
def save_with_autosave_associations(perform_validation = true)
|
||||
returning(save_without_autosave_associations(perform_validation)) do |valid|
|
||||
if valid
|
||||
self.class.reflect_on_all_autosave_associations.each do |reflection|
|
||||
if (association = association_instance_get(reflection.name)) && association.loaded?
|
||||
if association.is_a?(Array)
|
||||
association.proxy_target.each do |child|
|
||||
child.marked_for_destruction? ? child.destroy : child.save(perform_validation)
|
||||
end
|
||||
else
|
||||
association.marked_for_destruction? ? association.destroy : association.save(perform_validation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns whether or not the parent, <tt>self</tt>, and any loaded autosave associations are valid.
|
||||
def valid_with_autosave_associations?
|
||||
if valid_without_autosave_associations?
|
||||
self.class.reflect_on_all_autosave_associations.all? do |reflection|
|
||||
if (association = association_instance_get(reflection.name)) && association.loaded?
|
||||
if association.is_a?(Array)
|
||||
association.proxy_target.all? { |child| autosave_association_valid?(reflection, child) }
|
||||
else
|
||||
autosave_association_valid?(reflection, association)
|
||||
end
|
||||
else
|
||||
true # association not loaded yet, so it should be valid
|
||||
end
|
||||
end
|
||||
else
|
||||
false # self was not valid
|
||||
end
|
||||
end
|
||||
|
||||
# Returns whether or not the association is valid and applies any errors to the parent, <tt>self</tt>, if it wasn't.
|
||||
def autosave_association_valid?(reflection, association)
|
||||
returning(association.valid?) do |valid|
|
||||
association.errors.each do |attribute, message|
|
||||
errors.add "#{reflection.name}_#{attribute}", message
|
||||
end unless valid
|
||||
end
|
||||
end
|
||||
|
||||
# Reloads the attributes of the object as usual and removes a mark for destruction.
|
||||
def reload_with_autosave_associations(options = nil)
|
||||
@marked_for_destruction = false
|
||||
reload_without_autosave_associations(options)
|
||||
end
|
||||
|
||||
# Marks this record to be destroyed as part of the parents save transaction.
|
||||
# This does _not_ actually destroy the record yet, rather it will be destroyed when <tt>parent.save</tt> is called.
|
||||
#
|
||||
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
||||
def mark_for_destruction
|
||||
@marked_for_destruction = true
|
||||
end
|
||||
|
||||
# Returns whether or not this record will be destroyed as part of the parents save transaction.
|
||||
#
|
||||
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
||||
def marked_for_destruction?
|
||||
@marked_for_destruction
|
||||
end
|
||||
end
|
||||
end
|
350
vendor/rails/activerecord/lib/active_record/base.rb
vendored
350
vendor/rails/activerecord/lib/active_record/base.rb
vendored
|
@ -327,7 +327,7 @@ module ActiveRecord #:nodoc:
|
|||
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
|
||||
#
|
||||
# You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
|
||||
# descendent of a class not in the hierarchy. Example:
|
||||
# descendant of a class not in the hierarchy. Example:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# serialize :preferences, Hash
|
||||
|
@ -389,6 +389,8 @@ module ActiveRecord #:nodoc:
|
|||
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
|
||||
# instances in the current object space.
|
||||
class Base
|
||||
##
|
||||
# :singleton-method:
|
||||
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
|
||||
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
|
||||
cattr_accessor :logger, :instance_writer => false
|
||||
|
@ -414,7 +416,9 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
@@subclasses = {}
|
||||
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Contains the database configuration - as is typically stored in config/database.yml -
|
||||
# as a Hash.
|
||||
#
|
||||
|
@ -443,6 +447,8 @@ module ActiveRecord #:nodoc:
|
|||
cattr_accessor :configurations, :instance_writer => false
|
||||
@@configurations = {}
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
|
||||
# :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
|
||||
# the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
||||
|
@ -450,34 +456,46 @@ module ActiveRecord #:nodoc:
|
|||
cattr_accessor :primary_key_prefix_type, :instance_writer => false
|
||||
@@primary_key_prefix_type = nil
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
|
||||
# table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
|
||||
# for tables in a shared database. By default, the prefix is the empty string.
|
||||
cattr_accessor :table_name_prefix, :instance_writer => false
|
||||
@@table_name_prefix = ""
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
|
||||
# "people_basecamp"). By default, the suffix is the empty string.
|
||||
cattr_accessor :table_name_suffix, :instance_writer => false
|
||||
@@table_name_suffix = ""
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Indicates whether table names should be the pluralized versions of the corresponding class names.
|
||||
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
|
||||
# See table_name for the full rules on table/class naming. This is true, by default.
|
||||
cattr_accessor :pluralize_table_names, :instance_writer => false
|
||||
@@pluralize_table_names = true
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
|
||||
# make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
|
||||
# may complicate matters if you use software like syslog. This is true, by default.
|
||||
cattr_accessor :colorize_logging, :instance_writer => false
|
||||
@@colorize_logging = true
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
|
||||
# This is set to :local by default.
|
||||
cattr_accessor :default_timezone, :instance_writer => false
|
||||
@@default_timezone = :local
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Specifies the format to use when dumping the database schema with Rails'
|
||||
# Rakefile. If :sql, the schema is dumped as (potentially database-
|
||||
# specific) SQL statements. If :ruby, the schema is dumped as an
|
||||
|
@ -487,6 +505,8 @@ module ActiveRecord #:nodoc:
|
|||
cattr_accessor :schema_format , :instance_writer => false
|
||||
@@schema_format = :ruby
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Specify whether or not to use timestamps for migration numbers
|
||||
cattr_accessor :timestamped_migrations , :instance_writer => false
|
||||
@@timestamped_migrations = true
|
||||
|
@ -495,6 +515,10 @@ module ActiveRecord #:nodoc:
|
|||
superclass_delegating_accessor :store_full_sti_class
|
||||
self.store_full_sti_class = false
|
||||
|
||||
# Stores the default scope for the class
|
||||
class_inheritable_accessor :default_scoping, :instance_writer => false
|
||||
self.default_scoping = []
|
||||
|
||||
class << self # Class methods
|
||||
# Find operates with four different retrieval approaches:
|
||||
#
|
||||
|
@ -517,10 +541,12 @@ module ActiveRecord #:nodoc:
|
|||
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
|
||||
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
|
||||
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
||||
# * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
|
||||
# * <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 rows 0 through 4.
|
||||
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
|
||||
# or named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s).
|
||||
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
|
||||
# named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
|
||||
# or an array containing a mixture of both strings and named associations.
|
||||
# If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
||||
# Pass <tt>:readonly => false</tt> to override.
|
||||
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
|
||||
|
@ -635,16 +661,24 @@ module ActiveRecord #:nodoc:
|
|||
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
|
||||
end
|
||||
|
||||
# Checks whether a record exists in the database that matches conditions given. These conditions
|
||||
# can either be a single integer representing a primary key id to be found, or a condition to be
|
||||
# matched like using ActiveRecord#find.
|
||||
|
||||
# Returns true if a record exists in the table that matches the +id+ or
|
||||
# conditions given, or false otherwise. The argument can take four forms:
|
||||
#
|
||||
# The +id_or_conditions+ parameter can be an Integer or a String if you want to search the primary key
|
||||
# column of the table for a matching id, or if you're looking to match against a condition you can use
|
||||
# an Array or a Hash.
|
||||
# * Integer - Finds the record with this primary key.
|
||||
# * String - Finds the record with a primary key corresponding to this
|
||||
# string (such as <tt>'5'</tt>).
|
||||
# * Array - Finds the record that matches these +find+-style conditions
|
||||
# (such as <tt>['color = ?', 'red']</tt>).
|
||||
# * Hash - Finds the record that matches these +find+-style conditions
|
||||
# (such as <tt>{:color => 'red'}</tt>).
|
||||
#
|
||||
# Possible gotcha: You can't pass in a condition as a string e.g. "name = 'Jamie'", this would be
|
||||
# sanitized and then queried against the primary key column as "id = 'name = \'Jamie"
|
||||
# For more information about specifying conditions as a Hash or Array,
|
||||
# see the Conditions section in the introduction to ActiveRecord::Base.
|
||||
#
|
||||
# Note: You can't pass in a condition as a string (like <tt>name =
|
||||
# 'Jamie'</tt>), since it would be sanitized and then queried against
|
||||
# the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
# Person.exists?(5)
|
||||
|
@ -722,25 +756,26 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Delete an object (or multiple objects) where the +id+ given matches the primary_key. A SQL +DELETE+ command
|
||||
# is executed on the database which means that no callbacks are fired off running this. This is an efficient method
|
||||
# of deleting records that don't need cleaning up after or other actions to be taken.
|
||||
# Deletes the row with a primary key matching the +id+ argument, using a
|
||||
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
||||
# Record objects are not instantiated, so the object's callbacks are not
|
||||
# executed, including any <tt>:dependent</tt> association options or
|
||||
# Observer methods.
|
||||
#
|
||||
# Objects are _not_ instantiated with this method, and so +:dependent+ rules
|
||||
# defined on associations are not honered.
|
||||
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * +id+ - Can be either an Integer or an Array of Integers.
|
||||
# Note: Although it is often much faster than the alternative,
|
||||
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
|
||||
# your application that ensures referential integrity or performs other
|
||||
# essential jobs.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # Delete a single object
|
||||
# # Delete a single row
|
||||
# Todo.delete(1)
|
||||
#
|
||||
# # Delete multiple objects
|
||||
# todos = [1,2,3]
|
||||
# Todo.delete(todos)
|
||||
# # Delete multiple rows
|
||||
# Todo.delete([2,3,4])
|
||||
def delete(id)
|
||||
delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
|
||||
end
|
||||
|
@ -778,8 +813,7 @@ module ActiveRecord #:nodoc:
|
|||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * +updates+ - A string of column and value pairs that will be set on any records that match conditions.
|
||||
# What goes into the SET clause.
|
||||
# * +updates+ - A string of column and value pairs that will be set on any records that match conditions. This creates the SET clause of the generated SQL.
|
||||
# * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
|
||||
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
|
||||
#
|
||||
|
@ -817,25 +851,32 @@ module ActiveRecord #:nodoc:
|
|||
connection.update(sql, "#{name} Update")
|
||||
end
|
||||
|
||||
# Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method.
|
||||
# This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting
|
||||
# many records. If you want to simply delete records without worrying about dependent associations or
|
||||
# callbacks, use the much faster +delete_all+ method instead.
|
||||
# Destroys the records matching +conditions+ by instantiating each
|
||||
# record and calling its +destroy+ method. Each object's callbacks are
|
||||
# executed (including <tt>:dependent</tt> association options and
|
||||
# +before_destroy+/+after_destroy+ Observer methods). Returns the
|
||||
# collection of objects that were destroyed; each will be frozen, to
|
||||
# reflect that no changes should be made (since they can't be
|
||||
# persisted).
|
||||
#
|
||||
# Note: Instantiation, callback execution, and deletion of each
|
||||
# record can be time consuming when you're removing many records at
|
||||
# once. It generates at least one SQL +DELETE+ query per record (or
|
||||
# possibly more, to enforce your callbacks). If you want to delete many
|
||||
# rows quickly, without concern for their associations or callbacks, use
|
||||
# +delete_all+ instead.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
||||
# * +conditions+ - A string, array, or hash that specifies which records
|
||||
# to destroy. If omitted, all records are destroyed. See the
|
||||
# Conditions section in the introduction to ActiveRecord::Base for
|
||||
# more information.
|
||||
#
|
||||
# ==== Example
|
||||
# ==== Examples
|
||||
#
|
||||
# Person.destroy_all("last_login < '2004-04-04'")
|
||||
#
|
||||
# This loads and destroys each person one by one, including its dependent associations and before_ and
|
||||
# after_destroy callbacks.
|
||||
#
|
||||
# +conditions+ can be anything that +find+ also accepts:
|
||||
#
|
||||
# Person.destroy_all(:last_login => 6.hours.ago)
|
||||
# Person.destroy_all(:status => "inactive")
|
||||
def destroy_all(conditions = nil)
|
||||
find(:all, :conditions => conditions).each { |object| object.destroy }
|
||||
end
|
||||
|
@ -886,7 +927,7 @@ module ActiveRecord #:nodoc:
|
|||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * +id+ - The id of the object you wish to update a counter on.
|
||||
# * +id+ - The id of the object you wish to update a counter on or an Array of ids.
|
||||
# * +counters+ - An Array of Hashes containing the names of the fields
|
||||
# to update as keys and the amount to update the field by as values.
|
||||
#
|
||||
|
@ -900,12 +941,27 @@ module ActiveRecord #:nodoc:
|
|||
# # SET comment_count = comment_count - 1,
|
||||
# # action_count = action_count + 1
|
||||
# # WHERE id = 5
|
||||
#
|
||||
# # For the Posts with id of 10 and 15, increment the comment_count by 1
|
||||
# Post.update_counters [10, 15], :comment_count => 1
|
||||
# # Executes the following SQL:
|
||||
# # UPDATE posts
|
||||
# # SET comment_count = comment_count + 1,
|
||||
# # WHERE id IN (10, 15)
|
||||
def update_counters(id, counters)
|
||||
updates = counters.inject([]) { |list, (counter_name, increment)|
|
||||
sign = increment < 0 ? "-" : "+"
|
||||
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)}")
|
||||
|
||||
if id.is_a?(Array)
|
||||
ids_list = id.map {|i| quote_value(i)}.join(', ')
|
||||
condition = "IN (#{ids_list})"
|
||||
else
|
||||
condition = "= #{quote_value(id)}"
|
||||
end
|
||||
|
||||
update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
|
||||
end
|
||||
|
||||
# Increment a number field by one, usually representing a count.
|
||||
|
@ -1384,8 +1440,8 @@ module ActiveRecord #:nodoc:
|
|||
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
||||
if logger && logger.level <= log_level
|
||||
result = nil
|
||||
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
||||
logger.add(log_level, "#{title} (#{'%.1f' % (seconds * 1000)}ms)")
|
||||
ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
|
||||
logger.add(log_level, '%s (%.1fms)' % [title, ms])
|
||||
result
|
||||
else
|
||||
yield
|
||||
|
@ -1424,7 +1480,10 @@ module ActiveRecord #:nodoc:
|
|||
def respond_to?(method_id, include_private = false)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
|
@ -1462,11 +1521,16 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
if scoped?(:find, :order)
|
||||
scoped_order = reverse_sql_order(scope(:find, :order))
|
||||
scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
|
||||
scope = scope(:find)
|
||||
original_scoped_order = scope[:order]
|
||||
scope[:order] = reverse_sql_order(original_scoped_order)
|
||||
end
|
||||
|
||||
find_initial(options.merge({ :order => order }))
|
||||
begin
|
||||
find_initial(options.merge({ :order => order }))
|
||||
ensure
|
||||
scope[:order] = original_scoped_order if original_scoped_order
|
||||
end
|
||||
end
|
||||
|
||||
def reverse_sql_order(order_query)
|
||||
|
@ -1628,7 +1692,7 @@ module ActiveRecord #:nodoc:
|
|||
add_joins!(sql, options[:joins], scope)
|
||||
add_conditions!(sql, options[:conditions], scope)
|
||||
|
||||
add_group!(sql, options[:group], scope)
|
||||
add_group!(sql, options[:group], options[:having], scope)
|
||||
add_order!(sql, options[:order], scope)
|
||||
add_limit!(sql, options, scope)
|
||||
add_lock!(sql, options, scope)
|
||||
|
@ -1651,7 +1715,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
join
|
||||
end
|
||||
joins.flatten.uniq
|
||||
joins.flatten.map{|j| j.strip}.uniq
|
||||
else
|
||||
joins.collect{|j| safe_to_array(j)}.flatten.uniq
|
||||
end
|
||||
|
@ -1684,13 +1748,15 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def add_group!(sql, group, scope = :auto)
|
||||
def add_group!(sql, group, having, scope = :auto)
|
||||
if group
|
||||
sql << " GROUP BY #{group}"
|
||||
sql << " HAVING #{having}" if having
|
||||
else
|
||||
scope = scope(:find) if :auto == scope
|
||||
if scope && (scoped_group = scope[:group])
|
||||
sql << " GROUP BY #{scoped_group}"
|
||||
sql << " HAVING #{scoped_having}" if (scoped_having = scope[:having])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1760,17 +1826,19 @@ module ActiveRecord #:nodoc:
|
|||
table_name
|
||||
end
|
||||
|
||||
# Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
|
||||
# find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
|
||||
# respectively. Also works for find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]).
|
||||
# Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
|
||||
# that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and
|
||||
# <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for
|
||||
# <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
|
||||
#
|
||||
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
|
||||
# is actually find_all_by_amount(amount, options).
|
||||
# It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
|
||||
# is actually <tt>find_all_by_amount(amount, options)</tt>.
|
||||
#
|
||||
# This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
|
||||
# or find_or_create_by_user_and_password(user, password).
|
||||
# Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
|
||||
# are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
|
||||
# respectively.
|
||||
#
|
||||
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# attempts to use it do not run through method_missing.
|
||||
def method_missing(method_id, *arguments, &block)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
|
@ -1779,10 +1847,31 @@ module ActiveRecord #:nodoc:
|
|||
if match.finder?
|
||||
finder = match.finder
|
||||
bang = match.bang?
|
||||
# def self.find_by_login_and_activated(*args)
|
||||
# options = args.extract_options!
|
||||
# attributes = construct_attributes_from_arguments(
|
||||
# [:login,:activated],
|
||||
# args
|
||||
# )
|
||||
# finder_options = { :conditions => attributes }
|
||||
# validate_find_options(options)
|
||||
# set_readonly_option!(options)
|
||||
#
|
||||
# if options[:conditions]
|
||||
# with_scope(:find => finder_options) do
|
||||
# find(:first, options)
|
||||
# end
|
||||
# else
|
||||
# find(:first, options.merge(finder_options))
|
||||
# end
|
||||
# end
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args)
|
||||
options = args.extract_options!
|
||||
attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
|
||||
attributes = construct_attributes_from_arguments(
|
||||
[:#{attribute_names.join(',:')}],
|
||||
args
|
||||
)
|
||||
finder_options = { :conditions => attributes }
|
||||
validate_find_options(options)
|
||||
set_readonly_option!(options)
|
||||
|
@ -1794,12 +1883,37 @@ module ActiveRecord #:nodoc:
|
|||
else
|
||||
find(:#{finder}, options.merge(finder_options))
|
||||
end
|
||||
#{'result || raise(RecordNotFound)' if bang}
|
||||
#{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
elsif match.instantiator?
|
||||
instantiator = match.instantiator
|
||||
# def self.find_or_create_by_user_id(*args)
|
||||
# guard_protected_attributes = false
|
||||
#
|
||||
# if args[0].is_a?(Hash)
|
||||
# guard_protected_attributes = true
|
||||
# attributes = args[0].with_indifferent_access
|
||||
# find_attributes = attributes.slice(*[:user_id])
|
||||
# else
|
||||
# find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
|
||||
# end
|
||||
#
|
||||
# options = { :conditions => find_attributes }
|
||||
# set_readonly_option!(options)
|
||||
#
|
||||
# record = find(:first, options)
|
||||
#
|
||||
# if record.nil?
|
||||
# record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
|
||||
# yield(record) if block_given?
|
||||
# record.save
|
||||
# record
|
||||
# else
|
||||
# record
|
||||
# end
|
||||
# end
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args)
|
||||
guard_protected_attributes = false
|
||||
|
@ -1829,6 +1943,22 @@ module ActiveRecord #:nodoc:
|
|||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments, &block)
|
||||
end
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
attribute_names = match.attribute_names
|
||||
super unless all_attributes_exists?(attribute_names)
|
||||
if match.scope?
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
|
||||
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
|
||||
) # )
|
||||
#
|
||||
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
||||
end # end
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
|
@ -1924,7 +2054,11 @@ module ActiveRecord #:nodoc:
|
|||
# end
|
||||
#
|
||||
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
|
||||
# <tt>:conditions</tt> and <tt>:include</tt> options in <tt>:find</tt>, which are merged.
|
||||
# <tt>:conditions</tt>, <tt>:include</tt>, and <tt>:joins</tt> options in <tt>:find</tt>, which are merged.
|
||||
#
|
||||
# <tt>:joins</tt> options are uniqued so multiple scopes can join in the same table without table aliasing
|
||||
# problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
|
||||
# array of strings format for your joins.
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# def self.find_with_scope
|
||||
|
@ -1978,7 +2112,11 @@ module ActiveRecord #:nodoc:
|
|||
(hash[method].keys + params.keys).uniq.each do |key|
|
||||
merge = hash[method][key] && params[key] # merge if both scopes have the same key
|
||||
if key == :conditions && merge
|
||||
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
||||
if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
|
||||
hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
|
||||
else
|
||||
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
||||
end
|
||||
elsif key == :include && merge
|
||||
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
|
||||
elsif key == :joins && merge
|
||||
|
@ -1988,7 +2126,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
else
|
||||
hash[method] = params.merge(hash[method])
|
||||
hash[method] = hash[method].merge(params)
|
||||
end
|
||||
else
|
||||
hash[method] = params
|
||||
|
@ -2016,6 +2154,16 @@ module ActiveRecord #:nodoc:
|
|||
@@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
|
||||
end
|
||||
|
||||
# Sets the default options for the model. The format of the
|
||||
# <tt>options</tt> argument is the same as in find.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# default_scope :order => 'last_name, first_name'
|
||||
# end
|
||||
def default_scope(options = {})
|
||||
self.default_scoping << { :find => options, :create => (options.is_a?(Hash) && options.has_key?(:conditions)) ? options[:conditions] : {} }
|
||||
end
|
||||
|
||||
# Test whether the given method and optional key are scoped.
|
||||
def scoped?(method, key = nil) #:nodoc:
|
||||
if current_scoped_methods && (scope = current_scoped_methods[method])
|
||||
|
@ -2031,14 +2179,14 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def scoped_methods #:nodoc:
|
||||
Thread.current[:"#{self}_scoped_methods"] ||= []
|
||||
Thread.current[:"#{self}_scoped_methods"] ||= self.default_scoping.dup
|
||||
end
|
||||
|
||||
def current_scoped_methods #:nodoc:
|
||||
scoped_methods.last
|
||||
end
|
||||
|
||||
# Returns the class type of the record using the current module as a prefix. So descendents of
|
||||
# Returns the class type of the record using the current module as a prefix. So descendants of
|
||||
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
||||
def compute_type(type_name)
|
||||
modularized_name = type_name_with_module(type_name)
|
||||
|
@ -2051,7 +2199,8 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the class descending directly from Active Record in the inheritance hierarchy.
|
||||
# Returns the class descending directly from ActiveRecord::Base or an
|
||||
# abstract class, if any, in the inheritance hierarchy.
|
||||
def class_of_active_record_descendant(klass)
|
||||
if klass.superclass == Base || klass.superclass.abstract_class?
|
||||
klass
|
||||
|
@ -2245,7 +2394,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
|
||||
:order, :select, :readonly, :group, :from, :lock ]
|
||||
:order, :select, :readonly, :group, :having, :from, :lock ]
|
||||
|
||||
def validate_find_options(options) #:nodoc:
|
||||
options.assert_valid_keys(VALID_FIND_OPTIONS)
|
||||
|
@ -2303,15 +2452,15 @@ module ActiveRecord #:nodoc:
|
|||
# object. The default implementation returns this record's id as a String,
|
||||
# or nil if this record's unsaved.
|
||||
#
|
||||
# For example, suppose that you have a Users model, and that you have a
|
||||
# <tt>map.resources :users</tt> route. Normally, +users_path+ will
|
||||
# construct an URI with the user object's 'id' in it:
|
||||
# For example, suppose that you have a User model, and that you have a
|
||||
# <tt>map.resources :users</tt> route. Normally, +user_path+ will
|
||||
# construct a path with the user object's 'id' in it:
|
||||
#
|
||||
# user = User.find_by_name('Phusion')
|
||||
# user_path(path) # => "/users/1"
|
||||
# user_path(user) # => "/users/1"
|
||||
#
|
||||
# You can override +to_param+ in your model to make +users_path+ construct
|
||||
# an URI using the user's name instead of the user's id:
|
||||
# You can override +to_param+ in your model to make +user_path+ construct
|
||||
# a path using the user's name instead of the user's id:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# def to_param # overridden
|
||||
|
@ -2320,7 +2469,7 @@ module ActiveRecord #:nodoc:
|
|||
# end
|
||||
#
|
||||
# user = User.find_by_name('Phusion')
|
||||
# user_path(path) # => "/users/Phusion"
|
||||
# user_path(user) # => "/users/Phusion"
|
||||
def to_param
|
||||
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
||||
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
|
||||
|
@ -2357,9 +2506,9 @@ module ActiveRecord #:nodoc:
|
|||
write_attribute(self.class.primary_key, value)
|
||||
end
|
||||
|
||||
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
|
||||
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
|
||||
def new_record?
|
||||
defined?(@new_record) && @new_record
|
||||
@new_record || false
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
|
@ -2400,14 +2549,16 @@ module ActiveRecord #:nodoc:
|
|||
create_or_update || raise(RecordNotSaved)
|
||||
end
|
||||
|
||||
# Deletes the record in the database and freezes this instance to reflect that no changes should
|
||||
# be made (since they can't be persisted).
|
||||
# Deletes the record in the database and freezes this instance to
|
||||
# reflect that no changes should be made (since they can't be
|
||||
# persisted). Returns the frozen instance.
|
||||
#
|
||||
# Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+
|
||||
# callbacks, nor will it enforce any association +:dependent+ rules.
|
||||
#
|
||||
# In addition to deleting this record, any defined +before_delete+ and +after_delete+
|
||||
# callbacks are run, and +:dependent+ rules defined on associations are run.
|
||||
# The row is simply removed with a SQL +DELETE+ statement on the
|
||||
# record's primary key, and no callbacks are executed.
|
||||
#
|
||||
# To enforce the object's +before_destroy+ and +after_destroy+
|
||||
# callbacks, Observer methods, or any <tt>:dependent</tt> association
|
||||
# options, use <tt>#destroy</tt>.
|
||||
def delete
|
||||
self.class.delete(id) unless new_record?
|
||||
freeze
|
||||
|
@ -2608,7 +2759,19 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Format attributes nicely for inspect.
|
||||
# Returns an <tt>#inspect</tt>-like string for the value of the
|
||||
# attribute +attr_name+. String attributes are elided after 50
|
||||
# characters, and Date and Time attributes are returned in the
|
||||
# <tt>:db</tt> format. Other attributes return the value of
|
||||
# <tt>#inspect</tt> without modification.
|
||||
#
|
||||
# person = Person.create!(:name => "David Heinemeier Hansson " * 3)
|
||||
#
|
||||
# person.attribute_for_inspect(:name)
|
||||
# # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
|
||||
#
|
||||
# person.attribute_for_inspect(:created_at)
|
||||
# # => '"2009-01-12 04:48:57"'
|
||||
def attribute_for_inspect(attr_name)
|
||||
value = read_attribute(attr_name)
|
||||
|
||||
|
@ -2737,7 +2900,7 @@ module ActiveRecord #:nodoc:
|
|||
id
|
||||
end
|
||||
|
||||
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent.
|
||||
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
|
||||
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
|
||||
# set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
|
||||
# Message class in that example.
|
||||
|
@ -2964,4 +3127,23 @@ module ActiveRecord #:nodoc:
|
|||
value
|
||||
end
|
||||
end
|
||||
|
||||
Base.class_eval do
|
||||
extend QueryCache::ClassMethods
|
||||
include Validations
|
||||
include Locking::Optimistic, Locking::Pessimistic
|
||||
include AttributeMethods
|
||||
include Dirty
|
||||
include Callbacks, Observing, Timestamp
|
||||
include Associations, AssociationPreload, NamedScope
|
||||
|
||||
# AutosaveAssociation needs to be included before Transactions, because we want
|
||||
# #save_with_autosave_associations to be wrapped inside a transaction.
|
||||
include AutosaveAssociation, NestedAttributes
|
||||
|
||||
include Aggregations, Transactions, Reflection, Calculations, Serialization
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove this and make it work with LAZY flag
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
|
|
@ -48,30 +48,38 @@ module ActiveRecord
|
|||
calculate(:count, *construct_count_options_from_args(*args))
|
||||
end
|
||||
|
||||
# Calculates the average value on a given column. The value is returned as a float. See +calculate+ for examples with options.
|
||||
# Calculates the average value on a given column. The value is returned as
|
||||
# a float, or +nil+ if there's no row. See +calculate+ for examples with
|
||||
# options.
|
||||
#
|
||||
# Person.average('age')
|
||||
# Person.average('age') # => 35.8
|
||||
def average(column_name, options = {})
|
||||
calculate(:avg, column_name, options)
|
||||
end
|
||||
|
||||
# Calculates the minimum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
|
||||
# Calculates the minimum value on a given column. The value is returned
|
||||
# with the same data type of the column, or +nil+ if there's no row. See
|
||||
# +calculate+ for examples with options.
|
||||
#
|
||||
# Person.minimum('age')
|
||||
# Person.minimum('age') # => 7
|
||||
def minimum(column_name, options = {})
|
||||
calculate(:min, column_name, options)
|
||||
end
|
||||
|
||||
# Calculates the maximum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
|
||||
# Calculates the maximum value on a given column. The value is returned
|
||||
# with the same data type of the column, or +nil+ if there's no row. See
|
||||
# +calculate+ for examples with options.
|
||||
#
|
||||
# Person.maximum('age')
|
||||
# Person.maximum('age') # => 93
|
||||
def maximum(column_name, options = {})
|
||||
calculate(:max, column_name, options)
|
||||
end
|
||||
|
||||
# Calculates the sum of values on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
|
||||
# Calculates the sum of values on a given column. The value is returned
|
||||
# with the same data type of the column, 0 if there's no row. See
|
||||
# +calculate+ for examples with options.
|
||||
#
|
||||
# Person.sum('age')
|
||||
# Person.sum('age') # => 4562
|
||||
def sum(column_name, options = {})
|
||||
calculate(:sum, column_name, options)
|
||||
end
|
||||
|
|
|
@ -77,7 +77,7 @@ module ActiveRecord
|
|||
#
|
||||
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
|
||||
# you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
|
||||
# when you want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
|
||||
# when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks.
|
||||
#
|
||||
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
|
||||
# associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
|
||||
|
@ -219,8 +219,9 @@ module ActiveRecord
|
|||
def after_save() end
|
||||
def create_or_update_with_callbacks #:nodoc:
|
||||
return false if callback(:before_save) == false
|
||||
result = create_or_update_without_callbacks
|
||||
callback(:after_save)
|
||||
if result = create_or_update_without_callbacks
|
||||
callback(:after_save)
|
||||
end
|
||||
result
|
||||
end
|
||||
private :create_or_update_with_callbacks
|
||||
|
|
|
@ -7,6 +7,8 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The connection handler
|
||||
cattr_accessor :connection_handler, :instance_writer => false
|
||||
@@connection_handler = ConnectionAdapters::ConnectionHandler.new
|
||||
|
@ -121,6 +123,7 @@ module ActiveRecord
|
|||
connection_handler.retrieve_connection(self)
|
||||
end
|
||||
|
||||
# Returns true if +ActiveRecord+ is connected.
|
||||
def connected?
|
||||
connection_handler.connected?(self)
|
||||
end
|
||||
|
|
|
@ -31,13 +31,13 @@ module ActiveRecord
|
|||
# Returns an array of arrays containing the field values.
|
||||
# Order is the same as that returned by +columns+.
|
||||
def select_rows(sql, name = nil)
|
||||
raise NotImplementedError, "select_rows is an abstract method"
|
||||
end
|
||||
undef_method :select_rows
|
||||
|
||||
# Executes the SQL statement in the context of this connection.
|
||||
def execute(sql, name = nil)
|
||||
raise NotImplementedError, "execute is an abstract method"
|
||||
def execute(sql, name = nil, skip_logging = false)
|
||||
end
|
||||
undef_method :execute
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
|
@ -53,36 +53,124 @@ module ActiveRecord
|
|||
def delete(sql, name = nil)
|
||||
delete_sql(sql, name)
|
||||
end
|
||||
|
||||
# Checks whether there is currently no transaction active. This is done
|
||||
# by querying the database driver, and does not use the transaction
|
||||
# house-keeping information recorded by #increment_open_transactions and
|
||||
# friends.
|
||||
#
|
||||
# Returns true if there is no transaction active, false if there is a
|
||||
# transaction active, and nil if this information is unknown.
|
||||
#
|
||||
# Not all adapters supports transaction state introspection. Currently,
|
||||
# only the PostgreSQL adapter supports this.
|
||||
def outside_transaction?
|
||||
nil
|
||||
end
|
||||
|
||||
# Runs the given block in a database transaction, and returns the result
|
||||
# of the block.
|
||||
#
|
||||
# == Nested transactions support
|
||||
#
|
||||
# Most databases don't support true nested transactions. At the time of
|
||||
# writing, the only database that supports true nested transactions that
|
||||
# we're aware of, is MS-SQL.
|
||||
#
|
||||
# In order to get around this problem, #transaction will emulate the effect
|
||||
# of nested transactions, by using savepoints:
|
||||
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
|
||||
# Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
|
||||
#
|
||||
# It is safe to call this method if a database transaction is already open,
|
||||
# i.e. if #transaction is called within another #transaction block. In case
|
||||
# of a nested call, #transaction will behave as follows:
|
||||
#
|
||||
# - The block will be run without doing anything. All database statements
|
||||
# that happen within the block are effectively appended to the already
|
||||
# open database transaction.
|
||||
# - However, if +:requires_new+ is set, the block will be wrapped in a
|
||||
# database savepoint acting as a sub-transaction.
|
||||
#
|
||||
# === Caveats
|
||||
#
|
||||
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
|
||||
# then any created savepoints will be automatically released. For example,
|
||||
# if you've created a savepoint, then you execute a CREATE TABLE statement,
|
||||
# then the savepoint that was created will be automatically released.
|
||||
#
|
||||
# This means that, on MySQL, you shouldn't execute DDL operations inside
|
||||
# a #transaction call that you know might create a savepoint. Otherwise,
|
||||
# #transaction will raise exceptions when it tries to release the
|
||||
# already-automatically-released savepoints:
|
||||
#
|
||||
# Model.connection.transaction do # BEGIN
|
||||
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
|
||||
# Model.connection.create_table(...)
|
||||
# # active_record_1 now automatically released
|
||||
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
||||
# end
|
||||
def transaction(options = {})
|
||||
options.assert_valid_keys :requires_new, :joinable
|
||||
|
||||
last_transaction_joinable = @transaction_joinable
|
||||
if options.has_key?(:joinable)
|
||||
@transaction_joinable = options[:joinable]
|
||||
else
|
||||
@transaction_joinable = true
|
||||
end
|
||||
requires_new = options[:requires_new] || !last_transaction_joinable
|
||||
|
||||
# Wrap a block in a transaction. Returns result of block.
|
||||
def transaction(start_db_transaction = true)
|
||||
transaction_open = false
|
||||
begin
|
||||
if block_given?
|
||||
if start_db_transaction
|
||||
begin_db_transaction
|
||||
if requires_new || open_transactions == 0
|
||||
if open_transactions == 0
|
||||
begin_db_transaction
|
||||
elsif requires_new
|
||||
create_savepoint
|
||||
end
|
||||
increment_open_transactions
|
||||
transaction_open = true
|
||||
end
|
||||
yield
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
if transaction_open
|
||||
if transaction_open && !outside_transaction?
|
||||
transaction_open = false
|
||||
rollback_db_transaction
|
||||
decrement_open_transactions
|
||||
if open_transactions == 0
|
||||
rollback_db_transaction
|
||||
else
|
||||
rollback_to_savepoint
|
||||
end
|
||||
end
|
||||
raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
||||
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
|
||||
end
|
||||
ensure
|
||||
if transaction_open
|
||||
@transaction_joinable = last_transaction_joinable
|
||||
|
||||
if outside_transaction?
|
||||
@open_transactions = 0
|
||||
elsif transaction_open
|
||||
decrement_open_transactions
|
||||
begin
|
||||
commit_db_transaction
|
||||
if open_transactions == 0
|
||||
commit_db_transaction
|
||||
else
|
||||
release_savepoint
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
rollback_db_transaction
|
||||
if open_transactions == 0
|
||||
rollback_db_transaction
|
||||
else
|
||||
rollback_to_savepoint
|
||||
end
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Begins the transaction (and turns off auto-committing).
|
||||
def begin_db_transaction() end
|
||||
|
||||
|
@ -163,8 +251,8 @@ module ActiveRecord
|
|||
# Returns an array of record hashes with the column names as keys and
|
||||
# column values as values.
|
||||
def select(sql, name = nil)
|
||||
raise NotImplementedError, "select is an abstract method"
|
||||
end
|
||||
undef_method :select
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
|
|
|
@ -14,12 +14,12 @@ module ActiveRecord
|
|||
def dirties_query_cache(base, *method_names)
|
||||
method_names.each do |method_name|
|
||||
base.class_eval <<-end_code, __FILE__, __LINE__
|
||||
def #{method_name}_with_query_dirty(*args)
|
||||
clear_query_cache if @query_cache_enabled
|
||||
#{method_name}_without_query_dirty(*args)
|
||||
end
|
||||
|
||||
alias_method_chain :#{method_name}, :query_dirty
|
||||
def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
|
||||
clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
|
||||
#{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
|
||||
end # end
|
||||
#
|
||||
alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
|
||||
end_code
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,10 +32,12 @@ module ActiveRecord
|
|||
@primary = nil
|
||||
end
|
||||
|
||||
# Returns +true+ if the column is either of type string or text.
|
||||
def text?
|
||||
type == :string || type == :text
|
||||
end
|
||||
|
||||
# Returns +true+ if the column is either of type integer, float or decimal.
|
||||
def number?
|
||||
type == :integer || type == :float || type == :decimal
|
||||
end
|
||||
|
@ -295,7 +297,7 @@ module ActiveRecord
|
|||
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def self.down
|
||||
# ...
|
||||
# end
|
||||
|
@ -474,12 +476,12 @@ module ActiveRecord
|
|||
|
||||
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
|
||||
class_eval <<-EOV
|
||||
def #{column_type}(*args)
|
||||
options = args.extract_options!
|
||||
column_names = args
|
||||
|
||||
column_names.each { |name| column(name, '#{column_type}', options) }
|
||||
end
|
||||
def #{column_type}(*args) # def string(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
column_names = args # column_names = args
|
||||
#
|
||||
column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) }
|
||||
end # end
|
||||
EOV
|
||||
end
|
||||
|
||||
|
@ -674,24 +676,24 @@ module ActiveRecord
|
|||
# t.string(:goat, :sheep)
|
||||
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
|
||||
class_eval <<-EOV
|
||||
def #{column_type}(*args)
|
||||
options = args.extract_options!
|
||||
column_names = args
|
||||
|
||||
column_names.each do |name|
|
||||
column = ColumnDefinition.new(@base, name, '#{column_type}')
|
||||
if options[:limit]
|
||||
column.limit = options[:limit]
|
||||
elsif native['#{column_type}'.to_sym].is_a?(Hash)
|
||||
column.limit = native['#{column_type}'.to_sym][:limit]
|
||||
end
|
||||
column.precision = options[:precision]
|
||||
column.scale = options[:scale]
|
||||
column.default = options[:default]
|
||||
column.null = options[:null]
|
||||
@base.add_column(@table_name, name, column.sql_type, options)
|
||||
end
|
||||
end
|
||||
def #{column_type}(*args) # def string(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
column_names = args # column_names = args
|
||||
#
|
||||
column_names.each do |name| # column_names.each do |name|
|
||||
column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
|
||||
if options[:limit] # if options[:limit]
|
||||
column.limit = options[:limit] # column.limit = options[:limit]
|
||||
elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
|
||||
column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
|
||||
end # end
|
||||
column.precision = options[:precision] # column.precision = options[:precision]
|
||||
column.scale = options[:scale] # column.scale = options[:scale]
|
||||
column.default = options[:default] # column.default = options[:default]
|
||||
column.null = options[:null] # column.null = options[:null]
|
||||
@base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
|
||||
end # end
|
||||
end # end
|
||||
EOV
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ require 'date'
|
|||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
|
||||
# TODO: Autoload these files
|
||||
require 'active_record/connection_adapters/abstract/schema_definitions'
|
||||
require 'active_record/connection_adapters/abstract/schema_statements'
|
||||
require 'active_record/connection_adapters/abstract/database_statements'
|
||||
|
@ -65,6 +66,12 @@ module ActiveRecord
|
|||
def supports_ddl_transactions?
|
||||
false
|
||||
end
|
||||
|
||||
# Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
|
||||
# does not.
|
||||
def supports_savepoints?
|
||||
false
|
||||
end
|
||||
|
||||
# Should primary key values be selected from their corresponding
|
||||
# sequence before the insert statement? If true, next_sequence_value
|
||||
|
@ -159,9 +166,26 @@ module ActiveRecord
|
|||
@open_transactions -= 1
|
||||
end
|
||||
|
||||
def log_info(sql, name, seconds)
|
||||
def transaction_joinable=(joinable)
|
||||
@transaction_joinable = joinable
|
||||
end
|
||||
|
||||
def create_savepoint
|
||||
end
|
||||
|
||||
def rollback_to_savepoint
|
||||
end
|
||||
|
||||
def release_savepoint
|
||||
end
|
||||
|
||||
def current_savepoint_name
|
||||
"active_record_#{open_transactions}"
|
||||
end
|
||||
|
||||
def log_info(sql, name, ms)
|
||||
if @logger && @logger.debug?
|
||||
name = "#{name.nil? ? "SQL" : name} (#{sprintf("%.1f", seconds * 1000)}ms)"
|
||||
name = '%s (%.1fms)' % [name || 'SQL', ms]
|
||||
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
|
||||
end
|
||||
end
|
||||
|
@ -170,9 +194,9 @@ module ActiveRecord
|
|||
def log(sql, name)
|
||||
if block_given?
|
||||
result = nil
|
||||
seconds = Benchmark.realtime { result = yield }
|
||||
@runtime += seconds
|
||||
log_info(sql, name, seconds)
|
||||
ms = Benchmark.ms { result = yield }
|
||||
@runtime += ms
|
||||
log_info(sql, name, ms)
|
||||
result
|
||||
else
|
||||
log_info(sql, name, 0)
|
||||
|
|
|
@ -13,23 +13,25 @@ module MysqlCompat #:nodoc:
|
|||
# C driver >= 2.7 returns null values in each_hash
|
||||
if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
|
||||
target.class_eval <<-'end_eval'
|
||||
def all_hashes
|
||||
rows = []
|
||||
each_hash { |row| rows << row }
|
||||
rows
|
||||
end
|
||||
def all_hashes # def all_hashes
|
||||
rows = [] # rows = []
|
||||
each_hash { |row| rows << row } # each_hash { |row| rows << row }
|
||||
rows # rows
|
||||
end # end
|
||||
end_eval
|
||||
|
||||
# adapters before 2.7 don't have a version constant
|
||||
# and don't return null values in each_hash
|
||||
else
|
||||
target.class_eval <<-'end_eval'
|
||||
def all_hashes
|
||||
rows = []
|
||||
all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
|
||||
each_hash { |row| rows << all_fields.dup.update(row) }
|
||||
rows
|
||||
end
|
||||
def all_hashes # def all_hashes
|
||||
rows = [] # rows = []
|
||||
all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
|
||||
fields[f.name] = nil; fields # fields[f.name] = nil; fields
|
||||
} # }
|
||||
each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
|
||||
rows # rows
|
||||
end # end
|
||||
end_eval
|
||||
end
|
||||
|
||||
|
@ -150,19 +152,23 @@ 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>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
|
||||
# * <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.
|
||||
# * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
|
||||
#
|
||||
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
|
||||
# as boolean. If you wish to disable this emulation (which was the default
|
||||
# behavior in versions 0.13.1 and earlier) you can add the following line
|
||||
# to your environment.rb file:
|
||||
#
|
||||
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
|
||||
class MysqlAdapter < AbstractAdapter
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
|
||||
# as boolean. If you wish to disable this emulation (which was the default
|
||||
# behavior in versions 0.13.1 and earlier) you can add the following line
|
||||
# to your environment.rb file:
|
||||
#
|
||||
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
|
||||
cattr_accessor :emulate_booleans
|
||||
self.emulate_booleans = true
|
||||
|
||||
|
@ -205,6 +211,10 @@ module ActiveRecord
|
|||
def supports_migrations? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def supports_savepoints? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def native_database_types #:nodoc:
|
||||
NATIVE_DATABASE_TYPES
|
||||
|
@ -305,6 +315,7 @@ module ActiveRecord
|
|||
rows
|
||||
end
|
||||
|
||||
# Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
|
||||
def execute(sql, name = nil) #:nodoc:
|
||||
log(sql, name) { @connection.query(sql) }
|
||||
rescue ActiveRecord::StatementInvalid => exception
|
||||
|
@ -343,6 +354,17 @@ module ActiveRecord
|
|||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
def create_savepoint
|
||||
execute("SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def rollback_to_savepoint
|
||||
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def release_savepoint
|
||||
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def add_limit_offset!(sql, options) #:nodoc:
|
||||
if limit = options[:limit]
|
||||
|
@ -411,7 +433,9 @@ module ActiveRecord
|
|||
|
||||
def tables(name = nil) #:nodoc:
|
||||
tables = []
|
||||
execute("SHOW TABLES", name).each { |field| tables << field[0] }
|
||||
result = execute("SHOW TABLES", name)
|
||||
result.each { |field| tables << field[0] }
|
||||
result.free
|
||||
tables
|
||||
end
|
||||
|
||||
|
@ -422,7 +446,8 @@ module ActiveRecord
|
|||
def indexes(table_name, name = nil)#:nodoc:
|
||||
indexes = []
|
||||
current_index = nil
|
||||
execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
|
||||
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
|
||||
result.each do |row|
|
||||
if current_index != row[2]
|
||||
next if row[2] == "PRIMARY" # skip the primary key
|
||||
current_index = row[2]
|
||||
|
@ -431,13 +456,16 @@ module ActiveRecord
|
|||
|
||||
indexes.last.columns << row[4]
|
||||
end
|
||||
result.free
|
||||
indexes
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)#:nodoc:
|
||||
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
||||
columns = []
|
||||
execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
||||
result = execute(sql, name)
|
||||
result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
||||
result.free
|
||||
columns
|
||||
end
|
||||
|
||||
|
@ -518,9 +546,11 @@ module ActiveRecord
|
|||
# Returns a table's primary key and belonging sequence.
|
||||
def pk_and_sequence_for(table) #:nodoc:
|
||||
keys = []
|
||||
execute("describe #{quote_table_name(table)}").each_hash do |h|
|
||||
result = execute("describe #{quote_table_name(table)}")
|
||||
result.each_hash do |h|
|
||||
keys << h["Field"]if h["Key"] == "PRI"
|
||||
end
|
||||
result.free
|
||||
keys.length == 1 ? [keys.first, nil] : nil
|
||||
end
|
||||
|
||||
|
@ -534,8 +564,6 @@ module ActiveRecord
|
|||
|
||||
private
|
||||
def connect
|
||||
@connection.reconnect = true if @connection.respond_to?(:reconnect=)
|
||||
|
||||
encoding = @config[:encoding]
|
||||
if encoding
|
||||
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
||||
|
@ -546,6 +574,10 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
@connection.real_connect(*@connection_options)
|
||||
|
||||
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
|
||||
@connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
|
||||
|
||||
configure_connection
|
||||
end
|
||||
|
||||
|
|
|
@ -272,6 +272,10 @@ module ActiveRecord
|
|||
def supports_ddl_transactions?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_savepoints?
|
||||
true
|
||||
end
|
||||
|
||||
# Returns the configured supported identifier length supported by PostgreSQL,
|
||||
# or report the default of 63 on PostgreSQL 7.x.
|
||||
|
@ -528,45 +532,26 @@ module ActiveRecord
|
|||
def rollback_db_transaction
|
||||
execute "ROLLBACK"
|
||||
end
|
||||
|
||||
# ruby-pg defines Ruby constants for transaction status,
|
||||
# ruby-postgres does not.
|
||||
PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0
|
||||
|
||||
# Check whether a transaction is active.
|
||||
def transaction_active?
|
||||
@connection.transaction_status != PQTRANS_IDLE
|
||||
end
|
||||
|
||||
# Wrap a block in a transaction. Returns result of block.
|
||||
def transaction(start_db_transaction = true)
|
||||
transaction_open = false
|
||||
begin
|
||||
if block_given?
|
||||
if start_db_transaction
|
||||
begin_db_transaction
|
||||
transaction_open = true
|
||||
end
|
||||
yield
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
if transaction_open && transaction_active?
|
||||
transaction_open = false
|
||||
rollback_db_transaction
|
||||
end
|
||||
raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
||||
end
|
||||
ensure
|
||||
if transaction_open && transaction_active?
|
||||
begin
|
||||
commit_db_transaction
|
||||
rescue Exception => database_transaction_rollback
|
||||
rollback_db_transaction
|
||||
raise
|
||||
end
|
||||
|
||||
if defined?(PGconn::PQTRANS_IDLE)
|
||||
# The ruby-pg driver supports inspecting the transaction status,
|
||||
# while the ruby-postgres driver does not.
|
||||
def outside_transaction?
|
||||
@connection.transaction_status == PGconn::PQTRANS_IDLE
|
||||
end
|
||||
end
|
||||
|
||||
def create_savepoint
|
||||
execute("SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def rollback_to_savepoint
|
||||
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def release_savepoint
|
||||
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
|
@ -950,13 +935,13 @@ module ActiveRecord
|
|||
# should know about this but can't detect it there, so deal with it here.
|
||||
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
||||
PostgreSQLColumn.module_eval(<<-end_eval)
|
||||
def extract_precision(sql_type)
|
||||
if sql_type =~ /^money$/
|
||||
#{money_precision}
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
def extract_precision(sql_type) # def extract_precision(sql_type)
|
||||
if sql_type =~ /^money$/ # if sql_type =~ /^money$/
|
||||
#{money_precision} # 19
|
||||
else # else
|
||||
super # super
|
||||
end # end
|
||||
end # end
|
||||
end_eval
|
||||
|
||||
configure_connection
|
||||
|
|
|
@ -306,7 +306,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def copy_table(from, to, options = {}) #:nodoc:
|
||||
options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
|
||||
options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
|
||||
create_table(to, options) do |definition|
|
||||
@definition = definition
|
||||
columns(from).each do |column|
|
||||
|
@ -402,6 +402,10 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
|
||||
raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
|
||||
end
|
||||
|
||||
alter_table(table_name) do |definition|
|
||||
definition.column(column_name, type, options)
|
||||
end
|
||||
|
|
|
@ -151,12 +151,12 @@ module ActiveRecord
|
|||
|
||||
def field_changed?(attr, old, value)
|
||||
if column = column_for_attribute(attr)
|
||||
if column.type == :integer && column.null && (old.nil? || old == 0)
|
||||
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
|
||||
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
|
||||
# For nullable numeric 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?
|
||||
value = nil
|
||||
else
|
||||
value = column.type_cast(value)
|
||||
end
|
||||
|
@ -174,7 +174,7 @@ module ActiveRecord
|
|||
alias_attribute_without_dirty(new_name, old_name)
|
||||
DIRTY_SUFFIXES.each do |suffix|
|
||||
module_eval <<-STR, __FILE__, __LINE__+1
|
||||
def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end
|
||||
def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
|
||||
STR
|
||||
end
|
||||
end
|
||||
|
|
25
vendor/rails/activerecord/lib/active_record/dynamic_scope_match.rb
vendored
Normal file
25
vendor/rails/activerecord/lib/active_record/dynamic_scope_match.rb
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
module ActiveRecord
|
||||
class DynamicScopeMatch
|
||||
def self.match(method)
|
||||
ds_match = self.new(method)
|
||||
ds_match.scope ? ds_match : nil
|
||||
end
|
||||
|
||||
def initialize(method)
|
||||
@scope = true
|
||||
case method.to_s
|
||||
when /^scoped_by_([_a-zA-Z]\w*)$/
|
||||
names = $1
|
||||
else
|
||||
@scope = nil
|
||||
end
|
||||
@attribute_names = names && names.split('_and_')
|
||||
end
|
||||
|
||||
attr_reader :scope, :attribute_names
|
||||
|
||||
def scope?
|
||||
!@scope.nil?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
require 'erb'
|
||||
require 'yaml'
|
||||
require 'csv'
|
||||
require 'active_support/dependencies'
|
||||
require 'active_support/test_case'
|
||||
|
||||
if RUBY_VERSION < '1.9'
|
||||
|
@ -515,7 +516,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
|||
|
||||
all_loaded_fixtures.update(fixtures_map)
|
||||
|
||||
connection.transaction(connection.open_transactions.zero?) do
|
||||
connection.transaction(:requires_new => true) do
|
||||
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
|
||||
fixtures.each { |fixture| fixture.insert_fixtures }
|
||||
|
||||
|
@ -813,186 +814,191 @@ class Fixture #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
class TestCase #:nodoc:
|
||||
setup :setup_fixtures
|
||||
teardown :teardown_fixtures
|
||||
module ActiveRecord
|
||||
module TestFixtures
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
setup :setup_fixtures
|
||||
teardown :teardown_fixtures
|
||||
|
||||
superclass_delegating_accessor :fixture_path
|
||||
superclass_delegating_accessor :fixture_table_names
|
||||
superclass_delegating_accessor :fixture_class_names
|
||||
superclass_delegating_accessor :use_transactional_fixtures
|
||||
superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
||||
superclass_delegating_accessor :pre_loaded_fixtures
|
||||
superclass_delegating_accessor :fixture_path
|
||||
superclass_delegating_accessor :fixture_table_names
|
||||
superclass_delegating_accessor :fixture_class_names
|
||||
superclass_delegating_accessor :use_transactional_fixtures
|
||||
superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
||||
superclass_delegating_accessor :pre_loaded_fixtures
|
||||
|
||||
self.fixture_table_names = []
|
||||
self.use_transactional_fixtures = false
|
||||
self.use_instantiated_fixtures = true
|
||||
self.pre_loaded_fixtures = false
|
||||
self.fixture_table_names = []
|
||||
self.use_transactional_fixtures = false
|
||||
self.use_instantiated_fixtures = true
|
||||
self.pre_loaded_fixtures = false
|
||||
|
||||
@@already_loaded_fixtures = {}
|
||||
self.fixture_class_names = {}
|
||||
|
||||
class << self
|
||||
def set_fixture_class(class_names = {})
|
||||
self.fixture_class_names = self.fixture_class_names.merge(class_names)
|
||||
end
|
||||
|
||||
def fixtures(*table_names)
|
||||
if table_names.first == :all
|
||||
table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
|
||||
table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
|
||||
else
|
||||
table_names = table_names.flatten.map { |n| n.to_s }
|
||||
end
|
||||
|
||||
self.fixture_table_names |= table_names
|
||||
require_fixture_classes(table_names)
|
||||
setup_fixture_accessors(table_names)
|
||||
end
|
||||
|
||||
def try_to_load_dependency(file_name)
|
||||
require_dependency file_name
|
||||
rescue LoadError => e
|
||||
# Let's hope the developer has included it himself
|
||||
|
||||
# Let's warn in case this is a subdependency, otherwise
|
||||
# subdependency error messages are totally cryptic
|
||||
if ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
|
||||
def require_fixture_classes(table_names = nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
file_name = table_name.to_s
|
||||
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
|
||||
try_to_load_dependency(file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def setup_fixture_accessors(table_names = nil)
|
||||
table_names = [table_names] if table_names && !table_names.respond_to?(:each)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
table_name = table_name.to_s.tr('.', '_')
|
||||
|
||||
define_method(table_name) do |*fixtures|
|
||||
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
|
||||
|
||||
@fixture_cache[table_name] ||= {}
|
||||
|
||||
instances = fixtures.map do |fixture|
|
||||
@fixture_cache[table_name].delete(fixture) if force_reload
|
||||
|
||||
if @loaded_fixtures[table_name][fixture.to_s]
|
||||
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
||||
else
|
||||
raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
|
||||
end
|
||||
end
|
||||
|
||||
instances.size == 1 ? instances.first : instances
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def uses_transaction(*methods)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.concat methods.map(&:to_s)
|
||||
end
|
||||
|
||||
def uses_transaction?(method)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.include?(method.to_s)
|
||||
end
|
||||
self.fixture_class_names = {}
|
||||
end
|
||||
|
||||
def use_transactional_fixtures?
|
||||
use_transactional_fixtures &&
|
||||
!self.class.uses_transaction?(method_name)
|
||||
end
|
||||
|
||||
def setup_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
if pre_loaded_fixtures && !use_transactional_fixtures
|
||||
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
||||
end
|
||||
|
||||
@fixture_cache = {}
|
||||
|
||||
# Load fixtures once and begin transaction.
|
||||
if use_transactional_fixtures?
|
||||
if @@already_loaded_fixtures[self.class]
|
||||
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
||||
else
|
||||
load_fixtures
|
||||
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
||||
end
|
||||
ActiveRecord::Base.connection.increment_open_transactions
|
||||
ActiveRecord::Base.connection.begin_db_transaction
|
||||
# Load fixtures for every test.
|
||||
else
|
||||
Fixtures.reset_cache
|
||||
@@already_loaded_fixtures[self.class] = nil
|
||||
load_fixtures
|
||||
end
|
||||
|
||||
# Instantiate fixtures for every test if requested.
|
||||
instantiate_fixtures if use_instantiated_fixtures
|
||||
end
|
||||
|
||||
def teardown_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
unless use_transactional_fixtures?
|
||||
Fixtures.reset_cache
|
||||
end
|
||||
|
||||
# Rollback changes if a transaction is active.
|
||||
if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
|
||||
ActiveRecord::Base.connection.rollback_db_transaction
|
||||
ActiveRecord::Base.connection.decrement_open_transactions
|
||||
end
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
|
||||
private
|
||||
def load_fixtures
|
||||
@loaded_fixtures = {}
|
||||
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|
||||
unless fixtures.nil?
|
||||
if fixtures.instance_of?(Fixtures)
|
||||
@loaded_fixtures[fixtures.name] = fixtures
|
||||
else
|
||||
fixtures.each { |f| @loaded_fixtures[f.name] = f }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# for pre_loaded_fixtures, only require the classes once. huge speed improvement
|
||||
@@required_fixture_classes = false
|
||||
|
||||
def instantiate_fixtures
|
||||
if pre_loaded_fixtures
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
|
||||
unless @@required_fixture_classes
|
||||
self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
|
||||
@@required_fixture_classes = true
|
||||
end
|
||||
Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
|
||||
else
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
|
||||
@loaded_fixtures.each do |table_name, fixtures|
|
||||
Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_instances?
|
||||
use_instantiated_fixtures != :no_instances
|
||||
end
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def set_fixture_class(class_names = {})
|
||||
self.fixture_class_names = self.fixture_class_names.merge(class_names)
|
||||
end
|
||||
|
||||
def fixtures(*table_names)
|
||||
if table_names.first == :all
|
||||
table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
|
||||
table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
|
||||
else
|
||||
table_names = table_names.flatten.map { |n| n.to_s }
|
||||
end
|
||||
|
||||
self.fixture_table_names |= table_names
|
||||
require_fixture_classes(table_names)
|
||||
setup_fixture_accessors(table_names)
|
||||
end
|
||||
|
||||
def try_to_load_dependency(file_name)
|
||||
require_dependency file_name
|
||||
rescue LoadError => e
|
||||
# Let's hope the developer has included it himself
|
||||
|
||||
# Let's warn in case this is a subdependency, otherwise
|
||||
# subdependency error messages are totally cryptic
|
||||
if ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
|
||||
def require_fixture_classes(table_names = nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
file_name = table_name.to_s
|
||||
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
|
||||
try_to_load_dependency(file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def setup_fixture_accessors(table_names = nil)
|
||||
table_names = [table_names] if table_names && !table_names.respond_to?(:each)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
table_name = table_name.to_s.tr('.', '_')
|
||||
|
||||
define_method(table_name) do |*fixtures|
|
||||
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
|
||||
|
||||
@fixture_cache[table_name] ||= {}
|
||||
|
||||
instances = fixtures.map do |fixture|
|
||||
@fixture_cache[table_name].delete(fixture) if force_reload
|
||||
|
||||
if @loaded_fixtures[table_name][fixture.to_s]
|
||||
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
||||
else
|
||||
raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
|
||||
end
|
||||
end
|
||||
|
||||
instances.size == 1 ? instances.first : instances
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def uses_transaction(*methods)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.concat methods.map(&:to_s)
|
||||
end
|
||||
|
||||
def uses_transaction?(method)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.include?(method.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def run_in_transaction?
|
||||
use_transactional_fixtures &&
|
||||
!self.class.uses_transaction?(method_name)
|
||||
end
|
||||
|
||||
def setup_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
if pre_loaded_fixtures && !use_transactional_fixtures
|
||||
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
||||
end
|
||||
|
||||
@fixture_cache = {}
|
||||
@@already_loaded_fixtures ||= {}
|
||||
|
||||
# Load fixtures once and begin transaction.
|
||||
if run_in_transaction?
|
||||
if @@already_loaded_fixtures[self.class]
|
||||
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
||||
else
|
||||
load_fixtures
|
||||
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
||||
end
|
||||
ActiveRecord::Base.connection.increment_open_transactions
|
||||
ActiveRecord::Base.connection.transaction_joinable = false
|
||||
ActiveRecord::Base.connection.begin_db_transaction
|
||||
# Load fixtures for every test.
|
||||
else
|
||||
Fixtures.reset_cache
|
||||
@@already_loaded_fixtures[self.class] = nil
|
||||
load_fixtures
|
||||
end
|
||||
|
||||
# Instantiate fixtures for every test if requested.
|
||||
instantiate_fixtures if use_instantiated_fixtures
|
||||
end
|
||||
|
||||
def teardown_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
unless run_in_transaction?
|
||||
Fixtures.reset_cache
|
||||
end
|
||||
|
||||
# Rollback changes if a transaction is active.
|
||||
if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0
|
||||
ActiveRecord::Base.connection.rollback_db_transaction
|
||||
ActiveRecord::Base.connection.decrement_open_transactions
|
||||
end
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
|
||||
private
|
||||
def load_fixtures
|
||||
@loaded_fixtures = {}
|
||||
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|
||||
unless fixtures.nil?
|
||||
if fixtures.instance_of?(Fixtures)
|
||||
@loaded_fixtures[fixtures.name] = fixtures
|
||||
else
|
||||
fixtures.each { |f| @loaded_fixtures[f.name] = f }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# for pre_loaded_fixtures, only require the classes once. huge speed improvement
|
||||
@@required_fixture_classes = false
|
||||
|
||||
def instantiate_fixtures
|
||||
if pre_loaded_fixtures
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
|
||||
unless @@required_fixture_classes
|
||||
self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
|
||||
@@required_fixture_classes = true
|
||||
end
|
||||
Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
|
||||
else
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
|
||||
@loaded_fixtures.each do |table_name, fixtures|
|
||||
Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_instances?
|
||||
use_instantiated_fixtures != :no_instances
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ en:
|
|||
# blank: "This is a custom blank message for User login"
|
||||
# Will define custom blank validation message for User model and
|
||||
# custom blank validation message for login attribute of User model.
|
||||
models:
|
||||
#models:
|
||||
|
||||
# Translate model names. Used in Model.human_name().
|
||||
#models:
|
||||
|
|
|
@ -130,7 +130,9 @@ module ActiveRecord
|
|||
# To run migrations against the currently configured database, use
|
||||
# <tt>rake db:migrate</tt>. This will update the database by running all of the
|
||||
# pending migrations, creating the <tt>schema_migrations</tt> table
|
||||
# (see "About the schema_migrations table" section below) if missing.
|
||||
# (see "About the schema_migrations table" section below) if missing. It will also
|
||||
# invoke the db:schema:dump task, which will update your db/schema.rb file
|
||||
# to match the structure of your database.
|
||||
#
|
||||
# To roll the database back to a previous migration version, use
|
||||
# <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
|
||||
|
|
|
@ -39,7 +39,7 @@ module ActiveRecord
|
|||
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
|
||||
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
||||
#
|
||||
# All \scopes are available as class methods on the ActiveRecord::Base descendent upon which the \scopes were defined. But they are also available to
|
||||
# All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to
|
||||
# <tt>has_many</tt> associations. If,
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
|
|
279
vendor/rails/activerecord/lib/active_record/nested_attributes.rb
vendored
Normal file
279
vendor/rails/activerecord/lib/active_record/nested_attributes.rb
vendored
Normal file
|
@ -0,0 +1,279 @@
|
|||
module ActiveRecord
|
||||
module NestedAttributes #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false
|
||||
base.reject_new_nested_attributes_procs = {}
|
||||
end
|
||||
|
||||
# == Nested Attributes
|
||||
#
|
||||
# Nested attributes allow you to save attributes on associated records
|
||||
# through the parent. By default nested attribute updating is turned off,
|
||||
# you can enable it using the accepts_nested_attributes_for class method.
|
||||
# When you enable nested attributes an attribute writer is defined on
|
||||
# the model.
|
||||
#
|
||||
# The attribute writer is named after the association, which means that
|
||||
# in the following example, two new methods are added to your model:
|
||||
# <tt>author_attributes=(attributes)</tt> and
|
||||
# <tt>pages_attributes=(attributes)</tt>.
|
||||
#
|
||||
# class Book < ActiveRecord::Base
|
||||
# has_one :author
|
||||
# has_many :pages
|
||||
#
|
||||
# accepts_nested_attributes_for :author, :pages
|
||||
# end
|
||||
#
|
||||
# Note that the <tt>:autosave</tt> option is automatically enabled on every
|
||||
# association that accepts_nested_attributes_for is used for.
|
||||
#
|
||||
# === One-to-one
|
||||
#
|
||||
# Consider a Member model that has one Avatar:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_one :avatar
|
||||
# accepts_nested_attributes_for :avatar
|
||||
# end
|
||||
#
|
||||
# Enabling nested attributes on a one-to-one association allows you to
|
||||
# create the member and avatar in one go:
|
||||
#
|
||||
# params = { 'member' => { 'name' => 'Jack', 'avatar_attributes' => { 'icon' => 'smiling' } } }
|
||||
# member = Member.create(params)
|
||||
# member.avatar.icon #=> 'smiling'
|
||||
#
|
||||
# It also allows you to update the avatar through the member:
|
||||
#
|
||||
# params = { 'member' => { 'avatar_attributes' => { 'icon' => 'sad' } } }
|
||||
# member.update_attributes params['member']
|
||||
# member.avatar.icon #=> 'sad'
|
||||
#
|
||||
# By default you will only be able to set and update attributes on the
|
||||
# associated model. If you want to destroy the associated model through the
|
||||
# attributes hash, you have to enable it first using the
|
||||
# <tt>:allow_destroy</tt> option.
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_one :avatar
|
||||
# accepts_nested_attributes_for :avatar, :allow_destroy => true
|
||||
# end
|
||||
#
|
||||
# Now, when you add the <tt>_delete</tt> key to the attributes hash, with a
|
||||
# value that evaluates to +true+, you will destroy the associated model:
|
||||
#
|
||||
# member.avatar_attributes = { '_delete' => '1' }
|
||||
# member.avatar.marked_for_destruction? # => true
|
||||
# member.save
|
||||
# member.avatar #=> nil
|
||||
#
|
||||
# Note that the model will _not_ be destroyed until the parent is saved.
|
||||
#
|
||||
# === One-to-many
|
||||
#
|
||||
# Consider a member that has a number of posts:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_many :posts
|
||||
# accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
|
||||
# end
|
||||
#
|
||||
# You can now set or update attributes on an associated post model through
|
||||
# the attribute hash.
|
||||
#
|
||||
# For each key in the hash that starts with the string 'new' a new model
|
||||
# will be instantiated. When the proc given with the <tt>:reject_if</tt>
|
||||
# option evaluates to +false+ for a certain attribute hash no record will
|
||||
# be built for that hash.
|
||||
#
|
||||
# params = { 'member' => {
|
||||
# 'name' => 'joe', 'posts_attributes' => {
|
||||
# 'new_12345' => { 'title' => 'Kari, the awesome Ruby documentation browser!' },
|
||||
# 'new_54321' => { 'title' => 'The egalitarian assumption of the modern citizen' },
|
||||
# 'new_67890' => { 'title' => '' } # This one matches the :reject_if proc and will not be instantiated.
|
||||
# }
|
||||
# }}
|
||||
#
|
||||
# member = Member.create(params['member'])
|
||||
# member.posts.length #=> 2
|
||||
# member.posts.first.title #=> 'Kari, the awesome Ruby documentation browser!'
|
||||
# member.posts.second.title #=> 'The egalitarian assumption of the modern citizen'
|
||||
#
|
||||
# When the key for post attributes is an integer, the associated post with
|
||||
# that ID will be updated:
|
||||
#
|
||||
# member.attributes = {
|
||||
# 'name' => 'Joe',
|
||||
# 'posts_attributes' => {
|
||||
# '1' => { 'title' => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
|
||||
# '2' => { 'title' => '[UPDATED] other post' }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# By default the associated models are protected from being destroyed. If
|
||||
# you want to destroy any of the associated models through the attributes
|
||||
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
|
||||
# option.
|
||||
#
|
||||
# This will allow you to specify which models to destroy in the attributes
|
||||
# hash by setting the '_delete' attribute to a value that evaluates to
|
||||
# +true+:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_many :posts
|
||||
# accepts_nested_attributes_for :posts, :allow_destroy => true
|
||||
# end
|
||||
#
|
||||
# params = {'member' => { 'name' => 'joe', 'posts_attributes' => {
|
||||
# '2' => { '_delete' => '1' }
|
||||
# }}}
|
||||
# member.attributes = params['member']
|
||||
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
|
||||
# member.posts.length #=> 2
|
||||
# member.save
|
||||
# member.posts.length # => 1
|
||||
#
|
||||
# === Saving
|
||||
#
|
||||
# All changes to models, including the destruction of those marked for
|
||||
# destruction, are saved and destroyed automatically and atomically when
|
||||
# the parent model is saved. This happens inside the transaction initiated
|
||||
# by the parents save method. See ActiveRecord::AutosaveAssociation.
|
||||
module ClassMethods
|
||||
# Defines an attributes writer for the specified association(s).
|
||||
#
|
||||
# Supported options:
|
||||
# [:allow_destroy]
|
||||
# If true, destroys any members from the attributes hash with a
|
||||
# <tt>_delete</tt> key and a value that converts to +true+
|
||||
# (eg. 1, '1', true, or 'true'). This option is off by default.
|
||||
# [:reject_if]
|
||||
# Allows you to specify a Proc that checks whether a record should be
|
||||
# built for a certain attribute hash. The hash is passed to the Proc
|
||||
# and the Proc should return either +true+ or +false+. When no Proc
|
||||
# is specified a record will be built for all attribute hashes.
|
||||
#
|
||||
# Examples:
|
||||
# accepts_nested_attributes_for :avatar
|
||||
# accepts_nested_attributes_for :avatar, :allow_destroy => true
|
||||
# accepts_nested_attributes_for :avatar, :reject_if => proc { ... }
|
||||
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true, :reject_if => proc { ... }
|
||||
def accepts_nested_attributes_for(*attr_names)
|
||||
options = { :allow_destroy => false }
|
||||
options.update(attr_names.extract_options!)
|
||||
options.assert_valid_keys(:allow_destroy, :reject_if)
|
||||
|
||||
attr_names.each do |association_name|
|
||||
if reflection = reflect_on_association(association_name)
|
||||
type = case reflection.macro
|
||||
when :has_one, :belongs_to
|
||||
:one_to_one
|
||||
when :has_many, :has_and_belongs_to_many
|
||||
:collection
|
||||
end
|
||||
|
||||
reflection.options[:autosave] = true
|
||||
self.reject_new_nested_attributes_procs[association_name.to_sym] = options[:reject_if]
|
||||
|
||||
# def pirate_attributes=(attributes)
|
||||
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, false)
|
||||
# end
|
||||
class_eval %{
|
||||
def #{association_name}_attributes=(attributes)
|
||||
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]})
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
else
|
||||
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction?
|
||||
# It's used in conjunction with fields_for to build a form element
|
||||
# for the destruction of this association.
|
||||
#
|
||||
# See ActionView::Helpers::FormHelper::fields_for for more info.
|
||||
def _delete
|
||||
marked_for_destruction?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Assigns the given attributes to the association. An association will be
|
||||
# build if it doesn't exist yet.
|
||||
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
|
||||
if should_destroy_nested_attributes_record?(allow_destroy, attributes)
|
||||
send(association_name).mark_for_destruction
|
||||
else
|
||||
(send(association_name) || send("build_#{association_name}")).attributes = attributes
|
||||
end
|
||||
end
|
||||
|
||||
# Assigns the given attributes to the collection association.
|
||||
#
|
||||
# Keys containing an ID for an associated record will update that record.
|
||||
# Keys starting with <tt>new</tt> will instantiate a new record for that
|
||||
# association.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# assign_nested_attributes_for_collection_association(:people, {
|
||||
# '1' => { 'name' => 'Peter' },
|
||||
# 'new_43' => { 'name' => 'John' }
|
||||
# })
|
||||
#
|
||||
# Will update the name of the Person with ID 1 and create a new associated
|
||||
# person with the name 'John'.
|
||||
def assign_nested_attributes_for_collection_association(association_name, attributes, allow_destroy)
|
||||
unless attributes.is_a?(Hash)
|
||||
raise ArgumentError, "Hash expected, got #{attributes.class.name} (#{attributes.inspect})"
|
||||
end
|
||||
|
||||
# Make sure any new records sorted by their id before they're build.
|
||||
sorted_by_id = attributes.sort_by { |id, _| id.is_a?(String) ? id.sub(/^new_/, '').to_i : id }
|
||||
|
||||
sorted_by_id.each do |id, record_attributes|
|
||||
if id.acts_like?(:string) && id.starts_with?('new_')
|
||||
build_new_nested_attributes_record(association_name, record_attributes)
|
||||
else
|
||||
assign_to_or_destroy_nested_attributes_record(association_name, id, record_attributes, allow_destroy)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns +true+ if <tt>allow_destroy</tt> is enabled and the attributes
|
||||
# contains a truthy value for the key <tt>'_delete'</tt>.
|
||||
#
|
||||
# It will _always_ remove the <tt>'_delete'</tt> key, if present.
|
||||
def should_destroy_nested_attributes_record?(allow_destroy, attributes)
|
||||
ConnectionAdapters::Column.value_to_boolean(attributes.delete('_delete')) && allow_destroy
|
||||
end
|
||||
|
||||
# Builds a new record with the given attributes.
|
||||
#
|
||||
# If a <tt>:reject_if</tt> proc exists for this association, it will be
|
||||
# called with the attributes as its argument. If the proc returns a truthy
|
||||
# value, the record is _not_ build.
|
||||
def build_new_nested_attributes_record(association_name, attributes)
|
||||
if reject_proc = self.class.reject_new_nested_attributes_procs[association_name]
|
||||
return if reject_proc.call(attributes)
|
||||
end
|
||||
send(association_name).build(attributes)
|
||||
end
|
||||
|
||||
# Assigns the attributes to the record specified by +id+. Or marks it for
|
||||
# destruction if #should_destroy_nested_attributes_record? returns +true+.
|
||||
def assign_to_or_destroy_nested_attributes_record(association_name, id, attributes, allow_destroy)
|
||||
record = send(association_name).detect { |record| record.id == id.to_i }
|
||||
if should_destroy_nested_attributes_record?(allow_destroy, attributes)
|
||||
record.mark_for_destruction
|
||||
else
|
||||
record.attributes = attributes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,20 +1,32 @@
|
|||
module ActiveRecord
|
||||
module QueryCache
|
||||
# Enable the query cache within the block if Active Record is configured.
|
||||
def cache(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.cache(&block)
|
||||
class QueryCache
|
||||
module ClassMethods
|
||||
# Enable the query cache within the block if Active Record is configured.
|
||||
def cache(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.cache(&block)
|
||||
end
|
||||
end
|
||||
|
||||
# Disable the query cache within the block if Active Record is configured.
|
||||
def uncached(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.uncached(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Disable the query cache within the block if Active Record is configured.
|
||||
def uncached(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.uncached(&block)
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
ActiveRecord::Base.cache do
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -65,6 +65,11 @@ module ActiveRecord
|
|||
def reflect_on_association(association)
|
||||
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
||||
end
|
||||
|
||||
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
||||
def reflect_on_all_autosave_associations
|
||||
reflections.values.select { |reflection| reflection.options[:autosave] }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -198,6 +203,14 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def columns(tbl_name, log_msg)
|
||||
@columns ||= klass.connection.columns(tbl_name, log_msg)
|
||||
end
|
||||
|
||||
def reset_column_information
|
||||
@columns = nil
|
||||
end
|
||||
|
||||
def check_validity!
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ module ActiveRecord
|
|||
class SchemaDumper #:nodoc:
|
||||
private_class_method :new
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# A list of tables which should not be dumped to the schema.
|
||||
# Acceptable values are strings as well as regexp.
|
||||
# This setting is only used if ActiveRecord::Base.schema_format == :ruby
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'active_support/json'
|
||||
|
||||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
class Serializer #:nodoc:
|
||||
|
@ -95,4 +97,4 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
require 'active_record/serializers/xml_serializer'
|
||||
require 'active_record/serializers/json_serializer'
|
||||
require 'active_record/serializers/json_serializer'
|
||||
|
|
|
@ -23,11 +23,12 @@ module ActiveRecord #:nodoc:
|
|||
# </topic>
|
||||
#
|
||||
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
|
||||
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt> and <tt>:dasherize</tt>.
|
||||
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
|
||||
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
|
||||
# +attributes+ method. The default is to dasherize all column names, but you
|
||||
# can disable this setting <tt>:dasherize</tt> to +false+. To not have the
|
||||
# column type included in the XML output set <tt>:skip_types</tt> to +true+.
|
||||
# can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
|
||||
# to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
|
||||
# To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
|
||||
#
|
||||
# For instance:
|
||||
#
|
||||
|
@ -178,13 +179,22 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def root
|
||||
root = (options[:root] || @record.class.to_s.underscore).to_s
|
||||
dasherize? ? root.dasherize : root
|
||||
reformat_name(root)
|
||||
end
|
||||
|
||||
def dasherize?
|
||||
!options.has_key?(:dasherize) || options[:dasherize]
|
||||
end
|
||||
|
||||
def camelize?
|
||||
options.has_key?(:camelize) && options[:camelize]
|
||||
end
|
||||
|
||||
def reformat_name(name)
|
||||
name = name.camelize if camelize?
|
||||
dasherize? ? name.dasherize : name
|
||||
end
|
||||
|
||||
def serializable_attributes
|
||||
serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
|
||||
end
|
||||
|
@ -212,7 +222,7 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def add_tag(attribute)
|
||||
builder.tag!(
|
||||
dasherize? ? attribute.name.dasherize : attribute.name,
|
||||
reformat_name(attribute.name),
|
||||
attribute.value.to_s,
|
||||
attribute.decorations(!options[:skip_types])
|
||||
)
|
||||
|
@ -220,8 +230,7 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def add_associations(association, records, opts)
|
||||
if records.is_a?(Enumerable)
|
||||
tag = association.to_s
|
||||
tag = tag.dasherize if dasherize?
|
||||
tag = reformat_name(association.to_s)
|
||||
if records.empty?
|
||||
builder.tag!(tag, :type => :array)
|
||||
else
|
||||
|
|
314
vendor/rails/activerecord/lib/active_record/session_store.rb
vendored
Normal file
314
vendor/rails/activerecord/lib/active_record/session_store.rb
vendored
Normal file
|
@ -0,0 +1,314 @@
|
|||
module ActiveRecord
|
||||
# A session store backed by an Active Record class. A default class is
|
||||
# provided, but any object duck-typing to an Active Record Session class
|
||||
# with text +session_id+ and +data+ attributes is sufficient.
|
||||
#
|
||||
# The default assumes a +sessions+ tables with columns:
|
||||
# +id+ (numeric primary key),
|
||||
# +session_id+ (text, or longtext if your session data exceeds 65K), and
|
||||
# +data+ (text or longtext; careful if your session data exceeds 65KB).
|
||||
# The +session_id+ column should always be indexed for speedy lookups.
|
||||
# Session data is marshaled to the +data+ column in Base64 format.
|
||||
# If the data you write is larger than the column's size limit,
|
||||
# ActionController::SessionOverflowError will be raised.
|
||||
#
|
||||
# You may configure the table name, primary key, and data column.
|
||||
# For example, at the end of <tt>config/environment.rb</tt>:
|
||||
# ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
|
||||
# ActiveRecord::SessionStore::Session.primary_key = 'session_id'
|
||||
# ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
|
||||
# Note that setting the primary key to the +session_id+ frees you from
|
||||
# having a separate +id+ column if you don't want it. However, you must
|
||||
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
|
||||
# on ApplicationController is a good place.
|
||||
#
|
||||
# Since the default class is a simple Active Record, you get timestamps
|
||||
# for free if you add +created_at+ and +updated_at+ datetime columns to
|
||||
# the +sessions+ table, making periodic session expiration a snap.
|
||||
#
|
||||
# You may provide your own session class implementation, whether a
|
||||
# feature-packed Active Record or a bare-metal high-performance SQL
|
||||
# store, by setting
|
||||
# ActiveRecord::SessionStore.session_class = MySessionClass
|
||||
# You must implement these methods:
|
||||
# self.find_by_session_id(session_id)
|
||||
# initialize(hash_of_session_id_and_data)
|
||||
# attr_reader :session_id
|
||||
# attr_accessor :data
|
||||
# save
|
||||
# destroy
|
||||
#
|
||||
# The example SqlBypass class is a generic SQL session store. You may
|
||||
# use it as a basis for high-performance database-specific stores.
|
||||
class SessionStore < ActionController::Session::AbstractStore
|
||||
# The default Active Record class.
|
||||
class Session < ActiveRecord::Base
|
||||
##
|
||||
# :singleton-method:
|
||||
# Customizable data column name. Defaults to 'data'.
|
||||
cattr_accessor :data_column_name
|
||||
self.data_column_name = 'data'
|
||||
|
||||
before_save :marshal_data!
|
||||
before_save :raise_on_session_data_overflow!
|
||||
|
||||
class << self
|
||||
def data_column_size_limit
|
||||
@data_column_size_limit ||= columns_hash[@@data_column_name].limit
|
||||
end
|
||||
|
||||
# Hook to set up sessid compatibility.
|
||||
def find_by_session_id(session_id)
|
||||
setup_sessid_compatibility!
|
||||
find_by_session_id(session_id)
|
||||
end
|
||||
|
||||
def marshal(data)
|
||||
ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
|
||||
end
|
||||
|
||||
def unmarshal(data)
|
||||
Marshal.load(ActiveSupport::Base64.decode64(data)) if data
|
||||
end
|
||||
|
||||
def create_table!
|
||||
connection.execute <<-end_sql
|
||||
CREATE TABLE #{table_name} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
#{connection.quote_column_name('session_id')} TEXT UNIQUE,
|
||||
#{connection.quote_column_name(@@data_column_name)} TEXT(255)
|
||||
)
|
||||
end_sql
|
||||
end
|
||||
|
||||
def drop_table!
|
||||
connection.execute "DROP TABLE #{table_name}"
|
||||
end
|
||||
|
||||
private
|
||||
# Compatibility with tables using sessid instead of session_id.
|
||||
def setup_sessid_compatibility!
|
||||
# Reset column info since it may be stale.
|
||||
reset_column_information
|
||||
if columns_hash['sessid']
|
||||
def self.find_by_session_id(*args)
|
||||
find_by_sessid(*args)
|
||||
end
|
||||
|
||||
define_method(:session_id) { sessid }
|
||||
define_method(:session_id=) { |session_id| self.sessid = session_id }
|
||||
else
|
||||
def self.find_by_session_id(session_id)
|
||||
find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Lazy-unmarshal session state.
|
||||
def data
|
||||
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
|
||||
end
|
||||
|
||||
attr_writer :data
|
||||
|
||||
# Has the session been loaded yet?
|
||||
def loaded?
|
||||
!!@data
|
||||
end
|
||||
|
||||
private
|
||||
def marshal_data!
|
||||
return false if !loaded?
|
||||
write_attribute(@@data_column_name, self.class.marshal(self.data))
|
||||
end
|
||||
|
||||
# Ensures that the data about to be stored in the database is not
|
||||
# larger than the data storage column. Raises
|
||||
# ActionController::SessionOverflowError.
|
||||
def raise_on_session_data_overflow!
|
||||
return false if !loaded?
|
||||
limit = self.class.data_column_size_limit
|
||||
if loaded? and limit and read_attribute(@@data_column_name).size > limit
|
||||
raise ActionController::SessionOverflowError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A barebones session store which duck-types with the default session
|
||||
# store but bypasses Active Record and issues SQL directly. This is
|
||||
# an example session model class meant as a basis for your own classes.
|
||||
#
|
||||
# The database connection, table name, and session id and data columns
|
||||
# are configurable class attributes. Marshaling and unmarshaling
|
||||
# are implemented as class methods that you may override. By default,
|
||||
# marshaling data is
|
||||
#
|
||||
# ActiveSupport::Base64.encode64(Marshal.dump(data))
|
||||
#
|
||||
# and unmarshaling data is
|
||||
#
|
||||
# Marshal.load(ActiveSupport::Base64.decode64(data))
|
||||
#
|
||||
# This marshaling behavior is intended to store the widest range of
|
||||
# binary session data in a +text+ column. For higher performance,
|
||||
# store in a +blob+ column instead and forgo the Base64 encoding.
|
||||
class SqlBypass
|
||||
##
|
||||
# :singleton-method:
|
||||
# Use the ActiveRecord::Base.connection by default.
|
||||
cattr_accessor :connection
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The table name defaults to 'sessions'.
|
||||
cattr_accessor :table_name
|
||||
@@table_name = 'sessions'
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The session id field defaults to 'session_id'.
|
||||
cattr_accessor :session_id_column
|
||||
@@session_id_column = 'session_id'
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The data field defaults to 'data'.
|
||||
cattr_accessor :data_column
|
||||
@@data_column = 'data'
|
||||
|
||||
class << self
|
||||
def connection
|
||||
@@connection ||= ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
# Look up a session by id and unmarshal its data if found.
|
||||
def find_by_session_id(session_id)
|
||||
if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
|
||||
new(:session_id => session_id, :marshaled_data => record['data'])
|
||||
end
|
||||
end
|
||||
|
||||
def marshal(data)
|
||||
ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
|
||||
end
|
||||
|
||||
def unmarshal(data)
|
||||
Marshal.load(ActiveSupport::Base64.decode64(data)) if data
|
||||
end
|
||||
|
||||
def create_table!
|
||||
@@connection.execute <<-end_sql
|
||||
CREATE TABLE #{table_name} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
#{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
|
||||
#{@@connection.quote_column_name(data_column)} TEXT
|
||||
)
|
||||
end_sql
|
||||
end
|
||||
|
||||
def drop_table!
|
||||
@@connection.execute "DROP TABLE #{table_name}"
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :session_id
|
||||
attr_writer :data
|
||||
|
||||
# Look for normal and marshaled data, self.find_by_session_id's way of
|
||||
# telling us to postpone unmarshaling until the data is requested.
|
||||
# We need to handle a normal data attribute in case of a new record.
|
||||
def initialize(attributes)
|
||||
@session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
|
||||
@new_record = @marshaled_data.nil?
|
||||
end
|
||||
|
||||
def new_record?
|
||||
@new_record
|
||||
end
|
||||
|
||||
# Lazy-unmarshal session state.
|
||||
def data
|
||||
unless @data
|
||||
if @marshaled_data
|
||||
@data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
|
||||
else
|
||||
@data = {}
|
||||
end
|
||||
end
|
||||
@data
|
||||
end
|
||||
|
||||
def loaded?
|
||||
!!@data
|
||||
end
|
||||
|
||||
def save
|
||||
return false if !loaded?
|
||||
marshaled_data = self.class.marshal(data)
|
||||
|
||||
if @new_record
|
||||
@new_record = false
|
||||
@@connection.update <<-end_sql, 'Create session'
|
||||
INSERT INTO #{@@table_name} (
|
||||
#{@@connection.quote_column_name(@@session_id_column)},
|
||||
#{@@connection.quote_column_name(@@data_column)} )
|
||||
VALUES (
|
||||
#{@@connection.quote(session_id)},
|
||||
#{@@connection.quote(marshaled_data)} )
|
||||
end_sql
|
||||
else
|
||||
@@connection.update <<-end_sql, 'Update session'
|
||||
UPDATE #{@@table_name}
|
||||
SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
|
||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
||||
end_sql
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
unless @new_record
|
||||
@@connection.delete <<-end_sql, 'Destroy session'
|
||||
DELETE FROM #{@@table_name}
|
||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
||||
end_sql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The class used for session storage. Defaults to
|
||||
# ActiveRecord::SessionStore::Session
|
||||
cattr_accessor :session_class
|
||||
self.session_class = Session
|
||||
|
||||
SESSION_RECORD_KEY = 'rack.session.record'.freeze
|
||||
|
||||
private
|
||||
def get_session(env, sid)
|
||||
Base.silence do
|
||||
sid ||= generate_sid
|
||||
session = @@session_class.find_by_session_id(sid)
|
||||
session ||= @@session_class.new(:session_id => sid, :data => {})
|
||||
env[SESSION_RECORD_KEY] = session
|
||||
[sid, session.data]
|
||||
end
|
||||
end
|
||||
|
||||
def set_session(env, sid, session_data)
|
||||
Base.silence do
|
||||
record = env[SESSION_RECORD_KEY]
|
||||
record.data = session_data
|
||||
return false unless record.save
|
||||
|
||||
session_data = record.data
|
||||
if session_data && session_data.respond_to?(:each_value)
|
||||
session_data.each_value do |obj|
|
||||
obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +1,7 @@
|
|||
require "active_support/test_case"
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveRecord
|
||||
class TestCase < ActiveSupport::TestCase #:nodoc:
|
||||
self.fixture_path = FIXTURES_ROOT
|
||||
self.use_instantiated_fixtures = false
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
def create_fixtures(*table_names, &block)
|
||||
Fixtures.create_fixtures(FIXTURES_ROOT, table_names, {}, &block)
|
||||
end
|
||||
|
||||
def assert_date_from_db(expected, actual, message = nil)
|
||||
# SybaseAdapter doesn't have a separate column type just for dates,
|
||||
# so the time is in the string and incorrectly formatted
|
||||
|
@ -35,6 +27,7 @@ module ActiveRecord
|
|||
$queries_executed = []
|
||||
yield
|
||||
ensure
|
||||
%w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
|
||||
assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
|
||||
end
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ module ActiveRecord
|
|||
write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
|
||||
write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
|
||||
|
||||
write_attribute('updated_at', t) if respond_to?(:updated_at)
|
||||
write_attribute('updated_on', t) if respond_to?(:updated_on)
|
||||
write_attribute('updated_at', t) if respond_to?(:updated_at) && updated_at.nil?
|
||||
write_attribute('updated_on', t) if respond_to?(:updated_on) && updated_on.nil?
|
||||
end
|
||||
create_without_timestamps
|
||||
end
|
||||
|
|
|
@ -120,16 +120,66 @@ module ActiveRecord
|
|||
# end
|
||||
#
|
||||
# One should restart the entire transaction if a StatementError occurred.
|
||||
#
|
||||
# == Nested transactions
|
||||
#
|
||||
# #transaction calls can be nested. By default, this makes all database
|
||||
# statements in the nested transaction block become part of the parent
|
||||
# transaction. For example:
|
||||
#
|
||||
# User.transaction do
|
||||
# User.create(:username => 'Kotori')
|
||||
# User.transaction do
|
||||
# User.create(:username => 'Nemu')
|
||||
# raise ActiveRecord::Rollback
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# User.find(:all) # => empty
|
||||
#
|
||||
# It is also possible to requires a sub-transaction by passing
|
||||
# <tt>:requires_new => true</tt>. If anything goes wrong, the
|
||||
# database rolls back to the beginning of the sub-transaction
|
||||
# without rolling back the parent transaction. For example:
|
||||
#
|
||||
# User.transaction do
|
||||
# User.create(:username => 'Kotori')
|
||||
# User.transaction(:requires_new => true) do
|
||||
# User.create(:username => 'Nemu')
|
||||
# raise ActiveRecord::Rollback
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# User.find(:all) # => Returns only Kotori
|
||||
#
|
||||
# Most databases don't support true nested transactions. At the time of
|
||||
# writing, the only database that we're aware of that supports true nested
|
||||
# transactions, is MS-SQL. Because of this, Active Record emulates nested
|
||||
# transactions by using savepoints. See
|
||||
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
|
||||
# for more information about savepoints.
|
||||
#
|
||||
# === Caveats
|
||||
#
|
||||
# If you're on MySQL, then do not use DDL operations in nested transactions
|
||||
# blocks that are emulated with savepoints. That is, do not execute statements
|
||||
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
|
||||
# releases all savepoints upon executing a DDL operation. When #transaction
|
||||
# is finished and tries to release the savepoint it created earlier, a
|
||||
# database error will occur because the savepoint has already been
|
||||
# automatically released. The following example demonstrates the problem:
|
||||
#
|
||||
# Model.connection.transaction do # BEGIN
|
||||
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
|
||||
# Model.connection.create_table(...) # active_record_1 now automatically released
|
||||
# end # RELEASE savepoint active_record_1
|
||||
# # ^^^^ BOOM! database error!
|
||||
# end
|
||||
module ClassMethods
|
||||
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
|
||||
def transaction(&block)
|
||||
connection.increment_open_transactions
|
||||
|
||||
begin
|
||||
connection.transaction(connection.open_transactions == 1, &block)
|
||||
ensure
|
||||
connection.decrement_open_transactions
|
||||
end
|
||||
def transaction(options = {}, &block)
|
||||
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
|
||||
connection.transaction(options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -147,7 +197,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def save_with_transactions! #:nodoc:
|
||||
rollback_active_record_state! { transaction { save_without_transactions! } }
|
||||
rollback_active_record_state! { self.class.transaction { save_without_transactions! } }
|
||||
end
|
||||
|
||||
# Reset id and @new_record if the transaction rolls back.
|
||||
|
@ -175,7 +225,7 @@ module ActiveRecord
|
|||
# instance.
|
||||
def with_transaction_returning_status(method, *args)
|
||||
status = nil
|
||||
transaction do
|
||||
self.class.transaction do
|
||||
status = send(method, *args)
|
||||
raise ActiveRecord::Rollback unless status
|
||||
end
|
||||
|
|
|
@ -203,9 +203,8 @@ module ActiveRecord
|
|||
if attr == "base"
|
||||
full_messages << message
|
||||
else
|
||||
#key = :"activerecord.att.#{@base.class.name.underscore.to_sym}.#{attr}"
|
||||
attr_name = @base.class.human_attribute_name(attr)
|
||||
full_messages << attr_name + ' ' + message
|
||||
full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -494,18 +493,20 @@ module ActiveRecord
|
|||
# The first_name attribute must be in the object and it cannot be blank.
|
||||
#
|
||||
# If you want to validate the presence of a boolean field (where the real values are true and false),
|
||||
# you will want to use validates_inclusion_of :field_name, :in => [true, false]
|
||||
# This is due to the way Object#blank? handles boolean values. false.blank? # => true
|
||||
# you will want to use <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
|
||||
#
|
||||
# This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
|
||||
#
|
||||
# Configuration options:
|
||||
# * <tt>message</tt> - A custom error message (default is: "can't be blank").
|
||||
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
||||
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
|
||||
# <tt>:update</tt>).
|
||||
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
|
||||
# The method, proc or string should return or evaluate to a true or false value.
|
||||
# * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
# 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_presence_of(*attr_names)
|
||||
configuration = { :on => :save }
|
||||
|
@ -574,6 +575,8 @@ module ActiveRecord
|
|||
# Get range option and value.
|
||||
option = range_options.first
|
||||
option_value = options[range_options.first]
|
||||
key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
|
||||
custom_message = options[:message] || options[key]
|
||||
|
||||
case option
|
||||
when :within, :in
|
||||
|
@ -582,9 +585,9 @@ module ActiveRecord
|
|||
validates_each(attrs, options) do |record, attr, value|
|
||||
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
||||
if value.nil? or value.size < option_value.begin
|
||||
record.errors.add(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
|
||||
record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin)
|
||||
elsif value.size > option_value.end
|
||||
record.errors.add(attr, :too_long, :default => options[:too_long], :count => option_value.end)
|
||||
record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end)
|
||||
end
|
||||
end
|
||||
when :is, :minimum, :maximum
|
||||
|
@ -592,13 +595,10 @@ module ActiveRecord
|
|||
|
||||
# Declare different validations per option.
|
||||
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
|
||||
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
|
||||
|
||||
validates_each(attrs, options) do |record, attr, value|
|
||||
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]
|
||||
record.errors.add(attr, key, :default => custom_message, :count => option_value)
|
||||
end
|
||||
end
|
||||
|
@ -903,7 +903,7 @@ module ActiveRecord
|
|||
configuration.update(attr_names.extract_options!)
|
||||
|
||||
validates_each(attr_names, configuration) do |record, attr_name, value|
|
||||
unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
|
||||
unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all?
|
||||
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
|
||||
end
|
||||
end
|
||||
|
@ -1047,15 +1047,15 @@ module ActiveRecord
|
|||
|
||||
protected
|
||||
# Overwrite this method for validation checks on all saves and use <tt>Errors.add(field, msg)</tt> for invalid attributes.
|
||||
def validate #:doc:
|
||||
def validate
|
||||
end
|
||||
|
||||
# Overwrite this method for validation checks used only on creation.
|
||||
def validate_on_create #:doc:
|
||||
def validate_on_create
|
||||
end
|
||||
|
||||
# Overwrite this method for validation checks used only on updates.
|
||||
def validate_on_update # :doc:
|
||||
def validate_on_update
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module ActiveRecord
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 2
|
||||
TINY = 2
|
||||
MINOR = 3
|
||||
TINY = 0
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require "cases/helper"
|
||||
require 'active_record/schema'
|
||||
|
||||
if ActiveRecord::Base.connection.supports_migrations?
|
||||
|
||||
|
|
|
@ -104,6 +104,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
|
|||
authors.first.posts.first.special_comments.first.post.very_special_comment
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_association_loading_where_first_level_returns_nil
|
||||
authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC')
|
||||
assert_equal [authors(:mary), authors(:david)], authors
|
||||
assert_no_queries do
|
||||
authors[1].post_about_thinking.comments.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'models/vertex'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require "cases/helper"
|
||||
require 'models/post'
|
||||
require 'models/tagging'
|
||||
require 'models/tag'
|
||||
require 'models/comment'
|
||||
require 'models/author'
|
||||
require 'models/category'
|
||||
|
@ -145,7 +146,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
|
||||
post = posts(:welcome)
|
||||
post.update_attributes!(:author => nil)
|
||||
post = assert_queries(2) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the address
|
||||
post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address
|
||||
assert_no_queries do
|
||||
assert_equal nil, post.author_with_address
|
||||
end
|
||||
|
@ -385,12 +386,28 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
assert_equal count, posts.size
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_ond_high_offset
|
||||
def test_eager_with_has_many_and_limit_and_high_offset
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
|
||||
assert_equal 0, posts.size
|
||||
end
|
||||
|
||||
def test_count_eager_with_has_many_and_limit_ond_high_offset
|
||||
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions
|
||||
assert_queries(1) do
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
|
||||
:conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ])
|
||||
assert_equal 0, posts.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions
|
||||
assert_queries(1) do
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
|
||||
:conditions => { 'authors.name' => 'David', 'comments.body' => 'go crazy' })
|
||||
assert_equal 0, posts.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_count_eager_with_has_many_and_limit_and_high_offset
|
||||
posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
|
||||
assert_equal 0, posts
|
||||
end
|
||||
|
@ -689,4 +706,117 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
def test_order_on_join_table_with_include_and_limit
|
||||
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
|
||||
end
|
||||
|
||||
def test_eager_loading_with_order_on_joined_table_preloads
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC')
|
||||
end
|
||||
assert_equal posts(:eager_other), posts[0]
|
||||
assert_equal authors(:mary), assert_no_queries { posts[0].author}
|
||||
end
|
||||
|
||||
def test_eager_loading_with_conditions_on_joined_table_preloads
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal posts(:welcome, :thinking), posts
|
||||
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
|
||||
end
|
||||
assert_equal posts(:welcome, :thinking), posts
|
||||
|
||||
end
|
||||
|
||||
def test_eager_loading_with_conditions_on_string_joined_table_preloads
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
end
|
||||
|
||||
def test_eager_loading_with_select_on_joined_table_preloads
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id')
|
||||
end
|
||||
assert_equal 'David', posts[0].author_name
|
||||
assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments}
|
||||
end
|
||||
|
||||
def test_eager_loading_with_conditions_on_join_model_preloads
|
||||
authors = assert_queries(2) do
|
||||
Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'")
|
||||
end
|
||||
assert_equal authors(:david), authors[0]
|
||||
assert_equal author_addresses(:david_address), authors[0].author_address
|
||||
end
|
||||
|
||||
def test_preload_belongs_to_uses_exclusive_scope
|
||||
people = Person.males.find(:all, :include => :primary_contact)
|
||||
assert_not_equal people.length, 0
|
||||
people.each do |person|
|
||||
assert_no_queries {assert_not_nil person.primary_contact}
|
||||
assert_equal Person.find(person.id).primary_contact, person.primary_contact
|
||||
end
|
||||
end
|
||||
|
||||
def test_preload_has_many_uses_exclusive_scope
|
||||
people = Person.males.find :all, :include => :agents
|
||||
people.each do |person|
|
||||
assert_equal Person.find(person.id).agents, person.agents
|
||||
end
|
||||
end
|
||||
|
||||
def test_preload_has_many_using_primary_key
|
||||
expected = Firm.find(:first).clients_using_primary_key.to_a
|
||||
firm = Firm.find :first, :include => :clients_using_primary_key
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.clients_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_has_many_using_primary_key
|
||||
expected = Firm.find(1).clients_using_primary_key.sort_by &:name
|
||||
firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.clients_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
def test_preload_has_one_using_primary_key
|
||||
expected = Firm.find(:first).account_using_primary_key
|
||||
firm = Firm.find :first, :include => :account_using_primary_key
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.account_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_has_one_using_primary_key
|
||||
expected = Firm.find(1).account_using_primary_key
|
||||
firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.account_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -658,6 +658,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 1, categories(:technology).posts_gruoped_by_title.size
|
||||
end
|
||||
|
||||
def test_find_scoped_grouped_having
|
||||
assert_equal 2, projects(:active_record).well_payed_salary_groups.size
|
||||
assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 }
|
||||
end
|
||||
|
||||
def test_get_ids
|
||||
assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort
|
||||
assert_equal [projects(:active_record).id], developers(:jamis).project_ids
|
||||
|
@ -770,4 +775,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_caching_of_columns
|
||||
david = Developer.find(1)
|
||||
# clear cache possibly created by other tests
|
||||
david.projects.reset_column_information
|
||||
assert_queries(0) { david.projects.columns; david.projects.columns }
|
||||
# and again to verify that reset_column_information clears the cache correctly
|
||||
david.projects.reset_column_information
|
||||
assert_queries(0) { david.projects.columns; david.projects.columns }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -255,6 +255,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 2, companies(:first_firm).clients_grouped_by_name.length
|
||||
end
|
||||
|
||||
def test_find_scoped_grouped_having
|
||||
assert_equal 1, authors(:david).popular_grouped_posts.length
|
||||
assert_equal 0, authors(:mary).popular_grouped_posts.length
|
||||
end
|
||||
|
||||
def test_adding
|
||||
force_signal37_to_load_all_clients_of_firm
|
||||
natural = Client.new("name" => "Natural Company")
|
||||
|
@ -660,6 +665,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 1, Client.find_all_by_client_of(firm.id).size
|
||||
end
|
||||
|
||||
def test_dependent_association_respects_optional_hash_conditions_on_delete
|
||||
firm = companies(:odegy)
|
||||
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
|
||||
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
|
||||
# only one of two clients is included in the association due to the :conditions key
|
||||
assert_equal 2, Client.find_all_by_client_of(firm.id).size
|
||||
assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
|
||||
firm.destroy
|
||||
# only the correctly associated client should have been deleted
|
||||
assert_equal 1, Client.find_all_by_client_of(firm.id).size
|
||||
end
|
||||
|
||||
|
||||
def test_creation_respects_hash_condition
|
||||
ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build
|
||||
|
||||
|
@ -680,7 +698,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
authors(:david).destroy
|
||||
end
|
||||
|
||||
assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids[authors(:david).id]
|
||||
assert_equal nil, AuthorAddress.find_by_id(authors(:david).author_address_id)
|
||||
assert_equal nil, AuthorAddress.find_by_id(authors(:david).author_address_extra_id)
|
||||
end
|
||||
|
||||
def test_invalid_belongs_to_dependent_option_raises_exception
|
||||
|
@ -1097,5 +1116,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert !client_association.respond_to?(:private_method)
|
||||
assert client_association.respond_to?(:private_method, true)
|
||||
end
|
||||
|
||||
def test_creating_using_primary_key
|
||||
firm = Firm.find(:first)
|
||||
client = firm.clients_using_primary_key.create!(:name => 'test')
|
||||
assert_equal firm.name, client.firm_name
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ require 'models/post'
|
|||
require 'models/person'
|
||||
require 'models/reader'
|
||||
require 'models/comment'
|
||||
require 'models/tag'
|
||||
require 'models/tagging'
|
||||
require 'models/author'
|
||||
|
||||
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
||||
fixtures :posts, :readers, :people, :comments, :authors
|
||||
|
@ -201,6 +204,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 2, people(:michael).posts.count(:include => :readers)
|
||||
end
|
||||
|
||||
def test_inner_join_with_quoted_table_name
|
||||
assert_equal 2, people(:michael).jobs.size
|
||||
end
|
||||
|
||||
def test_get_ids
|
||||
assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require "cases/helper"
|
||||
require 'models/club'
|
||||
require 'models/member_type'
|
||||
require 'models/member'
|
||||
require 'models/membership'
|
||||
require 'models/sponsor'
|
||||
|
@ -7,7 +8,7 @@ require 'models/organization'
|
|||
require 'models/member_detail'
|
||||
|
||||
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
||||
fixtures :members, :clubs, :memberships, :sponsors, :organizations
|
||||
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
|
||||
|
||||
def setup
|
||||
@member = members(:groucho)
|
||||
|
@ -158,4 +159,18 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
|||
assert @new_organization.members.include?(@member)
|
||||
end
|
||||
|
||||
def test_preloading_has_one_through_on_belongs_to
|
||||
assert_not_nil @member.member_type
|
||||
@organization = organizations(:nsa)
|
||||
@member_detail = MemberDetail.new
|
||||
@member.member_detail = @member_detail
|
||||
@member.organization = @organization
|
||||
@member_details = assert_queries(3) do
|
||||
MemberDetail.find(:all, :include => :member_type)
|
||||
end
|
||||
@new_detail = @member_details[0]
|
||||
assert @new_detail.loaded_member_type?
|
||||
assert_not_nil assert_no_queries { @new_detail.member_type }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
386
vendor/rails/activerecord/test/cases/autosave_association_test.rb
vendored
Normal file
386
vendor/rails/activerecord/test/cases/autosave_association_test.rb
vendored
Normal file
|
@ -0,0 +1,386 @@
|
|||
require "cases/helper"
|
||||
require "models/pirate"
|
||||
require "models/ship"
|
||||
require "models/ship_part"
|
||||
require "models/bird"
|
||||
require "models/parrot"
|
||||
require "models/treasure"
|
||||
|
||||
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
|
||||
def test_autosave_should_be_a_valid_option_for_has_one
|
||||
assert base.valid_keys_for_has_one_association.include?(:autosave)
|
||||
end
|
||||
|
||||
def test_autosave_should_be_a_valid_option_for_belongs_to
|
||||
assert base.valid_keys_for_belongs_to_association.include?(:autosave)
|
||||
end
|
||||
|
||||
def test_autosave_should_be_a_valid_option_for_has_many
|
||||
assert base.valid_keys_for_has_many_association.include?(:autosave)
|
||||
end
|
||||
|
||||
def test_autosave_should_be_a_valid_option_for_has_and_belongs_to_many
|
||||
assert base.valid_keys_for_has_and_belongs_to_many_association.include?(:autosave)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def base
|
||||
ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
|
||||
end
|
||||
|
||||
# reload
|
||||
def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload
|
||||
@pirate.mark_for_destruction
|
||||
@pirate.ship.mark_for_destruction
|
||||
|
||||
assert !@pirate.reload.marked_for_destruction?
|
||||
assert !@pirate.ship.marked_for_destruction?
|
||||
end
|
||||
|
||||
# has_one
|
||||
def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
|
||||
assert !@pirate.ship.marked_for_destruction?
|
||||
|
||||
@pirate.ship.mark_for_destruction
|
||||
id = @pirate.ship.id
|
||||
|
||||
assert @pirate.ship.marked_for_destruction?
|
||||
assert Ship.find_by_id(id)
|
||||
|
||||
@pirate.save
|
||||
assert_nil @pirate.reload.ship
|
||||
assert_nil Ship.find_by_id(id)
|
||||
end
|
||||
|
||||
def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_child
|
||||
# Stub the save method of the @pirate.ship instance to destroy and then raise an exception
|
||||
class << @pirate.ship
|
||||
def save(*args)
|
||||
super
|
||||
destroy
|
||||
raise 'Oh noes!'
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise(RuntimeError) { assert !@pirate.save }
|
||||
assert_not_nil @pirate.reload.ship
|
||||
end
|
||||
|
||||
# belongs_to
|
||||
def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
|
||||
assert !@ship.pirate.marked_for_destruction?
|
||||
|
||||
@ship.pirate.mark_for_destruction
|
||||
id = @ship.pirate.id
|
||||
|
||||
assert @ship.pirate.marked_for_destruction?
|
||||
assert Pirate.find_by_id(id)
|
||||
|
||||
@ship.save
|
||||
assert_nil @ship.reload.pirate
|
||||
assert_nil Pirate.find_by_id(id)
|
||||
end
|
||||
|
||||
def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent
|
||||
# Stub the save method of the @ship.pirate instance to destroy and then raise an exception
|
||||
class << @ship.pirate
|
||||
def save(*args)
|
||||
super
|
||||
destroy
|
||||
raise 'Oh noes!'
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise(RuntimeError) { assert !@ship.save }
|
||||
assert_not_nil @ship.reload.pirate
|
||||
end
|
||||
|
||||
# has_many & has_and_belongs_to
|
||||
%w{ parrots birds }.each do |association_name|
|
||||
define_method("test_should_destroy_#{association_name}_as_part_of_the_save_transaction_if_they_were_marked_for_destroyal") do
|
||||
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
|
||||
|
||||
assert !@pirate.send(association_name).any? { |child| child.marked_for_destruction? }
|
||||
|
||||
@pirate.send(association_name).each { |child| child.mark_for_destruction }
|
||||
klass = @pirate.send(association_name).first.class
|
||||
ids = @pirate.send(association_name).map(&:id)
|
||||
|
||||
assert @pirate.send(association_name).all? { |child| child.marked_for_destruction? }
|
||||
ids.each { |id| assert klass.find_by_id(id) }
|
||||
|
||||
@pirate.save
|
||||
assert @pirate.reload.send(association_name).empty?
|
||||
ids.each { |id| assert_nil klass.find_by_id(id) }
|
||||
end
|
||||
|
||||
define_method("test_should_rollback_destructions_if_an_exception_occurred_while_saving_#{association_name}") do
|
||||
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
|
||||
before = @pirate.send(association_name).map { |c| c }
|
||||
|
||||
# Stub the save method of the first child to destroy and the second to raise an exception
|
||||
class << before.first
|
||||
def save(*args)
|
||||
super
|
||||
destroy
|
||||
end
|
||||
end
|
||||
class << before.last
|
||||
def save(*args)
|
||||
super
|
||||
raise 'Oh noes!'
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise(RuntimeError) { assert !@pirate.save }
|
||||
assert_equal before, @pirate.reload.send(association_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
|
||||
end
|
||||
|
||||
def test_should_still_work_without_an_associated_model
|
||||
@ship.destroy
|
||||
@pirate.reload.catchphrase = "Arr"
|
||||
@pirate.save
|
||||
assert 'Arr', @pirate.reload.catchphrase
|
||||
end
|
||||
|
||||
def test_should_automatically_save_the_associated_model
|
||||
@pirate.ship.name = 'The Vile Insanity'
|
||||
@pirate.save
|
||||
assert_equal 'The Vile Insanity', @pirate.reload.ship.name
|
||||
end
|
||||
|
||||
def test_should_automatically_validate_the_associated_model
|
||||
@pirate.ship.name = ''
|
||||
assert !@pirate.valid?
|
||||
assert !@pirate.errors.on(:ship_name).blank?
|
||||
end
|
||||
|
||||
def test_should_still_allow_to_bypass_validations_on_the_associated_model
|
||||
@pirate.catchphrase = ''
|
||||
@pirate.ship.name = ''
|
||||
@pirate.save(false)
|
||||
assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name]
|
||||
end
|
||||
|
||||
def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth
|
||||
2.times { |i| @pirate.ship.parts.create!(:name => "part #{i}") }
|
||||
|
||||
@pirate.catchphrase = ''
|
||||
@pirate.ship.name = ''
|
||||
@pirate.ship.parts.each { |part| part.name = '' }
|
||||
@pirate.save(false)
|
||||
|
||||
values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
|
||||
assert_equal ['', '', '', ''], values
|
||||
end
|
||||
|
||||
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
|
||||
@pirate.ship.name = ''
|
||||
assert_raise(ActiveRecord::RecordInvalid) do
|
||||
@pirate.save!
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
|
||||
before = [@pirate.catchphrase, @pirate.ship.name]
|
||||
|
||||
@pirate.catchphrase = 'Arr'
|
||||
@pirate.ship.name = 'The Vile Insanity'
|
||||
|
||||
# Stub the save method of the @pirate.ship instance to raise an exception
|
||||
class << @pirate.ship
|
||||
def save(*args)
|
||||
super
|
||||
raise 'Oh noes!'
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise(RuntimeError) { assert !@pirate.save }
|
||||
assert_equal before, [@pirate.reload.catchphrase, @pirate.ship.name]
|
||||
end
|
||||
|
||||
def test_should_not_load_the_associated_model
|
||||
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
|
||||
end
|
||||
end
|
||||
|
||||
class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@ship = Ship.create(:name => 'Nights Dirty Lightning')
|
||||
@pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
end
|
||||
|
||||
def test_should_still_work_without_an_associated_model
|
||||
@pirate.destroy
|
||||
@ship.reload.name = "The Vile Insanity"
|
||||
@ship.save
|
||||
assert 'The Vile Insanity', @ship.reload.name
|
||||
end
|
||||
|
||||
def test_should_automatically_save_the_associated_model
|
||||
@ship.pirate.catchphrase = 'Arr'
|
||||
@ship.save
|
||||
assert_equal 'Arr', @ship.reload.pirate.catchphrase
|
||||
end
|
||||
|
||||
def test_should_automatically_validate_the_associated_model
|
||||
@ship.pirate.catchphrase = ''
|
||||
assert !@ship.valid?
|
||||
assert !@ship.errors.on(:pirate_catchphrase).blank?
|
||||
end
|
||||
|
||||
def test_should_still_allow_to_bypass_validations_on_the_associated_model
|
||||
@ship.pirate.catchphrase = ''
|
||||
@ship.name = ''
|
||||
@ship.save(false)
|
||||
assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
|
||||
end
|
||||
|
||||
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
|
||||
@ship.pirate.catchphrase = ''
|
||||
assert_raise(ActiveRecord::RecordInvalid) do
|
||||
@ship.save!
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
|
||||
before = [@ship.pirate.catchphrase, @ship.name]
|
||||
|
||||
@ship.pirate.catchphrase = 'Arr'
|
||||
@ship.name = 'The Vile Insanity'
|
||||
|
||||
# Stub the save method of the @ship.pirate instance to raise an exception
|
||||
class << @ship.pirate
|
||||
def save(*args)
|
||||
super
|
||||
raise 'Oh noes!'
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise(RuntimeError) { assert !@ship.save }
|
||||
# TODO: Why does using reload on @ship looses the associated pirate?
|
||||
assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
|
||||
end
|
||||
|
||||
def test_should_not_load_the_associated_model
|
||||
assert_queries(1) { @ship.name = 'The Vile Insanity'; @ship.save! }
|
||||
end
|
||||
end
|
||||
|
||||
module AutosaveAssociationOnACollectionAssociationTests
|
||||
def test_should_automatically_save_the_associated_models
|
||||
new_names = ['Grace OMalley', 'Privateers Greed']
|
||||
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
|
||||
|
||||
@pirate.save
|
||||
assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
|
||||
end
|
||||
|
||||
def test_should_automatically_validate_the_associated_models
|
||||
@pirate.send(@association_name).each { |child| child.name = '' }
|
||||
|
||||
assert !@pirate.valid?
|
||||
assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
|
||||
assert @pirate.errors.on(@association_name).blank?
|
||||
end
|
||||
|
||||
def test_should_still_allow_to_bypass_validations_on_the_associated_models
|
||||
@pirate.catchphrase = ''
|
||||
@pirate.send(@association_name).each { |child| child.name = '' }
|
||||
|
||||
assert @pirate.save(false)
|
||||
assert_equal ['', '', ''], [
|
||||
@pirate.reload.catchphrase,
|
||||
@pirate.send(@association_name).first.name,
|
||||
@pirate.send(@association_name).last.name
|
||||
]
|
||||
end
|
||||
|
||||
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
|
||||
before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
|
||||
new_names = ['Grace OMalley', 'Privateers Greed']
|
||||
|
||||
@pirate.catchphrase = 'Arr'
|
||||
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
|
||||
|
||||
# Stub the save method of the first child instance to raise an exception
|
||||
class << @pirate.send(@association_name).first
|
||||
def save(*args)
|
||||
super
|
||||
raise 'Oh noes!'
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise(RuntimeError) { assert !@pirate.save }
|
||||
assert_equal before, [@pirate.reload.catchphrase, *@pirate.send(@association_name).map(&:name)]
|
||||
end
|
||||
|
||||
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
|
||||
@pirate.send(@association_name).each { |child| child.name = '' }
|
||||
assert_raise(ActiveRecord::RecordInvalid) do
|
||||
@pirate.save!
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet
|
||||
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
|
||||
|
||||
assert_queries(2) do
|
||||
@pirate.catchphrase = 'Yarr'
|
||||
new_names = ['Grace OMalley', 'Privateers Greed']
|
||||
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
|
||||
@pirate.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@association_name = :birds
|
||||
|
||||
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
@child_1 = @pirate.birds.create(:name => 'Posideons Killer')
|
||||
@child_2 = @pirate.birds.create(:name => 'Killer bandita Dionne')
|
||||
end
|
||||
|
||||
include AutosaveAssociationOnACollectionAssociationTests
|
||||
end
|
||||
|
||||
class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@association_name = :parrots
|
||||
@habtm = true
|
||||
|
||||
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
@child_1 = @pirate.parrots.create(:name => 'Posideons Killer')
|
||||
@child_2 = @pirate.parrots.create(:name => 'Killer bandita Dionne')
|
||||
end
|
||||
|
||||
include AutosaveAssociationOnACollectionAssociationTests
|
||||
end
|
|
@ -16,6 +16,7 @@ require 'models/post'
|
|||
require 'models/comment'
|
||||
require 'models/minimalistic'
|
||||
require 'models/warehouse_thing'
|
||||
require 'models/parrot'
|
||||
require 'rexml/document'
|
||||
|
||||
class Category < ActiveRecord::Base; end
|
||||
|
@ -638,6 +639,13 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
category.reload
|
||||
assert_not_nil category.categorizations_count
|
||||
assert_equal 4, category.categorizations_count
|
||||
|
||||
category_2 = categories(:technology)
|
||||
count_1, count_2 = (category.categorizations_count || 0), (category_2.categorizations_count || 0)
|
||||
Category.update_counters([category.id, category_2.id], "categorizations_count" => 2)
|
||||
category.reload; category_2.reload
|
||||
assert_equal count_1 + 2, category.categorizations_count
|
||||
assert_equal count_2 + 2, category_2.categorizations_count
|
||||
end
|
||||
|
||||
def test_update_all
|
||||
|
@ -1197,6 +1205,11 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert b_true.value?
|
||||
end
|
||||
|
||||
def test_new_record_returns_boolean
|
||||
assert_equal Topic.new.new_record?, true
|
||||
assert_equal Topic.find(1).new_record?, false
|
||||
end
|
||||
|
||||
def test_clone
|
||||
topic = Topic.find(1)
|
||||
cloned_topic = nil
|
||||
|
@ -2071,6 +2084,15 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
ActiveRecord::Base.logger = original_logger
|
||||
end
|
||||
|
||||
def test_create_with_custom_timestamps
|
||||
custom_datetime = 1.hour.ago.beginning_of_day
|
||||
|
||||
%w(created_at created_on updated_at updated_on).each do |attribute|
|
||||
parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime)
|
||||
assert_equal custom_datetime, parrot[attribute]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_kcode(kcode)
|
||||
if RUBY_VERSION < '1.9'
|
||||
|
|
|
@ -171,8 +171,9 @@ class CalculationsTest < ActiveRecord::TestCase
|
|||
Account.expects(:columns).at_least_once.returns([column])
|
||||
|
||||
c = Account.count(:all, :group => :firm)
|
||||
assert_equal Firm, c.first.first.class
|
||||
assert_equal 1, c.first.last
|
||||
first_key = c.keys.first
|
||||
assert_equal Firm, first_key.class
|
||||
assert_equal 1, c[first_key]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -121,9 +121,19 @@ end
|
|||
|
||||
class CallbackCancellationDeveloper < ActiveRecord::Base
|
||||
set_table_name 'developers'
|
||||
def before_create
|
||||
false
|
||||
end
|
||||
|
||||
attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called
|
||||
attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy
|
||||
|
||||
def before_save; !@cancel_before_save; end
|
||||
def before_create; !@cancel_before_create; end
|
||||
def before_update; !@cancel_before_update; end
|
||||
def before_destroy; !@cancel_before_destroy; end
|
||||
|
||||
def after_save; @after_save_called = true; end
|
||||
def after_update; @after_update_called = true; end
|
||||
def after_create; @after_create_called = true; end
|
||||
def after_destroy; @after_destroy_called = true; end
|
||||
end
|
||||
|
||||
class CallbacksTest < ActiveRecord::TestCase
|
||||
|
@ -349,20 +359,48 @@ class CallbacksTest < ActiveRecord::TestCase
|
|||
assert !david.valid?
|
||||
assert !david.save
|
||||
assert_raises(ActiveRecord::RecordInvalid) { david.save! }
|
||||
|
||||
someone = CallbackCancellationDeveloper.find(1)
|
||||
someone.cancel_before_save = true
|
||||
assert someone.valid?
|
||||
assert !someone.save
|
||||
assert_save_callbacks_not_called(someone)
|
||||
end
|
||||
|
||||
def test_before_create_returning_false
|
||||
someone = CallbackCancellationDeveloper.new
|
||||
someone.cancel_before_create = true
|
||||
assert someone.valid?
|
||||
assert !someone.save
|
||||
assert_save_callbacks_not_called(someone)
|
||||
end
|
||||
|
||||
def test_before_update_returning_false
|
||||
someone = CallbackCancellationDeveloper.find(1)
|
||||
someone.cancel_before_update = true
|
||||
assert someone.valid?
|
||||
assert !someone.save
|
||||
assert_save_callbacks_not_called(someone)
|
||||
end
|
||||
|
||||
def test_before_destroy_returning_false
|
||||
david = ImmutableDeveloper.find(1)
|
||||
assert !david.destroy
|
||||
assert_not_nil ImmutableDeveloper.find_by_id(1)
|
||||
|
||||
someone = CallbackCancellationDeveloper.find(1)
|
||||
someone.cancel_before_destroy = true
|
||||
assert !someone.destroy
|
||||
assert !someone.after_destroy_called
|
||||
end
|
||||
|
||||
def assert_save_callbacks_not_called(someone)
|
||||
assert !someone.after_save_called
|
||||
assert !someone.after_create_called
|
||||
assert !someone.after_update_called
|
||||
end
|
||||
private :assert_save_callbacks_not_called
|
||||
|
||||
def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper
|
||||
david = CallbackDeveloper.find(1)
|
||||
CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false }
|
||||
|
|
|
@ -2,9 +2,24 @@ require "cases/helper"
|
|||
|
||||
class MysqlConnectionTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
super
|
||||
@connection = ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
def test_mysql_reconnect_attribute_after_connection_with_reconnect_true
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true}))
|
||||
assert ActiveRecord::Base.connection.raw_connection.reconnect
|
||||
end
|
||||
end
|
||||
|
||||
def test_mysql_reconnect_attribute_after_connection_with_reconnect_false
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false}))
|
||||
assert !ActiveRecord::Base.connection.raw_connection.reconnect
|
||||
end
|
||||
end
|
||||
|
||||
def test_no_automatic_reconnection_after_timeout
|
||||
assert @connection.active?
|
||||
@connection.update('set @@wait_timeout=1')
|
||||
|
@ -27,4 +42,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase
|
|||
@connection.verify!
|
||||
assert @connection.active?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run_without_connection
|
||||
original_connection = ActiveRecord::Base.remove_connection
|
||||
begin
|
||||
yield original_connection
|
||||
ensure
|
||||
ActiveRecord::Base.establish_connection(original_connection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,17 @@ class CopyTableTest < ActiveRecord::TestCase
|
|||
test_copy_table('developers_projects', 'programmers_projects')
|
||||
end
|
||||
|
||||
def test_copy_table_with_id_col_that_is_not_primary_key
|
||||
test_copy_table('goofy_string_id', 'goofy_string_id2') do |from, to, options|
|
||||
original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' }
|
||||
copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' }
|
||||
assert_equal original_id.type, copied_id.type
|
||||
assert_equal original_id.sql_type, copied_id.sql_type
|
||||
assert_equal original_id.limit, copied_id.limit
|
||||
assert_equal original_id.primary, copied_id.primary
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def copy_table(from, to, options = {})
|
||||
@connection.copy_table(from, to, {:temporary => true}.merge(options))
|
||||
|
|
|
@ -18,11 +18,43 @@ class DefaultTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
if current_adapter?(:PostgreSQLAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter)
|
||||
def test_default_integers
|
||||
default = Default.new
|
||||
assert_instance_of Fixnum, default.positive_integer
|
||||
assert_equal 1, default.positive_integer
|
||||
assert_instance_of Fixnum, default.negative_integer
|
||||
assert_equal -1, default.negative_integer
|
||||
assert_instance_of BigDecimal, default.decimal_number
|
||||
assert_equal BigDecimal.new("2.78"), default.decimal_number
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
def test_multiline_default_text
|
||||
# older postgres versions represent the default with escapes ("\\012" for a newline)
|
||||
assert ( "--- []\n\n" == Default.columns_hash['multiline_default'].default ||
|
||||
"--- []\\012\\012" == Default.columns_hash['multiline_default'].default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#MySQL 5 and higher is quirky with not null text/blob columns.
|
||||
#With MySQL Text/blob columns cannot have defaults. If the column is not null MySQL will report that the column has a null default
|
||||
#but it behaves as though the column had a default of ''
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
|
||||
# ActiveRecord::Base#create! (and #save and other related methods) will
|
||||
# open a new transaction. When in transactional fixtures mode, this will
|
||||
# cause ActiveRecord to create a new savepoint. However, since MySQL doesn't
|
||||
# support DDL transactions, creating a table will result in any created
|
||||
# savepoints to be automatically released. This in turn causes the savepoint
|
||||
# release code in AbstractAdapter#transaction to fail.
|
||||
#
|
||||
# We don't want that to happen, so we disable transactional fixtures here.
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
# MySQL 5 and higher is quirky with not null text/blob columns.
|
||||
# With MySQL Text/blob columns cannot have defaults. If the column is not
|
||||
# null MySQL will report that the column has a null default
|
||||
# but it behaves as though the column had a default of ''
|
||||
def test_mysql_text_not_null_defaults
|
||||
klass = Class.new(ActiveRecord::Base)
|
||||
klass.table_name = 'test_mysql_text_not_null_defaults'
|
||||
|
@ -48,8 +80,7 @@ class DefaultTest < ActiveRecord::TestCase
|
|||
ensure
|
||||
klass.connection.drop_table(klass.table_name) rescue nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
# MySQL uses an implicit default 0 rather than NULL unless in strict mode.
|
||||
# We use an implicit NULL so schema.rb is compatible with other databases.
|
||||
def test_mysql_integer_not_null_defaults
|
||||
|
@ -77,24 +108,4 @@ class DefaultTest < ActiveRecord::TestCase
|
|||
klass.connection.drop_table(klass.table_name) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter)
|
||||
def test_default_integers
|
||||
default = Default.new
|
||||
assert_instance_of Fixnum, default.positive_integer
|
||||
assert_equal 1, default.positive_integer
|
||||
assert_instance_of Fixnum, default.negative_integer
|
||||
assert_equal -1, default.negative_integer
|
||||
assert_instance_of BigDecimal, default.decimal_number
|
||||
assert_equal BigDecimal.new("2.78"), default.decimal_number
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
def test_multiline_default_text
|
||||
# older postgres versions represent the default with escapes ("\\012" for a newline)
|
||||
assert ( "--- []\n\n" == Default.columns_hash['multiline_default'].default ||
|
||||
"--- []\\012\\012" == Default.columns_hash['multiline_default'].default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,10 @@ private
|
|||
end
|
||||
end
|
||||
|
||||
class NumericData < ActiveRecord::Base
|
||||
self.table_name = 'numeric_data'
|
||||
end
|
||||
|
||||
class DirtyTest < ActiveRecord::TestCase
|
||||
def test_attribute_changes
|
||||
# New record - no changes.
|
||||
|
@ -58,7 +62,7 @@ class DirtyTest < ActiveRecord::TestCase
|
|||
assert_equal parrot.name_change, parrot.title_change
|
||||
end
|
||||
|
||||
def test_nullable_integer_not_marked_as_changed_if_new_value_is_blank
|
||||
def test_nullable_number_not_marked_as_changed_if_new_value_is_blank
|
||||
pirate = Pirate.new
|
||||
|
||||
["", nil].each do |value|
|
||||
|
@ -68,6 +72,38 @@ class DirtyTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_nullable_decimal_not_marked_as_changed_if_new_value_is_blank
|
||||
numeric_data = NumericData.new
|
||||
|
||||
["", nil].each do |value|
|
||||
numeric_data.bank_balance = value
|
||||
assert !numeric_data.bank_balance_changed?
|
||||
assert_nil numeric_data.bank_balance_change
|
||||
end
|
||||
end
|
||||
|
||||
def test_nullable_float_not_marked_as_changed_if_new_value_is_blank
|
||||
numeric_data = NumericData.new
|
||||
|
||||
["", nil].each do |value|
|
||||
numeric_data.temperature = value
|
||||
assert !numeric_data.temperature_changed?
|
||||
assert_nil numeric_data.temperature_change
|
||||
end
|
||||
end
|
||||
|
||||
def test_nullable_integer_zero_to_string_zero_not_marked_as_changed
|
||||
pirate = Pirate.new
|
||||
pirate.parrot_id = 0
|
||||
pirate.catchphrase = 'arrr'
|
||||
assert pirate.save!
|
||||
|
||||
assert !pirate.changed?
|
||||
|
||||
pirate.parrot_id = '0'
|
||||
assert !pirate.changed?
|
||||
end
|
||||
|
||||
def test_zero_to_blank_marked_as_changed
|
||||
pirate = Pirate.new
|
||||
pirate.catchphrase = "Yarrrr, me hearties"
|
||||
|
@ -130,7 +166,7 @@ class DirtyTest < ActiveRecord::TestCase
|
|||
|
||||
def test_association_assignment_changes_foreign_key
|
||||
pirate = Pirate.create!(:catchphrase => 'jarl')
|
||||
pirate.parrot = Parrot.create!
|
||||
pirate.parrot = Parrot.create!(:name => 'Lorre')
|
||||
assert pirate.changed?
|
||||
assert_equal %w(parrot_id), pirate.changed
|
||||
end
|
||||
|
|
|
@ -175,6 +175,13 @@ class FinderTest < ActiveRecord::TestCase
|
|||
assert_equal 4, developers.map(&:salary).uniq.size
|
||||
end
|
||||
|
||||
def test_find_with_group_and_having
|
||||
developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary")
|
||||
assert_equal 3, developers.size
|
||||
assert_equal 3, developers.map(&:salary).uniq.size
|
||||
assert developers.all? { |developer| developer.salary > 10000 }
|
||||
end
|
||||
|
||||
def test_find_with_entire_select_statement
|
||||
topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
|
||||
|
||||
|
|
|
@ -641,15 +641,15 @@ end
|
|||
class FixtureLoadingTest < ActiveRecord::TestCase
|
||||
uses_mocha 'reloading_fixtures_through_accessor_methods' do
|
||||
def test_logs_message_for_failed_dependency_load
|
||||
Test::Unit::TestCase.expects(:require_dependency).with(:does_not_exist).raises(LoadError)
|
||||
ActiveRecord::TestCase.expects(:require_dependency).with(:does_not_exist).raises(LoadError)
|
||||
ActiveRecord::Base.logger.expects(:warn)
|
||||
Test::Unit::TestCase.try_to_load_dependency(:does_not_exist)
|
||||
ActiveRecord::TestCase.try_to_load_dependency(:does_not_exist)
|
||||
end
|
||||
|
||||
def test_does_not_logs_message_for_successful_dependency_load
|
||||
Test::Unit::TestCase.expects(:require_dependency).with(:works_out_fine)
|
||||
ActiveRecord::TestCase.expects(:require_dependency).with(:works_out_fine)
|
||||
ActiveRecord::Base.logger.expects(:warn).never
|
||||
Test::Unit::TestCase.try_to_load_dependency(:works_out_fine)
|
||||
ActiveRecord::TestCase.try_to_load_dependency(:works_out_fine)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
36
vendor/rails/activerecord/test/cases/helper.rb
vendored
36
vendor/rails/activerecord/test/cases/helper.rb
vendored
|
@ -5,10 +5,12 @@ require 'config'
|
|||
require 'test/unit'
|
||||
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
require 'active_record/test_case'
|
||||
require 'active_record/fixtures'
|
||||
require 'connection'
|
||||
|
||||
require 'cases/repair_helper'
|
||||
|
||||
# Show backtraces for deprecated behavior for quicker cleanup.
|
||||
ActiveSupport::Deprecation.debug = true
|
||||
|
||||
|
@ -24,6 +26,7 @@ end
|
|||
|
||||
def uses_mocha(description)
|
||||
require 'rubygems'
|
||||
gem 'mocha', '>= 0.9.3'
|
||||
require 'mocha'
|
||||
yield
|
||||
rescue LoadError
|
||||
|
@ -31,7 +34,7 @@ rescue LoadError
|
|||
end
|
||||
|
||||
ActiveRecord::Base.connection.class.class_eval do
|
||||
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/]
|
||||
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/]
|
||||
|
||||
def execute_with_query_record(sql, name = nil, &block)
|
||||
$queries_executed ||= []
|
||||
|
@ -48,15 +51,24 @@ class << ActiveRecord::Base
|
|||
end
|
||||
|
||||
unless ENV['FIXTURE_DEBUG']
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
class << TestCase #:nodoc:
|
||||
def try_to_load_dependency_with_silence(*args)
|
||||
ActiveRecord::Base.logger.silence { try_to_load_dependency_without_silence(*args)}
|
||||
end
|
||||
|
||||
alias_method_chain :try_to_load_dependency, :silence
|
||||
end
|
||||
module ActiveRecord::TestFixtures::ClassMethods
|
||||
def try_to_load_dependency_with_silence(*args)
|
||||
ActiveRecord::Base.logger.silence { try_to_load_dependency_without_silence(*args)}
|
||||
end
|
||||
|
||||
alias_method_chain :try_to_load_dependency, :silence
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ActiveSupport::TestCase
|
||||
include ActiveRecord::TestFixtures
|
||||
include ActiveRecord::Testing::RepairHelper
|
||||
|
||||
self.fixture_path = FIXTURES_ROOT
|
||||
self.use_instantiated_fixtures = false
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
def create_fixtures(*table_names, &block)
|
||||
Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, {}, &block)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -200,6 +200,6 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
|
|||
2 => @mary
|
||||
}
|
||||
|
||||
assert_equal %({1: {"name": "David"}}), authors_hash.to_json(:only => [1, :name])
|
||||
assert_equal %({"1": {"name": "David"}}), authors_hash.to_json(:only => [1, :name])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,24 @@ class MethodScopingTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_last
|
||||
highest_salary = Developer.find(:first, :order => "salary DESC")
|
||||
|
||||
Developer.with_scope(:find => { :order => "salary" }) do
|
||||
assert_equal highest_salary, Developer.last
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_last_preserves_scope
|
||||
lowest_salary = Developer.find(:first, :order => "salary ASC")
|
||||
highest_salary = Developer.find(:first, :order => "salary DESC")
|
||||
|
||||
Developer.with_scope(:find => { :order => "salary" }) do
|
||||
assert_equal highest_salary, Developer.last
|
||||
assert_equal lowest_salary, Developer.first
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_combines_conditions
|
||||
Developer.with_scope(:find => { :conditions => "salary = 9000" }) do
|
||||
assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
|
||||
|
@ -168,6 +186,16 @@ class MethodScopingTest < ActiveRecord::TestCase
|
|||
assert_equal authors(:david).attributes, scoped_authors.first.attributes
|
||||
end
|
||||
|
||||
def test_scoped_find_strips_spaces_from_string_joins_and_eliminates_duplicate_string_joins
|
||||
scoped_authors = Author.with_scope(:find => { :joins => ' INNER JOIN posts ON posts.author_id = authors.id '}) do
|
||||
Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN posts ON posts.author_id = authors.id'], :conditions => 'posts.id = 1')
|
||||
end
|
||||
assert scoped_authors.include?(authors(:david))
|
||||
assert !scoped_authors.include?(authors(:mary))
|
||||
assert_equal 1, scoped_authors.size
|
||||
assert_equal authors(:david).attributes, scoped_authors.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
|
||||
|
@ -522,6 +550,73 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
|
||||
class DefaultScopingTest < ActiveRecord::TestCase
|
||||
fixtures :developers
|
||||
|
||||
def test_default_scope
|
||||
expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_default_scoping_with_threads
|
||||
scope = [{ :create => {}, :find => { :order => 'salary DESC' } }]
|
||||
|
||||
2.times do
|
||||
Thread.new { assert_equal scope, DeveloperOrderedBySalary.send(:scoped_methods) }.join
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_scoping_with_inheritance
|
||||
scope = [{ :create => {}, :find => { :order => 'salary DESC' } }]
|
||||
|
||||
# Inherit a class having a default scope and define a new default scope
|
||||
klass = Class.new(DeveloperOrderedBySalary)
|
||||
klass.send :default_scope, {}
|
||||
|
||||
# Scopes added on children should append to parent scope
|
||||
expected_klass_scope = [{ :create => {}, :find => { :order => 'salary DESC' }}, { :create => {}, :find => {} }]
|
||||
assert_equal expected_klass_scope, klass.send(:scoped_methods)
|
||||
|
||||
# Parent should still have the original scope
|
||||
assert_equal scope, DeveloperOrderedBySalary.send(:scoped_methods)
|
||||
end
|
||||
|
||||
def test_method_scope
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_nested_scope
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.with_scope(:find => { :order => 'name DESC'}) do
|
||||
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
end
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_named_scope
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_nested_exclusive_scope
|
||||
expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.with_exclusive_scope(:find => { :limit => 100 }) do
|
||||
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
end
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_overwriting_default_scope
|
||||
expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ class NamedScopeTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_should_use_where_in_query_for_named_scope
|
||||
assert_equal Developer.find_all_by_name('Jamis'), Developer.find_all_by_id(Developer.jamises)
|
||||
assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set
|
||||
end
|
||||
|
||||
def test_size_should_use_count_when_results_are_not_loaded
|
||||
|
@ -277,4 +277,44 @@ class NamedScopeTest < ActiveRecord::TestCase
|
|||
post = Post.find(1)
|
||||
assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
|
||||
end
|
||||
|
||||
def test_chanining_should_use_latest_conditions_when_creating
|
||||
post1 = Topic.rejected.approved.new
|
||||
assert post1.approved?
|
||||
|
||||
post2 = Topic.approved.rejected.new
|
||||
assert ! post2.approved?
|
||||
end
|
||||
|
||||
def test_chanining_should_use_latest_conditions_when_searching
|
||||
# Normal hash conditions
|
||||
assert_equal Topic.all(:conditions => {:approved => true}), Topic.rejected.approved.all
|
||||
assert_equal Topic.all(:conditions => {:approved => false}), Topic.approved.rejected.all
|
||||
|
||||
# Nested hash conditions with same keys
|
||||
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all
|
||||
|
||||
# Nested hash conditions with different keys
|
||||
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicScopeMatchTest < ActiveRecord::TestCase
|
||||
def test_scoped_by_no_match
|
||||
assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
|
||||
end
|
||||
|
||||
def test_scoped_by
|
||||
match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
|
||||
assert_not_nil match
|
||||
assert match.scope?
|
||||
assert_equal %w(age sex location), match.attribute_names
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicScopeTest < ActiveRecord::TestCase
|
||||
def test_dynamic_scope
|
||||
assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
|
||||
assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
|
||||
end
|
||||
end
|
||||
|
|
359
vendor/rails/activerecord/test/cases/nested_attributes_test.rb
vendored
Normal file
359
vendor/rails/activerecord/test/cases/nested_attributes_test.rb
vendored
Normal file
|
@ -0,0 +1,359 @@
|
|||
require "cases/helper"
|
||||
require "models/pirate"
|
||||
require "models/ship"
|
||||
require "models/bird"
|
||||
require "models/parrot"
|
||||
require "models/treasure"
|
||||
|
||||
module AssertRaiseWithMessage
|
||||
def assert_raise_with_message(expected_exception, expected_message)
|
||||
begin
|
||||
error_raised = false
|
||||
yield
|
||||
rescue expected_exception => error
|
||||
error_raised = true
|
||||
actual_message = error.message
|
||||
end
|
||||
assert error_raised
|
||||
assert_equal expected_message, actual_message
|
||||
end
|
||||
end
|
||||
|
||||
class TestNestedAttributesInGeneral < ActiveRecord::TestCase
|
||||
include AssertRaiseWithMessage
|
||||
|
||||
def teardown
|
||||
Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true
|
||||
end
|
||||
|
||||
def test_base_should_have_an_empty_reject_new_nested_attributes_procs
|
||||
assert_equal Hash.new, ActiveRecord::Base.reject_new_nested_attributes_procs
|
||||
end
|
||||
|
||||
def test_should_add_a_proc_to_reject_new_nested_attributes_procs
|
||||
[:parrots, :birds].each do |name|
|
||||
assert_instance_of Proc, Pirate.reject_new_nested_attributes_procs[name]
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_raise_an_ArgumentError_for_non_existing_associations
|
||||
assert_raise_with_message ArgumentError, "No association found for name `honesty'. Has it been defined yet?" do
|
||||
Pirate.accepts_nested_attributes_for :honesty
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_disable_allow_destroy_by_default
|
||||
Pirate.accepts_nested_attributes_for :ship
|
||||
|
||||
pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
ship = pirate.create_ship(:name => 'Nights Dirty Lightning')
|
||||
|
||||
assert_no_difference('Ship.count') do
|
||||
pirate.update_attributes(:ship_attributes => { '_delete' => true })
|
||||
end
|
||||
end
|
||||
|
||||
def test_a_model_should_respond_to_underscore_delete_and_return_if_it_is_marked_for_destruction
|
||||
ship = Ship.create!(:name => 'Nights Dirty Lightning')
|
||||
assert !ship._delete
|
||||
ship.mark_for_destruction
|
||||
assert ship._delete
|
||||
end
|
||||
end
|
||||
|
||||
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
|
||||
def setup
|
||||
@pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
|
||||
end
|
||||
|
||||
def test_should_define_an_attribute_writer_method_for_the_association
|
||||
assert_respond_to @pirate, :ship_attributes=
|
||||
end
|
||||
|
||||
def test_should_automatically_instantiate_an_associated_model_if_there_is_none
|
||||
@ship.destroy
|
||||
@pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
|
||||
|
||||
assert @pirate.ship.new_record?
|
||||
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
|
||||
end
|
||||
|
||||
def test_should_take_a_hash_and_assign_the_attributes_to_the_existing_associated_model
|
||||
@pirate.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
|
||||
assert !@pirate.ship.new_record?
|
||||
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
|
||||
end
|
||||
|
||||
def test_should_also_work_with_a_HashWithIndifferentAccess
|
||||
@pirate.ship_attributes = HashWithIndifferentAccess.new(:name => 'Davy Jones Gold Dagger')
|
||||
assert !@pirate.ship.new_record?
|
||||
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
|
||||
end
|
||||
|
||||
def test_should_work_with_update_attributes_as_well
|
||||
@pirate.update_attributes({ :catchphrase => 'Arr', :ship_attributes => { :name => 'Mister Pablo' } })
|
||||
@pirate.reload
|
||||
|
||||
assert_equal 'Arr', @pirate.catchphrase
|
||||
assert_equal 'Mister Pablo', @pirate.ship.name
|
||||
end
|
||||
|
||||
def test_should_be_possible_to_destroy_the_associated_model
|
||||
@pirate.ship.destroy
|
||||
['1', 1, 'true', true].each do |true_variable|
|
||||
@pirate.reload.create_ship(:name => 'Mister Pablo')
|
||||
assert_difference('Ship.count', -1) do
|
||||
@pirate.update_attributes(:ship_attributes => { '_delete' => true_variable })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument
|
||||
[nil, '0', 0, 'false', false].each do |false_variable|
|
||||
assert_no_difference('Ship.count') do
|
||||
@pirate.update_attributes(:ship_attributes => { '_delete' => false_variable })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
|
||||
assert_no_difference('Ship.count') do
|
||||
@pirate.attributes = { :ship_attributes => { '_delete' => true } }
|
||||
end
|
||||
assert_difference('Ship.count', -1) { @pirate.save }
|
||||
end
|
||||
|
||||
def test_should_automatically_enable_autosave_on_the_association
|
||||
assert Pirate.reflect_on_association(:ship).options[:autosave]
|
||||
end
|
||||
end
|
||||
|
||||
class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
|
||||
def setup
|
||||
@ship = Ship.create!(:name => 'Nights Dirty Lightning')
|
||||
@pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
end
|
||||
|
||||
def test_should_define_an_attribute_writer_method_for_the_association
|
||||
assert_respond_to @ship, :pirate_attributes=
|
||||
end
|
||||
|
||||
def test_should_automatically_instantiate_an_associated_model_if_there_is_none
|
||||
@pirate.destroy
|
||||
@ship.reload.pirate_attributes = { :catchphrase => 'Arr' }
|
||||
|
||||
assert @ship.pirate.new_record?
|
||||
assert_equal 'Arr', @ship.pirate.catchphrase
|
||||
end
|
||||
|
||||
def test_should_take_a_hash_and_assign_the_attributes_to_the_existing_associated_model
|
||||
@ship.pirate_attributes = { :catchphrase => 'Arr' }
|
||||
assert !@ship.pirate.new_record?
|
||||
assert_equal 'Arr', @ship.pirate.catchphrase
|
||||
end
|
||||
|
||||
def test_should_also_work_with_a_HashWithIndifferentAccess
|
||||
@ship.pirate_attributes = HashWithIndifferentAccess.new(:catchphrase => 'Arr')
|
||||
assert !@ship.pirate.new_record?
|
||||
assert_equal 'Arr', @ship.pirate.catchphrase
|
||||
end
|
||||
|
||||
def test_should_work_with_update_attributes_as_well
|
||||
@ship.update_attributes({ :name => 'Mister Pablo', :pirate_attributes => { :catchphrase => 'Arr' } })
|
||||
@ship.reload
|
||||
|
||||
assert_equal 'Mister Pablo', @ship.name
|
||||
assert_equal 'Arr', @ship.pirate.catchphrase
|
||||
end
|
||||
|
||||
def test_should_be_possible_to_destroy_the_associated_model
|
||||
@ship.pirate.destroy
|
||||
['1', 1, 'true', true].each do |true_variable|
|
||||
@ship.reload.create_pirate(:catchphrase => 'Arr')
|
||||
assert_difference('Pirate.count', -1) do
|
||||
@ship.update_attributes(:pirate_attributes => { '_delete' => true_variable })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument
|
||||
[nil, '', '0', 0, 'false', false].each do |false_variable|
|
||||
assert_no_difference('Pirate.count') do
|
||||
@ship.update_attributes(:pirate_attributes => { '_delete' => false_variable })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
|
||||
assert_no_difference('Pirate.count') do
|
||||
@ship.attributes = { :pirate_attributes => { '_delete' => true } }
|
||||
end
|
||||
assert_difference('Pirate.count', -1) { @ship.save }
|
||||
end
|
||||
|
||||
def test_should_automatically_enable_autosave_on_the_association
|
||||
assert Ship.reflect_on_association(:pirate).options[:autosave]
|
||||
end
|
||||
end
|
||||
|
||||
module NestedAttributesOnACollectionAssociationTests
|
||||
include AssertRaiseWithMessage
|
||||
|
||||
def test_should_define_an_attribute_writer_method_for_the_association
|
||||
assert_respond_to @pirate, association_setter
|
||||
end
|
||||
|
||||
def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models
|
||||
@alternate_params[association_getter].stringify_keys!
|
||||
@pirate.update_attributes @alternate_params
|
||||
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
|
||||
end
|
||||
|
||||
def test_should_also_work_with_a_HashWithIndifferentAccess
|
||||
@pirate.send(association_setter, HashWithIndifferentAccess.new(@child_1.id => HashWithIndifferentAccess.new(:name => 'Grace OMalley')))
|
||||
@pirate.save
|
||||
assert_equal 'Grace OMalley', @child_1.reload.name
|
||||
end
|
||||
|
||||
def test_should_take_a_hash_with_integer_keys_and_assign_the_attributes_to_the_associated_models
|
||||
@pirate.attributes = @alternate_params
|
||||
assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
|
||||
assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
|
||||
end
|
||||
|
||||
def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_starts_with_the_string_new_
|
||||
@pirate.send(@association_name).destroy_all
|
||||
@pirate.reload.attributes = { association_getter => { 'new_1' => { :name => 'Grace OMalley' }, 'new_2' => { :name => 'Privateers Greed' }}}
|
||||
|
||||
assert @pirate.send(@association_name).first.new_record?
|
||||
assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
|
||||
|
||||
assert @pirate.send(@association_name).last.new_record?
|
||||
assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
|
||||
end
|
||||
|
||||
def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models
|
||||
attributes = ActiveSupport::OrderedHash.new
|
||||
attributes['new_123726353'] = { :name => 'Grace OMalley' }
|
||||
attributes['new_2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353
|
||||
@pirate.send(association_setter, attributes)
|
||||
|
||||
assert_equal ['Posideons Killer', 'Killer bandita Dionne', 'Privateers Greed', 'Grace OMalley'].to_set, @pirate.send(@association_name).map(&:name).to_set
|
||||
end
|
||||
|
||||
def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
|
||||
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
|
||||
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, ActiveSupport::OrderedHash.new) }
|
||||
|
||||
assert_raise_with_message ArgumentError, 'Hash expected, got String ("foo")' do
|
||||
@pirate.send(association_setter, "foo")
|
||||
end
|
||||
assert_raise_with_message ArgumentError, 'Hash expected, got Array ([:foo, :bar])' do
|
||||
@pirate.send(association_setter, [:foo, :bar])
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_work_with_update_attributes_as_well
|
||||
@pirate.update_attributes({ :catchphrase => 'Arr', association_getter => { @child_1.id => { :name => 'Grace OMalley' }}})
|
||||
assert_equal 'Grace OMalley', @child_1.reload.name
|
||||
end
|
||||
|
||||
def test_should_automatically_reject_any_new_record_if_a_reject_if_proc_exists_and_returns_false
|
||||
@alternate_params[association_getter]["new_12345"] = {}
|
||||
assert_no_difference("@pirate.send(@association_name).length") do
|
||||
@pirate.attributes = @alternate_params
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_update_existing_records_and_add_new_ones_that_have_an_id_that_start_with_the_string_new_
|
||||
@alternate_params[association_getter]['new_12345'] = { :name => 'Buccaneers Servant' }
|
||||
assert_difference('@pirate.send(@association_name).count', +1) do
|
||||
@pirate.update_attributes @alternate_params
|
||||
end
|
||||
assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set
|
||||
end
|
||||
|
||||
def test_should_be_possible_to_destroy_a_record
|
||||
['1', 1, 'true', true].each do |true_variable|
|
||||
record = @pirate.reload.send(@association_name).create!(:name => 'Grace OMalley')
|
||||
@pirate.send(association_setter,
|
||||
@alternate_params[association_getter].merge(record.id => { '_delete' => true_variable })
|
||||
)
|
||||
|
||||
assert_difference('@pirate.send(@association_name).count', -1) do
|
||||
@pirate.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument
|
||||
[nil, '', '0', 0, 'false', false].each do |false_variable|
|
||||
@alternate_params[association_getter][@child_1.id]['_delete'] = false_variable
|
||||
assert_no_difference('@pirate.send(@association_name).count') do
|
||||
@pirate.update_attributes(@alternate_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
|
||||
assert_no_difference('@pirate.send(@association_name).count') do
|
||||
@pirate.send(association_setter, @alternate_params[association_getter].merge(@child_1.id => { '_delete' => true }))
|
||||
end
|
||||
assert_difference('@pirate.send(@association_name).count', -1) { @pirate.save }
|
||||
end
|
||||
|
||||
def test_should_automatically_enable_autosave_on_the_association
|
||||
assert Pirate.reflect_on_association(@association_name).options[:autosave]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def association_setter
|
||||
@association_setter ||= "#{@association_name}_attributes=".to_sym
|
||||
end
|
||||
|
||||
def association_getter
|
||||
@association_getter ||= "#{@association_name}_attributes".to_sym
|
||||
end
|
||||
end
|
||||
|
||||
class TestNestedAttributesOnAHasManyAssociation < ActiveRecord::TestCase
|
||||
def setup
|
||||
@association_type = :has_many
|
||||
@association_name = :birds
|
||||
|
||||
@pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
@child_1 = @pirate.birds.create!(:name => 'Posideons Killer')
|
||||
@child_2 = @pirate.birds.create!(:name => 'Killer bandita Dionne')
|
||||
|
||||
@alternate_params = {
|
||||
:birds_attributes => {
|
||||
@child_1.id => { :name => 'Grace OMalley' },
|
||||
@child_2.id => { :name => 'Privateers Greed' }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
include NestedAttributesOnACollectionAssociationTests
|
||||
end
|
||||
|
||||
class TestNestedAttributesOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
|
||||
def setup
|
||||
@association_type = :has_and_belongs_to_many
|
||||
@association_name = :parrots
|
||||
|
||||
@pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
@child_1 = @pirate.parrots.create!(:name => 'Posideons Killer')
|
||||
@child_2 = @pirate.parrots.create!(:name => 'Killer bandita Dionne')
|
||||
|
||||
@alternate_params = {
|
||||
:parrots_attributes => {
|
||||
@child_1.id => { :name => 'Grace OMalley' },
|
||||
@child_2.id => { :name => 'Privateers Greed' }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
include NestedAttributesOnACollectionAssociationTests
|
||||
end
|
|
@ -91,6 +91,15 @@ class ReflectionTest < ActiveRecord::TestCase
|
|||
assert_equal Money, Customer.reflect_on_aggregation(:balance).klass
|
||||
end
|
||||
|
||||
def test_reflect_on_all_autosave_associations
|
||||
expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] }
|
||||
received = Pirate.reflect_on_all_autosave_associations
|
||||
|
||||
assert !received.empty?
|
||||
assert_not_equal Pirate.reflect_on_all_associations.length, received.length
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_has_many_reflection
|
||||
reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ require 'models/owner'
|
|||
require 'models/pet'
|
||||
|
||||
class ReloadModelsTest < ActiveRecord::TestCase
|
||||
fixtures :pets
|
||||
|
||||
def test_has_one_with_reload
|
||||
pet = Pet.find_by_name('parrot')
|
||||
pet.owner = Owner.find_by_name('ashley')
|
||||
|
@ -17,4 +19,4 @@ class ReloadModelsTest < ActiveRecord::TestCase
|
|||
pet.owner = Owner.find_by_name('ashley')
|
||||
assert_equal pet.owner, Owner.find_by_name('ashley')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
50
vendor/rails/activerecord/test/cases/repair_helper.rb
vendored
Normal file
50
vendor/rails/activerecord/test/cases/repair_helper.rb
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
module ActiveRecord
|
||||
module Testing
|
||||
module RepairHelper
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module Toolbox
|
||||
def self.record_validations(*model_classes)
|
||||
model_classes.inject({}) do |repair, klass|
|
||||
repair[klass] ||= {}
|
||||
[:validate, :validate_on_create, :validate_on_update].each do |callback|
|
||||
the_callback = klass.instance_variable_get("@#{callback.to_s}_callbacks")
|
||||
repair[klass][callback] = (the_callback.nil? ? nil : the_callback.dup)
|
||||
end
|
||||
repair
|
||||
end
|
||||
end
|
||||
|
||||
def self.reset_validations(recorded)
|
||||
recorded.each do |klass, repairs|
|
||||
[:validate, :validate_on_create, :validate_on_update].each do |callback|
|
||||
klass.instance_variable_set("@#{callback.to_s}_callbacks", repairs[callback])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def repair_validations(*model_classes)
|
||||
setup do
|
||||
@validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
|
||||
end
|
||||
teardown do
|
||||
ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(@validation_repairs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def repair_validations(*model_classes, &block)
|
||||
validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
|
||||
return block.call
|
||||
ensure
|
||||
ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(validation_repairs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,4 @@
|
|||
require "cases/helper"
|
||||
require 'active_record/schema_dumper'
|
||||
require 'stringio'
|
||||
|
||||
|
||||
|
|
|
@ -213,11 +213,104 @@ class TransactionTest < ActiveRecord::TestCase
|
|||
assert Topic.find(2).approved?, "Second should still be approved"
|
||||
end
|
||||
|
||||
def test_invalid_keys_for_transaction
|
||||
assert_raises ArgumentError do
|
||||
Topic.transaction :nested => true do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_force_savepoint_in_nested_transaction
|
||||
Topic.transaction do
|
||||
@first.approved = true
|
||||
@second.approved = false
|
||||
@first.save!
|
||||
@second.save!
|
||||
|
||||
begin
|
||||
Topic.transaction :requires_new => true do
|
||||
@first.happy = false
|
||||
@first.save!
|
||||
raise
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
assert @first.reload.approved?
|
||||
assert !@second.reload.approved?
|
||||
end if Topic.connection.supports_savepoints?
|
||||
|
||||
def test_no_savepoint_in_nested_transaction_without_force
|
||||
Topic.transaction do
|
||||
@first.approved = true
|
||||
@second.approved = false
|
||||
@first.save!
|
||||
@second.save!
|
||||
|
||||
begin
|
||||
Topic.transaction do
|
||||
@first.approved = false
|
||||
@first.save!
|
||||
raise
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
assert !@first.reload.approved?
|
||||
assert !@second.reload.approved?
|
||||
end if Topic.connection.supports_savepoints?
|
||||
|
||||
def test_many_savepoints
|
||||
Topic.transaction do
|
||||
@first.content = "One"
|
||||
@first.save!
|
||||
|
||||
begin
|
||||
Topic.transaction :requires_new => true do
|
||||
@first.content = "Two"
|
||||
@first.save!
|
||||
|
||||
begin
|
||||
Topic.transaction :requires_new => true do
|
||||
@first.content = "Three"
|
||||
@first.save!
|
||||
|
||||
begin
|
||||
Topic.transaction :requires_new => true do
|
||||
@first.content = "Four"
|
||||
@first.save!
|
||||
raise
|
||||
end
|
||||
rescue
|
||||
end
|
||||
|
||||
@three = @first.reload.content
|
||||
raise
|
||||
end
|
||||
rescue
|
||||
end
|
||||
|
||||
@two = @first.reload.content
|
||||
raise
|
||||
end
|
||||
rescue
|
||||
end
|
||||
|
||||
@one = @first.reload.content
|
||||
end
|
||||
|
||||
assert_equal "One", @one
|
||||
assert_equal "Two", @two
|
||||
assert_equal "Three", @three
|
||||
end if Topic.connection.supports_savepoints?
|
||||
|
||||
uses_mocha 'mocking connection.commit_db_transaction' do
|
||||
def test_rollback_when_commit_raises
|
||||
Topic.connection.expects(:begin_db_transaction)
|
||||
Topic.connection.expects(:transaction_active?).returns(true) if current_adapter?(:PostgreSQLAdapter)
|
||||
Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
|
||||
Topic.connection.expects(:outside_transaction?).returns(false)
|
||||
Topic.connection.expects(:rollback_db_transaction)
|
||||
|
||||
assert_raise RuntimeError do
|
||||
|
@ -227,6 +320,38 @@ class TransactionTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE)
|
||||
def test_outside_transaction_works
|
||||
assert Topic.connection.outside_transaction?
|
||||
Topic.connection.begin_db_transaction
|
||||
assert !Topic.connection.outside_transaction?
|
||||
Topic.connection.rollback_db_transaction
|
||||
assert Topic.connection.outside_transaction?
|
||||
end
|
||||
|
||||
uses_mocha 'mocking connection.rollback_db_transaction' do
|
||||
def test_rollback_wont_be_executed_if_no_transaction_active
|
||||
assert_raise RuntimeError do
|
||||
Topic.transaction do
|
||||
Topic.connection.rollback_db_transaction
|
||||
Topic.connection.expects(:rollback_db_transaction).never
|
||||
raise "Rails doesn't scale!"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_open_transactions_count_is_reset_to_zero_if_no_transaction_active
|
||||
Topic.transaction do
|
||||
Topic.transaction do
|
||||
Topic.connection.rollback_db_transaction
|
||||
end
|
||||
assert_equal 0, Topic.connection.open_transactions
|
||||
end
|
||||
assert_equal 0, Topic.connection.open_transactions
|
||||
end
|
||||
end
|
||||
|
||||
def test_sqlite_add_column_in_transaction_raises_statement_invalid
|
||||
return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter)
|
||||
|
@ -282,6 +407,45 @@ class TransactionTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
|
||||
self.use_transactional_fixtures = true
|
||||
fixtures :topics
|
||||
|
||||
def test_automatic_savepoint_in_outer_transaction
|
||||
@first = Topic.find(1)
|
||||
|
||||
begin
|
||||
Topic.transaction do
|
||||
@first.approved = true
|
||||
@first.save!
|
||||
raise
|
||||
end
|
||||
rescue
|
||||
assert !@first.reload.approved?
|
||||
end
|
||||
end
|
||||
|
||||
def test_no_automatic_savepoint_for_inner_transaction
|
||||
@first = Topic.find(1)
|
||||
|
||||
Topic.transaction do
|
||||
@first.approved = true
|
||||
@first.save!
|
||||
|
||||
begin
|
||||
Topic.transaction do
|
||||
@first.approved = false
|
||||
@first.save!
|
||||
raise
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
assert !@first.reload.approved?
|
||||
end
|
||||
end if Topic.connection.supports_savepoints?
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
class ConcurrentTransactionTest < TransactionTest
|
||||
use_concurrent_connections
|
||||
|
|
|
@ -2,7 +2,7 @@ require "cases/helper"
|
|||
require 'models/topic'
|
||||
require 'models/reply'
|
||||
|
||||
class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
|
||||
class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
|
||||
def setup
|
||||
reset_callbacks Topic
|
||||
@topic = Topic.new
|
||||
|
@ -506,7 +506,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
|
|||
|
||||
# validates_length_of :is w/o mocha
|
||||
|
||||
def test_validates_length_of_within_finds_custom_model_key_translation
|
||||
def test_validates_length_of_is_finds_custom_model_key_translation
|
||||
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
|
||||
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
|
||||
|
||||
|
@ -515,7 +515,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
|
|||
assert_equal 'custom message', @topic.errors.on(:title)
|
||||
end
|
||||
|
||||
def test_validates_length_of_within_finds_global_default_translation
|
||||
def test_validates_length_of_is_finds_global_default_translation
|
||||
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
|
||||
|
||||
Topic.validates_length_of :title, :is => 5
|
||||
|
@ -525,7 +525,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
|
|||
|
||||
# validates_uniqueness_of w/o mocha
|
||||
|
||||
def test_validates_length_of_within_finds_custom_model_key_translation
|
||||
def test_validates_length_of_is_finds_custom_model_key_translation
|
||||
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
|
||||
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
|
||||
|
||||
|
@ -534,7 +534,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
|
|||
assert_equal 'custom message', @topic.errors.on(:title)
|
||||
end
|
||||
|
||||
def test_validates_length_of_within_finds_global_default_translation
|
||||
def test_validates_length_of_is_finds_global_default_translation
|
||||
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
|
||||
|
||||
Topic.validates_length_of :title, :is => 5
|
||||
|
|
|
@ -6,6 +6,8 @@ require 'models/person'
|
|||
require 'models/developer'
|
||||
require 'models/warehouse_thing'
|
||||
require 'models/guid'
|
||||
require 'models/owner'
|
||||
require 'models/pet'
|
||||
|
||||
# The following methods in Topic are used in test_conditional_validation_*
|
||||
class Topic
|
||||
|
@ -31,10 +33,6 @@ class UniqueReply < Reply
|
|||
validates_uniqueness_of :content, :scope => 'parent_id'
|
||||
end
|
||||
|
||||
class PlagiarizedReply < Reply
|
||||
validates_acceptance_of :author_name
|
||||
end
|
||||
|
||||
class SillyUniqueReply < UniqueReply
|
||||
end
|
||||
|
||||
|
@ -58,11 +56,9 @@ end
|
|||
class ValidationsTest < ActiveRecord::TestCase
|
||||
fixtures :topics, :developers, 'warehouse-things'
|
||||
|
||||
def setup
|
||||
Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
end
|
||||
# Most of the tests mess with the validations of Topic, so lets repair it all the time.
|
||||
# Other classes we mess with will be dealt with in the specific tests
|
||||
repair_validations(Topic)
|
||||
|
||||
def test_single_field_validation
|
||||
r = Reply.new
|
||||
|
@ -134,7 +130,7 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_exception_on_create_bang_with_block
|
||||
assert_raises(ActiveRecord::RecordInvalid) do
|
||||
Reply.create!({ "title" => "OK" }) do |r|
|
||||
|
@ -142,7 +138,7 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_exception_on_create_bang_many_with_block
|
||||
assert_raises(ActiveRecord::RecordInvalid) do
|
||||
Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
|
||||
|
@ -229,21 +225,16 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validates_each
|
||||
perform = true
|
||||
hits = 0
|
||||
Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
|
||||
if perform
|
||||
record.errors.add attr, 'gotcha'
|
||||
hits += 1
|
||||
end
|
||||
record.errors.add attr, 'gotcha'
|
||||
hits += 1
|
||||
end
|
||||
t = Topic.new("title" => "valid", "content" => "whatever")
|
||||
assert !t.save
|
||||
assert_equal 4, hits
|
||||
assert_equal %w(gotcha gotcha), t.errors.on(:title)
|
||||
assert_equal %w(gotcha gotcha), t.errors.on(:content)
|
||||
ensure
|
||||
perform = false
|
||||
end
|
||||
|
||||
def test_no_title_confirmation
|
||||
|
@ -315,8 +306,12 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validates_acceptance_of_as_database_column
|
||||
reply = PlagiarizedReply.create("author_name" => "Dan Brown")
|
||||
assert_equal "Dan Brown", reply["author_name"]
|
||||
repair_validations(Reply) do
|
||||
Reply.validates_acceptance_of(:author_name)
|
||||
|
||||
reply = Reply.create("author_name" => "Dan Brown")
|
||||
assert_equal "Dan Brown", reply["author_name"]
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_acceptance_of_with_non_existant_table
|
||||
|
@ -372,22 +367,24 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validate_uniqueness_with_scope
|
||||
Reply.validates_uniqueness_of(:content, :scope => "parent_id")
|
||||
repair_validations(Reply) do
|
||||
Reply.validates_uniqueness_of(:content, :scope => "parent_id")
|
||||
|
||||
t = Topic.create("title" => "I'm unique!")
|
||||
t = Topic.create("title" => "I'm unique!")
|
||||
|
||||
r1 = t.replies.create "title" => "r1", "content" => "hello world"
|
||||
assert r1.valid?, "Saving r1"
|
||||
r1 = t.replies.create "title" => "r1", "content" => "hello world"
|
||||
assert r1.valid?, "Saving r1"
|
||||
|
||||
r2 = t.replies.create "title" => "r2", "content" => "hello world"
|
||||
assert !r2.valid?, "Saving r2 first time"
|
||||
r2 = t.replies.create "title" => "r2", "content" => "hello world"
|
||||
assert !r2.valid?, "Saving r2 first time"
|
||||
|
||||
r2.content = "something else"
|
||||
assert r2.save, "Saving r2 second time"
|
||||
r2.content = "something else"
|
||||
assert r2.save, "Saving r2 second time"
|
||||
|
||||
t2 = Topic.create("title" => "I'm unique too!")
|
||||
r3 = t2.replies.create "title" => "r3", "content" => "hello world"
|
||||
assert r3.valid?, "Saving r3"
|
||||
t2 = Topic.create("title" => "I'm unique too!")
|
||||
r3 = t2.replies.create "title" => "r3", "content" => "hello world"
|
||||
assert r3.valid?, "Saving r3"
|
||||
end
|
||||
end
|
||||
|
||||
def test_validate_uniqueness_scoped_to_defining_class
|
||||
|
@ -406,27 +403,29 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validate_uniqueness_with_scope_array
|
||||
Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
|
||||
repair_validations(Reply) do
|
||||
Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
|
||||
|
||||
t = Topic.create("title" => "The earth is actually flat!")
|
||||
t = Topic.create("title" => "The earth is actually flat!")
|
||||
|
||||
r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
|
||||
assert r1.valid?, "Saving r1"
|
||||
r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
|
||||
assert r1.valid?, "Saving r1"
|
||||
|
||||
r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
|
||||
assert !r2.valid?, "Saving r2. Double reply by same author."
|
||||
r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
|
||||
assert !r2.valid?, "Saving r2. Double reply by same author."
|
||||
|
||||
r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
|
||||
assert r2.save, "Saving r2 the second time."
|
||||
r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
|
||||
assert r2.save, "Saving r2 the second time."
|
||||
|
||||
r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
|
||||
assert !r3.valid?, "Saving r3"
|
||||
r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
|
||||
assert !r3.valid?, "Saving r3"
|
||||
|
||||
r3.author_name = "jj"
|
||||
assert r3.save, "Saving r3 the second time."
|
||||
r3.author_name = "jj"
|
||||
assert r3.save, "Saving r3 the second time."
|
||||
|
||||
r3.author_name = "jeremy"
|
||||
assert !r3.save, "Saving r3 the third time."
|
||||
r3.author_name = "jeremy"
|
||||
assert !r3.save, "Saving r3 the third time."
|
||||
end
|
||||
end
|
||||
|
||||
def test_validate_case_insensitive_uniqueness
|
||||
|
@ -523,10 +522,12 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validate_uniqueness_with_columns_which_are_sql_keywords
|
||||
Guid.validates_uniqueness_of :key
|
||||
g = Guid.new
|
||||
g.key = "foo"
|
||||
assert_nothing_raised { !g.valid? }
|
||||
repair_validations(Guid) do
|
||||
Guid.validates_uniqueness_of :key
|
||||
g = Guid.new
|
||||
g.key = "foo"
|
||||
assert_nothing_raised { !g.valid? }
|
||||
end
|
||||
end
|
||||
|
||||
def test_validate_straight_inheritance_uniqueness
|
||||
|
@ -648,10 +649,12 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_numericality_with_getter_method
|
||||
Developer.validates_numericality_of( :salary )
|
||||
developer = Developer.new("name" => "michael", "salary" => nil)
|
||||
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
|
||||
assert developer.valid?
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_numericality_of( :salary )
|
||||
developer = Developer.new("name" => "michael", "salary" => nil)
|
||||
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
|
||||
assert developer.valid?
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_length_of_with_allow_nil
|
||||
|
@ -684,10 +687,12 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_numericality_with_allow_nil_and_getter_method
|
||||
Developer.validates_numericality_of( :salary, :allow_nil => true)
|
||||
developer = Developer.new("name" => "michael", "salary" => nil)
|
||||
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
|
||||
assert developer.valid?
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_numericality_of( :salary, :allow_nil => true)
|
||||
developer = Developer.new("name" => "michael", "salary" => nil)
|
||||
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
|
||||
assert developer.valid?
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_exclusion_of
|
||||
|
@ -892,26 +897,30 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validates_size_of_association
|
||||
assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
|
||||
t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
|
||||
assert !t.save
|
||||
assert t.errors.on(:replies)
|
||||
reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
|
||||
assert t.valid?
|
||||
repair_validations(Owner) do
|
||||
assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
|
||||
o = Owner.new('name' => 'nopets')
|
||||
assert !o.save
|
||||
assert o.errors.on(:pets)
|
||||
pet = o.pets.build('name' => 'apet')
|
||||
assert o.valid?
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_size_of_association_using_within
|
||||
assert_nothing_raised { Topic.validates_size_of :replies, :within => 1..2 }
|
||||
t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
|
||||
assert !t.save
|
||||
assert t.errors.on(:replies)
|
||||
repair_validations(Owner) do
|
||||
assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
|
||||
o = Owner.new('name' => 'nopets')
|
||||
assert !o.save
|
||||
assert o.errors.on(:pets)
|
||||
|
||||
reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
|
||||
assert t.valid?
|
||||
pet = o.pets.build('name' => 'apet')
|
||||
assert o.valid?
|
||||
|
||||
2.times { t.replies.build('title' => 'areply', 'content' => 'whateveragain') }
|
||||
assert !t.save
|
||||
assert t.errors.on(:replies)
|
||||
2.times { o.pets.build('name' => 'apet') }
|
||||
assert !o.save
|
||||
assert o.errors.on(:pets)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_length_of_nasty_params
|
||||
|
@ -949,6 +958,19 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
assert_equal "boo 5", t.errors["title"]
|
||||
end
|
||||
|
||||
def test_validates_length_of_custom_errors_for_in
|
||||
Topic.validates_length_of(:title, :in => 10..20, :message => "hoo {{count}}")
|
||||
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
|
||||
assert !t.valid?
|
||||
assert t.errors.on(:title)
|
||||
assert_equal "hoo 10", t.errors["title"]
|
||||
|
||||
t = Topic.create("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
|
||||
assert !t.valid?
|
||||
assert t.errors.on(:title)
|
||||
assert_equal "hoo 20", t.errors["title"]
|
||||
end
|
||||
|
||||
def test_validates_length_of_custom_errors_for_maximum_with_too_long
|
||||
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}" )
|
||||
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
|
||||
|
@ -1102,13 +1124,15 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validates_size_of_association_utf8
|
||||
with_kcode('UTF8') do
|
||||
assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
|
||||
t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ')
|
||||
assert !t.save
|
||||
assert t.errors.on(:replies)
|
||||
t.replies.build('title' => 'あいうえお', 'content' => 'かきくけこ')
|
||||
assert t.valid?
|
||||
repair_validations(Owner) do
|
||||
with_kcode('UTF8') do
|
||||
assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
|
||||
o = Owner.new('name' => 'あいうえおかきくけこ')
|
||||
assert !o.save
|
||||
assert o.errors.on(:pets)
|
||||
o.pets.build('name' => 'あいうえおかきくけこ')
|
||||
assert o.valid?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1127,14 +1151,16 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validates_associated_one
|
||||
Reply.validates_associated( :topic )
|
||||
Topic.validates_presence_of( :content )
|
||||
r = Reply.new("title" => "A reply", "content" => "with content!")
|
||||
r.topic = Topic.create("title" => "uhohuhoh")
|
||||
assert !r.valid?
|
||||
assert r.errors.on(:topic)
|
||||
r.topic.content = "non-empty"
|
||||
assert r.valid?
|
||||
repair_validations(Reply) do
|
||||
Reply.validates_associated( :topic )
|
||||
Topic.validates_presence_of( :content )
|
||||
r = Reply.new("title" => "A reply", "content" => "with content!")
|
||||
r.topic = Topic.create("title" => "uhohuhoh")
|
||||
assert !r.valid?
|
||||
assert r.errors.on(:topic)
|
||||
r.topic.content = "non-empty"
|
||||
assert r.valid?
|
||||
end
|
||||
end
|
||||
|
||||
def test_validate_block
|
||||
|
@ -1158,85 +1184,105 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validates_acceptance_of_with_custom_error_using_quotes
|
||||
Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.salary = "0"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.salary = "0"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_confirmation_of_with_custom_error_using_quotes
|
||||
Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "John"
|
||||
d.name_confirmation = "Johnny"
|
||||
assert !d.valid?
|
||||
assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "John"
|
||||
d.name_confirmation = "Johnny"
|
||||
assert !d.valid?
|
||||
assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_format_of_with_custom_error_using_quotes
|
||||
Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = d.name_confirmation = "John 32"
|
||||
assert !d.valid?
|
||||
assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = d.name_confirmation = "John 32"
|
||||
assert !d.valid?
|
||||
assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_inclusion_of_with_custom_error_using_quotes
|
||||
Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.salary = "90,000"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.salary = "90,000"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_length_of_with_custom_too_long_using_quotes
|
||||
Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Jeffrey"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Jeffrey"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_length_of_with_custom_too_short_using_quotes
|
||||
Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Joe"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Joe"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_length_of_with_custom_message_using_quotes
|
||||
Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Joe"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Joe"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_presence_of_with_custom_message_using_quotes
|
||||
Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Joe"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "Joe"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_uniqueness_of_with_custom_message_using_quotes
|
||||
Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "David"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
|
||||
repair_validations(Developer) do
|
||||
Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
d = Developer.new
|
||||
d.name = "David"
|
||||
assert !d.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_associated_with_custom_message_using_quotes
|
||||
Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
Topic.validates_presence_of :content
|
||||
r = Reply.create("title" => "A reply", "content" => "with content!")
|
||||
r.topic = Topic.create("title" => "uhohuhoh")
|
||||
assert !r.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).last
|
||||
repair_validations(Reply) do
|
||||
Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
|
||||
Topic.validates_presence_of :content
|
||||
r = Reply.create("title" => "A reply", "content" => "with content!")
|
||||
r.topic = Topic.create("title" => "uhohuhoh")
|
||||
assert !r.valid?
|
||||
assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic)
|
||||
end
|
||||
end
|
||||
|
||||
def test_if_validation_using_method_true
|
||||
|
@ -1346,13 +1392,15 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_validates_associated_missing
|
||||
Reply.validates_presence_of(:topic)
|
||||
r = Reply.create("title" => "A reply", "content" => "with content!")
|
||||
assert !r.valid?
|
||||
assert r.errors.on(:topic)
|
||||
repair_validations(Reply) do
|
||||
Reply.validates_presence_of(:topic)
|
||||
r = Reply.create("title" => "A reply", "content" => "with content!")
|
||||
assert !r.valid?
|
||||
assert r.errors.on(:topic)
|
||||
|
||||
r.topic = Topic.find :first
|
||||
assert r.valid?
|
||||
r.topic = Topic.find :first
|
||||
assert r.valid?
|
||||
end
|
||||
end
|
||||
|
||||
def test_errors_to_xml
|
||||
|
@ -1364,14 +1412,14 @@ class ValidationsTest < ActiveRecord::TestCase
|
|||
assert xml.include?("<error>Content Empty</error>")
|
||||
end
|
||||
|
||||
def test_validation_order
|
||||
Topic.validates_presence_of :title
|
||||
Topic.validates_length_of :title, :minimum => 2
|
||||
def test_validation_order
|
||||
Topic.validates_presence_of :title
|
||||
Topic.validates_length_of :title, :minimum => 2
|
||||
|
||||
t = Topic.new("title" => "")
|
||||
assert !t.valid?
|
||||
assert_equal "can't be blank", t.errors.on("title").first
|
||||
end
|
||||
t = Topic.new("title" => "")
|
||||
assert !t.valid?
|
||||
assert_equal "can't be blank", t.errors.on("title").first
|
||||
end
|
||||
|
||||
# previous implementation of validates_presence_of eval'd the
|
||||
# string with the wrong binding, this regression test is to
|
||||
|
@ -1423,11 +1471,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
|
|||
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)
|
||||
Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
end
|
||||
repair_validations(Topic)
|
||||
|
||||
def test_default_validates_numericality_of
|
||||
Topic.validates_numericality_of :approved
|
||||
|
|
|
@ -31,6 +31,13 @@ class XmlSerializationTest < ActiveRecord::TestCase
|
|||
assert_match %r{<created_at}, @xml
|
||||
end
|
||||
|
||||
def test_should_allow_camelized_tags
|
||||
@xml = Contact.new.to_xml :root => 'xml_contact', :camelize => true
|
||||
assert_match %r{^<XmlContact>}, @xml
|
||||
assert_match %r{</XmlContact>$}, @xml
|
||||
assert_match %r{<CreatedAt}, @xml
|
||||
end
|
||||
|
||||
def test_should_include_yielded_additions
|
||||
@xml = Contact.new.to_xml do |xml|
|
||||
xml.creator "David"
|
||||
|
|
18
vendor/rails/activerecord/test/connections/jdbc_jdbcderby/connection.rb
vendored
Normal file
18
vendor/rails/activerecord/test/connections/jdbc_jdbcderby/connection.rb
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
print "Using Derby via JRuby, activerecord-jdbc-adapter and activerecord-jdbcderby-adapter\n"
|
||||
require_dependency 'models/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'jdbcderby',
|
||||
:database => 'activerecord_unittest'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'jdbcderby',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
18
vendor/rails/activerecord/test/connections/jdbc_jdbch2/connection.rb
vendored
Normal file
18
vendor/rails/activerecord/test/connections/jdbc_jdbch2/connection.rb
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
print "Using H2 via JRuby, activerecord-jdbc-adapter and activerecord-jdbch2-adapter\n"
|
||||
require_dependency 'models/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'jdbch2',
|
||||
:database => 'activerecord_unittest'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'jdbch2',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
18
vendor/rails/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb
vendored
Normal file
18
vendor/rails/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
print "Using HSQLDB via JRuby, activerecord-jdbc-adapter and activerecord-jdbchsqldb-adapter\n"
|
||||
require_dependency 'models/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'jdbchsqldb',
|
||||
:database => 'activerecord_unittest'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'jdbchsqldb',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
26
vendor/rails/activerecord/test/connections/jdbc_jdbcmysql/connection.rb
vendored
Normal file
26
vendor/rails/activerecord/test/connections/jdbc_jdbcmysql/connection.rb
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
print "Using MySQL via JRuby, activerecord-jdbc-adapter and activerecord-jdbcmysql-adapter\n"
|
||||
require_dependency 'models/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
|
||||
# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'jdbcmysql',
|
||||
:username => 'rails',
|
||||
:encoding => 'utf8',
|
||||
:database => 'activerecord_unittest',
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'jdbcmysql',
|
||||
:username => 'rails',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
|
26
vendor/rails/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb
vendored
Normal file
26
vendor/rails/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
print "Using Postgrsql via JRuby, activerecord-jdbc-adapter and activerecord-postgresql-adapter\n"
|
||||
require_dependency 'models/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
# createuser rails --createdb --no-superuser --no-createrole
|
||||
# createdb -O rails activerecord_unittest
|
||||
# createdb -O rails activerecord_unittest2
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'jdbcpostgresql',
|
||||
:username => ENV['USER'] || 'rails',
|
||||
:database => 'activerecord_unittest'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'jdbcpostgresql',
|
||||
:username => ENV['USER'] || 'rails',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
|
25
vendor/rails/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb
vendored
Normal file
25
vendor/rails/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
print "Using SQLite3 via JRuby, activerecord-jdbc-adapter and activerecord-jdbcsqlite3-adapter\n"
|
||||
require_dependency 'models/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
class SqliteError < StandardError
|
||||
end
|
||||
|
||||
BASE_DIR = FIXTURES_ROOT
|
||||
sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3"
|
||||
sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3"
|
||||
|
||||
def make_connection(clazz, db_file)
|
||||
ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'jdbcsqlite3', :database => db_file, :timeout => 5000 } }
|
||||
unless File.exist?(db_file)
|
||||
puts "SQLite3 database not found at #{db_file}. Rebuilding it."
|
||||
sqlite_command = %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"}
|
||||
puts "Executing '#{sqlite_command}'"
|
||||
raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command)
|
||||
end
|
||||
clazz.establish_connection(clazz.name)
|
||||
end
|
||||
|
||||
make_connection(ActiveRecord::Base, sqlite_test_db)
|
||||
make_connection(Course, sqlite_test_db2)
|
6
vendor/rails/activerecord/test/fixtures/member_types.yml
vendored
Normal file
6
vendor/rails/activerecord/test/fixtures/member_types.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
founding:
|
||||
id: 1
|
||||
name: Founding
|
||||
provisional:
|
||||
id: 2
|
||||
name: Provisional
|
|
@ -1,4 +1,6 @@
|
|||
groucho:
|
||||
name: Groucho Marx
|
||||
member_type_id: 1
|
||||
some_other_guy:
|
||||
name: Englebert Humperdink
|
||||
name: Englebert Humperdink
|
||||
member_type_id: 2
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
michael:
|
||||
id: 1
|
||||
first_name: Michael
|
||||
primary_contact_id: 2
|
||||
gender: M
|
||||
david:
|
||||
id: 2
|
||||
first_name: David
|
||||
first_name: David
|
||||
primary_contact_id: 3
|
||||
gender: M
|
||||
susan:
|
||||
id: 3
|
||||
first_name: Susan
|
||||
primary_contact_id: 2
|
||||
gender: F
|
|
@ -1,6 +1,7 @@
|
|||
class Author < ActiveRecord::Base
|
||||
has_many :posts
|
||||
has_many :posts_with_comments, :include => :comments, :class_name => "Post"
|
||||
has_many :popular_grouped_posts, :include => :comments, :class_name => "Post", :group => "type", :having => "SUM(comments_count) > 1", :select => "type"
|
||||
has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
|
||||
has_many :posts_sorted_by_id_limited, :class_name => "Post", :order => 'posts.id', :limit => 1
|
||||
has_many :posts_with_categories, :include => :categories, :class_name => "Post"
|
||||
|
|
3
vendor/rails/activerecord/test/models/bird.rb
vendored
Normal file
3
vendor/rails/activerecord/test/models/bird.rb
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Bird < ActiveRecord::Base
|
||||
validates_presence_of :name
|
||||
end
|
|
@ -14,6 +14,7 @@ class Category < ActiveRecord::Base
|
|||
:class_name => 'Post',
|
||||
:conditions => { :title => 'Yet Another Testing Title' }
|
||||
|
||||
has_and_belongs_to_many :popular_grouped_posts, :class_name => "Post", :group => "posts.type", :having => "sum(comments.post_id) > 2", :include => :comments
|
||||
has_and_belongs_to_many :posts_gruoped_by_title, :class_name => "Post", :group => "title", :select => "title"
|
||||
|
||||
def self.what_are_you
|
||||
|
|
|
@ -80,6 +80,7 @@ class ExclusivelyDependentFirm < Company
|
|||
has_one :account, :foreign_key => "firm_id", :dependent => :delete
|
||||
has_many :dependent_sanitized_conditional_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all, :conditions => "name = 'BigShot Inc.'"
|
||||
has_many :dependent_conditional_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all, :conditions => ["name = ?", 'BigShot Inc.']
|
||||
has_many :dependent_hash_conditional_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all, :conditions => {:name => 'BigShot Inc.'}
|
||||
end
|
||||
|
||||
class Client < Company
|
||||
|
|
|
@ -77,3 +77,15 @@ class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
|
|||
raise if projects.empty?
|
||||
end
|
||||
end
|
||||
|
||||
class DeveloperOrderedBySalary < ActiveRecord::Base
|
||||
self.table_name = 'developers'
|
||||
default_scope :order => 'salary DESC'
|
||||
named_scope :by_name, :order => 'name DESC'
|
||||
|
||||
def self.all_ordered_by_name
|
||||
with_scope(:find => { :order => 'name DESC' }) do
|
||||
find(:all)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,4 +8,5 @@ class Member < ActiveRecord::Base
|
|||
has_one :sponsor_club, :through => :sponsor
|
||||
has_one :member_detail
|
||||
has_one :organization, :through => :member_detail
|
||||
belongs_to :member_type
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
class MemberDetail < ActiveRecord::Base
|
||||
belongs_to :member
|
||||
belongs_to :organization
|
||||
has_one :member_type, :through => :member
|
||||
end
|
||||
|
|
3
vendor/rails/activerecord/test/models/member_type.rb
vendored
Normal file
3
vendor/rails/activerecord/test/models/member_type.rb
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
class MemberType < ActiveRecord::Base
|
||||
has_many :members
|
||||
end
|
|
@ -4,6 +4,8 @@ class Parrot < ActiveRecord::Base
|
|||
has_and_belongs_to_many :treasures
|
||||
has_many :loots, :as => :looter
|
||||
alias_attribute :title, :name
|
||||
|
||||
validates_presence_of :name
|
||||
end
|
||||
|
||||
class LiveParrot < Parrot
|
||||
|
|
|
@ -7,4 +7,10 @@ class Person < ActiveRecord::Base
|
|||
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'
|
||||
|
||||
belongs_to :primary_contact, :class_name => 'Person'
|
||||
has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id'
|
||||
|
||||
named_scope :males, :conditions => { :gender => 'M' }
|
||||
named_scope :females, :conditions => { :gender => 'F' }
|
||||
end
|
||||
|
|
|
@ -5,5 +5,12 @@ class Pirate < ActiveRecord::Base
|
|||
|
||||
has_many :treasure_estimates, :through => :treasures, :source => :price_estimates
|
||||
|
||||
# These both have :autosave enabled because accepts_nested_attributes_for is used on them.
|
||||
has_one :ship
|
||||
has_many :birds
|
||||
|
||||
accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
|
||||
accepts_nested_attributes_for :ship, :allow_destroy => true
|
||||
|
||||
validates_presence_of :catchphrase
|
||||
end
|
||||
|
|
|
@ -17,6 +17,12 @@ class Post < ActiveRecord::Base
|
|||
|
||||
has_one :last_comment, :class_name => 'Comment', :order => 'id desc'
|
||||
|
||||
named_scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} }
|
||||
named_scope :with_very_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'VerySpecialComment'} }
|
||||
named_scope :with_post, lambda {|post_id|
|
||||
{ :joins => :comments, :conditions => {:comments => {:post_id => post_id} } }
|
||||
}
|
||||
|
||||
has_many :comments, :order => "body" do
|
||||
def find_most_recent
|
||||
find(:first, :order => "id DESC")
|
||||
|
|
|
@ -13,6 +13,7 @@ class Project < ActiveRecord::Base
|
|||
:after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id || '<new>'}"},
|
||||
:before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
|
||||
:after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}
|
||||
has_and_belongs_to_many :well_payed_salary_groups, :class_name => "Developer", :group => "salary", :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary"
|
||||
|
||||
attr_accessor :developers_log
|
||||
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
class Ship < ActiveRecord::Base
|
||||
self.record_timestamps = false
|
||||
|
||||
belongs_to :pirate
|
||||
has_many :parts, :class_name => 'ShipPart', :autosave => true
|
||||
|
||||
accepts_nested_attributes_for :pirate, :allow_destroy => true
|
||||
|
||||
validates_presence_of :name
|
||||
end
|
5
vendor/rails/activerecord/test/models/ship_part.rb
vendored
Normal file
5
vendor/rails/activerecord/test/models/ship_part.rb
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
class ShipPart < ActiveRecord::Base
|
||||
belongs_to :ship
|
||||
|
||||
validates_presence_of :name
|
||||
end
|
|
@ -4,6 +4,8 @@ class Topic < ActiveRecord::Base
|
|||
{ :conditions => ['written_on < ?', time] }
|
||||
}
|
||||
named_scope :approved, :conditions => {:approved => true}
|
||||
named_scope :rejected, :conditions => {:approved => false}
|
||||
|
||||
named_scope :by_lifo, :conditions => {:author_name => 'lifo'}
|
||||
|
||||
named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
|
||||
|
|
28
vendor/rails/activerecord/test/schema/schema.rb
vendored
28
vendor/rails/activerecord/test/schema/schema.rb
vendored
|
@ -55,6 +55,11 @@ ActiveRecord::Schema.define do
|
|||
t.binary :data
|
||||
end
|
||||
|
||||
create_table :birds, :force => true do |t|
|
||||
t.string :name
|
||||
t.integer :pirate_id
|
||||
end
|
||||
|
||||
create_table :books, :force => true do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
|
@ -154,6 +159,11 @@ ActiveRecord::Schema.define do
|
|||
t.string :name
|
||||
end
|
||||
|
||||
create_table :goofy_string_id, :force => true, :id => false do |t|
|
||||
t.string :id, :null => false
|
||||
t.string :info
|
||||
end
|
||||
|
||||
create_table :items, :force => true do |t|
|
||||
t.column :name, :integer
|
||||
end
|
||||
|
@ -195,6 +205,7 @@ ActiveRecord::Schema.define do
|
|||
|
||||
create_table :members, :force => true do |t|
|
||||
t.string :name
|
||||
t.integer :member_type_id
|
||||
end
|
||||
|
||||
create_table :member_details, :force => true do |t|
|
||||
|
@ -210,6 +221,10 @@ ActiveRecord::Schema.define do
|
|||
t.string :type
|
||||
end
|
||||
|
||||
create_table :member_types, :force => true do |t|
|
||||
t.string :name
|
||||
end
|
||||
|
||||
create_table :references, :force => true do |t|
|
||||
t.integer :person_id
|
||||
t.integer :job_id
|
||||
|
@ -247,6 +262,7 @@ ActiveRecord::Schema.define do
|
|||
t.decimal :world_population, :precision => 10, :scale => 0
|
||||
t.decimal :my_house_population, :precision => 2, :scale => 0
|
||||
t.decimal :decimal_number_with_default, :precision => 3, :scale => 2, :default => 2.78
|
||||
t.float :temperature
|
||||
end
|
||||
|
||||
create_table :orders, :force => true do |t|
|
||||
|
@ -293,8 +309,10 @@ ActiveRecord::Schema.define do
|
|||
end
|
||||
|
||||
create_table :people, :force => true do |t|
|
||||
t.string :first_name, :null => false
|
||||
t.integer :lock_version, :null => false, :default => 0
|
||||
t.string :first_name, :null => false
|
||||
t.references :primary_contact
|
||||
t.string :gender, :limit => 1
|
||||
t.integer :lock_version, :null => false, :default => 0
|
||||
end
|
||||
|
||||
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
|
||||
|
@ -343,12 +361,18 @@ ActiveRecord::Schema.define do
|
|||
|
||||
create_table :ships, :force => true do |t|
|
||||
t.string :name
|
||||
t.integer :pirate_id
|
||||
t.datetime :created_at
|
||||
t.datetime :created_on
|
||||
t.datetime :updated_at
|
||||
t.datetime :updated_on
|
||||
end
|
||||
|
||||
create_table :ship_parts, :force => true do |t|
|
||||
t.string :name
|
||||
t.integer :ship_id
|
||||
end
|
||||
|
||||
create_table :sponsors, :force => true do |t|
|
||||
t.integer :club_id
|
||||
t.integer :sponsorable_id
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue