mirror of
https://github.com/TracksApp/tracks.git
synced 2026-03-11 07:02:36 +01:00
Updated to svn tags/tracks-1.6
This commit is contained in:
parent
103fcb8049
commit
02496f2d44
2274 changed files with 0 additions and 0 deletions
5527
vendor/rails/activerecord/CHANGELOG
vendored
Normal file
5527
vendor/rails/activerecord/CHANGELOG
vendored
Normal file
File diff suppressed because it is too large
Load diff
20
vendor/rails/activerecord/MIT-LICENSE
vendored
Normal file
20
vendor/rails/activerecord/MIT-LICENSE
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2004-2007 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
346
vendor/rails/activerecord/README
vendored
Executable file
346
vendor/rails/activerecord/README
vendored
Executable file
|
|
@ -0,0 +1,346 @@
|
|||
= Active Record -- Object-relation mapping put on rails
|
||||
|
||||
Active Record connects business objects and database tables to create a persistable
|
||||
domain model where logic and data are presented in one wrapping. It's an implementation
|
||||
of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html]
|
||||
by the same name as described by Martin Fowler:
|
||||
|
||||
"An object that wraps a row in a database table or view, encapsulates
|
||||
the database access, and adds domain logic on that data."
|
||||
|
||||
Active Record's main contribution to the pattern is to relieve the original of two stunting problems:
|
||||
lack of associations and inheritance. By adding a simple domain language-like set of macros to describe
|
||||
the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the
|
||||
gap of functionality between the data mapper and active record approach.
|
||||
|
||||
A short rundown of the major features:
|
||||
|
||||
* Automated mapping between classes and tables, attributes and columns.
|
||||
|
||||
class Product < ActiveRecord::Base; end
|
||||
|
||||
...is automatically mapped to the table named "products", such as:
|
||||
|
||||
CREATE TABLE products (
|
||||
id int(11) NOT NULL auto_increment,
|
||||
name varchar(255),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
...which again gives Product#name and Product#name=(new_name)
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Base.html]
|
||||
|
||||
|
||||
* Associations between objects controlled by simple meta-programming macros.
|
||||
|
||||
class Firm < ActiveRecord::Base
|
||||
has_many :clients
|
||||
has_one :account
|
||||
belongs_to :conglomorate
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
|
||||
|
||||
|
||||
* Aggregations of value objects controlled by simple meta-programming macros.
|
||||
|
||||
class Account < ActiveRecord::Base
|
||||
composed_of :balance, :class_name => "Money",
|
||||
:mapping => %w(balance amount)
|
||||
composed_of :address,
|
||||
:mapping => [%w(address_street street), %w(address_city city)]
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
|
||||
|
||||
|
||||
* Validation rules that can differ for new or existing objects.
|
||||
|
||||
class Account < ActiveRecord::Base
|
||||
validates_presence_of :subdomain, :name, :email_address, :password
|
||||
validates_uniqueness_of :subdomain
|
||||
validates_acceptance_of :terms_of_service, :on => :create
|
||||
validates_confirmation_of :password, :email_address, :on => :create
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Validations.html]
|
||||
|
||||
* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
|
||||
|
||||
class Person < ActiveRecord::Base
|
||||
def before_destroy # is called just before Person#destroy
|
||||
CreditCard.find(credit_card_id).destroy
|
||||
end
|
||||
end
|
||||
|
||||
class Account < ActiveRecord::Base
|
||||
after_find :eager_load, 'self.class.announce(#{id})'
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Callbacks.html]
|
||||
|
||||
|
||||
* Observers for the entire lifecycle
|
||||
|
||||
class CommentObserver < ActiveRecord::Observer
|
||||
def after_create(comment) # is called just after Comment#save
|
||||
Notifications.deliver_new_comment("david@loudthinking.com", comment)
|
||||
end
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Observer.html]
|
||||
|
||||
|
||||
* Inheritance hierarchies
|
||||
|
||||
class Company < ActiveRecord::Base; end
|
||||
class Firm < Company; end
|
||||
class Client < Company; end
|
||||
class PriorityClient < Client; end
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Base.html]
|
||||
|
||||
|
||||
* Transaction support on both a database and object level. The latter is implemented
|
||||
by using Transaction::Simple[http://railsmanual.com/module/Transaction::Simple]
|
||||
|
||||
# Just database transaction
|
||||
Account.transaction do
|
||||
david.withdrawal(100)
|
||||
mary.deposit(100)
|
||||
end
|
||||
|
||||
# Database and object transaction
|
||||
Account.transaction(david, mary) do
|
||||
david.withdrawal(100)
|
||||
mary.deposit(100)
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
|
||||
|
||||
|
||||
* Reflections on columns, associations, and aggregations
|
||||
|
||||
reflection = Firm.reflect_on_association(:clients)
|
||||
reflection.klass # => Client (class)
|
||||
Firm.columns # Returns an array of column descriptors for the firms table
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
|
||||
|
||||
|
||||
* Direct manipulation (instead of service invocation)
|
||||
|
||||
So instead of (Hibernate[http://www.hibernate.org/] example):
|
||||
|
||||
long pkId = 1234;
|
||||
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
|
||||
// something interesting involving a cat...
|
||||
sess.save(cat);
|
||||
sess.flush(); // force the SQL INSERT
|
||||
|
||||
Active Record lets you:
|
||||
|
||||
pkId = 1234
|
||||
cat = Cat.find(pkId)
|
||||
# something even more interesting involving the same cat...
|
||||
cat.save
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Base.html]
|
||||
|
||||
|
||||
* Database abstraction through simple adapters (~100 lines) with a shared connector
|
||||
|
||||
ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile")
|
||||
|
||||
ActiveRecord::Base.establish_connection(
|
||||
:adapter => "mysql",
|
||||
:host => "localhost",
|
||||
:username => "me",
|
||||
:password => "secret",
|
||||
:database => "activerecord"
|
||||
)
|
||||
|
||||
{Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for
|
||||
MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html].
|
||||
|
||||
|
||||
* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
|
||||
|
||||
|
||||
== Simple example (1/2): Defining tables and classes (using MySQL)
|
||||
|
||||
Data definitions are specified only in the database. Active Record queries the database for
|
||||
the column names (that then serves to determine which attributes are valid) on regular
|
||||
object instantiation through the new constructor and relies on the column names in the rows
|
||||
with the finders.
|
||||
|
||||
# CREATE TABLE companies (
|
||||
# id int(11) unsigned NOT NULL auto_increment,
|
||||
# client_of int(11),
|
||||
# name varchar(255),
|
||||
# type varchar(100),
|
||||
# PRIMARY KEY (id)
|
||||
# )
|
||||
|
||||
Active Record automatically links the "Company" object to the "companies" table
|
||||
|
||||
class Company < ActiveRecord::Base
|
||||
has_many :people, :class_name => "Person"
|
||||
end
|
||||
|
||||
class Firm < Company
|
||||
has_many :clients
|
||||
|
||||
def people_with_all_clients
|
||||
clients.inject([]) { |people, client| people + client.people }
|
||||
end
|
||||
end
|
||||
|
||||
The foreign_key is only necessary because we didn't use "firm_id" in the data definition
|
||||
|
||||
class Client < Company
|
||||
belongs_to :firm, :foreign_key => "client_of"
|
||||
end
|
||||
|
||||
# CREATE TABLE people (
|
||||
# id int(11) unsigned NOT NULL auto_increment,
|
||||
# name text,
|
||||
# company_id text,
|
||||
# PRIMARY KEY (id)
|
||||
# )
|
||||
|
||||
Active Record will also automatically link the "Person" object to the "people" table
|
||||
|
||||
class Person < ActiveRecord::Base
|
||||
belongs_to :company
|
||||
end
|
||||
|
||||
== Simple example (2/2): Using the domain
|
||||
|
||||
Picking a database connection for all the Active Records
|
||||
|
||||
ActiveRecord::Base.establish_connection(
|
||||
:adapter => "mysql",
|
||||
:host => "localhost",
|
||||
:username => "me",
|
||||
:password => "secret",
|
||||
:database => "activerecord"
|
||||
)
|
||||
|
||||
Create some fixtures
|
||||
|
||||
firm = Firm.new("name" => "Next Angle")
|
||||
# SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm")
|
||||
firm.save
|
||||
|
||||
client = Client.new("name" => "37signals", "client_of" => firm.id)
|
||||
# SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm")
|
||||
client.save
|
||||
|
||||
Lots of different finders
|
||||
|
||||
# SQL: SELECT * FROM companies WHERE id = 1
|
||||
next_angle = Company.find(1)
|
||||
|
||||
# SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm'
|
||||
next_angle = Firm.find(1)
|
||||
|
||||
# SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle'
|
||||
next_angle = Company.find(:first, :conditions => "name = 'Next Angle'")
|
||||
|
||||
next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first
|
||||
|
||||
The supertype, Company, will return subtype instances
|
||||
|
||||
Firm === next_angle
|
||||
|
||||
All the dynamic methods added by the has_many macro
|
||||
|
||||
next_angle.clients.empty? # true
|
||||
next_angle.clients.size # total number of clients
|
||||
all_clients = next_angle.clients
|
||||
|
||||
Constrained finds makes access security easier when ID comes from a web-app
|
||||
|
||||
# SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2
|
||||
thirty_seven_signals = next_angle.clients.find(2)
|
||||
|
||||
Bi-directional associations thanks to the "belongs_to" macro
|
||||
|
||||
thirty_seven_signals.firm.nil? # true
|
||||
|
||||
|
||||
== Examples
|
||||
|
||||
Active Record ships with a couple of examples that should give you a good feel for
|
||||
operating usage. Be sure to edit the <tt>examples/shared_setup.rb</tt> file for your
|
||||
own database before running the examples. Possibly also the table definition SQL in
|
||||
the examples themselves.
|
||||
|
||||
It's also highly recommended to have a look at the unit tests. Read more in link:files/RUNNING_UNIT_TESTS.html
|
||||
|
||||
|
||||
== Philosophy
|
||||
|
||||
Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
|
||||
object-relational mapping. The prime directive for this mapping has been to minimize
|
||||
the amount of code needed to build a real-world domain model. This is made possible
|
||||
by relying on a number of conventions that make it easy for Active Record to infer
|
||||
complex relations and structures from a minimal amount of explicit direction.
|
||||
|
||||
Convention over Configuration:
|
||||
* No XML-files!
|
||||
* Lots of reflection and run-time extension
|
||||
* Magic is not inherently a bad word
|
||||
|
||||
Admit the Database:
|
||||
* Lets you drop down to SQL for odd cases and performance
|
||||
* Doesn't attempt to duplicate or replace data definitions
|
||||
|
||||
|
||||
== Download
|
||||
|
||||
The latest version of Active Record can be found at
|
||||
|
||||
* http://rubyforge.org/project/showfiles.php?group_id=182
|
||||
|
||||
Documentation can be found at
|
||||
|
||||
* http://ar.rubyonrails.com
|
||||
|
||||
|
||||
== Installation
|
||||
|
||||
The prefered method of installing Active Record is through its GEM file. You'll need to have
|
||||
RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have,
|
||||
then use:
|
||||
|
||||
% [sudo] gem install activerecord-1.10.0.gem
|
||||
|
||||
You can also install Active Record the old-fashion way with the following command:
|
||||
|
||||
% [sudo] ruby install.rb
|
||||
|
||||
from its distribution directory.
|
||||
|
||||
|
||||
== License
|
||||
|
||||
Active Record is released under the MIT license.
|
||||
|
||||
|
||||
== Support
|
||||
|
||||
The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record
|
||||
RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says:
|
||||
|
||||
Feel free to submit commits or feature requests. If you send a patch,
|
||||
remember to update the corresponding unit tests. If fact, I prefer
|
||||
new feature to be submitted in the form of new unit tests.
|
||||
|
||||
For other information, feel free to ask on the ruby-talk mailing list
|
||||
(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
|
||||
33
vendor/rails/activerecord/RUNNING_UNIT_TESTS
vendored
Normal file
33
vendor/rails/activerecord/RUNNING_UNIT_TESTS
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
== Creating the test database
|
||||
|
||||
The default names for the test databases are "activerecord_unittest" and
|
||||
"activerecord_unittest2". If you want to use another database name then be sure
|
||||
to update the connection adapter setups you want to test with in
|
||||
test/connections/<your database>/connection.rb.
|
||||
When you have the database online, you can import the fixture tables with
|
||||
the test/fixtures/db_definitions/*.sql files.
|
||||
|
||||
Make sure that you create database objects with the same user that you specified in
|
||||
connection.rb otherwise (on Postgres, at least) tests for default values will fail.
|
||||
|
||||
== Running with Rake
|
||||
|
||||
The easiest way to run the unit tests is through Rake. The default task runs
|
||||
the entire test suite for all the adapters. You can also run the suite on just
|
||||
one adapter by using the tasks test_mysql, test_sqlite, test_postgresql or any
|
||||
of the other test_ tasks. For more information, checkout the full array of rake
|
||||
tasks with "rake -T"
|
||||
|
||||
Rake can be found at http://rake.rubyforge.org
|
||||
|
||||
== Running by hand
|
||||
|
||||
Unit tests are located in test directory. If you only want to run a single test suite,
|
||||
you can do so with:
|
||||
|
||||
rake test_mysql TEST=base_test.rb
|
||||
|
||||
That'll run the base suite using the MySQL-Ruby adapter.
|
||||
|
||||
|
||||
|
||||
248
vendor/rails/activerecord/Rakefile
vendored
Executable file
248
vendor/rails/activerecord/Rakefile
vendored
Executable file
|
|
@ -0,0 +1,248 @@
|
|||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
PKG_NAME = 'activerecord'
|
||||
PKG_VERSION = ActiveRecord::VERSION::STRING + PKG_BUILD
|
||||
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
||||
|
||||
RELEASE_NAME = "REL #{PKG_VERSION}"
|
||||
|
||||
RUBY_FORGE_PROJECT = "activerecord"
|
||||
RUBY_FORGE_USER = "webster132"
|
||||
|
||||
PKG_FILES = FileList[
|
||||
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
|
||||
].exclude(/\bCVS\b|~$/)
|
||||
|
||||
|
||||
desc 'Run mysql, sqlite, and postgresql tests by default'
|
||||
task :default => :test
|
||||
|
||||
desc 'Run mysql, sqlite, and postgresql tests'
|
||||
task :test => %w(test_mysql test_sqlite test_sqlite3 test_postgresql)
|
||||
|
||||
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
|
||||
Rake::TestTask.new("test_#{adapter}") { |t|
|
||||
t.libs << "test" << "test/connections/native_#{adapter}"
|
||||
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
|
||||
t.pattern = "test/**/*_test{,_#{adapter_short}}.rb"
|
||||
t.verbose = true
|
||||
}
|
||||
|
||||
namespace adapter do
|
||||
task :test => "test_#{adapter}"
|
||||
end
|
||||
end
|
||||
|
||||
SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
|
||||
|
||||
namespace :mysql do
|
||||
desc 'Build the MySQL test databases'
|
||||
task :build_databases do
|
||||
%x( mysqladmin create activerecord_unittest )
|
||||
%x( mysqladmin create activerecord_unittest2 )
|
||||
%x( mysql -e "grant all on activerecord_unittest.* to rails@localhost" )
|
||||
%x( mysql -e "grant all on activerecord_unittest2.* to rails@localhost" )
|
||||
end
|
||||
|
||||
desc 'Drop the MySQL test databases'
|
||||
task :drop_databases do
|
||||
%x( mysqladmin -f drop activerecord_unittest )
|
||||
%x( mysqladmin -f drop activerecord_unittest2 )
|
||||
end
|
||||
|
||||
desc 'Rebuild the MySQL test databases'
|
||||
task :rebuild_databases => [:drop_databases, :build_databases]
|
||||
end
|
||||
|
||||
task :build_mysql_databases => 'mysql:build_databases'
|
||||
task :drop_mysql_databases => 'mysql:drop_databases'
|
||||
task :rebuild_mysql_databases => 'mysql:rebuild_databases'
|
||||
|
||||
|
||||
namespace :postgresql do
|
||||
desc 'Build the PostgreSQL test databases'
|
||||
task :build_databases do
|
||||
%x( createdb -U postgres activerecord_unittest )
|
||||
%x( createdb -U postgres activerecord_unittest2 )
|
||||
%x( psql activerecord_unittest -f #{File.join(SCHEMA_PATH, 'postgresql.sql')} postgres )
|
||||
%x( psql activerecord_unittest2 -f #{File.join(SCHEMA_PATH, 'postgresql2.sql')} postgres )
|
||||
end
|
||||
|
||||
desc 'Drop the PostgreSQL test databases'
|
||||
task :drop_databases do
|
||||
%x( dropdb -U postgres activerecord_unittest )
|
||||
%x( dropdb -U postgres activerecord_unittest2 )
|
||||
end
|
||||
|
||||
desc 'Rebuild the PostgreSQL test databases'
|
||||
task :rebuild_databases => [:drop_databases, :build_databases]
|
||||
end
|
||||
|
||||
task :build_postgresql_databases => 'postgresql:build_databases'
|
||||
task :drop_postgresql_databases => 'postgresql:drop_databases'
|
||||
task :rebuild_postgresql_databases => 'postgresql:rebuild_databases'
|
||||
|
||||
|
||||
namespace :frontbase do
|
||||
desc 'Build the FrontBase test databases'
|
||||
task :build_databases => :rebuild_frontbase_databases
|
||||
|
||||
desc 'Rebuild the FrontBase test databases'
|
||||
task :rebuild_databases do
|
||||
build_frontbase_database = Proc.new do |db_name, sql_definition_file|
|
||||
%(
|
||||
STOP DATABASE #{db_name};
|
||||
DELETE DATABASE #{db_name};
|
||||
CREATE DATABASE #{db_name};
|
||||
|
||||
CONNECT TO #{db_name} AS SESSION_NAME USER _SYSTEM;
|
||||
SET COMMIT FALSE;
|
||||
|
||||
CREATE USER RAILS;
|
||||
CREATE SCHEMA RAILS AUTHORIZATION RAILS;
|
||||
COMMIT;
|
||||
|
||||
SET SESSION AUTHORIZATION RAILS;
|
||||
SCRIPT '#{sql_definition_file}';
|
||||
|
||||
COMMIT;
|
||||
|
||||
DISCONNECT ALL;
|
||||
)
|
||||
end
|
||||
create_activerecord_unittest = build_frontbase_database['activerecord_unittest', File.join(SCHEMA_PATH, 'frontbase.sql')]
|
||||
create_activerecord_unittest2 = build_frontbase_database['activerecord_unittest2', File.join(SCHEMA_PATH, 'frontbase2.sql')]
|
||||
execute_frontbase_sql = Proc.new do |sql|
|
||||
system(<<-SHELL)
|
||||
/Library/FrontBase/bin/sql92 <<-SQL
|
||||
#{sql}
|
||||
SQL
|
||||
SHELL
|
||||
end
|
||||
execute_frontbase_sql[create_activerecord_unittest]
|
||||
execute_frontbase_sql[create_activerecord_unittest2]
|
||||
end
|
||||
end
|
||||
|
||||
task :build_frontbase_databases => 'frontbase:build_databases'
|
||||
task :rebuild_frontbase_databases => 'frontbase:rebuild_databases'
|
||||
|
||||
|
||||
# Generate the RDoc documentation
|
||||
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Active Record -- Object-relation mapping put on rails"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
rdoc.options << '--charset' << 'utf-8'
|
||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
||||
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
|
||||
rdoc.rdoc_files.include('dev-utils/*.rb')
|
||||
}
|
||||
|
||||
# Enhance rdoc task to copy referenced images also
|
||||
task :rdoc do
|
||||
FileUtils.mkdir_p "doc/files/examples/"
|
||||
FileUtils.copy "examples/associations.png", "doc/files/examples/associations.png"
|
||||
end
|
||||
|
||||
|
||||
# Create compressed packages
|
||||
|
||||
dist_dirs = [ "lib", "test", "examples" ]
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.name = PKG_NAME
|
||||
s.version = PKG_VERSION
|
||||
s.summary = "Implements the ActiveRecord pattern for ORM."
|
||||
s.description = %q{Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.}
|
||||
|
||||
s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG" ]
|
||||
dist_dirs.each do |dir|
|
||||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD)
|
||||
|
||||
s.files.delete "test/fixtures/fixture_database.sqlite"
|
||||
s.files.delete "test/fixtures/fixture_database_2.sqlite"
|
||||
s.files.delete "test/fixtures/fixture_database.sqlite3"
|
||||
s.files.delete "test/fixtures/fixture_database_2.sqlite3"
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'active_record'
|
||||
|
||||
s.has_rdoc = true
|
||||
s.extra_rdoc_files = %w( README )
|
||||
s.rdoc_options.concat ['--main', 'README']
|
||||
|
||||
s.author = "David Heinemeier Hansson"
|
||||
s.email = "david@loudthinking.com"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
s.rubyforge_project = "activerecord"
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |p|
|
||||
p.gem_spec = spec
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
end
|
||||
|
||||
task :lines do
|
||||
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
|
||||
|
||||
for file_name in FileList["lib/active_record/**/*.rb"]
|
||||
next if file_name =~ /vendor/
|
||||
f = File.open(file_name)
|
||||
|
||||
while line = f.gets
|
||||
lines += 1
|
||||
next if line =~ /^\s*$/
|
||||
next if line =~ /^\s*#/
|
||||
codelines += 1
|
||||
end
|
||||
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
||||
|
||||
total_lines += lines
|
||||
total_codelines += codelines
|
||||
|
||||
lines, codelines = 0, 0
|
||||
end
|
||||
|
||||
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
|
||||
end
|
||||
|
||||
|
||||
# Publishing ------------------------------------------------------
|
||||
|
||||
desc "Publish the beta gem"
|
||||
task :pgem => [:package] do
|
||||
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
|
||||
end
|
||||
|
||||
desc "Publish the release files to RubyForge."
|
||||
task :release => [ :package ] do
|
||||
require 'rubyforge'
|
||||
|
||||
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
|
||||
|
||||
rubyforge = RubyForge.new
|
||||
rubyforge.login
|
||||
rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
|
||||
end
|
||||
BIN
vendor/rails/activerecord/examples/associations.png
vendored
Normal file
BIN
vendor/rails/activerecord/examples/associations.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
30
vendor/rails/activerecord/install.rb
vendored
Normal file
30
vendor/rails/activerecord/install.rb
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
require 'rbconfig'
|
||||
require 'find'
|
||||
require 'ftools'
|
||||
|
||||
include Config
|
||||
|
||||
# this was adapted from rdoc's install.rb by ways of Log4r
|
||||
|
||||
$sitedir = CONFIG["sitelibdir"]
|
||||
unless $sitedir
|
||||
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
|
||||
$libdir = File.join(CONFIG["libdir"], "ruby", version)
|
||||
$sitedir = $:.find {|x| x =~ /site_ruby/ }
|
||||
if !$sitedir
|
||||
$sitedir = File.join($libdir, "site_ruby")
|
||||
elsif $sitedir !~ Regexp.quote(version)
|
||||
$sitedir = File.join($sitedir, version)
|
||||
end
|
||||
end
|
||||
|
||||
# the actual gruntwork
|
||||
Dir.chdir("lib")
|
||||
|
||||
Find.find("active_record", "active_record.rb") { |f|
|
||||
if f[-3..-1] == ".rb"
|
||||
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
|
||||
else
|
||||
File::makedirs(File.join($sitedir, *f.split(/\//)))
|
||||
end
|
||||
}
|
||||
76
vendor/rails/activerecord/lib/active_record.rb
vendored
Executable file
76
vendor/rails/activerecord/lib/active_record.rb
vendored
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
#--
|
||||
# Copyright (c) 2004-2007 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
|
||||
unless defined? ActiveSupport
|
||||
active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
|
||||
if File.exist?(active_support_path)
|
||||
$:.unshift active_support_path
|
||||
require 'active_support'
|
||||
else
|
||||
require 'rubygems'
|
||||
gem 'activesupport'
|
||||
require 'active_support'
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_record/base'
|
||||
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/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'
|
||||
|
||||
ActiveRecord::Base.class_eval do
|
||||
extend ActiveRecord::QueryCache
|
||||
include ActiveRecord::Validations
|
||||
include ActiveRecord::Locking::Optimistic
|
||||
include ActiveRecord::Locking::Pessimistic
|
||||
include ActiveRecord::Callbacks
|
||||
include ActiveRecord::Observing
|
||||
include ActiveRecord::Timestamp
|
||||
include ActiveRecord::Associations
|
||||
include ActiveRecord::Aggregations
|
||||
include ActiveRecord::Transactions
|
||||
include ActiveRecord::Reflection
|
||||
include ActiveRecord::Calculations
|
||||
include ActiveRecord::Serialization
|
||||
include ActiveRecord::AttributeMethods
|
||||
end
|
||||
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
||||
require 'active_record/schema_dumper'
|
||||
180
vendor/rails/activerecord/lib/active_record/aggregations.rb
vendored
Normal file
180
vendor/rails/activerecord/lib/active_record/aggregations.rb
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
module ActiveRecord
|
||||
module Aggregations # :nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def clear_aggregation_cache #:nodoc:
|
||||
self.class.reflect_on_all_aggregations.to_a.each do |assoc|
|
||||
instance_variable_set "@#{assoc.name}", nil
|
||||
end unless self.new_record?
|
||||
end
|
||||
|
||||
# Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
|
||||
# as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
|
||||
# composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
|
||||
# attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
|
||||
# and how it can be turned back into attributes (when the entity is saved to the database). Example:
|
||||
#
|
||||
# class Customer < ActiveRecord::Base
|
||||
# composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
|
||||
# composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
|
||||
# end
|
||||
#
|
||||
# The customer class now has the following methods to manipulate the value objects:
|
||||
# * <tt>Customer#balance, Customer#balance=(money)</tt>
|
||||
# * <tt>Customer#address, Customer#address=(address)</tt>
|
||||
#
|
||||
# These methods will operate with value objects like the ones described below:
|
||||
#
|
||||
# class Money
|
||||
# include Comparable
|
||||
# attr_reader :amount, :currency
|
||||
# EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
|
||||
#
|
||||
# def initialize(amount, currency = "USD")
|
||||
# @amount, @currency = amount, currency
|
||||
# end
|
||||
#
|
||||
# def exchange_to(other_currency)
|
||||
# exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
|
||||
# Money.new(exchanged_amount, other_currency)
|
||||
# end
|
||||
#
|
||||
# def ==(other_money)
|
||||
# amount == other_money.amount && currency == other_money.currency
|
||||
# end
|
||||
#
|
||||
# def <=>(other_money)
|
||||
# if currency == other_money.currency
|
||||
# amount <=> amount
|
||||
# else
|
||||
# amount <=> other_money.exchange_to(currency).amount
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Address
|
||||
# attr_reader :street, :city
|
||||
# def initialize(street, city)
|
||||
# @street, @city = street, city
|
||||
# end
|
||||
#
|
||||
# def close_to?(other_address)
|
||||
# city == other_address.city
|
||||
# end
|
||||
#
|
||||
# def ==(other_address)
|
||||
# city == other_address.city && street == other_address.street
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
|
||||
# composition the same as the attribute's name, it will be the only way to access that attribute. That's the case with our
|
||||
# +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
|
||||
#
|
||||
# customer.balance = Money.new(20) # sets the Money value object and the attribute
|
||||
# customer.balance # => Money value object
|
||||
# customer.balance.exchanged_to("DKK") # => Money.new(120, "DKK")
|
||||
# customer.balance > Money.new(10) # => true
|
||||
# customer.balance == Money.new(20) # => true
|
||||
# customer.balance < Money.new(5) # => false
|
||||
#
|
||||
# Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
|
||||
# determine the order of the parameters. Example:
|
||||
#
|
||||
# customer.address_street = "Hyancintvej"
|
||||
# customer.address_city = "Copenhagen"
|
||||
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
|
||||
# customer.address = Address.new("May Street", "Chicago")
|
||||
# customer.address_street # => "May Street"
|
||||
# customer.address_city # => "Chicago"
|
||||
#
|
||||
# == Writing value objects
|
||||
#
|
||||
# Value objects are immutable and interchangeable objects that represent a given value, such as a +Money+ object representing
|
||||
# $5. Two +Money+ objects both representing $5 should be equal (through methods such as == and <=> from +Comparable+ if ranking
|
||||
# makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as +Customer+ can
|
||||
# easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
|
||||
# relational unique identifiers (such as primary keys). Normal <tt>ActiveRecord::Base</tt> classes are entity objects.
|
||||
#
|
||||
# It's also important to treat the value objects as immutable. Don't allow the +Money+ object to have its amount changed after
|
||||
# creation. Create a new +Money+ object with the new value instead. This is exemplified by the <tt>Money#exchanged_to</tt> method that
|
||||
# returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
|
||||
# changed through means other than the writer method.
|
||||
#
|
||||
# The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
|
||||
# change it afterwards will result in a <tt>TypeError</tt>.
|
||||
#
|
||||
# Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
|
||||
# immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
|
||||
module ClassMethods
|
||||
# Adds reader and writer methods for manipulating a value object:
|
||||
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
|
||||
#
|
||||
# Options are:
|
||||
# * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
|
||||
# from the part id. So <tt>composed_of :address</tt> will by default be linked to the +Address+ class, but
|
||||
# if the real class name is +CompanyAddress+, you'll have to specify it with this option.
|
||||
# * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
|
||||
# to a constructor parameter on the value class.
|
||||
# * <tt>:allow_nil</tt> - specifies that the aggregate object will not be instantiated when all mapped
|
||||
# attributes are +nil+. Setting the aggregate class to +nil+ has the effect of writing +nil+ to all mapped attributes.
|
||||
# This defaults to +false+.
|
||||
#
|
||||
# An optional block can be passed to convert the argument that is passed to the writer method into an instance of
|
||||
# <tt>:class_name</tt>. The block will only be called if the argument is not already an instance of <tt>:class_name</tt>.
|
||||
#
|
||||
# Option examples:
|
||||
# composed_of :temperature, :mapping => %w(reading celsius)
|
||||
# composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) {|balance| balance.to_money }
|
||||
# composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
|
||||
# composed_of :gps_location
|
||||
# composed_of :gps_location, :allow_nil => true
|
||||
#
|
||||
def composed_of(part_id, options = {}, &block)
|
||||
options.assert_valid_keys(:class_name, :mapping, :allow_nil)
|
||||
|
||||
name = part_id.id2name
|
||||
class_name = options[:class_name] || name.camelize
|
||||
mapping = options[:mapping] || [ name, name ]
|
||||
mapping = [ mapping ] unless mapping.first.is_a?(Array)
|
||||
allow_nil = options[:allow_nil] || false
|
||||
|
||||
reader_method(name, class_name, mapping, allow_nil)
|
||||
writer_method(name, class_name, mapping, allow_nil, block)
|
||||
|
||||
create_reflection(:composed_of, part_id, options, self)
|
||||
end
|
||||
|
||||
private
|
||||
def reader_method(name, class_name, mapping, allow_nil)
|
||||
module_eval do
|
||||
define_method(name) do |*args|
|
||||
force_reload = args.first || false
|
||||
if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
|
||||
instance_variable_set("@#{name}", class_name.constantize.new(*mapping.collect {|pair| read_attribute(pair.first)}))
|
||||
end
|
||||
return instance_variable_get("@#{name}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def writer_method(name, class_name, mapping, allow_nil, conversion)
|
||||
module_eval do
|
||||
define_method("#{name}=") do |part|
|
||||
if part.nil? && allow_nil
|
||||
mapping.each { |pair| @attributes[pair.first] = nil }
|
||||
instance_variable_set("@#{name}", nil)
|
||||
else
|
||||
part = conversion.call(part) unless part.is_a?(class_name.constantize) || conversion.nil?
|
||||
mapping.each { |pair| @attributes[pair.first] = part.send(pair.last) }
|
||||
instance_variable_set("@#{name}", part.freeze)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
1769
vendor/rails/activerecord/lib/active_record/associations.rb
vendored
Executable file
1769
vendor/rails/activerecord/lib/active_record/associations.rb
vendored
Executable file
File diff suppressed because it is too large
Load diff
240
vendor/rails/activerecord/lib/active_record/associations/association_collection.rb
vendored
Normal file
240
vendor/rails/activerecord/lib/active_record/associations/association_collection.rb
vendored
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
require 'set'
|
||||
|
||||
module ActiveRecord
|
||||
module Associations
|
||||
class AssociationCollection < AssociationProxy #:nodoc:
|
||||
def to_ary
|
||||
load_target
|
||||
@target.to_ary
|
||||
end
|
||||
|
||||
def reset
|
||||
reset_target!
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
# Add +records+ to this association. Returns +self+ so method calls may be chained.
|
||||
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
|
||||
def <<(*records)
|
||||
result = true
|
||||
load_target if @owner.new_record?
|
||||
|
||||
@owner.transaction do
|
||||
flatten_deeper(records).each do |record|
|
||||
raise_on_type_mismatch(record)
|
||||
callback(:before_add, record)
|
||||
result &&= insert_record(record) unless @owner.new_record?
|
||||
@target << record
|
||||
callback(:after_add, record)
|
||||
end
|
||||
end
|
||||
|
||||
result && self
|
||||
end
|
||||
|
||||
alias_method :push, :<<
|
||||
alias_method :concat, :<<
|
||||
|
||||
# Remove all records from this association
|
||||
def delete_all
|
||||
load_target
|
||||
delete(@target)
|
||||
reset_target!
|
||||
end
|
||||
|
||||
# Calculate sum using SQL, not Enumerable
|
||||
def sum(*args, &block)
|
||||
calculate(:sum, *args, &block)
|
||||
end
|
||||
|
||||
# Remove +records+ from this association. Does not destroy +records+.
|
||||
def delete(*records)
|
||||
records = flatten_deeper(records)
|
||||
records.each { |record| raise_on_type_mismatch(record) }
|
||||
records.reject! { |record| @target.delete(record) if record.new_record? }
|
||||
return if records.empty?
|
||||
|
||||
@owner.transaction do
|
||||
records.each { |record| callback(:before_remove, record) }
|
||||
delete_records(records)
|
||||
records.each do |record|
|
||||
@target.delete(record)
|
||||
callback(:after_remove, record)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Removes all records from this association. Returns +self+ so method calls may be chained.
|
||||
def clear
|
||||
return self if length.zero? # forces load_target if it hasn't happened already
|
||||
|
||||
if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
|
||||
destroy_all
|
||||
else
|
||||
delete_all
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def destroy_all
|
||||
@owner.transaction do
|
||||
each { |record| record.destroy }
|
||||
end
|
||||
|
||||
reset_target!
|
||||
end
|
||||
|
||||
def create(attrs = {})
|
||||
if attrs.is_a?(Array)
|
||||
attrs.collect { |attr| create(attr) }
|
||||
else
|
||||
create_record(attrs) { |record| record.save }
|
||||
end
|
||||
end
|
||||
|
||||
def create!(attrs = {})
|
||||
create_record(attrs) { |record| record.save! }
|
||||
end
|
||||
|
||||
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
|
||||
# calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
|
||||
# and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
|
||||
def size
|
||||
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
|
||||
@target.size
|
||||
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
|
||||
unsaved_records = Array(@target.detect { |r| r.new_record? })
|
||||
unsaved_records.size + count_records
|
||||
else
|
||||
count_records
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
|
||||
# whether the collection is empty, use collection.length.zero? instead of collection.empty?
|
||||
def length
|
||||
load_target.size
|
||||
end
|
||||
|
||||
def empty?
|
||||
size.zero?
|
||||
end
|
||||
|
||||
def any?(&block)
|
||||
if block_given?
|
||||
method_missing(:any?, &block)
|
||||
else
|
||||
!empty?
|
||||
end
|
||||
end
|
||||
|
||||
def uniq(collection = self)
|
||||
seen = Set.new
|
||||
collection.inject([]) do |kept, record|
|
||||
unless seen.include?(record.id)
|
||||
kept << record
|
||||
seen << record.id
|
||||
end
|
||||
kept
|
||||
end
|
||||
end
|
||||
|
||||
# Replace this collection with +other_array+
|
||||
# This will perform a diff and delete/add only records that have changed.
|
||||
def replace(other_array)
|
||||
other_array.each { |val| raise_on_type_mismatch(val) }
|
||||
|
||||
load_target
|
||||
other = other_array.size < 100 ? other_array : other_array.to_set
|
||||
current = @target.size < 100 ? @target : @target.to_set
|
||||
|
||||
@owner.transaction do
|
||||
delete(@target.select { |v| !other.include?(v) })
|
||||
concat(other_array.select { |v| !current.include?(v) })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
def method_missing(method, *args, &block)
|
||||
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
||||
super
|
||||
else
|
||||
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
|
||||
end
|
||||
end
|
||||
|
||||
# overloaded in derived Association classes to provide useful scoping depending on association type.
|
||||
def construct_scope
|
||||
{}
|
||||
end
|
||||
|
||||
def reset_target!
|
||||
@target = Array.new
|
||||
end
|
||||
|
||||
def find_target
|
||||
records =
|
||||
if @reflection.options[:finder_sql]
|
||||
@reflection.klass.find_by_sql(@finder_sql)
|
||||
else
|
||||
find(:all)
|
||||
end
|
||||
|
||||
@reflection.options[:uniq] ? uniq(records) : records
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_record(attrs, &block)
|
||||
ensure_owner_is_not_new
|
||||
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) }
|
||||
add_record_to_target_with_callbacks(record, &block)
|
||||
end
|
||||
|
||||
def build_record(attrs, &block)
|
||||
record = @reflection.klass.new(attrs)
|
||||
add_record_to_target_with_callbacks(record, &block)
|
||||
end
|
||||
|
||||
def add_record_to_target_with_callbacks(record)
|
||||
callback(:before_add, record)
|
||||
yield(record) if block_given?
|
||||
@target ||= [] unless loaded?
|
||||
@target << record
|
||||
callback(:after_add, record)
|
||||
record
|
||||
end
|
||||
|
||||
def callback(method, record)
|
||||
callbacks_for(method).each do |callback|
|
||||
case callback
|
||||
when Symbol
|
||||
@owner.send(callback, record)
|
||||
when Proc, Method
|
||||
callback.call(@owner, record)
|
||||
else
|
||||
if callback.respond_to?(method)
|
||||
callback.send(method, @owner, record)
|
||||
else
|
||||
raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def callbacks_for(callback_name)
|
||||
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
|
||||
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
|
||||
end
|
||||
|
||||
def ensure_owner_is_not_new
|
||||
if @owner.new_record?
|
||||
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
160
vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb
vendored
Normal file
160
vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class AssociationProxy #:nodoc:
|
||||
attr_reader :reflection
|
||||
alias_method :proxy_respond_to?, :respond_to?
|
||||
alias_method :proxy_extend, :extend
|
||||
delegate :to_param, :to => :proxy_target
|
||||
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
|
||||
|
||||
def initialize(owner, reflection)
|
||||
@owner, @reflection = owner, reflection
|
||||
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
||||
reset
|
||||
end
|
||||
|
||||
def proxy_owner
|
||||
@owner
|
||||
end
|
||||
|
||||
def proxy_reflection
|
||||
@reflection
|
||||
end
|
||||
|
||||
def proxy_target
|
||||
@target
|
||||
end
|
||||
|
||||
def respond_to?(symbol, include_priv = false)
|
||||
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
|
||||
end
|
||||
|
||||
# Explicitly proxy === because the instance method removal above
|
||||
# doesn't catch it.
|
||||
def ===(other)
|
||||
load_target
|
||||
other === @target
|
||||
end
|
||||
|
||||
def aliased_table_name
|
||||
@reflection.klass.table_name
|
||||
end
|
||||
|
||||
def conditions
|
||||
@conditions ||= interpolate_sql(sanitize_sql(@reflection.options[:conditions])) if @reflection.options[:conditions]
|
||||
end
|
||||
alias :sql_conditions :conditions
|
||||
|
||||
def reset
|
||||
@loaded = false
|
||||
@target = nil
|
||||
end
|
||||
|
||||
def reload
|
||||
reset
|
||||
load_target
|
||||
self unless @target.nil?
|
||||
end
|
||||
|
||||
def loaded?
|
||||
@loaded
|
||||
end
|
||||
|
||||
def loaded
|
||||
@loaded = true
|
||||
end
|
||||
|
||||
def target
|
||||
@target
|
||||
end
|
||||
|
||||
def target=(target)
|
||||
@target = target
|
||||
loaded
|
||||
end
|
||||
|
||||
def inspect
|
||||
reload unless loaded?
|
||||
@target.inspect
|
||||
end
|
||||
|
||||
protected
|
||||
def dependent?
|
||||
@reflection.options[:dependent]
|
||||
end
|
||||
|
||||
def quoted_record_ids(records)
|
||||
records.map { |record| record.quoted_id }.join(',')
|
||||
end
|
||||
|
||||
def interpolate_sql_options!(options, *keys)
|
||||
keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
|
||||
end
|
||||
|
||||
def interpolate_sql(sql, record = nil)
|
||||
@owner.send(:interpolate_sql, sql, record)
|
||||
end
|
||||
|
||||
def sanitize_sql(sql)
|
||||
@reflection.klass.send(:sanitize_sql, sql)
|
||||
end
|
||||
|
||||
def set_belongs_to_association_for(record)
|
||||
if @reflection.options[:as]
|
||||
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?
|
||||
end
|
||||
end
|
||||
|
||||
def merge_options_from_reflection!(options)
|
||||
options.reverse_merge!(
|
||||
:group => @reflection.options[:group],
|
||||
:limit => @reflection.options[:limit],
|
||||
:offset => @reflection.options[:offset],
|
||||
:joins => @reflection.options[:joins],
|
||||
:include => @reflection.options[:include],
|
||||
:select => @reflection.options[:select]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method, *args, &block)
|
||||
if load_target
|
||||
@target.send(method, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def load_target
|
||||
return nil unless defined?(@loaded)
|
||||
|
||||
if !loaded? and (!@owner.new_record? || foreign_key_present)
|
||||
@target = find_target
|
||||
end
|
||||
|
||||
@loaded = true
|
||||
@target
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
reset
|
||||
end
|
||||
|
||||
# Can be overwritten by associations that might have the foreign key available for an association without
|
||||
# having the object itself (and still being a new record). Currently, only belongs_to presents this scenario.
|
||||
def foreign_key_present
|
||||
false
|
||||
end
|
||||
|
||||
def raise_on_type_mismatch(record)
|
||||
unless record.is_a?(@reflection.klass)
|
||||
raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.klass} expected, got #{record.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
|
||||
def flatten_deeper(array)
|
||||
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
56
vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb
vendored
Normal file
56
vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class BelongsToAssociation < AssociationProxy #:nodoc:
|
||||
def create(attributes = {})
|
||||
replace(@reflection.klass.create(attributes))
|
||||
end
|
||||
|
||||
def build(attributes = {})
|
||||
replace(@reflection.klass.new(attributes))
|
||||
end
|
||||
|
||||
def replace(record)
|
||||
counter_cache_name = @reflection.counter_cache_column
|
||||
|
||||
if record.nil?
|
||||
if counter_cache_name && @owner[counter_cache_name] && !@owner.new_record?
|
||||
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
|
||||
end
|
||||
|
||||
@target = @owner[@reflection.primary_key_name] = nil
|
||||
else
|
||||
raise_on_type_mismatch(record)
|
||||
|
||||
if counter_cache_name && !@owner.new_record?
|
||||
@reflection.klass.increment_counter(counter_cache_name, record.id)
|
||||
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
|
||||
end
|
||||
|
||||
@target = (AssociationProxy === record ? record.target : record)
|
||||
@owner[@reflection.primary_key_name] = record.id unless record.new_record?
|
||||
@updated = true
|
||||
end
|
||||
|
||||
loaded
|
||||
record
|
||||
end
|
||||
|
||||
def updated?
|
||||
@updated
|
||||
end
|
||||
|
||||
private
|
||||
def find_target
|
||||
@reflection.klass.find(
|
||||
@owner[@reflection.primary_key_name],
|
||||
:conditions => conditions,
|
||||
:include => @reflection.options[:include]
|
||||
)
|
||||
end
|
||||
|
||||
def foreign_key_present
|
||||
!@owner[@reflection.primary_key_name].nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
vendored
Normal file
50
vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
|
||||
def replace(record)
|
||||
if record.nil?
|
||||
@target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
|
||||
else
|
||||
@target = (AssociationProxy === record ? record.target : record)
|
||||
|
||||
unless record.new_record?
|
||||
@owner[@reflection.primary_key_name] = record.id
|
||||
@owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
|
||||
end
|
||||
|
||||
@updated = true
|
||||
end
|
||||
|
||||
loaded
|
||||
record
|
||||
end
|
||||
|
||||
def updated?
|
||||
@updated
|
||||
end
|
||||
|
||||
private
|
||||
def find_target
|
||||
return nil if association_class.nil?
|
||||
|
||||
if @reflection.options[:conditions]
|
||||
association_class.find(
|
||||
@owner[@reflection.primary_key_name],
|
||||
:conditions => conditions,
|
||||
:include => @reflection.options[:include]
|
||||
)
|
||||
else
|
||||
association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
|
||||
end
|
||||
end
|
||||
|
||||
def foreign_key_present
|
||||
!@owner[@reflection.primary_key_name].nil?
|
||||
end
|
||||
|
||||
def association_class
|
||||
@owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
164
vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
vendored
Normal file
164
vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
vendored
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
construct_sql
|
||||
end
|
||||
|
||||
def build(attributes = {})
|
||||
load_target
|
||||
build_record(attributes)
|
||||
end
|
||||
|
||||
def create(attributes = {})
|
||||
create_record(attributes) { |record| insert_record(record) }
|
||||
end
|
||||
|
||||
def create!(attributes = {})
|
||||
create_record(attributes) { |record| insert_record(record, true) }
|
||||
end
|
||||
|
||||
def find_first
|
||||
load_target.first
|
||||
end
|
||||
|
||||
def find(*args)
|
||||
options = args.extract_options!
|
||||
|
||||
# If using a custom finder_sql, scan the entire collection.
|
||||
if @reflection.options[:finder_sql]
|
||||
expects_array = args.first.kind_of?(Array)
|
||||
ids = args.flatten.compact.uniq
|
||||
|
||||
if ids.size == 1
|
||||
id = ids.first.to_i
|
||||
record = load_target.detect { |record| id == record.id }
|
||||
expects_array ? [record] : record
|
||||
else
|
||||
load_target.select { |record| ids.include?(record.id) }
|
||||
end
|
||||
else
|
||||
conditions = "#{@finder_sql}"
|
||||
|
||||
if sanitized_conditions = sanitize_sql(options[:conditions])
|
||||
conditions << " AND (#{sanitized_conditions})"
|
||||
end
|
||||
|
||||
options[:conditions] = conditions
|
||||
options[:joins] = @join_sql
|
||||
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
|
||||
|
||||
if options[:order] && @reflection.options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
||||
elsif @reflection.options[:order]
|
||||
options[:order] = @reflection.options[:order]
|
||||
end
|
||||
|
||||
merge_options_from_reflection!(options)
|
||||
|
||||
options[:select] ||= (@reflection.options[:select] || '*')
|
||||
|
||||
# Pass through args exactly as we received them.
|
||||
args << options
|
||||
@reflection.klass.find(*args)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def count_records
|
||||
load_target.size
|
||||
end
|
||||
|
||||
def insert_record(record, force=true)
|
||||
if record.new_record?
|
||||
if force
|
||||
record.save!
|
||||
else
|
||||
return false unless record.save
|
||||
end
|
||||
end
|
||||
|
||||
if @reflection.options[:insert_sql]
|
||||
@owner.connection.execute(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 |attributes, column|
|
||||
case column.name
|
||||
when @reflection.primary_key_name
|
||||
attributes[column.name] = @owner.quoted_id
|
||||
when @reflection.association_foreign_key
|
||||
attributes[column.name] = record.quoted_id
|
||||
else
|
||||
if record.attributes.has_key?(column.name)
|
||||
value = @owner.send(:quote_value, record[column.name], column)
|
||||
attributes[column.name] = value unless value.nil?
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
sql =
|
||||
"INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
||||
"VALUES (#{attributes.values.join(', ')})"
|
||||
|
||||
@owner.connection.execute(sql)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def delete_records(records)
|
||||
if sql = @reflection.options[:delete_sql]
|
||||
records.each { |record| @owner.connection.execute(interpolate_sql(sql, record)) }
|
||||
else
|
||||
ids = quoted_record_ids(records)
|
||||
sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
|
||||
@owner.connection.execute(sql)
|
||||
end
|
||||
end
|
||||
|
||||
def construct_sql
|
||||
interpolate_sql_options!(@reflection.options, :finder_sql)
|
||||
|
||||
if @reflection.options[:finder_sql]
|
||||
@finder_sql = @reflection.options[:finder_sql]
|
||||
else
|
||||
@finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
end
|
||||
|
||||
@join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
||||
end
|
||||
|
||||
def construct_scope
|
||||
{ :find => { :conditions => @finder_sql,
|
||||
:joins => @join_sql,
|
||||
:readonly => false,
|
||||
:order => @reflection.options[:order],
|
||||
:limit => @reflection.options[:limit] } }
|
||||
end
|
||||
|
||||
# Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
|
||||
# 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
|
||||
end
|
||||
|
||||
private
|
||||
def create_record(attributes)
|
||||
# Can't use Base.create because the foreign key may be a protected attribute.
|
||||
ensure_owner_is_not_new
|
||||
if attributes.is_a?(Array)
|
||||
attributes.collect { |attr| create(attr) }
|
||||
else
|
||||
record = build(attributes)
|
||||
yield(record)
|
||||
record
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
174
vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb
vendored
Normal file
174
vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasManyAssociation < AssociationCollection #:nodoc:
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
construct_sql
|
||||
end
|
||||
|
||||
def build(attributes = {})
|
||||
if attributes.is_a?(Array)
|
||||
attributes.collect { |attr| build(attr) }
|
||||
else
|
||||
build_record(attributes) { |record| set_belongs_to_association_for(record) }
|
||||
end
|
||||
end
|
||||
|
||||
# Count the number of associated records. All arguments are optional.
|
||||
def count(*args)
|
||||
if @reflection.options[:counter_sql]
|
||||
@reflection.klass.count_by_sql(@counter_sql)
|
||||
elsif @reflection.options[:finder_sql]
|
||||
@reflection.klass.count_by_sql(@finder_sql)
|
||||
else
|
||||
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
||||
options[:conditions] = options[:conditions].nil? ?
|
||||
@finder_sql :
|
||||
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
|
||||
options[:include] ||= @reflection.options[:include]
|
||||
|
||||
@reflection.klass.count(column_name, options)
|
||||
end
|
||||
end
|
||||
|
||||
def find(*args)
|
||||
options = args.extract_options!
|
||||
|
||||
# If using a custom finder_sql, scan the entire collection.
|
||||
if @reflection.options[:finder_sql]
|
||||
expects_array = args.first.kind_of?(Array)
|
||||
ids = args.flatten.compact.uniq.map(&:to_i)
|
||||
|
||||
if ids.size == 1
|
||||
id = ids.first
|
||||
record = load_target.detect { |record| id == record.id }
|
||||
expects_array ? [ record ] : record
|
||||
else
|
||||
load_target.select { |record| ids.include?(record.id) }
|
||||
end
|
||||
else
|
||||
conditions = "#{@finder_sql}"
|
||||
if sanitized_conditions = sanitize_sql(options[:conditions])
|
||||
conditions << " AND (#{sanitized_conditions})"
|
||||
end
|
||||
options[:conditions] = conditions
|
||||
|
||||
if options[:order] && @reflection.options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
||||
elsif @reflection.options[:order]
|
||||
options[:order] = @reflection.options[:order]
|
||||
end
|
||||
|
||||
merge_options_from_reflection!(options)
|
||||
|
||||
# Pass through args exactly as we received them.
|
||||
args << options
|
||||
@reflection.klass.find(*args)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def load_target
|
||||
if !@owner.new_record? || foreign_key_present
|
||||
begin
|
||||
if !loaded?
|
||||
if @target.is_a?(Array) && @target.any?
|
||||
@target = (find_target + @target).uniq
|
||||
else
|
||||
@target = find_target
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
reset
|
||||
end
|
||||
end
|
||||
|
||||
loaded if target
|
||||
target
|
||||
end
|
||||
|
||||
def count_records
|
||||
count = if has_cached_counter?
|
||||
@owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
elsif @reflection.options[:counter_sql]
|
||||
@reflection.klass.count_by_sql(@counter_sql)
|
||||
else
|
||||
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
|
||||
end
|
||||
|
||||
@target = [] and loaded if count == 0
|
||||
|
||||
if @reflection.options[:limit]
|
||||
count = [ @reflection.options[:limit], count ].min
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
def has_cached_counter?
|
||||
@owner.attribute_present?(cached_counter_attribute_name)
|
||||
end
|
||||
|
||||
def cached_counter_attribute_name
|
||||
"#{@reflection.name}_count"
|
||||
end
|
||||
|
||||
def insert_record(record)
|
||||
set_belongs_to_association_for(record)
|
||||
record.save
|
||||
end
|
||||
|
||||
def delete_records(records)
|
||||
case @reflection.options[:dependent]
|
||||
when :destroy
|
||||
records.each(&:destroy)
|
||||
when :delete_all
|
||||
@reflection.klass.delete(records.map(&:id))
|
||||
else
|
||||
ids = quoted_record_ids(records)
|
||||
@reflection.klass.update_all(
|
||||
"#{@reflection.primary_key_name} = NULL",
|
||||
"#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def target_obsolete?
|
||||
false
|
||||
end
|
||||
|
||||
def construct_sql
|
||||
case
|
||||
when @reflection.options[:finder_sql]
|
||||
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
||||
|
||||
when @reflection.options[:as]
|
||||
@finder_sql =
|
||||
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
||||
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
|
||||
else
|
||||
@finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
end
|
||||
|
||||
if @reflection.options[:counter_sql]
|
||||
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
||||
elsif @reflection.options[:finder_sql]
|
||||
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
|
||||
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
||||
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
||||
else
|
||||
@counter_sql = @finder_sql
|
||||
end
|
||||
end
|
||||
|
||||
def construct_scope
|
||||
create_scoping = {}
|
||||
set_belongs_to_association_for(create_scoping)
|
||||
{ :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] }, :create => create_scoping }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
284
vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb
vendored
Normal file
284
vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb
vendored
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasManyThroughAssociation < AssociationProxy #:nodoc:
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
reflection.check_validity!
|
||||
@finder_sql = construct_conditions
|
||||
construct_sql
|
||||
end
|
||||
|
||||
def find(*args)
|
||||
options = args.extract_options!
|
||||
|
||||
conditions = "#{@finder_sql}"
|
||||
if sanitized_conditions = sanitize_sql(options[:conditions])
|
||||
conditions << " AND (#{sanitized_conditions})"
|
||||
end
|
||||
options[:conditions] = conditions
|
||||
|
||||
if options[:order] && @reflection.options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
||||
elsif @reflection.options[:order]
|
||||
options[:order] = @reflection.options[:order]
|
||||
end
|
||||
|
||||
options[:select] = construct_select(options[:select])
|
||||
options[:from] ||= construct_from
|
||||
options[:joins] = construct_joins(options[:joins])
|
||||
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
|
||||
|
||||
merge_options_from_reflection!(options)
|
||||
|
||||
# Pass through args exactly as we received them.
|
||||
args << options
|
||||
@reflection.klass.find(*args)
|
||||
end
|
||||
|
||||
def reset
|
||||
@target = []
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
# Adds records to the association. The source record and its associates
|
||||
# must have ids in order to create records associating them, so this
|
||||
# will raise ActiveRecord::HasManyThroughCantAssociateNewRecords if
|
||||
# either is a new record. Calls create! so you can rescue errors.
|
||||
#
|
||||
# The :before_add and :after_add callbacks are not yet supported.
|
||||
def <<(*records)
|
||||
return if records.empty?
|
||||
through = @reflection.through_reflection
|
||||
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) if @owner.new_record?
|
||||
|
||||
klass = through.klass
|
||||
klass.transaction do
|
||||
flatten_deeper(records).each do |associate|
|
||||
raise_on_type_mismatch(associate)
|
||||
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
|
||||
|
||||
@owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(associate)) { klass.create! }
|
||||
@target << associate if loaded?
|
||||
end
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
[:push, :concat].each { |method| alias_method method, :<< }
|
||||
|
||||
# Removes +records+ from this association. Does not destroy +records+.
|
||||
def delete(*records)
|
||||
records = flatten_deeper(records)
|
||||
records.each { |associate| raise_on_type_mismatch(associate) }
|
||||
|
||||
through = @reflection.through_reflection
|
||||
raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) if @owner.new_record?
|
||||
|
||||
load_target
|
||||
|
||||
klass = through.klass
|
||||
klass.transaction do
|
||||
flatten_deeper(records).each do |associate|
|
||||
raise_on_type_mismatch(associate)
|
||||
raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
|
||||
|
||||
@owner.send(through.name).proxy_target.delete(klass.delete_all(construct_join_attributes(associate)))
|
||||
@target.delete(associate)
|
||||
end
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def build(attrs = nil)
|
||||
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, @reflection.through_reflection)
|
||||
end
|
||||
alias_method :new, :build
|
||||
|
||||
def create!(attrs = nil)
|
||||
@reflection.klass.transaction do
|
||||
self << (object = @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! })
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
|
||||
# calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
|
||||
# and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
|
||||
def size
|
||||
return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
|
||||
return @target.size if loaded?
|
||||
return count
|
||||
end
|
||||
|
||||
# Calculate sum using SQL, not Enumerable
|
||||
def sum(*args, &block)
|
||||
calculate(:sum, *args, &block)
|
||||
end
|
||||
|
||||
def count(*args)
|
||||
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
||||
if @reflection.options[:uniq]
|
||||
# This is needed because 'SELECT count(DISTINCT *)..' is not valid sql statement.
|
||||
column_name = "#{@reflection.klass.table_name}.#{@reflection.klass.primary_key}" if column_name == :all
|
||||
options.merge!(:distinct => true)
|
||||
end
|
||||
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
|
||||
end
|
||||
|
||||
protected
|
||||
def method_missing(method, *args, &block)
|
||||
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
||||
super
|
||||
else
|
||||
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
|
||||
end
|
||||
end
|
||||
|
||||
def find_target
|
||||
records = @reflection.klass.find(:all,
|
||||
:select => construct_select,
|
||||
:conditions => construct_conditions,
|
||||
:from => construct_from,
|
||||
:joins => construct_joins,
|
||||
:order => @reflection.options[:order],
|
||||
:limit => @reflection.options[:limit],
|
||||
:group => @reflection.options[:group],
|
||||
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
|
||||
)
|
||||
|
||||
records.uniq! if @reflection.options[:uniq]
|
||||
records
|
||||
end
|
||||
|
||||
# Construct attributes for associate pointing to owner.
|
||||
def construct_owner_attributes(reflection)
|
||||
if as = reflection.options[:as]
|
||||
{ "#{as}_id" => @owner.id,
|
||||
"#{as}_type" => @owner.class.base_class.name.to_s }
|
||||
else
|
||||
{ reflection.primary_key_name => @owner.id }
|
||||
end
|
||||
end
|
||||
|
||||
# Construct attributes for :through pointing to owner and associate.
|
||||
def construct_join_attributes(associate)
|
||||
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
||||
if @reflection.options[:source_type]
|
||||
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
|
||||
end
|
||||
join_attributes
|
||||
end
|
||||
|
||||
# Associate attributes pointing to owner, quoted.
|
||||
def construct_quoted_owner_attributes(reflection)
|
||||
if as = reflection.options[:as]
|
||||
{ "#{as}_id" => @owner.quoted_id,
|
||||
"#{as}_type" => reflection.klass.quote_value(
|
||||
@owner.class.base_class.name.to_s,
|
||||
reflection.klass.columns_hash["#{as}_type"]) }
|
||||
else
|
||||
{ reflection.primary_key_name => @owner.quoted_id }
|
||||
end
|
||||
end
|
||||
|
||||
# Build SQL conditions from attributes, qualified by table name.
|
||||
def construct_conditions
|
||||
table_name = @reflection.through_reflection.table_name
|
||||
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
|
||||
"#{table_name}.#{attr} = #{value}"
|
||||
end
|
||||
conditions << sql_conditions if sql_conditions
|
||||
"(" + conditions.join(') AND (') + ")"
|
||||
end
|
||||
|
||||
def construct_from
|
||||
@reflection.table_name
|
||||
end
|
||||
|
||||
def construct_select(custom_select = nil)
|
||||
selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
|
||||
end
|
||||
|
||||
def construct_joins(custom_joins = nil)
|
||||
polymorphic_join = nil
|
||||
if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
|
||||
reflection_primary_key = @reflection.klass.primary_key
|
||||
source_primary_key = @reflection.source_reflection.primary_key_name
|
||||
if @reflection.options[:source_type]
|
||||
polymorphic_join = "AND %s.%s = %s" % [
|
||||
@reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
|
||||
@owner.class.quote_value(@reflection.options[:source_type])
|
||||
]
|
||||
end
|
||||
else
|
||||
reflection_primary_key = @reflection.source_reflection.primary_key_name
|
||||
source_primary_key = @reflection.klass.primary_key
|
||||
if @reflection.source_reflection.options[:as]
|
||||
polymorphic_join = "AND %s.%s = %s" % [
|
||||
@reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type",
|
||||
@owner.class.quote_value(@reflection.through_reflection.klass.name)
|
||||
]
|
||||
end
|
||||
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,
|
||||
polymorphic_join
|
||||
]
|
||||
end
|
||||
|
||||
def construct_scope
|
||||
{ :create => construct_owner_attributes(@reflection),
|
||||
:find => { :from => construct_from,
|
||||
:conditions => construct_conditions,
|
||||
:joins => construct_joins,
|
||||
:select => construct_select,
|
||||
:order => @reflection.options[:order],
|
||||
:limit => @reflection.options[:limit] } }
|
||||
end
|
||||
|
||||
def construct_sql
|
||||
case
|
||||
when @reflection.options[:finder_sql]
|
||||
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
||||
|
||||
@finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
end
|
||||
|
||||
if @reflection.options[:counter_sql]
|
||||
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
||||
elsif @reflection.options[:finder_sql]
|
||||
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
|
||||
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
||||
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
||||
else
|
||||
@counter_sql = @finder_sql
|
||||
end
|
||||
end
|
||||
|
||||
def conditions
|
||||
@conditions ||= [
|
||||
(interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
|
||||
(interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]),
|
||||
(interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.source_reflection.options[:conditions])) if @reflection.source_reflection.options[:conditions]),
|
||||
("#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}" unless @reflection.through_reflection.klass.descends_from_active_record?)
|
||||
].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && !@reflection.source_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?)
|
||||
end
|
||||
|
||||
alias_method :sql_conditions, :conditions
|
||||
|
||||
def has_cached_counter?
|
||||
@owner.attribute_present?(cached_counter_attribute_name)
|
||||
end
|
||||
|
||||
def cached_counter_attribute_name
|
||||
"#{@reflection.name}_count"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
96
vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb
vendored
Normal file
96
vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasOneAssociation < BelongsToAssociation #:nodoc:
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
construct_sql
|
||||
end
|
||||
|
||||
def create(attrs = {}, replace_existing = true)
|
||||
new_record(replace_existing) { |klass| klass.create(attrs) }
|
||||
end
|
||||
|
||||
def create!(attrs = {}, replace_existing = true)
|
||||
new_record(replace_existing) { |klass| klass.create!(attrs) }
|
||||
end
|
||||
|
||||
def build(attrs = {}, replace_existing = true)
|
||||
new_record(replace_existing) { |klass| klass.new(attrs) }
|
||||
end
|
||||
|
||||
def replace(obj, dont_save = false)
|
||||
load_target
|
||||
|
||||
unless @target.nil?
|
||||
if dependent? && !dont_save && @target != obj
|
||||
@target.destroy unless @target.new_record?
|
||||
@owner.clear_association_cache
|
||||
else
|
||||
@target[@reflection.primary_key_name] = nil
|
||||
@target.save unless @owner.new_record? || @target.new_record?
|
||||
end
|
||||
end
|
||||
|
||||
if obj.nil?
|
||||
@target = nil
|
||||
else
|
||||
raise_on_type_mismatch(obj)
|
||||
set_belongs_to_association_for(obj)
|
||||
@target = (AssociationProxy === obj ? obj.target : obj)
|
||||
end
|
||||
|
||||
@loaded = true
|
||||
|
||||
unless @owner.new_record? or obj.nil? or dont_save
|
||||
return (obj.save ? self : false)
|
||||
else
|
||||
return (obj.nil? ? nil : self)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def find_target
|
||||
@reflection.klass.find(:first,
|
||||
:conditions => @finder_sql,
|
||||
:order => @reflection.options[:order],
|
||||
:include => @reflection.options[:include]
|
||||
)
|
||||
end
|
||||
|
||||
def construct_sql
|
||||
case
|
||||
when @reflection.options[:as]
|
||||
@finder_sql =
|
||||
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
||||
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
||||
else
|
||||
@finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
end
|
||||
@finder_sql << " AND (#{conditions})" if conditions
|
||||
end
|
||||
|
||||
def construct_scope
|
||||
create_scoping = {}
|
||||
set_belongs_to_association_for(create_scoping)
|
||||
{ :create => create_scoping }
|
||||
end
|
||||
|
||||
def new_record(replace_existing)
|
||||
# Make sure we load the target first, if we plan on replacing the existing
|
||||
# instance. Otherwise, if the target has not previously been loaded
|
||||
# elsewhere, the instance we create will get orphaned.
|
||||
load_target if replace_existing
|
||||
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
|
||||
|
||||
if replace_existing
|
||||
replace(record, true)
|
||||
else
|
||||
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
||||
self.target = record
|
||||
end
|
||||
|
||||
record
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
328
vendor/rails/activerecord/lib/active_record/attribute_methods.rb
vendored
Normal file
328
vendor/rails/activerecord/lib/active_record/attribute_methods.rb
vendored
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
module ActiveRecord
|
||||
module AttributeMethods #:nodoc:
|
||||
DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
|
||||
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
base.attribute_method_suffix *DEFAULT_SUFFIXES
|
||||
base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
|
||||
base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
||||
end
|
||||
|
||||
# Declare and check for suffixed attribute methods.
|
||||
module ClassMethods
|
||||
# Declare a method available for all attributes with the given suffix.
|
||||
# Uses method_missing and respond_to? to rewrite the method
|
||||
# #{attr}#{suffix}(*args, &block)
|
||||
# to
|
||||
# attribute#{suffix}(#{attr}, *args, &block)
|
||||
#
|
||||
# An attribute#{suffix} instance method must exist and accept at least
|
||||
# the attr argument.
|
||||
#
|
||||
# For example:
|
||||
# class Person < ActiveRecord::Base
|
||||
# attribute_method_suffix '_changed?'
|
||||
#
|
||||
# private
|
||||
# def attribute_changed?(attr)
|
||||
# ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# person = Person.find(1)
|
||||
# person.name_changed? # => false
|
||||
# person.name = 'Hubert'
|
||||
# person.name_changed? # => true
|
||||
def attribute_method_suffix(*suffixes)
|
||||
attribute_method_suffixes.concat suffixes
|
||||
rebuild_attribute_method_regexp
|
||||
end
|
||||
|
||||
# Returns MatchData if method_name is an attribute method.
|
||||
def match_attribute_method?(method_name)
|
||||
rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
|
||||
@@attribute_method_regexp.match(method_name)
|
||||
end
|
||||
|
||||
|
||||
# Contains the names of the generated attribute methods.
|
||||
def generated_methods #:nodoc:
|
||||
@generated_methods ||= Set.new
|
||||
end
|
||||
|
||||
def generated_methods?
|
||||
!generated_methods.empty?
|
||||
end
|
||||
|
||||
# generates all the attribute related methods for columns in the database
|
||||
# accessors, mutators and query methods
|
||||
def define_attribute_methods
|
||||
return if generated_methods?
|
||||
columns_hash.each do |name, column|
|
||||
unless instance_method_already_implemented?(name)
|
||||
if self.serialized_attributes[name]
|
||||
define_read_method_for_serialized_attribute(name)
|
||||
else
|
||||
define_read_method(name.to_sym, name, column)
|
||||
end
|
||||
end
|
||||
|
||||
unless instance_method_already_implemented?("#{name}=")
|
||||
define_write_method(name.to_sym)
|
||||
end
|
||||
|
||||
unless instance_method_already_implemented?("#{name}?")
|
||||
define_question_method(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Check to see if the method is defined in the model or any of its subclasses that also derive from ActiveRecord.
|
||||
# Raise DangerousAttributeError if the method is defined by ActiveRecord though.
|
||||
def instance_method_already_implemented?(method_name)
|
||||
return true if method_name =~ /^id(=$|\?$|$)/
|
||||
@_defined_class_methods ||= Set.new(ancestors.first(ancestors.index(ActiveRecord::Base)).collect! { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.flatten)
|
||||
@@_defined_activerecord_methods ||= Set.new(ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false))
|
||||
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
|
||||
@_defined_class_methods.include?(method_name)
|
||||
end
|
||||
|
||||
alias :define_read_methods :define_attribute_methods
|
||||
|
||||
# +cache_attributes+ allows you to declare which converted attribute values should
|
||||
# be cached. Usually caching only pays off for attributes with expensive conversion
|
||||
# methods, like date columns (e.g. created_at, updated_at).
|
||||
def cache_attributes(*attribute_names)
|
||||
attribute_names.each {|attr| cached_attributes << attr.to_s}
|
||||
end
|
||||
|
||||
# returns the attributes where
|
||||
def cached_attributes
|
||||
@cached_attributes ||=
|
||||
columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
|
||||
end
|
||||
|
||||
def cache_attribute?(attr_name)
|
||||
cached_attributes.include?(attr_name)
|
||||
end
|
||||
|
||||
private
|
||||
# Suffixes a, ?, c become regexp /(a|\?|c)$/
|
||||
def rebuild_attribute_method_regexp
|
||||
suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
|
||||
@@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
|
||||
end
|
||||
|
||||
# Default to =, ?, _before_type_cast
|
||||
def attribute_method_suffixes
|
||||
@@attribute_method_suffixes ||= []
|
||||
end
|
||||
|
||||
# Define an attribute reader method. Cope with nil column.
|
||||
def define_read_method(symbol, attr_name, column)
|
||||
cast_code = column.type_cast_code('v') if column
|
||||
access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
|
||||
|
||||
unless attr_name.to_s == self.primary_key.to_s
|
||||
access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
|
||||
end
|
||||
|
||||
if cache_attribute?(attr_name)
|
||||
access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
|
||||
end
|
||||
evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end"
|
||||
end
|
||||
|
||||
# Define read method for serialized attribute.
|
||||
def define_read_method_for_serialized_attribute(attr_name)
|
||||
evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
|
||||
end
|
||||
|
||||
# Define an attribute ? method.
|
||||
def define_question_method(attr_name)
|
||||
evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
|
||||
end
|
||||
|
||||
def define_write_method(attr_name)
|
||||
evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
|
||||
end
|
||||
|
||||
# Evaluate the definition for an attribute related method
|
||||
def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
|
||||
|
||||
unless method_name.to_s == primary_key.to_s
|
||||
generated_methods << method_name
|
||||
end
|
||||
|
||||
begin
|
||||
class_eval(method_definition, __FILE__, __LINE__)
|
||||
rescue SyntaxError => err
|
||||
generated_methods.delete(attr_name)
|
||||
if logger
|
||||
logger.warn "Exception occurred during reader method compilation."
|
||||
logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
|
||||
logger.warn "#{err.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end # ClassMethods
|
||||
|
||||
|
||||
# Allows access to the object attributes, which are held in the @attributes hash, as though they
|
||||
# were first-class methods. So a Person class with a name attribute can use Person#name and
|
||||
# Person#name= and never directly use the attributes hash -- except for multiple assigns with
|
||||
# ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
|
||||
# the completed attribute is not nil or 0.
|
||||
#
|
||||
# It's also possible to instantiate related objects, so a Client class belonging to the clients
|
||||
# table with a master_id foreign key can instantiate master through Client#master.
|
||||
def method_missing(method_id, *args, &block)
|
||||
method_name = method_id.to_s
|
||||
|
||||
# If we haven't generated any methods yet, generate them, then
|
||||
# see if we've created the method we're looking for.
|
||||
if !self.class.generated_methods?
|
||||
self.class.define_attribute_methods
|
||||
if self.class.generated_methods.include?(method_name)
|
||||
return self.send(method_id, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
if self.class.primary_key.to_s == method_name
|
||||
id
|
||||
elsif md = self.class.match_attribute_method?(method_name)
|
||||
attribute_name, method_type = md.pre_match, md.to_s
|
||||
if @attributes.include?(attribute_name)
|
||||
__send__("attribute#{method_type}", attribute_name, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
elsif @attributes.include?(method_name)
|
||||
read_attribute(method_name)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
||||
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
||||
def read_attribute(attr_name)
|
||||
attr_name = attr_name.to_s
|
||||
if !(value = @attributes[attr_name]).nil?
|
||||
if column = column_for_attribute(attr_name)
|
||||
if unserializable_attribute?(attr_name, column)
|
||||
unserialize_attribute(attr_name)
|
||||
else
|
||||
column.type_cast(value)
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def read_attribute_before_type_cast(attr_name)
|
||||
@attributes[attr_name]
|
||||
end
|
||||
|
||||
# Returns true if the attribute is of a text column and marked for serialization.
|
||||
def unserializable_attribute?(attr_name, column)
|
||||
column.text? && self.class.serialized_attributes[attr_name]
|
||||
end
|
||||
|
||||
# Returns the unserialized object of the attribute.
|
||||
def unserialize_attribute(attr_name)
|
||||
unserialized_object = object_from_yaml(@attributes[attr_name])
|
||||
|
||||
if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
|
||||
@attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
|
||||
else
|
||||
raise SerializationTypeMismatch,
|
||||
"#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
|
||||
# columns are turned into nil.
|
||||
def write_attribute(attr_name, value)
|
||||
attr_name = attr_name.to_s
|
||||
@attributes_cache.delete(attr_name)
|
||||
if (column = column_for_attribute(attr_name)) && column.number?
|
||||
@attributes[attr_name] = convert_number_column_value(value)
|
||||
else
|
||||
@attributes[attr_name] = value
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def query_attribute(attr_name)
|
||||
unless value = read_attribute(attr_name)
|
||||
false
|
||||
else
|
||||
column = self.class.columns_hash[attr_name]
|
||||
if column.nil?
|
||||
if Numeric === value || value !~ /[^0-9]/
|
||||
!value.to_i.zero?
|
||||
else
|
||||
!value.blank?
|
||||
end
|
||||
elsif column.number?
|
||||
!value.zero?
|
||||
else
|
||||
!value.blank?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
|
||||
# person.respond_to?("name?") which will all return true.
|
||||
alias :respond_to_without_attributes? :respond_to?
|
||||
def respond_to?(method, include_priv = false)
|
||||
method_name = method.to_s
|
||||
if super
|
||||
return true
|
||||
elsif !self.class.generated_methods?
|
||||
self.class.define_attribute_methods
|
||||
if self.class.generated_methods.include?(method_name)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if @attributes.nil?
|
||||
return super
|
||||
elsif @attributes.include?(method_name)
|
||||
return true
|
||||
elsif md = self.class.match_attribute_method?(method_name)
|
||||
return true if @attributes.include?(md.pre_match)
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def missing_attribute(attr_name, stack)
|
||||
raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
|
||||
end
|
||||
|
||||
# Handle *? for method_missing.
|
||||
def attribute?(attribute_name)
|
||||
query_attribute(attribute_name)
|
||||
end
|
||||
|
||||
# Handle *= for method_missing.
|
||||
def attribute=(attribute_name, value)
|
||||
write_attribute(attribute_name, value)
|
||||
end
|
||||
|
||||
# Handle *_before_type_cast for method_missing.
|
||||
def attribute_before_type_cast(attribute_name)
|
||||
read_attribute_before_type_cast(attribute_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
2471
vendor/rails/activerecord/lib/active_record/base.rb
vendored
Executable file
2471
vendor/rails/activerecord/lib/active_record/base.rb
vendored
Executable file
File diff suppressed because it is too large
Load diff
267
vendor/rails/activerecord/lib/active_record/calculations.rb
vendored
Normal file
267
vendor/rails/activerecord/lib/active_record/calculations.rb
vendored
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
module ActiveRecord
|
||||
module Calculations #:nodoc:
|
||||
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include]
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Count operates using three different approaches.
|
||||
#
|
||||
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
|
||||
# * Count using column : By passing a column name to count, it will return a count of all the rows for the model with supplied column present
|
||||
# * Count using options will find the row count matched by the options used.
|
||||
#
|
||||
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
|
||||
#
|
||||
# * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
|
||||
# * <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 :include option, which will perform an INNER JOIN on the associated table(s).
|
||||
# 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 :readonly => false to override.
|
||||
# * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
|
||||
# to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting.
|
||||
# See eager loading under Associations.
|
||||
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
||||
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
||||
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
|
||||
# include the joined columns.
|
||||
# * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
|
||||
#
|
||||
# Examples for counting all:
|
||||
# Person.count # returns the total count of all people
|
||||
#
|
||||
# Examples for counting by column:
|
||||
# Person.count(:age) # returns the total count of all people whose age is present in database
|
||||
#
|
||||
# Examples for count with options:
|
||||
# Person.count(:conditions => "age > 26")
|
||||
# Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
|
||||
# Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
|
||||
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
|
||||
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
|
||||
#
|
||||
# Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead.
|
||||
def count(*args)
|
||||
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.
|
||||
#
|
||||
# Person.average('age')
|
||||
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.
|
||||
#
|
||||
# Person.minimum('age')
|
||||
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.
|
||||
#
|
||||
# Person.maximum('age')
|
||||
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.
|
||||
#
|
||||
# Person.sum('age')
|
||||
def sum(column_name, options = {})
|
||||
calculate(:sum, column_name, options)
|
||||
end
|
||||
|
||||
# This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
|
||||
# Options such as :conditions, :order, :group, :having, and :joins can be passed to customize the query.
|
||||
#
|
||||
# There are two basic forms of output:
|
||||
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
|
||||
# * Grouped values: This returns an ordered hash of the values and groups them by the :group option. It takes either a column name, or the name
|
||||
# of a belongs_to association.
|
||||
#
|
||||
# values = Person.maximum(:age, :group => 'last_name')
|
||||
# puts values["Drake"]
|
||||
# => 43
|
||||
#
|
||||
# drake = Family.find_by_last_name('Drake')
|
||||
# values = Person.maximum(:age, :group => :family) # Person belongs_to :family
|
||||
# puts values[drake]
|
||||
# => 43
|
||||
#
|
||||
# values.each do |family, max_age|
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
|
||||
# * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
|
||||
# * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
|
||||
# The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
||||
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
||||
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
||||
# * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
|
||||
# include the joined columns.
|
||||
# * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
|
||||
#
|
||||
# Examples:
|
||||
# Person.calculate(:count, :all) # The same as Person.count
|
||||
# Person.average(:age) # SELECT AVG(age) FROM people...
|
||||
# Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
|
||||
# Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
|
||||
def calculate(operation, column_name, options = {})
|
||||
validate_calculation_options(operation, options)
|
||||
column_name = options[:select] if options[:select]
|
||||
column_name = '*' if column_name == :all
|
||||
column = column_for column_name
|
||||
catch :invalid_query do
|
||||
if options[:group]
|
||||
return execute_grouped_calculation(operation, column_name, column, options)
|
||||
else
|
||||
return execute_simple_calculation(operation, column_name, column, options)
|
||||
end
|
||||
end
|
||||
0
|
||||
end
|
||||
|
||||
protected
|
||||
def construct_count_options_from_args(*args)
|
||||
options = {}
|
||||
column_name = :all
|
||||
|
||||
# We need to handle
|
||||
# count()
|
||||
# count(:column_name=:all)
|
||||
# count(options={})
|
||||
# count(column_name=:all, options={})
|
||||
case args.size
|
||||
when 1
|
||||
args[0].is_a?(Hash) ? options = args[0] : column_name = args[0]
|
||||
when 2
|
||||
column_name, options = args
|
||||
else
|
||||
raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
|
||||
end if args.size > 0
|
||||
|
||||
[column_name, options]
|
||||
end
|
||||
|
||||
def construct_calculation_sql(operation, column_name, options) #:nodoc:
|
||||
operation = operation.to_s.downcase
|
||||
options = options.symbolize_keys
|
||||
|
||||
scope = scope(:find)
|
||||
merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
|
||||
aggregate_alias = column_alias_for(operation, column_name)
|
||||
|
||||
if operation == 'count'
|
||||
if merged_includes.any?
|
||||
options[:distinct] = true
|
||||
column_name = options[:select] || [connection.quote_table_name(table_name), primary_key] * '.'
|
||||
end
|
||||
|
||||
if options[:distinct]
|
||||
use_workaround = !connection.supports_count_distinct?
|
||||
end
|
||||
end
|
||||
|
||||
sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}"
|
||||
|
||||
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
||||
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
|
||||
|
||||
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
|
||||
sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround
|
||||
sql << " FROM #{connection.quote_table_name(table_name)} "
|
||||
if merged_includes.any?
|
||||
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
|
||||
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
||||
end
|
||||
add_joins!(sql, options, scope)
|
||||
add_conditions!(sql, options[:conditions], scope)
|
||||
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
||||
|
||||
if options[:group]
|
||||
group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
|
||||
sql << " GROUP BY #{options[group_key]} "
|
||||
end
|
||||
|
||||
if options[:group] && options[:having]
|
||||
# FrontBase requires identifiers in the HAVING clause and chokes on function calls
|
||||
if connection.adapter_name == 'FrontBase'
|
||||
options[:having].downcase!
|
||||
options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
|
||||
end
|
||||
|
||||
sql << " HAVING #{options[:having]} "
|
||||
end
|
||||
|
||||
sql << " ORDER BY #{options[:order]} " if options[:order]
|
||||
add_limit!(sql, options, scope)
|
||||
sql << ')' if use_workaround
|
||||
sql
|
||||
end
|
||||
|
||||
def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
|
||||
value = connection.select_value(construct_calculation_sql(operation, column_name, options))
|
||||
type_cast_calculated_value(value, column, operation)
|
||||
end
|
||||
|
||||
def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
|
||||
group_attr = options[:group].to_s
|
||||
association = reflect_on_association(group_attr.to_sym)
|
||||
associated = association && association.macro == :belongs_to # only count belongs_to associations
|
||||
group_field = (associated ? "#{options[:group]}_id" : options[:group]).to_s
|
||||
group_alias = column_alias_for(group_field)
|
||||
group_column = column_for group_field
|
||||
sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
|
||||
calculated_data = connection.select_all(sql)
|
||||
aggregate_alias = column_alias_for(operation, column_name)
|
||||
|
||||
if association
|
||||
key_ids = calculated_data.collect { |row| row[group_alias] }
|
||||
key_records = association.klass.base_class.find(key_ids)
|
||||
key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
|
||||
end
|
||||
|
||||
calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
|
||||
key = type_cast_calculated_value(row[group_alias], group_column)
|
||||
key = key_records[key] if associated
|
||||
value = row[aggregate_alias]
|
||||
all << [key, type_cast_calculated_value(value, column, operation)]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def validate_calculation_options(operation, options = {})
|
||||
options.assert_valid_keys(CALCULATIONS_OPTIONS)
|
||||
end
|
||||
|
||||
# Converts a given key to the value that the database adapter returns as
|
||||
# a usable column name.
|
||||
# users.id #=> users_id
|
||||
# sum(id) #=> sum_id
|
||||
# count(distinct users.id) #=> count_distinct_users_id
|
||||
# count(*) #=> count_all
|
||||
def column_alias_for(*keys)
|
||||
connection.table_alias_for(keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_'))
|
||||
end
|
||||
|
||||
def column_for(field)
|
||||
field_name = field.to_s.split('.').last
|
||||
columns.detect { |c| c.name.to_s == field_name }
|
||||
end
|
||||
|
||||
def type_cast_calculated_value(value, column, operation = nil)
|
||||
operation = operation.to_s.downcase
|
||||
case operation
|
||||
when 'count' then value.to_i
|
||||
when 'avg' then value && value.to_f
|
||||
else column ? column.type_cast(value) : value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
341
vendor/rails/activerecord/lib/active_record/callbacks.rb
vendored
Executable file
341
vendor/rails/activerecord/lib/active_record/callbacks.rb
vendored
Executable file
|
|
@ -0,0 +1,341 @@
|
|||
require 'observer'
|
||||
|
||||
module ActiveRecord
|
||||
# Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
|
||||
# before or after an alteration of the object state. This can be used to make sure that associated and
|
||||
# dependent objects are deleted when destroy is called (by overwriting +before_destroy+) or to massage attributes
|
||||
# before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
|
||||
# the <tt>Base#save</tt> call:
|
||||
#
|
||||
# * (-) <tt>save</tt>
|
||||
# * (-) <tt>valid</tt>
|
||||
# * (1) <tt>before_validation</tt>
|
||||
# * (2) <tt>before_validation_on_create</tt>
|
||||
# * (-) <tt>validate</tt>
|
||||
# * (-) <tt>validate_on_create</tt>
|
||||
# * (3) <tt>after_validation</tt>
|
||||
# * (4) <tt>after_validation_on_create</tt>
|
||||
# * (5) <tt>before_save</tt>
|
||||
# * (6) <tt>before_create</tt>
|
||||
# * (-) <tt>create</tt>
|
||||
# * (7) <tt>after_create</tt>
|
||||
# * (8) <tt>after_save</tt>
|
||||
#
|
||||
# That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
|
||||
# Active Record lifecycle.
|
||||
#
|
||||
# Examples:
|
||||
# class CreditCard < ActiveRecord::Base
|
||||
# # Strip everything but digits, so the user can specify "555 234 34" or
|
||||
# # "5552-3434" or both will mean "55523434"
|
||||
# def before_validation_on_create
|
||||
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Subscription < ActiveRecord::Base
|
||||
# before_create :record_signup
|
||||
#
|
||||
# private
|
||||
# def record_signup
|
||||
# self.signed_up_on = Date.today
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Firm < ActiveRecord::Base
|
||||
# # Destroys the associated clients and people when the firm is destroyed
|
||||
# before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
|
||||
# before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
|
||||
# end
|
||||
#
|
||||
# == Inheritable callback queues
|
||||
#
|
||||
# Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
|
||||
# Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
|
||||
# hierarchy. Example:
|
||||
#
|
||||
# class Topic < ActiveRecord::Base
|
||||
# before_destroy :destroy_author
|
||||
# end
|
||||
#
|
||||
# class Reply < Topic
|
||||
# before_destroy :destroy_readers
|
||||
# end
|
||||
#
|
||||
# Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is run, both +destroy_author+ and
|
||||
# +destroy_readers+ are called. Contrast this to the situation where we've implemented the save behavior through overwriteable
|
||||
# methods:
|
||||
#
|
||||
# class Topic < ActiveRecord::Base
|
||||
# def before_destroy() destroy_author end
|
||||
# end
|
||||
#
|
||||
# class Reply < Topic
|
||||
# def before_destroy() destroy_readers end
|
||||
# end
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# *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
|
||||
# be inherited.
|
||||
#
|
||||
# == Types of callbacks
|
||||
#
|
||||
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
|
||||
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
|
||||
# recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
|
||||
# eval methods are deprecated.
|
||||
#
|
||||
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
|
||||
#
|
||||
# class Topic < ActiveRecord::Base
|
||||
# before_destroy :delete_parents
|
||||
#
|
||||
# private
|
||||
# def delete_parents
|
||||
# self.class.delete_all "parent_id = #{id}"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
|
||||
#
|
||||
# class BankAccount < ActiveRecord::Base
|
||||
# before_save EncryptionWrapper.new("credit_card_number")
|
||||
# after_save EncryptionWrapper.new("credit_card_number")
|
||||
# after_initialize EncryptionWrapper.new("credit_card_number")
|
||||
# end
|
||||
#
|
||||
# class EncryptionWrapper
|
||||
# def initialize(attribute)
|
||||
# @attribute = attribute
|
||||
# end
|
||||
#
|
||||
# def before_save(record)
|
||||
# record.credit_card_number = encrypt(record.credit_card_number)
|
||||
# end
|
||||
#
|
||||
# def after_save(record)
|
||||
# record.credit_card_number = decrypt(record.credit_card_number)
|
||||
# end
|
||||
#
|
||||
# alias_method :after_find, :after_save
|
||||
#
|
||||
# private
|
||||
# def encrypt(value)
|
||||
# # Secrecy is committed
|
||||
# end
|
||||
#
|
||||
# def decrypt(value)
|
||||
# # Secrecy is unveiled
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
|
||||
# a method by the name of the callback messaged.
|
||||
#
|
||||
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
|
||||
# which will then be evaluated within the binding of the callback. Example:
|
||||
#
|
||||
# class Topic < ActiveRecord::Base
|
||||
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
|
||||
# end
|
||||
#
|
||||
# Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback is triggered. Also note that these
|
||||
# inline callbacks can be stacked just like the regular ones:
|
||||
#
|
||||
# class Topic < ActiveRecord::Base
|
||||
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
|
||||
# 'puts "Evaluated after parents are destroyed"'
|
||||
# end
|
||||
#
|
||||
# == The +after_find+ and +after_initialize+ exceptions
|
||||
#
|
||||
# Because +after_find+ and +after_initialize+ are called for each object found and instantiated by a finder, such as <tt>Base.find(:all)</tt>, we've had
|
||||
# to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, +after_find+ and
|
||||
# +after_initialize+ will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
|
||||
# callback types will be called.
|
||||
#
|
||||
# == <tt>before_validation*</tt> returning statements
|
||||
#
|
||||
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+.
|
||||
# If <tt>Base#save!</tt> is called it will raise a +RecordNotSaved+ exception.
|
||||
# Nothing will be appended to the errors object.
|
||||
#
|
||||
# == Canceling callbacks
|
||||
#
|
||||
# If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are cancelled. If an <tt>after_*</tt> callback returns
|
||||
# +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
|
||||
# defined as methods on the model, which are called last.
|
||||
module Callbacks
|
||||
CALLBACKS = %w(
|
||||
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
|
||||
after_validation before_validation_on_create after_validation_on_create before_validation_on_update
|
||||
after_validation_on_update before_destroy after_destroy
|
||||
)
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend Observable
|
||||
|
||||
[:create_or_update, :valid?, :create, :update, :destroy].each do |method|
|
||||
base.send :alias_method_chain, method, :callbacks
|
||||
end
|
||||
|
||||
CALLBACKS.each do |method|
|
||||
base.class_eval <<-"end_eval"
|
||||
def self.#{method}(*callbacks, &block)
|
||||
callbacks << block if block_given?
|
||||
write_inheritable_array(#{method.to_sym.inspect}, callbacks)
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
|
||||
# Is called when the object was instantiated by one of the finders, like <tt>Base.find</tt>.
|
||||
#def after_find() end
|
||||
|
||||
# Is called after the object has been instantiated by a call to <tt>Base.new</tt>.
|
||||
#def after_initialize() end
|
||||
|
||||
# Is called _before_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save).
|
||||
def before_save() end
|
||||
|
||||
# Is called _after_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save).
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# after_save { logger.info( 'New contact saved!' ) }
|
||||
# end
|
||||
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)
|
||||
result
|
||||
end
|
||||
private :create_or_update_with_callbacks
|
||||
|
||||
# Is called _before_ <tt>Base.save</tt> on new objects that haven't been saved yet (no record exists).
|
||||
def before_create() end
|
||||
|
||||
# Is called _after_ <tt>Base.save</tt> on new objects that haven't been saved yet (no record exists).
|
||||
def after_create() end
|
||||
def create_with_callbacks #:nodoc:
|
||||
return false if callback(:before_create) == false
|
||||
result = create_without_callbacks
|
||||
callback(:after_create)
|
||||
result
|
||||
end
|
||||
private :create_with_callbacks
|
||||
|
||||
# Is called _before_ <tt>Base.save</tt> on existing objects that have a record.
|
||||
def before_update() end
|
||||
|
||||
# Is called _after_ <tt>Base.save</tt> on existing objects that have a record.
|
||||
def after_update() end
|
||||
|
||||
def update_with_callbacks #:nodoc:
|
||||
return false if callback(:before_update) == false
|
||||
result = update_without_callbacks
|
||||
callback(:after_update)
|
||||
result
|
||||
end
|
||||
private :update_with_callbacks
|
||||
|
||||
# Is called _before_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call).
|
||||
def before_validation() end
|
||||
|
||||
# Is called _after_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call).
|
||||
def after_validation() end
|
||||
|
||||
# Is called _before_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on new objects
|
||||
# that haven't been saved yet (no record exists).
|
||||
def before_validation_on_create() end
|
||||
|
||||
# Is called _after_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on new objects
|
||||
# that haven't been saved yet (no record exists).
|
||||
def after_validation_on_create() end
|
||||
|
||||
# Is called _before_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on
|
||||
# existing objects that have a record.
|
||||
def before_validation_on_update() end
|
||||
|
||||
# Is called _after_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on
|
||||
# existing objects that have a record.
|
||||
def after_validation_on_update() end
|
||||
|
||||
def valid_with_callbacks? #:nodoc:
|
||||
return false if callback(:before_validation) == false
|
||||
if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
|
||||
return false if result == false
|
||||
|
||||
result = valid_without_callbacks?
|
||||
|
||||
callback(:after_validation)
|
||||
if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
# Is called _before_ <tt>Base.destroy</tt>.
|
||||
#
|
||||
# Note: If you need to _destroy_ or _nullify_ associated records first,
|
||||
# use the <tt>:dependent</tt> option on your associations.
|
||||
def before_destroy() end
|
||||
|
||||
# Is called _after_ <tt>Base.destroy</tt> (and all the attributes have been frozen).
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# after_destroy { |record| logger.info( "Contact #{record.id} was destroyed." ) }
|
||||
# end
|
||||
def after_destroy() end
|
||||
def destroy_with_callbacks #:nodoc:
|
||||
return false if callback(:before_destroy) == false
|
||||
result = destroy_without_callbacks
|
||||
callback(:after_destroy)
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
def callback(method)
|
||||
notify(method)
|
||||
|
||||
callbacks_for(method).each do |callback|
|
||||
result = case callback
|
||||
when Symbol
|
||||
self.send(callback)
|
||||
when String
|
||||
eval(callback, binding)
|
||||
when Proc, Method
|
||||
callback.call(self)
|
||||
else
|
||||
if callback.respond_to?(method)
|
||||
callback.send(method, self)
|
||||
else
|
||||
raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
|
||||
end
|
||||
end
|
||||
return false if result == false
|
||||
end
|
||||
|
||||
result = send(method) if respond_to_without_attributes?(method)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
def callbacks_for(method)
|
||||
self.class.read_inheritable_attribute(method.to_sym) or []
|
||||
end
|
||||
|
||||
def invoke_and_notify(method)
|
||||
notify(method)
|
||||
send(method) if respond_to_without_attributes?(method)
|
||||
end
|
||||
|
||||
def notify(method) #:nodoc:
|
||||
self.class.changed
|
||||
self.class.notify_observers(method, self)
|
||||
end
|
||||
end
|
||||
end
|
||||
308
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
vendored
Normal file
308
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
vendored
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
require 'set'
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
class ConnectionSpecification #:nodoc:
|
||||
attr_reader :config, :adapter_method
|
||||
def initialize (config, adapter_method)
|
||||
@config, @adapter_method = config, adapter_method
|
||||
end
|
||||
end
|
||||
|
||||
# Check for activity after at least +verification_timeout+ seconds.
|
||||
# Defaults to 0 (always check.)
|
||||
cattr_accessor :verification_timeout, :instance_writer => false
|
||||
@@verification_timeout = 0
|
||||
|
||||
# The class -> [adapter_method, config] map
|
||||
@@defined_connections = {}
|
||||
|
||||
# The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
|
||||
@@active_connections = {}
|
||||
|
||||
class << self
|
||||
# Retrieve the connection cache.
|
||||
def thread_safe_active_connections #:nodoc:
|
||||
@@active_connections[Thread.current.object_id] ||= {}
|
||||
end
|
||||
|
||||
def single_threaded_active_connections #:nodoc:
|
||||
@@active_connections
|
||||
end
|
||||
|
||||
# pick up the right active_connection method from @@allow_concurrency
|
||||
if @@allow_concurrency
|
||||
alias_method :active_connections, :thread_safe_active_connections
|
||||
else
|
||||
alias_method :active_connections, :single_threaded_active_connections
|
||||
end
|
||||
|
||||
# set concurrency support flag (not thread safe, like most of the methods in this file)
|
||||
def allow_concurrency=(threaded) #:nodoc:
|
||||
logger.debug "allow_concurrency=#{threaded}" if logger
|
||||
return if @@allow_concurrency == threaded
|
||||
clear_all_cached_connections!
|
||||
@@allow_concurrency = threaded
|
||||
method_prefix = threaded ? "thread_safe" : "single_threaded"
|
||||
sing = (class << self; self; end)
|
||||
[:active_connections, :scoped_methods].each do |method|
|
||||
sing.send(:alias_method, method, "#{method_prefix}_#{method}")
|
||||
end
|
||||
log_connections if logger
|
||||
end
|
||||
|
||||
def active_connection_name #:nodoc:
|
||||
@active_connection_name ||=
|
||||
if active_connections[name] || @@defined_connections[name]
|
||||
name
|
||||
elsif self == ActiveRecord::Base
|
||||
nil
|
||||
else
|
||||
superclass.active_connection_name
|
||||
end
|
||||
end
|
||||
|
||||
def clear_active_connection_name #:nodoc:
|
||||
@active_connection_name = nil
|
||||
subclasses.each { |klass| klass.clear_active_connection_name }
|
||||
end
|
||||
|
||||
# Returns the connection currently associated with the class. This can
|
||||
# also be used to "borrow" the connection to do database work unrelated
|
||||
# to any of the specific Active Records.
|
||||
def connection
|
||||
if @active_connection_name && (conn = active_connections[@active_connection_name])
|
||||
conn
|
||||
else
|
||||
# retrieve_connection sets the cache key.
|
||||
conn = retrieve_connection
|
||||
active_connections[@active_connection_name] = conn
|
||||
end
|
||||
end
|
||||
|
||||
# Clears the cache which maps classes to connections.
|
||||
def clear_active_connections!
|
||||
clear_cache!(@@active_connections) do |name, conn|
|
||||
conn.disconnect!
|
||||
end
|
||||
end
|
||||
|
||||
# Clears the cache which maps classes
|
||||
def clear_reloadable_connections!
|
||||
if @@allow_concurrency
|
||||
# With concurrent connections @@active_connections is
|
||||
# a hash keyed by thread id.
|
||||
@@active_connections.each do |thread_id, conns|
|
||||
conns.each do |name, conn|
|
||||
if conn.requires_reloading?
|
||||
conn.disconnect!
|
||||
@@active_connections[thread_id].delete(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
@@active_connections.each do |name, conn|
|
||||
if conn.requires_reloading?
|
||||
conn.disconnect!
|
||||
@@active_connections.delete(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Verify active connections.
|
||||
def verify_active_connections! #:nodoc:
|
||||
if @@allow_concurrency
|
||||
remove_stale_cached_threads!(@@active_connections) do |name, conn|
|
||||
conn.disconnect!
|
||||
end
|
||||
end
|
||||
|
||||
active_connections.each_value do |connection|
|
||||
connection.verify!(@@verification_timeout)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def clear_cache!(cache, thread_id = nil, &block)
|
||||
if cache
|
||||
if @@allow_concurrency
|
||||
thread_id ||= Thread.current.object_id
|
||||
thread_cache, cache = cache, cache[thread_id]
|
||||
return unless cache
|
||||
end
|
||||
|
||||
cache.each(&block) if block_given?
|
||||
cache.clear
|
||||
end
|
||||
ensure
|
||||
if thread_cache && @@allow_concurrency
|
||||
thread_cache.delete(thread_id)
|
||||
end
|
||||
end
|
||||
|
||||
# Remove stale threads from the cache.
|
||||
def remove_stale_cached_threads!(cache, &block)
|
||||
stale = Set.new(cache.keys)
|
||||
|
||||
Thread.list.each do |thread|
|
||||
stale.delete(thread.object_id) if thread.alive?
|
||||
end
|
||||
|
||||
stale.each do |thread_id|
|
||||
clear_cache!(cache, thread_id, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def clear_all_cached_connections!
|
||||
if @@allow_concurrency
|
||||
@@active_connections.each_value do |connection_hash_for_thread|
|
||||
connection_hash_for_thread.each_value {|conn| conn.disconnect! }
|
||||
connection_hash_for_thread.clear
|
||||
end
|
||||
else
|
||||
@@active_connections.each_value {|conn| conn.disconnect! }
|
||||
end
|
||||
@@active_connections.clear
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the connection currently associated with the class. This can
|
||||
# also be used to "borrow" the connection to do database work that isn't
|
||||
# easily done without going straight to SQL.
|
||||
def connection
|
||||
self.class.connection
|
||||
end
|
||||
|
||||
# Establishes the connection to the database. Accepts a hash as input where
|
||||
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
||||
# example for regular databases (MySQL, Postgresql, etc):
|
||||
#
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# :adapter => "mysql",
|
||||
# :host => "localhost",
|
||||
# :username => "myuser",
|
||||
# :password => "mypass",
|
||||
# :database => "somedatabase"
|
||||
# )
|
||||
#
|
||||
# Example for SQLite database:
|
||||
#
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# :adapter => "sqlite",
|
||||
# :database => "path/to/dbfile"
|
||||
# )
|
||||
#
|
||||
# Also accepts keys as strings (for parsing from yaml for example):
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# "adapter" => "sqlite",
|
||||
# "database" => "path/to/dbfile"
|
||||
# )
|
||||
#
|
||||
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
||||
# may be returned on an error.
|
||||
def self.establish_connection(spec = nil)
|
||||
case spec
|
||||
when nil
|
||||
raise AdapterNotSpecified unless defined? RAILS_ENV
|
||||
establish_connection(RAILS_ENV)
|
||||
when ConnectionSpecification
|
||||
clear_active_connection_name
|
||||
@active_connection_name = name
|
||||
@@defined_connections[name] = spec
|
||||
when Symbol, String
|
||||
if configuration = configurations[spec.to_s]
|
||||
establish_connection(configuration)
|
||||
else
|
||||
raise AdapterNotSpecified, "#{spec} database is not configured"
|
||||
end
|
||||
else
|
||||
spec = spec.symbolize_keys
|
||||
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
||||
|
||||
begin
|
||||
require 'rubygems'
|
||||
gem "activerecord-#{spec[:adapter]}-adapter"
|
||||
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
||||
rescue LoadError
|
||||
begin
|
||||
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
||||
rescue LoadError
|
||||
raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
|
||||
end
|
||||
end
|
||||
|
||||
adapter_method = "#{spec[:adapter]}_connection"
|
||||
if !respond_to?(adapter_method)
|
||||
raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
|
||||
end
|
||||
|
||||
remove_connection
|
||||
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
||||
end
|
||||
end
|
||||
|
||||
# Locate the connection of the nearest super class. This can be an
|
||||
# active or defined connection: if it is the latter, it will be
|
||||
# opened and set as the active connection for the class it was defined
|
||||
# for (not necessarily the current class).
|
||||
def self.retrieve_connection #:nodoc:
|
||||
# Name is nil if establish_connection hasn't been called for
|
||||
# some class along the inheritance chain up to AR::Base yet.
|
||||
if name = active_connection_name
|
||||
if conn = active_connections[name]
|
||||
# Verify the connection.
|
||||
conn.verify!(@@verification_timeout)
|
||||
elsif spec = @@defined_connections[name]
|
||||
# Activate this connection specification.
|
||||
klass = name.constantize
|
||||
klass.connection = spec
|
||||
conn = active_connections[name]
|
||||
end
|
||||
end
|
||||
|
||||
conn or raise ConnectionNotEstablished
|
||||
end
|
||||
|
||||
# Returns true if a connection that's accessible to this class has already been opened.
|
||||
def self.connected?
|
||||
active_connections[active_connection_name] ? true : false
|
||||
end
|
||||
|
||||
# Remove the connection for this class. This will close the active
|
||||
# connection and the defined connection (if they exist). The result
|
||||
# can be used as an argument for establish_connection, for easily
|
||||
# re-establishing the connection.
|
||||
def self.remove_connection(klass=self)
|
||||
spec = @@defined_connections[klass.name]
|
||||
konn = active_connections[klass.name]
|
||||
@@defined_connections.delete_if { |key, value| value == spec }
|
||||
active_connections.delete_if { |key, value| value == konn }
|
||||
konn.disconnect! if konn
|
||||
spec.config if spec
|
||||
end
|
||||
|
||||
# Set the connection for the class.
|
||||
def self.connection=(spec) #:nodoc:
|
||||
if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
|
||||
active_connections[name] = spec
|
||||
elsif spec.kind_of?(ConnectionSpecification)
|
||||
config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
|
||||
self.connection = self.send(spec.adapter_method, config)
|
||||
elsif spec.nil?
|
||||
raise ConnectionNotEstablished
|
||||
else
|
||||
establish_connection spec
|
||||
end
|
||||
end
|
||||
|
||||
# connection state logging
|
||||
def self.log_connections #:nodoc:
|
||||
if logger
|
||||
logger.info "Defined connections: #{@@defined_connections.inspect}"
|
||||
logger.info "Active connections: #{active_connections.inspect}"
|
||||
logger.info "Active connection name: #{@active_connection_name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
171
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
vendored
Normal file
171
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
module DatabaseStatements
|
||||
# Returns an array of record hashes with the column names as keys and
|
||||
# column values as values.
|
||||
def select_all(sql, name = nil)
|
||||
select(sql, name)
|
||||
end
|
||||
|
||||
# Returns a record hash with the column names as keys and column values
|
||||
# as values.
|
||||
def select_one(sql, name = nil)
|
||||
result = select_all(sql, name)
|
||||
result.first if result
|
||||
end
|
||||
|
||||
# Returns a single value from a record
|
||||
def select_value(sql, name = nil)
|
||||
if result = select_one(sql, name)
|
||||
result.values.first
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array of the values of the first column in a select:
|
||||
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
||||
def select_values(sql, name = nil)
|
||||
result = select_rows(sql, name)
|
||||
result.map { |v| v[0] }
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
# Executes the SQL statement in the context of this connection.
|
||||
def execute(sql, name = nil)
|
||||
raise NotImplementedError, "execute is an abstract method"
|
||||
end
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
insert_sql(sql, name, pk, id_value, sequence_name)
|
||||
end
|
||||
|
||||
# Executes the update statement and returns the number of rows affected.
|
||||
def update(sql, name = nil)
|
||||
update_sql(sql, name)
|
||||
end
|
||||
|
||||
# Executes the delete statement and returns the number of rows affected.
|
||||
def delete(sql, name = nil)
|
||||
delete_sql(sql, name)
|
||||
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_open = false
|
||||
rollback_db_transaction
|
||||
end
|
||||
raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
||||
end
|
||||
ensure
|
||||
if transaction_open
|
||||
begin
|
||||
commit_db_transaction
|
||||
rescue Exception => database_transaction_rollback
|
||||
rollback_db_transaction
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Begins the transaction (and turns off auto-committing).
|
||||
def begin_db_transaction() end
|
||||
|
||||
# Commits the transaction (and turns on auto-committing).
|
||||
def commit_db_transaction() end
|
||||
|
||||
# Rolls back the transaction (and turns on auto-committing). Must be
|
||||
# done if the transaction block raises an exception or returns false.
|
||||
def rollback_db_transaction() end
|
||||
|
||||
# Alias for #add_limit_offset!.
|
||||
def add_limit!(sql, options)
|
||||
add_limit_offset!(sql, options) if options
|
||||
end
|
||||
|
||||
# Appends +LIMIT+ and +OFFSET+ options to an SQL statement.
|
||||
# This method *modifies* the +sql+ parameter.
|
||||
# ===== Examples
|
||||
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
||||
# generates
|
||||
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
||||
def add_limit_offset!(sql, options)
|
||||
if limit = options[:limit]
|
||||
sql << " LIMIT #{limit}"
|
||||
if offset = options[:offset]
|
||||
sql << " OFFSET #{offset}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Appends a locking clause to an SQL statement.
|
||||
# This method *modifies* the +sql+ parameter.
|
||||
# # SELECT * FROM suppliers FOR UPDATE
|
||||
# add_lock! 'SELECT * FROM suppliers', :lock => true
|
||||
# add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
|
||||
def add_lock!(sql, options)
|
||||
case lock = options[:lock]
|
||||
when true; sql << ' FOR UPDATE'
|
||||
when String; sql << " #{lock}"
|
||||
end
|
||||
end
|
||||
|
||||
def default_sequence_name(table, column)
|
||||
nil
|
||||
end
|
||||
|
||||
# Set the sequence to the max value of the table's column.
|
||||
def reset_sequence!(table, column, sequence = nil)
|
||||
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
||||
end
|
||||
|
||||
# Inserts the given fixture into the table. Overridden in adapters that require
|
||||
# something beyond a simple insert (eg. Oracle).
|
||||
def insert_fixture(fixture, table_name)
|
||||
execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
||||
end
|
||||
|
||||
def empty_insert_statement(table_name)
|
||||
"INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)"
|
||||
end
|
||||
|
||||
protected
|
||||
# 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
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
execute(sql, name)
|
||||
id_value
|
||||
end
|
||||
|
||||
# Executes the update statement and returns the number of rows affected.
|
||||
def update_sql(sql, name = nil)
|
||||
execute(sql, name)
|
||||
end
|
||||
|
||||
# Executes the delete statement and returns the number of rows affected.
|
||||
def delete_sql(sql, name = nil)
|
||||
update_sql(sql, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
87
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
vendored
Normal file
87
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
module QueryCache
|
||||
class << self
|
||||
def included(base)
|
||||
base.class_eval do
|
||||
attr_accessor :query_cache_enabled
|
||||
alias_method_chain :columns, :query_cache
|
||||
alias_method_chain :select_all, :query_cache
|
||||
end
|
||||
|
||||
dirties_query_cache base, :insert, :update, :delete
|
||||
end
|
||||
|
||||
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
|
||||
end_code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Enable the query cache within the block.
|
||||
def cache
|
||||
old, @query_cache_enabled = @query_cache_enabled, true
|
||||
@query_cache ||= {}
|
||||
yield
|
||||
ensure
|
||||
clear_query_cache
|
||||
@query_cache_enabled = old
|
||||
end
|
||||
|
||||
# Disable the query cache within the block.
|
||||
def uncached
|
||||
old, @query_cache_enabled = @query_cache_enabled, false
|
||||
yield
|
||||
ensure
|
||||
@query_cache_enabled = old
|
||||
end
|
||||
|
||||
def clear_query_cache
|
||||
@query_cache.clear if @query_cache
|
||||
end
|
||||
|
||||
def select_all_with_query_cache(*args)
|
||||
if @query_cache_enabled
|
||||
cache_sql(args.first) { select_all_without_query_cache(*args) }
|
||||
else
|
||||
select_all_without_query_cache(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def columns_with_query_cache(*args)
|
||||
if @query_cache_enabled
|
||||
@query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
|
||||
else
|
||||
columns_without_query_cache(*args)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def cache_sql(sql)
|
||||
result =
|
||||
if @query_cache.has_key?(sql)
|
||||
log_info(sql, "CACHE", 0.0)
|
||||
@query_cache[sql]
|
||||
else
|
||||
@query_cache[sql] = yield
|
||||
end
|
||||
|
||||
if Array === result
|
||||
result.collect { |row| row.dup }
|
||||
else
|
||||
result.duplicable? ? result.dup : result
|
||||
end
|
||||
rescue TypeError
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
69
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
vendored
Normal file
69
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
module Quoting
|
||||
# Quotes the column value to help prevent
|
||||
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
||||
def quote(value, column = nil)
|
||||
# records are quoted as their primary key
|
||||
return value.quoted_id if value.respond_to?(:quoted_id)
|
||||
|
||||
case value
|
||||
when String, ActiveSupport::Multibyte::Chars
|
||||
value = value.to_s
|
||||
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
||||
"#{quoted_string_prefix}'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
||||
elsif column && [:integer, :float].include?(column.type)
|
||||
value = column.type == :integer ? value.to_i : value.to_f
|
||||
value.to_s
|
||||
else
|
||||
"#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode)
|
||||
end
|
||||
when NilClass then "NULL"
|
||||
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
||||
when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
|
||||
when Float, Fixnum, Bignum then value.to_s
|
||||
# BigDecimals need to be output in a non-normalized form and quoted.
|
||||
when BigDecimal then value.to_s('F')
|
||||
else
|
||||
if value.acts_like?(:date) || value.acts_like?(:time)
|
||||
"'#{quoted_date(value)}'"
|
||||
else
|
||||
"#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
||||
# characters.
|
||||
def quote_string(s)
|
||||
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
||||
end
|
||||
|
||||
# Quotes the column name. Defaults to no quoting.
|
||||
def quote_column_name(column_name)
|
||||
column_name
|
||||
end
|
||||
|
||||
# Quotes the table name. Defaults to column name quoting.
|
||||
def quote_table_name(table_name)
|
||||
quote_column_name(table_name)
|
||||
end
|
||||
|
||||
def quoted_true
|
||||
"'t'"
|
||||
end
|
||||
|
||||
def quoted_false
|
||||
"'f'"
|
||||
end
|
||||
|
||||
def quoted_date(value)
|
||||
value.to_s(:db)
|
||||
end
|
||||
|
||||
def quoted_string_prefix
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
472
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
vendored
Normal file
472
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
vendored
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
require 'date'
|
||||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters #:nodoc:
|
||||
# An abstract definition of a column in a table.
|
||||
class Column
|
||||
module Format
|
||||
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
||||
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
||||
end
|
||||
|
||||
attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
|
||||
attr_accessor :primary
|
||||
|
||||
# Instantiates a new column in the table.
|
||||
#
|
||||
# +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
|
||||
# +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
|
||||
# +sql_type+ is only used to extract the column's length, if necessary. For example, <tt>company_name varchar(<b>60</b>)</tt>.
|
||||
# +null+ determines if this column allows +NULL+ values.
|
||||
def initialize(name, default, sql_type = nil, null = true)
|
||||
@name, @sql_type, @null = name, sql_type, null
|
||||
@limit, @precision, @scale = extract_limit(sql_type), extract_precision(sql_type), extract_scale(sql_type)
|
||||
@type = simplified_type(sql_type)
|
||||
@default = extract_default(default)
|
||||
|
||||
@primary = nil
|
||||
end
|
||||
|
||||
def text?
|
||||
[:string, :text].include? type
|
||||
end
|
||||
|
||||
def number?
|
||||
[:float, :integer, :decimal].include? type
|
||||
end
|
||||
|
||||
# Returns the Ruby class that corresponds to the abstract data type.
|
||||
def klass
|
||||
case type
|
||||
when :integer then Fixnum
|
||||
when :float then Float
|
||||
when :decimal then BigDecimal
|
||||
when :datetime then Time
|
||||
when :date then Date
|
||||
when :timestamp then Time
|
||||
when :time then Time
|
||||
when :text, :string then String
|
||||
when :binary then String
|
||||
when :boolean then Object
|
||||
end
|
||||
end
|
||||
|
||||
# Casts value (which is a String) to an appropriate instance.
|
||||
def type_cast(value)
|
||||
return nil if value.nil?
|
||||
case type
|
||||
when :string then value
|
||||
when :text then value
|
||||
when :integer then value.to_i rescue value ? 1 : 0
|
||||
when :float then value.to_f
|
||||
when :decimal then self.class.value_to_decimal(value)
|
||||
when :datetime then self.class.string_to_time(value)
|
||||
when :timestamp then self.class.string_to_time(value)
|
||||
when :time then self.class.string_to_dummy_time(value)
|
||||
when :date then self.class.string_to_date(value)
|
||||
when :binary then self.class.binary_to_string(value)
|
||||
when :boolean then self.class.value_to_boolean(value)
|
||||
else value
|
||||
end
|
||||
end
|
||||
|
||||
def type_cast_code(var_name)
|
||||
case type
|
||||
when :string then nil
|
||||
when :text then nil
|
||||
when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
|
||||
when :float then "#{var_name}.to_f"
|
||||
when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
|
||||
when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
|
||||
when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
|
||||
when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
|
||||
when :date then "#{self.class.name}.string_to_date(#{var_name})"
|
||||
when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
|
||||
when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
|
||||
else nil
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the human name of the column name.
|
||||
#
|
||||
# ===== Examples
|
||||
# Column.new('sales_stage', ...).human_name #=> 'Sales stage'
|
||||
def human_name
|
||||
Base.human_attribute_name(@name)
|
||||
end
|
||||
|
||||
def extract_default(default)
|
||||
type_cast(default)
|
||||
end
|
||||
|
||||
class << self
|
||||
# Used to convert from Strings to BLOBs
|
||||
def string_to_binary(value)
|
||||
value
|
||||
end
|
||||
|
||||
# Used to convert from BLOBs to Strings
|
||||
def binary_to_string(value)
|
||||
value
|
||||
end
|
||||
|
||||
def string_to_date(string)
|
||||
return string unless string.is_a?(String)
|
||||
return nil if string.empty?
|
||||
|
||||
fast_string_to_date(string) || fallback_string_to_date(string)
|
||||
end
|
||||
|
||||
def string_to_time(string)
|
||||
return string unless string.is_a?(String)
|
||||
return nil if string.empty?
|
||||
|
||||
fast_string_to_time(string) || fallback_string_to_time(string)
|
||||
end
|
||||
|
||||
def string_to_dummy_time(string)
|
||||
return string unless string.is_a?(String)
|
||||
return nil if string.empty?
|
||||
|
||||
string_to_time "2000-01-01 #{string}"
|
||||
end
|
||||
|
||||
# convert something to a boolean
|
||||
def value_to_boolean(value)
|
||||
if value == true || value == false
|
||||
value
|
||||
else
|
||||
%w(true t 1).include?(value.to_s.downcase)
|
||||
end
|
||||
end
|
||||
|
||||
# convert something to a BigDecimal
|
||||
def value_to_decimal(value)
|
||||
if value.is_a?(BigDecimal)
|
||||
value
|
||||
elsif value.respond_to?(:to_d)
|
||||
value.to_d
|
||||
else
|
||||
value.to_s.to_d
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# '0.123456' -> 123456
|
||||
# '1.123456' -> 123456
|
||||
def microseconds(time)
|
||||
((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
|
||||
end
|
||||
|
||||
def new_date(year, mon, mday)
|
||||
if year && year != 0
|
||||
Date.new(year, mon, mday) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
def new_time(year, mon, mday, hour, min, sec, microsec)
|
||||
# Treat 0000-00-00 00:00:00 as nil.
|
||||
return nil if year.nil? || year == 0
|
||||
|
||||
Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec)
|
||||
# Over/underflow to DateTime
|
||||
rescue ArgumentError, TypeError
|
||||
zone_offset = Base.default_timezone == :local ? DateTime.local_offset : 0
|
||||
DateTime.civil(year, mon, mday, hour, min, sec, zone_offset) rescue nil
|
||||
end
|
||||
|
||||
def fast_string_to_date(string)
|
||||
if string =~ Format::ISO_DATE
|
||||
new_date $1.to_i, $2.to_i, $3.to_i
|
||||
end
|
||||
end
|
||||
|
||||
# Doesn't handle time zones.
|
||||
def fast_string_to_time(string)
|
||||
if string =~ Format::ISO_DATETIME
|
||||
microsec = ($7.to_f * 1_000_000).to_i
|
||||
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
||||
end
|
||||
end
|
||||
|
||||
def fallback_string_to_date(string)
|
||||
new_date *ParseDate.parsedate(string)[0..2]
|
||||
end
|
||||
|
||||
def fallback_string_to_time(string)
|
||||
time_hash = Date._parse(string)
|
||||
time_hash[:sec_fraction] = microseconds(time_hash)
|
||||
|
||||
new_time *time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def extract_limit(sql_type)
|
||||
$1.to_i if sql_type =~ /\((.*)\)/
|
||||
end
|
||||
|
||||
def extract_precision(sql_type)
|
||||
$2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
|
||||
end
|
||||
|
||||
def extract_scale(sql_type)
|
||||
case sql_type
|
||||
when /^(numeric|decimal|number)\((\d+)\)/i then 0
|
||||
when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def simplified_type(field_type)
|
||||
case field_type
|
||||
when /int/i
|
||||
:integer
|
||||
when /float|double/i
|
||||
:float
|
||||
when /decimal|numeric|number/i
|
||||
extract_scale(field_type) == 0 ? :integer : :decimal
|
||||
when /datetime/i
|
||||
:datetime
|
||||
when /timestamp/i
|
||||
:timestamp
|
||||
when /time/i
|
||||
:time
|
||||
when /date/i
|
||||
:date
|
||||
when /clob/i, /text/i
|
||||
:text
|
||||
when /blob/i, /binary/i
|
||||
:binary
|
||||
when /char/i, /string/i
|
||||
:string
|
||||
when /boolean/i
|
||||
:boolean
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
|
||||
end
|
||||
|
||||
class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
|
||||
|
||||
def sql_type
|
||||
base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
|
||||
end
|
||||
|
||||
def to_sql
|
||||
column_sql = "#{base.quote_column_name(name)} #{sql_type}"
|
||||
add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
|
||||
column_sql
|
||||
end
|
||||
alias to_s :to_sql
|
||||
|
||||
private
|
||||
|
||||
def add_column_options!(sql, options)
|
||||
base.add_column_options!(sql, options.merge(:column => self))
|
||||
end
|
||||
end
|
||||
|
||||
# Represents a SQL table in an abstract way.
|
||||
# Columns are stored as a ColumnDefinition in the #columns attribute.
|
||||
class TableDefinition
|
||||
attr_accessor :columns
|
||||
|
||||
def initialize(base)
|
||||
@columns = []
|
||||
@base = base
|
||||
end
|
||||
|
||||
# Appends a primary key definition to the table definition.
|
||||
# Can be called multiple times, but this is probably not a good idea.
|
||||
def primary_key(name)
|
||||
column(name, :primary_key)
|
||||
end
|
||||
|
||||
# Returns a ColumnDefinition for the column with name +name+.
|
||||
def [](name)
|
||||
@columns.find {|column| column.name.to_s == name.to_s}
|
||||
end
|
||||
|
||||
# Instantiates a new column for the table.
|
||||
# The +type+ parameter is normally one of the migrations native types,
|
||||
# which is one of the following:
|
||||
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
|
||||
# <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
|
||||
# <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
|
||||
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
|
||||
#
|
||||
# You may use a type not in this list as long as it is supported by your
|
||||
# database (for example, "polygon" in MySQL), but this will not be database
|
||||
# agnostic and should usually be avoided.
|
||||
#
|
||||
# Available options are (none of these exists by default):
|
||||
# * <tt>:limit</tt> -
|
||||
# Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
|
||||
# <tt>:binary</tt> or <tt>:integer</tt> columns only)
|
||||
# * <tt>:default</tt> -
|
||||
# The column's default value. Use nil for NULL.
|
||||
# * <tt>:null</tt> -
|
||||
# Allows or disallows +NULL+ values in the column. This option could
|
||||
# have been named <tt>:null_allowed</tt>.
|
||||
# * <tt>:precision</tt> -
|
||||
# Specifies the precision for a <tt>:decimal</tt> column.
|
||||
# * <tt>:scale</tt> -
|
||||
# Specifies the scale for a <tt>:decimal</tt> column.
|
||||
#
|
||||
# Please be aware of different RDBMS implementations behavior with
|
||||
# <tt>:decimal</tt> columns:
|
||||
# * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
|
||||
# <tt>:precision</tt>, and makes no comments about the requirements of
|
||||
# <tt>:precision</tt>.
|
||||
# * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
|
||||
# Default is (10,0).
|
||||
# * PostgreSQL: <tt>:precision</tt> [1..infinity],
|
||||
# <tt>:scale</tt> [0..infinity]. No default.
|
||||
# * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
|
||||
# Internal storage as strings. No default.
|
||||
# * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
|
||||
# but the maximum supported <tt>:precision</tt> is 16. No default.
|
||||
# * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
|
||||
# Default is (38,0).
|
||||
# * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
|
||||
# Default unknown.
|
||||
# * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18].
|
||||
# Default (9,0). Internal types NUMERIC and DECIMAL have different
|
||||
# storage rules, decimal being better.
|
||||
# * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
|
||||
# Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for
|
||||
# NUMERIC is 19, and DECIMAL is 38.
|
||||
# * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
|
||||
# Default (38,0).
|
||||
# * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
|
||||
# Default (38,0).
|
||||
# * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.
|
||||
#
|
||||
# This method returns <tt>self</tt>.
|
||||
#
|
||||
# == Examples
|
||||
# # Assuming td is an instance of TableDefinition
|
||||
# td.column(:granted, :boolean)
|
||||
# #=> granted BOOLEAN
|
||||
#
|
||||
# td.column(:picture, :binary, :limit => 2.megabytes)
|
||||
# #=> picture BLOB(2097152)
|
||||
#
|
||||
# td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
|
||||
# #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
|
||||
#
|
||||
# def.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
|
||||
# #=> bill_gates_money DECIMAL(15,2)
|
||||
#
|
||||
# def.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
|
||||
# #=> sensor_reading DECIMAL(30,20)
|
||||
#
|
||||
# # While <tt>:scale</tt> defaults to zero on most databases, it
|
||||
# # probably wouldn't hurt to include it.
|
||||
# def.column(:huge_integer, :decimal, :precision => 30)
|
||||
# #=> huge_integer DECIMAL(30)
|
||||
#
|
||||
# == Short-hand examples
|
||||
#
|
||||
# Instead of calling column directly, you can also work with the short-hand definitions for the default types.
|
||||
# They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
|
||||
# in a single statement.
|
||||
#
|
||||
# What can be written like this with the regular calls to column:
|
||||
#
|
||||
# create_table "products", :force => true do |t|
|
||||
# t.column "shop_id", :integer
|
||||
# t.column "creator_id", :integer
|
||||
# t.column "name", :string, :default => "Untitled"
|
||||
# t.column "value", :string, :default => "Untitled"
|
||||
# t.column "created_at", :datetime
|
||||
# t.column "updated_at", :datetime
|
||||
# end
|
||||
#
|
||||
# Can also be written as follows using the short-hand:
|
||||
#
|
||||
# create_table :products do |t|
|
||||
# t.integer :shop_id, :creator_id
|
||||
# t.string :name, :value, :default => "Untitled"
|
||||
# t.timestamps
|
||||
# end
|
||||
#
|
||||
# There's a short-hand method for each of the type values declared at the top. And then there's
|
||||
# TableDefinition#timestamps that'll add created_at and updated_at as datetimes.
|
||||
#
|
||||
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
|
||||
# column if the :polymorphic option is supplied. If :polymorphic is a hash of options, these will be
|
||||
# used when creating the _type column. So what can be written like this:
|
||||
#
|
||||
# create_table :taggings do |t|
|
||||
# t.integer :tag_id, :tagger_id, :taggable_id
|
||||
# t.string :tagger_type
|
||||
# t.string :taggable_type, :default => 'Photo'
|
||||
# end
|
||||
#
|
||||
# Can also be written as follows using references:
|
||||
#
|
||||
# create_table :taggings do |t|
|
||||
# t.references :tag
|
||||
# t.references :tagger, :polymorphic => true
|
||||
# t.references :taggable, :polymorphic => { :default => 'Photo' }
|
||||
# end
|
||||
def column(name, type, options = {})
|
||||
column = self[name] || ColumnDefinition.new(@base, name, type)
|
||||
column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
|
||||
column.precision = options[:precision]
|
||||
column.scale = options[:scale]
|
||||
column.default = options[:default]
|
||||
column.null = options[:null]
|
||||
@columns << column unless @columns.include? column
|
||||
self
|
||||
end
|
||||
|
||||
%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
|
||||
EOV
|
||||
end
|
||||
|
||||
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
|
||||
# <tt>:updated_at</tt> to the table.
|
||||
def timestamps
|
||||
column(:created_at, :datetime)
|
||||
column(:updated_at, :datetime)
|
||||
end
|
||||
|
||||
def references(*args)
|
||||
options = args.extract_options!
|
||||
polymorphic = options.delete(:polymorphic)
|
||||
args.each do |col|
|
||||
column("#{col}_id", :integer, options)
|
||||
unless polymorphic.nil?
|
||||
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : {})
|
||||
end
|
||||
end
|
||||
end
|
||||
alias :belongs_to :references
|
||||
|
||||
# Returns a String whose contents are the column definitions
|
||||
# concatenated together. This string can then be prepended and appended to
|
||||
# to generate the final SQL to create the table.
|
||||
def to_sql
|
||||
@columns * ', '
|
||||
end
|
||||
|
||||
private
|
||||
def native
|
||||
@base.native_database_types
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
306
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
vendored
Normal file
306
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
vendored
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
module SchemaStatements
|
||||
# Returns a Hash of mappings from the abstract data types to the native
|
||||
# database types. See TableDefinition#column for details on the recognized
|
||||
# abstract data types.
|
||||
def native_database_types
|
||||
{}
|
||||
end
|
||||
|
||||
# This is the maximum length a table alias can be
|
||||
def table_alias_length
|
||||
255
|
||||
end
|
||||
|
||||
# Truncates a table alias according to the limits of the current adapter.
|
||||
def table_alias_for(table_name)
|
||||
table_name[0..table_alias_length-1].gsub(/\./, '_')
|
||||
end
|
||||
|
||||
# def tables(name = nil) end
|
||||
|
||||
# Returns an array of indexes for the given table.
|
||||
# def indexes(table_name, name = nil) end
|
||||
|
||||
# Returns an array of Column objects for the table specified by +table_name+.
|
||||
# See the concrete implementation for details on the expected parameter values.
|
||||
def columns(table_name, name = nil) end
|
||||
|
||||
# Creates a new table
|
||||
# There are two ways to work with #create_table. You can use the block
|
||||
# form or the regular form, like this:
|
||||
#
|
||||
# === Block form
|
||||
# # create_table() yields a TableDefinition instance
|
||||
# create_table(:suppliers) do |t|
|
||||
# t.column :name, :string, :limit => 60
|
||||
# # Other fields here
|
||||
# end
|
||||
#
|
||||
# === Regular form
|
||||
# create_table(:suppliers)
|
||||
# add_column(:suppliers, :name, :string, {:limit => 60})
|
||||
#
|
||||
# The +options+ hash can include the following keys:
|
||||
# [<tt>:id</tt>]
|
||||
# Whether to automatically add a primary key column. Defaults to true.
|
||||
# Join tables for has_and_belongs_to_many should set :id => false.
|
||||
# [<tt>:primary_key</tt>]
|
||||
# The name of the primary key, if one is to be added automatically.
|
||||
# Defaults to +id+.
|
||||
# [<tt>:options</tt>]
|
||||
# Any extra options you want appended to the table definition.
|
||||
# [<tt>:temporary</tt>]
|
||||
# Make a temporary table.
|
||||
# [<tt>:force</tt>]
|
||||
# Set to true to drop the table before creating it.
|
||||
# Defaults to false.
|
||||
#
|
||||
# ===== Examples
|
||||
# ====== Add a backend specific option to the generated SQL (MySQL)
|
||||
# create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
|
||||
# generates:
|
||||
# CREATE TABLE suppliers (
|
||||
# id int(11) DEFAULT NULL auto_increment PRIMARY KEY
|
||||
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||
#
|
||||
# ====== Rename the primary key column
|
||||
# create_table(:objects, :primary_key => 'guid') do |t|
|
||||
# t.column :name, :string, :limit => 80
|
||||
# end
|
||||
# generates:
|
||||
# CREATE TABLE objects (
|
||||
# guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
||||
# name varchar(80)
|
||||
# )
|
||||
#
|
||||
# ====== Do not add a primary key column
|
||||
# create_table(:categories_suppliers, :id => false) do |t|
|
||||
# t.column :category_id, :integer
|
||||
# t.column :supplier_id, :integer
|
||||
# end
|
||||
# generates:
|
||||
# CREATE TABLE categories_suppliers (
|
||||
# category_id int,
|
||||
# supplier_id int
|
||||
# )
|
||||
#
|
||||
# See also TableDefinition#column for details on how to create columns.
|
||||
def create_table(table_name, options = {})
|
||||
table_definition = TableDefinition.new(self)
|
||||
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
|
||||
|
||||
yield table_definition
|
||||
|
||||
if options[:force]
|
||||
drop_table(table_name, options) rescue nil
|
||||
end
|
||||
|
||||
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
||||
create_sql << "#{quote_table_name(table_name)} ("
|
||||
create_sql << table_definition.to_sql
|
||||
create_sql << ") #{options[:options]}"
|
||||
execute create_sql
|
||||
end
|
||||
|
||||
# Renames a table.
|
||||
# ===== Example
|
||||
# rename_table('octopuses', 'octopi')
|
||||
def rename_table(table_name, new_name)
|
||||
raise NotImplementedError, "rename_table is not implemented"
|
||||
end
|
||||
|
||||
# Drops a table from the database.
|
||||
def drop_table(table_name, options = {})
|
||||
execute "DROP TABLE #{quote_table_name(table_name)}"
|
||||
end
|
||||
|
||||
# Adds a new column to the named table.
|
||||
# See TableDefinition#column for details of the options you can use.
|
||||
def add_column(table_name, column_name, type, options = {})
|
||||
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
||||
add_column_options!(add_column_sql, options)
|
||||
execute(add_column_sql)
|
||||
end
|
||||
|
||||
# Removes the column from the table definition.
|
||||
# ===== Examples
|
||||
# remove_column(:suppliers, :qualification)
|
||||
def remove_column(table_name, column_name)
|
||||
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
|
||||
end
|
||||
|
||||
# Changes the column's definition according to the new options.
|
||||
# See TableDefinition#column for details of the options you can use.
|
||||
# ===== Examples
|
||||
# change_column(:suppliers, :name, :string, :limit => 80)
|
||||
# change_column(:accounts, :description, :text)
|
||||
def change_column(table_name, column_name, type, options = {})
|
||||
raise NotImplementedError, "change_column is not implemented"
|
||||
end
|
||||
|
||||
# Sets a new default value for a column. If you want to set the default
|
||||
# value to +NULL+, you are out of luck. You need to
|
||||
# DatabaseStatements#execute the appropriate SQL statement yourself.
|
||||
# ===== Examples
|
||||
# change_column_default(:suppliers, :qualification, 'new')
|
||||
# change_column_default(:accounts, :authorized, 1)
|
||||
def change_column_default(table_name, column_name, default)
|
||||
raise NotImplementedError, "change_column_default is not implemented"
|
||||
end
|
||||
|
||||
# Renames a column.
|
||||
# ===== Example
|
||||
# rename_column(:suppliers, :description, :name)
|
||||
def rename_column(table_name, column_name, new_column_name)
|
||||
raise NotImplementedError, "rename_column is not implemented"
|
||||
end
|
||||
|
||||
# Adds a new index to the table. +column_name+ can be a single Symbol, or
|
||||
# an Array of Symbols.
|
||||
#
|
||||
# The index will be named after the table and the first column name,
|
||||
# unless you pass +:name+ as an option.
|
||||
#
|
||||
# When creating an index on multiple columns, the first column is used as a name
|
||||
# for the index. For example, when you specify an index on two columns
|
||||
# [+:first+, +:last+], the DBMS creates an index for both columns as well as an
|
||||
# index for the first column +:first+. Using just the first name for this index
|
||||
# makes sense, because you will never have to create a singular index with this
|
||||
# name.
|
||||
#
|
||||
# ===== Examples
|
||||
# ====== Creating a simple index
|
||||
# add_index(:suppliers, :name)
|
||||
# generates
|
||||
# CREATE INDEX suppliers_name_index ON suppliers(name)
|
||||
# ====== Creating a unique index
|
||||
# add_index(:accounts, [:branch_id, :party_id], :unique => true)
|
||||
# generates
|
||||
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
|
||||
# ====== Creating a named index
|
||||
# add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
|
||||
# generates
|
||||
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
|
||||
def add_index(table_name, column_name, options = {})
|
||||
column_names = Array(column_name)
|
||||
index_name = index_name(table_name, :column => column_names)
|
||||
|
||||
if Hash === options # legacy support, since this param was a string
|
||||
index_type = options[:unique] ? "UNIQUE" : ""
|
||||
index_name = options[:name] || index_name
|
||||
else
|
||||
index_type = options
|
||||
end
|
||||
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
|
||||
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
|
||||
end
|
||||
|
||||
# Remove the given index from the table.
|
||||
#
|
||||
# Remove the suppliers_name_index in the suppliers table.
|
||||
# remove_index :suppliers, :name
|
||||
# Remove the index named accounts_branch_id_index in the accounts table.
|
||||
# remove_index :accounts, :column => :branch_id
|
||||
# Remove the index named accounts_branch_id_party_id_index in the accounts table.
|
||||
# remove_index :accounts, :column => [:branch_id, :party_id]
|
||||
# Remove the index named by_branch_party in the accounts table.
|
||||
# remove_index :accounts, :name => :by_branch_party
|
||||
def remove_index(table_name, options = {})
|
||||
execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{table_name}"
|
||||
end
|
||||
|
||||
def index_name(table_name, options) #:nodoc:
|
||||
if Hash === options # legacy support
|
||||
if options[:column]
|
||||
"index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
|
||||
elsif options[:name]
|
||||
options[:name]
|
||||
else
|
||||
raise ArgumentError, "You must specify the index name"
|
||||
end
|
||||
else
|
||||
index_name(table_name, :column => options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
|
||||
# entire structure of the database.
|
||||
def structure_dump
|
||||
end
|
||||
|
||||
# Should not be called normally, but this operation is non-destructive.
|
||||
# The migrations module handles this automatically.
|
||||
def initialize_schema_information
|
||||
begin
|
||||
execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:integer)})"
|
||||
execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(0)"
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# Schema has been initialized
|
||||
end
|
||||
end
|
||||
|
||||
def dump_schema_information #:nodoc:
|
||||
begin
|
||||
if (current_schema = ActiveRecord::Migrator.current_version) > 0
|
||||
return "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})"
|
||||
end
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# No Schema Info
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
||||
if native = native_database_types[type]
|
||||
column_type_sql = native.is_a?(Hash) ? native[:name] : native
|
||||
if type == :decimal # ignore limit, use precision and scale
|
||||
precision ||= native[:precision]
|
||||
scale ||= native[:scale]
|
||||
if precision
|
||||
if scale
|
||||
column_type_sql << "(#{precision},#{scale})"
|
||||
else
|
||||
column_type_sql << "(#{precision})"
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" if scale
|
||||
end
|
||||
column_type_sql
|
||||
else
|
||||
limit ||= native[:limit]
|
||||
column_type_sql << "(#{limit})" if limit
|
||||
column_type_sql
|
||||
end
|
||||
else
|
||||
column_type_sql = type
|
||||
end
|
||||
end
|
||||
|
||||
def add_column_options!(sql, options) #:nodoc:
|
||||
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
|
||||
sql << " NOT NULL" if options[:null] == false
|
||||
end
|
||||
|
||||
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
||||
# Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
|
||||
#
|
||||
# distinct("posts.id", "posts.created_at desc")
|
||||
def distinct(columns, order_by)
|
||||
"DISTINCT #{columns}"
|
||||
end
|
||||
|
||||
# ORDER BY clause for the passed order option.
|
||||
# PostgreSQL overrides this due to its stricter standards compliance.
|
||||
def add_order_by_for_association_limiting!(sql, options)
|
||||
sql << " ORDER BY #{options[:order]}"
|
||||
end
|
||||
|
||||
protected
|
||||
def options_include_default?(options)
|
||||
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
172
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
vendored
Executable file
172
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
vendored
Executable file
|
|
@ -0,0 +1,172 @@
|
|||
require 'benchmark'
|
||||
require 'date'
|
||||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
|
||||
require 'active_record/connection_adapters/abstract/schema_definitions'
|
||||
require 'active_record/connection_adapters/abstract/schema_statements'
|
||||
require 'active_record/connection_adapters/abstract/database_statements'
|
||||
require 'active_record/connection_adapters/abstract/quoting'
|
||||
require 'active_record/connection_adapters/abstract/connection_specification'
|
||||
require 'active_record/connection_adapters/abstract/query_cache'
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
# All the concrete database adapters follow the interface laid down in this class.
|
||||
# You can use this interface directly by borrowing the database connection from the Base with
|
||||
# Base.connection.
|
||||
#
|
||||
# Most of the methods in the adapter are useful during migrations. Most
|
||||
# notably, SchemaStatements#create_table, SchemaStatements#drop_table,
|
||||
# SchemaStatements#add_index, SchemaStatements#remove_index,
|
||||
# SchemaStatements#add_column, SchemaStatements#change_column and
|
||||
# SchemaStatements#remove_column are very useful.
|
||||
class AbstractAdapter
|
||||
include Quoting, DatabaseStatements, SchemaStatements
|
||||
include QueryCache
|
||||
@@row_even = true
|
||||
|
||||
def initialize(connection, logger = nil) #:nodoc:
|
||||
@connection, @logger = connection, logger
|
||||
@runtime = 0
|
||||
@last_verification = 0
|
||||
end
|
||||
|
||||
# Returns the human-readable name of the adapter. Use mixed case - one
|
||||
# can always use downcase if needed.
|
||||
def adapter_name
|
||||
'Abstract'
|
||||
end
|
||||
|
||||
# Does this adapter support migrations? Backend specific, as the
|
||||
# abstract adapter always returns +false+.
|
||||
def supports_migrations?
|
||||
false
|
||||
end
|
||||
|
||||
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
||||
# for all adapters except sqlite.
|
||||
def supports_count_distinct?
|
||||
true
|
||||
end
|
||||
|
||||
# Should primary key values be selected from their corresponding
|
||||
# sequence before the insert statement? If true, next_sequence_value
|
||||
# is called before each insert to set the record's primary key.
|
||||
# This is false for all adapters but Firebird.
|
||||
def prefetch_primary_key?(table_name = nil)
|
||||
false
|
||||
end
|
||||
|
||||
def reset_runtime #:nodoc:
|
||||
rt, @runtime = @runtime, 0
|
||||
rt
|
||||
end
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
# Override to return the quoted table name if the database needs it
|
||||
def quote_table_name(name)
|
||||
name
|
||||
end
|
||||
|
||||
# REFERENTIAL INTEGRITY ====================================
|
||||
|
||||
# Override to turn off referential integrity while executing +&block+
|
||||
def disable_referential_integrity(&block)
|
||||
yield
|
||||
end
|
||||
|
||||
# CONNECTION MANAGEMENT ====================================
|
||||
|
||||
# Is this connection active and ready to perform queries?
|
||||
def active?
|
||||
@active != false
|
||||
end
|
||||
|
||||
# Close this connection and open a new one in its place.
|
||||
def reconnect!
|
||||
@active = true
|
||||
end
|
||||
|
||||
# Close this connection
|
||||
def disconnect!
|
||||
@active = false
|
||||
end
|
||||
|
||||
# Returns true if its safe to reload the connection between requests for development mode.
|
||||
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
|
||||
def requires_reloading?
|
||||
false
|
||||
end
|
||||
|
||||
# Lazily verify this connection, calling +active?+ only if it hasn't
|
||||
# been called for +timeout+ seconds.
|
||||
def verify!(timeout)
|
||||
now = Time.now.to_i
|
||||
if (now - @last_verification) > timeout
|
||||
reconnect! unless active?
|
||||
@last_verification = now
|
||||
end
|
||||
end
|
||||
|
||||
# Provides access to the underlying database connection. Useful for
|
||||
# when you need to call a proprietary method such as postgresql's lo_*
|
||||
# methods
|
||||
def raw_connection
|
||||
@connection
|
||||
end
|
||||
|
||||
def log_info(sql, name, runtime)
|
||||
if @logger && @logger.debug?
|
||||
name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
|
||||
@logger.debug format_log_entry(name, sql.squeeze(' '))
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def log(sql, name)
|
||||
if block_given?
|
||||
if @logger and @logger.debug?
|
||||
result = nil
|
||||
seconds = Benchmark.realtime { result = yield }
|
||||
@runtime += seconds
|
||||
log_info(sql, name, seconds)
|
||||
result
|
||||
else
|
||||
yield
|
||||
end
|
||||
else
|
||||
log_info(sql, name, 0)
|
||||
nil
|
||||
end
|
||||
rescue Exception => e
|
||||
# Log message and raise exception.
|
||||
# Set last_verification to 0, so that connection gets verified
|
||||
# upon reentering the request loop
|
||||
@last_verification = 0
|
||||
message = "#{e.class.name}: #{e.message}: #{sql}"
|
||||
log_info(message, name, 0)
|
||||
raise ActiveRecord::StatementInvalid, message
|
||||
end
|
||||
|
||||
def format_log_entry(message, dump = nil)
|
||||
if ActiveRecord::Base.colorize_logging
|
||||
if @@row_even
|
||||
@@row_even = false
|
||||
message_color, dump_color = "4;36;1", "0;1"
|
||||
else
|
||||
@@row_even = true
|
||||
message_color, dump_color = "4;35;1", "0"
|
||||
end
|
||||
|
||||
log_entry = " \e[#{message_color}m#{message}\e[0m "
|
||||
log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
|
||||
log_entry
|
||||
else
|
||||
"%s %s" % [message, dump]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
496
vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
vendored
Executable file
496
vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
vendored
Executable file
|
|
@ -0,0 +1,496 @@
|
|||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
require 'set'
|
||||
|
||||
module MysqlCompat #:nodoc:
|
||||
# add all_hashes method to standard mysql-c bindings or pure ruby version
|
||||
def self.define_all_hashes_method!
|
||||
raise 'Mysql not loaded' unless defined?(::Mysql)
|
||||
|
||||
target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
|
||||
return if target.instance_methods.include?('all_hashes')
|
||||
|
||||
# Ruby driver has a version string and returns null values in each_hash
|
||||
# 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
|
||||
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
|
||||
end_eval
|
||||
end
|
||||
|
||||
unless target.instance_methods.include?('all_hashes') ||
|
||||
target.instance_methods.include?(:all_hashes)
|
||||
raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
def self.require_mysql
|
||||
# Include the MySQL driver if one hasn't already been loaded
|
||||
unless defined? Mysql
|
||||
begin
|
||||
require_library_or_gem 'mysql'
|
||||
rescue LoadError => cannot_require_mysql
|
||||
# Use the bundled Ruby/MySQL driver if no driver is already in place
|
||||
begin
|
||||
ActiveRecord::Base.logger.info(
|
||||
"WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. " +
|
||||
"Please install the C-based MySQL library instead (gem install mysql)."
|
||||
) if ActiveRecord::Base.logger
|
||||
|
||||
require 'active_record/vendor/mysql'
|
||||
rescue LoadError
|
||||
raise cannot_require_mysql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Define Mysql::Result.all_hashes
|
||||
MysqlCompat.define_all_hashes_method!
|
||||
end
|
||||
|
||||
# Establishes a connection to the database that's used by all Active Record objects.
|
||||
def self.mysql_connection(config) # :nodoc:
|
||||
config = config.symbolize_keys
|
||||
host = config[:host]
|
||||
port = config[:port]
|
||||
socket = config[:socket]
|
||||
username = config[:username] ? config[:username].to_s : 'root'
|
||||
password = config[:password].to_s
|
||||
|
||||
if config.has_key?(:database)
|
||||
database = config[:database]
|
||||
else
|
||||
raise ArgumentError, "No database specified. Missing argument: database."
|
||||
end
|
||||
|
||||
require_mysql
|
||||
mysql = Mysql.init
|
||||
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
|
||||
|
||||
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionAdapters
|
||||
class MysqlColumn < Column #:nodoc:
|
||||
def extract_default(default)
|
||||
if type == :binary || type == :text
|
||||
if default.blank?
|
||||
default
|
||||
else
|
||||
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
||||
end
|
||||
elsif missing_default_forged_as_empty_string?(default)
|
||||
nil
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def simplified_type(field_type)
|
||||
return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
|
||||
return :string if field_type =~ /enum/i
|
||||
super
|
||||
end
|
||||
|
||||
# MySQL misreports NOT NULL column default when none is given.
|
||||
# We can't detect this for columns which may have a legitimate ''
|
||||
# default (string) but we can for others (integer, datetime, boolean,
|
||||
# and the rest).
|
||||
#
|
||||
# Test whether the column has default '', is not null, and is not
|
||||
# a type allowing default ''.
|
||||
def missing_default_forged_as_empty_string?(default)
|
||||
type != :string && !null && default == ''
|
||||
end
|
||||
end
|
||||
|
||||
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
|
||||
# the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * <tt>:host</tt> -- Defaults to localhost
|
||||
# * <tt>:port</tt> -- Defaults to 3306
|
||||
# * <tt>:socket</tt> -- Defaults to /tmp/mysql.sock
|
||||
# * <tt>:username</tt> -- Defaults to root
|
||||
# * <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>: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 tinyint(1)
|
||||
# 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
|
||||
@@emulate_booleans = true
|
||||
cattr_accessor :emulate_booleans
|
||||
|
||||
LOST_CONNECTION_ERROR_MESSAGES = [
|
||||
"Server shutdown in progress",
|
||||
"Broken pipe",
|
||||
"Lost connection to MySQL server during query",
|
||||
"MySQL server has gone away"
|
||||
]
|
||||
|
||||
def initialize(connection, logger, connection_options, config)
|
||||
super(connection, logger)
|
||||
@connection_options, @config = connection_options, config
|
||||
|
||||
connect
|
||||
end
|
||||
|
||||
def adapter_name #:nodoc:
|
||||
'MySQL'
|
||||
end
|
||||
|
||||
def supports_migrations? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def native_database_types #:nodoc:
|
||||
{
|
||||
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
||||
:string => { :name => "varchar", :limit => 255 },
|
||||
:text => { :name => "text" },
|
||||
:integer => { :name => "int", :limit => 11 },
|
||||
:float => { :name => "float" },
|
||||
:decimal => { :name => "decimal" },
|
||||
:datetime => { :name => "datetime" },
|
||||
:timestamp => { :name => "datetime" },
|
||||
:time => { :name => "time" },
|
||||
:date => { :name => "date" },
|
||||
:binary => { :name => "blob" },
|
||||
:boolean => { :name => "tinyint", :limit => 1 }
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
def quote(value, column = nil)
|
||||
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
||||
s = column.class.string_to_binary(value).unpack("H*")[0]
|
||||
"x'#{s}'"
|
||||
elsif value.kind_of?(BigDecimal)
|
||||
"'#{value.to_s("F")}'"
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def quote_column_name(name) #:nodoc:
|
||||
"`#{name}`"
|
||||
end
|
||||
|
||||
def quote_table_name(name) #:nodoc:
|
||||
quote_column_name(name).gsub('.', '`.`')
|
||||
end
|
||||
|
||||
def quote_string(string) #:nodoc:
|
||||
@connection.quote(string)
|
||||
end
|
||||
|
||||
def quoted_true
|
||||
"1"
|
||||
end
|
||||
|
||||
def quoted_false
|
||||
"0"
|
||||
end
|
||||
|
||||
# REFERENTIAL INTEGRITY ====================================
|
||||
|
||||
def disable_referential_integrity(&block) #:nodoc:
|
||||
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
||||
|
||||
begin
|
||||
update("SET FOREIGN_KEY_CHECKS = 0")
|
||||
yield
|
||||
ensure
|
||||
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
||||
end
|
||||
end
|
||||
|
||||
# CONNECTION MANAGEMENT ====================================
|
||||
|
||||
def active?
|
||||
if @connection.respond_to?(:stat)
|
||||
@connection.stat
|
||||
else
|
||||
@connection.query 'select 1'
|
||||
end
|
||||
|
||||
# mysql-ruby doesn't raise an exception when stat fails.
|
||||
if @connection.respond_to?(:errno)
|
||||
@connection.errno.zero?
|
||||
else
|
||||
true
|
||||
end
|
||||
rescue Mysql::Error
|
||||
false
|
||||
end
|
||||
|
||||
def reconnect!
|
||||
disconnect!
|
||||
connect
|
||||
end
|
||||
|
||||
def disconnect!
|
||||
@connection.close rescue nil
|
||||
end
|
||||
|
||||
|
||||
# DATABASE STATEMENTS ======================================
|
||||
|
||||
def select_rows(sql, name = nil)
|
||||
@connection.query_with_result = true
|
||||
result = execute(sql, name)
|
||||
rows = []
|
||||
result.each { |row| rows << row }
|
||||
result.free
|
||||
rows
|
||||
end
|
||||
|
||||
def execute(sql, name = nil) #:nodoc:
|
||||
log(sql, name) { @connection.query(sql) }
|
||||
rescue ActiveRecord::StatementInvalid => exception
|
||||
if exception.message.split(":").first =~ /Packets out of order/
|
||||
raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
||||
super sql, name
|
||||
id_value || @connection.insert_id
|
||||
end
|
||||
|
||||
def update_sql(sql, name = nil) #:nodoc:
|
||||
super
|
||||
@connection.affected_rows
|
||||
end
|
||||
|
||||
def begin_db_transaction #:nodoc:
|
||||
execute "BEGIN"
|
||||
rescue Exception
|
||||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
def commit_db_transaction #:nodoc:
|
||||
execute "COMMIT"
|
||||
rescue Exception
|
||||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
def rollback_db_transaction #:nodoc:
|
||||
execute "ROLLBACK"
|
||||
rescue Exception
|
||||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
|
||||
def add_limit_offset!(sql, options) #:nodoc:
|
||||
if limit = options[:limit]
|
||||
unless offset = options[:offset]
|
||||
sql << " LIMIT #{limit}"
|
||||
else
|
||||
sql << " LIMIT #{offset}, #{limit}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
def structure_dump #:nodoc:
|
||||
if supports_views?
|
||||
sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
||||
else
|
||||
sql = "SHOW TABLES"
|
||||
end
|
||||
|
||||
select_all(sql).inject("") do |structure, table|
|
||||
table.delete('Table_type')
|
||||
structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
|
||||
end
|
||||
end
|
||||
|
||||
def recreate_database(name) #:nodoc:
|
||||
drop_database(name)
|
||||
create_database(name)
|
||||
end
|
||||
|
||||
# Create a new MySQL database with optional :charset and :collation.
|
||||
# Charset defaults to utf8.
|
||||
#
|
||||
# Example:
|
||||
# create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
|
||||
# create_database 'matt_development'
|
||||
# create_database 'matt_development', :charset => :big5
|
||||
def create_database(name, options = {})
|
||||
if options[:collation]
|
||||
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
||||
else
|
||||
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
|
||||
end
|
||||
end
|
||||
|
||||
def drop_database(name) #:nodoc:
|
||||
execute "DROP DATABASE IF EXISTS `#{name}`"
|
||||
end
|
||||
|
||||
def current_database
|
||||
select_value 'SELECT DATABASE() as db'
|
||||
end
|
||||
|
||||
# Returns the database character set.
|
||||
def charset
|
||||
show_variable 'character_set_database'
|
||||
end
|
||||
|
||||
# Returns the database collation strategy.
|
||||
def collation
|
||||
show_variable 'collation_database'
|
||||
end
|
||||
|
||||
def tables(name = nil) #:nodoc:
|
||||
tables = []
|
||||
execute("SHOW TABLES", name).each { |field| tables << field[0] }
|
||||
tables
|
||||
end
|
||||
|
||||
def drop_table(table_name, options = {})
|
||||
super(table_name, options)
|
||||
end
|
||||
|
||||
def indexes(table_name, name = nil)#:nodoc:
|
||||
indexes = []
|
||||
current_index = nil
|
||||
execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
|
||||
if current_index != row[2]
|
||||
next if row[2] == "PRIMARY" # skip the primary key
|
||||
current_index = row[2]
|
||||
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
|
||||
end
|
||||
|
||||
indexes.last.columns << row[4]
|
||||
end
|
||||
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") }
|
||||
columns
|
||||
end
|
||||
|
||||
def create_table(table_name, options = {}) #:nodoc:
|
||||
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
||||
end
|
||||
|
||||
def rename_table(table_name, new_name)
|
||||
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default) #:nodoc:
|
||||
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
||||
|
||||
execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
unless options_include_default?(options)
|
||||
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
||||
options[:default] = column.default
|
||||
else
|
||||
raise "No such column: #{table_name}.#{column_name}"
|
||||
end
|
||||
end
|
||||
|
||||
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
||||
add_column_options!(change_column_sql, options)
|
||||
execute(change_column_sql)
|
||||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
||||
execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
||||
end
|
||||
|
||||
|
||||
# SHOW VARIABLES LIKE 'name'
|
||||
def show_variable(name)
|
||||
variables = select_all("SHOW VARIABLES LIKE '#{name}'")
|
||||
variables.first['Value'] unless variables.empty?
|
||||
end
|
||||
|
||||
# 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|
|
||||
keys << h["Field"]if h["Key"] == "PRI"
|
||||
end
|
||||
keys.length == 1 ? [keys.first, nil] : nil
|
||||
end
|
||||
|
||||
private
|
||||
def connect
|
||||
encoding = @config[:encoding]
|
||||
if encoding
|
||||
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
||||
end
|
||||
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
|
||||
@connection.real_connect(*@connection_options)
|
||||
execute("SET NAMES '#{encoding}'") if encoding
|
||||
|
||||
# By default, MySQL 'where id is null' selects the last inserted id.
|
||||
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
||||
execute("SET SQL_AUTO_IS_NULL=0")
|
||||
end
|
||||
|
||||
def select(sql, name = nil)
|
||||
@connection.query_with_result = true
|
||||
result = execute(sql, name)
|
||||
rows = result.all_hashes
|
||||
result.free
|
||||
rows
|
||||
end
|
||||
|
||||
def supports_views?
|
||||
version[0] >= 5
|
||||
end
|
||||
|
||||
def version
|
||||
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
847
vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
vendored
Normal file
847
vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
vendored
Normal file
|
|
@ -0,0 +1,847 @@
|
|||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
# Establishes a connection to the database that's used by all Active Record objects
|
||||
def self.postgresql_connection(config) # :nodoc:
|
||||
require_library_or_gem 'postgres' unless self.class.const_defined?(:PGconn)
|
||||
|
||||
config = config.symbolize_keys
|
||||
host = config[:host]
|
||||
port = config[:port] || 5432
|
||||
username = config[:username].to_s
|
||||
password = config[:password].to_s
|
||||
|
||||
if config.has_key?(:database)
|
||||
database = config[:database]
|
||||
else
|
||||
raise ArgumentError, "No database specified. Missing argument: database."
|
||||
end
|
||||
|
||||
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
||||
# so just pass a nil connection object for the time being.
|
||||
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionAdapters
|
||||
# PostgreSQL-specific extensions to column definitions in a table.
|
||||
class PostgreSQLColumn < Column #:nodoc:
|
||||
# Instantiates a new PostgreSQL column definition in a table.
|
||||
def initialize(name, default, sql_type = nil, null = true)
|
||||
super(name, self.class.extract_value_from_default(default), sql_type, null)
|
||||
end
|
||||
|
||||
private
|
||||
# Extracts the scale from PostgreSQL-specific data types.
|
||||
def extract_scale(sql_type)
|
||||
# Money type has a fixed scale of 2.
|
||||
sql_type =~ /^money/ ? 2 : super
|
||||
end
|
||||
|
||||
# Extracts the precision from PostgreSQL-specific data types.
|
||||
def extract_precision(sql_type)
|
||||
# Actual code is defined dynamically in PostgreSQLAdapter.connect
|
||||
# depending on the server specifics
|
||||
super
|
||||
end
|
||||
|
||||
# Escapes binary strings for bytea input to the database.
|
||||
def self.string_to_binary(value)
|
||||
if PGconn.respond_to?(:escape_bytea)
|
||||
self.class.module_eval do
|
||||
define_method(:string_to_binary) do |value|
|
||||
PGconn.escape_bytea(value) if value
|
||||
end
|
||||
end
|
||||
else
|
||||
self.class.module_eval do
|
||||
define_method(:string_to_binary) do |value|
|
||||
if value
|
||||
result = ''
|
||||
value.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.class.string_to_binary(value)
|
||||
end
|
||||
|
||||
# Unescapes bytea output from a database to the binary string it represents.
|
||||
def self.binary_to_string(value)
|
||||
# In each case, check if the value actually is escaped PostgreSQL bytea output
|
||||
# or an unescaped Active Record attribute that was just written.
|
||||
if PGconn.respond_to?(:unescape_bytea)
|
||||
self.class.module_eval do
|
||||
define_method(:binary_to_string) do |value|
|
||||
if value =~ /\\\d{3}/
|
||||
PGconn.unescape_bytea(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self.class.module_eval do
|
||||
define_method(:binary_to_string) do |value|
|
||||
if value =~ /\\\d{3}/
|
||||
result = ''
|
||||
i, max = 0, value.size
|
||||
while i < max
|
||||
char = value[i]
|
||||
if char == ?\\
|
||||
if value[i+1] == ?\\
|
||||
char = ?\\
|
||||
i += 1
|
||||
else
|
||||
char = value[i+1..i+3].oct
|
||||
i += 3
|
||||
end
|
||||
end
|
||||
result << char
|
||||
i += 1
|
||||
end
|
||||
result
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.class.binary_to_string(value)
|
||||
end
|
||||
|
||||
# Maps PostgreSQL-specific data types to logical Rails types.
|
||||
def simplified_type(field_type)
|
||||
case field_type
|
||||
# Numeric and monetary types
|
||||
when /^(?:real|double precision)$/
|
||||
:float
|
||||
# Monetary types
|
||||
when /^money$/
|
||||
:decimal
|
||||
# Character types
|
||||
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
||||
:string
|
||||
# Binary data types
|
||||
when /^bytea$/
|
||||
:binary
|
||||
# Date/time types
|
||||
when /^timestamp with(?:out)? time zone$/
|
||||
:datetime
|
||||
when /^interval$/
|
||||
:string
|
||||
# Geometric types
|
||||
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
||||
:string
|
||||
# Network address types
|
||||
when /^(?:cidr|inet|macaddr)$/
|
||||
:string
|
||||
# Bit strings
|
||||
when /^bit(?: varying)?(?:\(\d+\))?$/
|
||||
:string
|
||||
# XML type
|
||||
when /^xml$/
|
||||
:string
|
||||
# Arrays
|
||||
when /^\D+\[\]$/
|
||||
:string
|
||||
# Object identifier types
|
||||
when /^oid$/
|
||||
:integer
|
||||
# Pass through all types that are not specific to PostgreSQL.
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Extracts the value from a PostgreSQL column default definition.
|
||||
def self.extract_value_from_default(default)
|
||||
case default
|
||||
# Numeric types
|
||||
when /\A-?\d+(\.\d*)?\z/
|
||||
default
|
||||
# Character types
|
||||
when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
|
||||
$1
|
||||
# Character types (8.1 formatting)
|
||||
when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
|
||||
$1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
|
||||
# Binary data types
|
||||
when /\A'(.*)'::bytea\z/m
|
||||
$1
|
||||
# Date/time types
|
||||
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
||||
$1
|
||||
when /\A'(.*)'::interval\z/
|
||||
$1
|
||||
# Boolean type
|
||||
when 'true'
|
||||
true
|
||||
when 'false'
|
||||
false
|
||||
# Geometric types
|
||||
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
||||
$1
|
||||
# Network address types
|
||||
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
||||
$1
|
||||
# Bit string types
|
||||
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
||||
$1
|
||||
# XML type
|
||||
when /\A'(.*)'::xml\z/m
|
||||
$1
|
||||
# Arrays
|
||||
when /\A'(.*)'::"?\D+"?\[\]\z/
|
||||
$1
|
||||
# Object identifier types
|
||||
when /\A-?\d+\z/
|
||||
$1
|
||||
else
|
||||
# Anything else is blank, some user type, or some function
|
||||
# and we can't know the value of that, so return nil.
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionAdapters
|
||||
# The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
|
||||
# Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * <tt>:host</tt> -- Defaults to localhost
|
||||
# * <tt>:port</tt> -- Defaults to 5432
|
||||
# * <tt>:username</tt> -- Defaults to nothing
|
||||
# * <tt>:password</tt> -- Defaults to nothing
|
||||
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
||||
# * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option.
|
||||
# * <tt>:encoding</tt> -- An optional client encoding that is used in a SET client_encoding TO <encoding> call on the connection.
|
||||
# * <tt>:min_messages</tt> -- An optional client min messages that is used in a SET client_min_messages TO <min_messages> call on the connection.
|
||||
# * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
|
||||
class PostgreSQLAdapter < AbstractAdapter
|
||||
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
||||
def adapter_name
|
||||
'PostgreSQL'
|
||||
end
|
||||
|
||||
# Initializes and connects a PostgreSQL adapter.
|
||||
def initialize(connection, logger, connection_parameters, config)
|
||||
super(connection, logger)
|
||||
@connection_parameters, @config = connection_parameters, config
|
||||
|
||||
connect
|
||||
end
|
||||
|
||||
# Is this connection alive and ready for queries?
|
||||
def active?
|
||||
if @connection.respond_to?(:status)
|
||||
@connection.status == PGconn::CONNECTION_OK
|
||||
else
|
||||
# We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
|
||||
@connection.query 'SELECT 1'
|
||||
true
|
||||
end
|
||||
# postgres-pr raises a NoMethodError when querying if no connection is available.
|
||||
rescue PGError, NoMethodError
|
||||
false
|
||||
end
|
||||
|
||||
# Close then reopen the connection.
|
||||
def reconnect!
|
||||
if @connection.respond_to?(:reset)
|
||||
@connection.reset
|
||||
configure_connection
|
||||
else
|
||||
disconnect!
|
||||
connect
|
||||
end
|
||||
end
|
||||
|
||||
# Close the connection.
|
||||
def disconnect!
|
||||
@connection.close rescue nil
|
||||
end
|
||||
|
||||
def native_database_types #:nodoc:
|
||||
{
|
||||
:primary_key => "serial primary key",
|
||||
:string => { :name => "character varying", :limit => 255 },
|
||||
:text => { :name => "text" },
|
||||
:integer => { :name => "integer" },
|
||||
:float => { :name => "float" },
|
||||
:decimal => { :name => "decimal" },
|
||||
:datetime => { :name => "timestamp" },
|
||||
:timestamp => { :name => "timestamp" },
|
||||
:time => { :name => "time" },
|
||||
:date => { :name => "date" },
|
||||
:binary => { :name => "bytea" },
|
||||
:boolean => { :name => "boolean" }
|
||||
}
|
||||
end
|
||||
|
||||
# Does PostgreSQL support migrations?
|
||||
def supports_migrations?
|
||||
true
|
||||
end
|
||||
|
||||
# Does PostgreSQL support standard conforming strings?
|
||||
def supports_standard_conforming_strings?
|
||||
# Temporarily set the client message level above error to prevent unintentional
|
||||
# error messages in the logs when working on a PostgreSQL database server that
|
||||
# does not support standard conforming strings.
|
||||
client_min_messages_old = client_min_messages
|
||||
self.client_min_messages = 'panic'
|
||||
|
||||
# postgres-pr does not raise an exception when client_min_messages is set higher
|
||||
# than error and "SHOW standard_conforming_strings" fails, but returns an empty
|
||||
# PGresult instead.
|
||||
has_support = execute('SHOW standard_conforming_strings')[0][0] rescue false
|
||||
self.client_min_messages = client_min_messages_old
|
||||
has_support
|
||||
end
|
||||
|
||||
# Returns the configured supported identifier length supported by PostgreSQL,
|
||||
# or report the default of 63 on PostgreSQL 7.x.
|
||||
def table_alias_length
|
||||
@table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
|
||||
end
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
# Quotes PostgreSQL-specific data types for SQL input.
|
||||
def quote(value, column = nil) #:nodoc:
|
||||
if value.kind_of?(String) && column && column.type == :binary
|
||||
"#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
|
||||
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
|
||||
"xml '#{quote_string(value)}'"
|
||||
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
|
||||
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
||||
"'#{value.to_s}'"
|
||||
elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
|
||||
case value
|
||||
when /^[01]*$/
|
||||
"B'#{value}'" # Bit-string notation
|
||||
when /^[0-9A-F]*$/i
|
||||
"X'#{value}'" # Hexadecimal notation
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Quotes strings for use in SQL input in the postgres driver for better performance.
|
||||
def quote_string(s) #:nodoc:
|
||||
if PGconn.respond_to?(:escape)
|
||||
self.class.instance_eval do
|
||||
define_method(:quote_string) do |s|
|
||||
PGconn.escape(s)
|
||||
end
|
||||
end
|
||||
else
|
||||
# There are some incorrectly compiled postgres drivers out there
|
||||
# that don't define PGconn.escape.
|
||||
self.class.instance_eval do
|
||||
undef_method(:quote_string)
|
||||
end
|
||||
end
|
||||
quote_string(s)
|
||||
end
|
||||
|
||||
# Quotes column names for use in SQL queries.
|
||||
def quote_column_name(name) #:nodoc:
|
||||
%("#{name}")
|
||||
end
|
||||
|
||||
# Quote date/time values for use in SQL input. Includes microseconds
|
||||
# if the value is a Time responding to usec.
|
||||
def quoted_date(value) #:nodoc:
|
||||
if value.acts_like?(:time) && value.respond_to?(:usec)
|
||||
"#{super}.#{sprintf("%06d", value.usec)}"
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# REFERENTIAL INTEGRITY ====================================
|
||||
|
||||
def disable_referential_integrity(&block) #:nodoc:
|
||||
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
||||
yield
|
||||
ensure
|
||||
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
||||
end
|
||||
|
||||
# DATABASE STATEMENTS ======================================
|
||||
|
||||
# Executes a SELECT query and returns an array of rows. Each row is an
|
||||
# array of field values.
|
||||
def select_rows(sql, name = nil)
|
||||
select_raw(sql, name).last
|
||||
end
|
||||
|
||||
# Executes an INSERT query and returns the new record's ID
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
table = sql.split(" ", 4)[2]
|
||||
super || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
|
||||
end
|
||||
|
||||
# Queries the database and returns the results in an Array or nil otherwise.
|
||||
def query(sql, name = nil) #:nodoc:
|
||||
log(sql, name) do
|
||||
if @async
|
||||
@connection.async_query(sql)
|
||||
else
|
||||
@connection.query(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Executes an SQL statement, returning a PGresult object on success
|
||||
# or raising a PGError exception otherwise.
|
||||
def execute(sql, name = nil)
|
||||
log(sql, name) do
|
||||
if @async
|
||||
@connection.async_exec(sql)
|
||||
else
|
||||
@connection.exec(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Executes an UPDATE query and returns the number of affected tuples.
|
||||
def update_sql(sql, name = nil)
|
||||
super.cmdtuples
|
||||
end
|
||||
|
||||
# Begins a transaction.
|
||||
def begin_db_transaction
|
||||
execute "BEGIN"
|
||||
end
|
||||
|
||||
# Commits a transaction.
|
||||
def commit_db_transaction
|
||||
execute "COMMIT"
|
||||
end
|
||||
|
||||
# Aborts a transaction.
|
||||
def rollback_db_transaction
|
||||
execute "ROLLBACK"
|
||||
end
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
# Returns the list of all tables in the schema search path or a specified schema.
|
||||
def tables(name = nil)
|
||||
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
||||
query(<<-SQL, name).map { |row| row[0] }
|
||||
SELECT tablename
|
||||
FROM pg_tables
|
||||
WHERE schemaname IN (#{schemas})
|
||||
SQL
|
||||
end
|
||||
|
||||
# Returns the list of all indexes for a table.
|
||||
def indexes(table_name, name = nil)
|
||||
result = query(<<-SQL, name)
|
||||
SELECT i.relname, d.indisunique, a.attname
|
||||
FROM pg_class t, pg_class i, pg_index d, pg_attribute a
|
||||
WHERE i.relkind = 'i'
|
||||
AND d.indexrelid = i.oid
|
||||
AND d.indisprimary = 'f'
|
||||
AND t.oid = d.indrelid
|
||||
AND t.relname = '#{table_name}'
|
||||
AND a.attrelid = t.oid
|
||||
AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
|
||||
OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
|
||||
OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
|
||||
OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
|
||||
OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
|
||||
ORDER BY i.relname
|
||||
SQL
|
||||
|
||||
current_index = nil
|
||||
indexes = []
|
||||
|
||||
result.each do |row|
|
||||
if current_index != row[0]
|
||||
indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
|
||||
current_index = row[0]
|
||||
end
|
||||
|
||||
indexes.last.columns << row[2]
|
||||
end
|
||||
|
||||
indexes
|
||||
end
|
||||
|
||||
# Returns the list of all column definitions for a table.
|
||||
def columns(table_name, name = nil)
|
||||
# Limit, precision, and scale are all handled by the superclass.
|
||||
column_definitions(table_name).collect do |name, type, default, notnull|
|
||||
PostgreSQLColumn.new(name, default, type, notnull == 'f')
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the schema search path to a string of comma-separated schema names.
|
||||
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
||||
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
|
||||
#
|
||||
# This should be not be called manually but set in database.yml.
|
||||
def schema_search_path=(schema_csv)
|
||||
if schema_csv
|
||||
execute "SET search_path TO #{schema_csv}"
|
||||
@schema_search_path = schema_csv
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the active schema search path.
|
||||
def schema_search_path
|
||||
@schema_search_path ||= query('SHOW search_path')[0][0]
|
||||
end
|
||||
|
||||
# Returns the current client message level.
|
||||
def client_min_messages
|
||||
query('SHOW client_min_messages')[0][0]
|
||||
end
|
||||
|
||||
# Set the client message level.
|
||||
def client_min_messages=(level)
|
||||
execute("SET client_min_messages TO '#{level}'")
|
||||
end
|
||||
|
||||
# Returns the sequence name for a table's primary key or some other specified key.
|
||||
def default_sequence_name(table_name, pk = nil) #:nodoc:
|
||||
default_pk, default_seq = pk_and_sequence_for(table_name)
|
||||
default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
|
||||
end
|
||||
|
||||
# Resets the sequence of a table's primary key to the maximum value.
|
||||
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
||||
unless pk and sequence
|
||||
default_pk, default_sequence = pk_and_sequence_for(table)
|
||||
pk ||= default_pk
|
||||
sequence ||= default_sequence
|
||||
end
|
||||
if pk
|
||||
if sequence
|
||||
select_value <<-end_sql, 'Reset sequence'
|
||||
SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
|
||||
end_sql
|
||||
else
|
||||
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a table's primary key and belonging sequence.
|
||||
def pk_and_sequence_for(table) #:nodoc:
|
||||
# First try looking for a sequence with a dependency on the
|
||||
# given table's primary key.
|
||||
result = query(<<-end_sql, 'PK and serial sequence')[0]
|
||||
SELECT attr.attname, seq.relname
|
||||
FROM pg_class seq,
|
||||
pg_attribute attr,
|
||||
pg_depend dep,
|
||||
pg_namespace name,
|
||||
pg_constraint cons
|
||||
WHERE seq.oid = dep.objid
|
||||
AND seq.relkind = 'S'
|
||||
AND attr.attrelid = dep.refobjid
|
||||
AND attr.attnum = dep.refobjsubid
|
||||
AND attr.attrelid = cons.conrelid
|
||||
AND attr.attnum = cons.conkey[1]
|
||||
AND cons.contype = 'p'
|
||||
AND dep.refobjid = '#{table}'::regclass
|
||||
end_sql
|
||||
|
||||
if result.nil? or result.empty?
|
||||
# If that fails, try parsing the primary key's default value.
|
||||
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
||||
# the 8.1+ nextval('foo'::regclass).
|
||||
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
||||
SELECT attr.attname, split_part(def.adsrc, '''', 2)
|
||||
FROM pg_class t
|
||||
JOIN pg_attribute attr ON (t.oid = attrelid)
|
||||
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
||||
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
||||
WHERE t.oid = '#{table}'::regclass
|
||||
AND cons.contype = 'p'
|
||||
AND def.adsrc ~* 'nextval'
|
||||
end_sql
|
||||
end
|
||||
# [primary_key, sequence]
|
||||
[result.first, result.last]
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
# Renames a table.
|
||||
def rename_table(name, new_name)
|
||||
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
||||
end
|
||||
|
||||
# Adds a column to a table.
|
||||
def add_column(table_name, column_name, type, options = {})
|
||||
default = options[:default]
|
||||
notnull = options[:null] == false
|
||||
|
||||
# Add the column.
|
||||
execute("ALTER TABLE #{table_name} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}")
|
||||
|
||||
change_column_default(table_name, column_name, default) if options_include_default?(options)
|
||||
change_column_null(table_name, column_name, false, default) if notnull
|
||||
end
|
||||
|
||||
# Changes the column of a table.
|
||||
def change_column(table_name, column_name, type, options = {})
|
||||
begin
|
||||
execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
|
||||
begin_db_transaction
|
||||
tmp_column_name = "#{column_name}_ar_tmp"
|
||||
add_column(table_name, tmp_column_name, type, options)
|
||||
execute "UPDATE #{table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
|
||||
remove_column(table_name, column_name)
|
||||
rename_column(table_name, tmp_column_name, column_name)
|
||||
commit_db_transaction
|
||||
end
|
||||
|
||||
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
||||
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
||||
end
|
||||
|
||||
# Changes the default value of a table column.
|
||||
def change_column_default(table_name, column_name, default)
|
||||
execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
||||
end
|
||||
|
||||
def change_column_null(table_name, column_name, null, default = nil)
|
||||
unless null || default.nil?
|
||||
execute("UPDATE #{table_name} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
||||
end
|
||||
execute("ALTER TABLE #{table_name} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
||||
end
|
||||
|
||||
# Renames a column in a table.
|
||||
def rename_column(table_name, column_name, new_column_name)
|
||||
execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
||||
end
|
||||
|
||||
# Drops an index from a table.
|
||||
def remove_index(table_name, options = {})
|
||||
execute "DROP INDEX #{index_name(table_name, options)}"
|
||||
end
|
||||
|
||||
# Maps logical Rails types to PostgreSQL-specific data types.
|
||||
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
||||
return super unless type.to_s == 'integer'
|
||||
|
||||
if limit.nil? || limit == 4
|
||||
'integer'
|
||||
elsif limit < 4
|
||||
'smallint'
|
||||
else
|
||||
'bigint'
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
||||
#
|
||||
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
||||
# requires that the ORDER BY include the distinct column.
|
||||
#
|
||||
# distinct("posts.id", "posts.created_at desc")
|
||||
def distinct(columns, order_by) #:nodoc:
|
||||
return "DISTINCT #{columns}" if order_by.blank?
|
||||
|
||||
# Construct a clean list of column names from the ORDER BY clause, removing
|
||||
# any ASC/DESC modifiers
|
||||
order_columns = order_by.split(',').collect { |s| s.split.first }
|
||||
order_columns.delete_if &:blank?
|
||||
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
||||
|
||||
# Return a DISTINCT ON() clause that's distinct on the columns we want but includes
|
||||
# all the required columns for the ORDER BY to work properly.
|
||||
sql = "DISTINCT ON (#{columns}) #{columns}, "
|
||||
sql << order_columns * ', '
|
||||
end
|
||||
|
||||
# Returns an ORDER BY clause for the passed order option.
|
||||
#
|
||||
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
|
||||
# by wrapping the sql as a sub-select and ordering in that query.
|
||||
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
|
||||
return sql if options[:order].blank?
|
||||
|
||||
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
||||
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
|
||||
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
|
||||
|
||||
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
|
||||
end
|
||||
|
||||
protected
|
||||
# Returns the version of the connected PostgreSQL version.
|
||||
def postgresql_version
|
||||
@postgresql_version ||=
|
||||
if @connection.respond_to?(:server_version)
|
||||
@connection.server_version
|
||||
else
|
||||
# Mimic PGconn.server_version behavior
|
||||
begin
|
||||
query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
|
||||
($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
|
||||
rescue
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# The internal PostgreSQL identifer of the money data type.
|
||||
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
||||
|
||||
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
||||
# connected server's characteristics.
|
||||
def connect
|
||||
@connection = PGconn.connect(*@connection_parameters)
|
||||
PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
|
||||
|
||||
# Ignore async_exec and async_query when using postgres-pr.
|
||||
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
||||
|
||||
# Use escape string syntax if available. We cannot do this lazily when encountering
|
||||
# the first string, because that could then break any transactions in progress.
|
||||
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
||||
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
|
||||
# support escape string syntax. Don't override the inherited quoted_string_prefix.
|
||||
if supports_standard_conforming_strings?
|
||||
self.class.instance_eval do
|
||||
define_method(:quoted_string_prefix) { 'E' }
|
||||
end
|
||||
end
|
||||
|
||||
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
||||
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
||||
# 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
|
||||
end_eval
|
||||
|
||||
configure_connection
|
||||
end
|
||||
|
||||
# Configures the encoding, verbosity, and schema search path of the connection.
|
||||
# This is called by #connect and should not be called manually.
|
||||
def configure_connection
|
||||
if @config[:encoding]
|
||||
if @connection.respond_to?(:set_client_encoding)
|
||||
@connection.set_client_encoding(@config[:encoding])
|
||||
else
|
||||
execute("SET client_encoding TO '#{@config[:encoding]}'")
|
||||
end
|
||||
end
|
||||
self.client_min_messages = @config[:min_messages] if @config[:min_messages]
|
||||
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
||||
end
|
||||
|
||||
# Returns the current ID of a table's sequence.
|
||||
def last_insert_id(table, sequence_name) #:nodoc:
|
||||
Integer(select_value("SELECT currval('#{sequence_name}')"))
|
||||
end
|
||||
|
||||
# Executes a SELECT query and returns the results, performing any data type
|
||||
# conversions that are required to be performed here instead of in PostgreSQLColumn.
|
||||
def select(sql, name = nil)
|
||||
fields, rows = select_raw(sql, name)
|
||||
result = []
|
||||
for row in rows
|
||||
row_hash = {}
|
||||
fields.each_with_index do |f, i|
|
||||
row_hash[f] = row[i]
|
||||
end
|
||||
result << row_hash
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def select_raw(sql, name = nil)
|
||||
res = execute(sql, name)
|
||||
results = res.result
|
||||
fields = []
|
||||
rows = []
|
||||
if results.length > 0
|
||||
fields = res.fields
|
||||
results.each do |row|
|
||||
hashed_row = {}
|
||||
row.each_index do |cell_index|
|
||||
# If this is a money type column and there are any currency symbols,
|
||||
# then strip them off. Indeed it would be prettier to do this in
|
||||
# PostgreSQLColumn.string_to_decimal but would break form input
|
||||
# fields that call value_before_type_cast.
|
||||
if res.type(cell_index) == MONEY_COLUMN_TYPE_OID
|
||||
# Because money output is formatted according to the locale, there are two
|
||||
# cases to consider (note the decimal separators):
|
||||
# (1) $12,345,678.12
|
||||
# (2) $12.345.678,12
|
||||
case column = row[cell_index]
|
||||
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
||||
row[cell_index] = column.gsub(/[^-\d\.]/, '')
|
||||
when /^-?\D+[\d\.]+,\d{2}$/ # (2)
|
||||
row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
|
||||
end
|
||||
end
|
||||
|
||||
hashed_row[fields[cell_index]] = column
|
||||
end
|
||||
rows << row
|
||||
end
|
||||
end
|
||||
res.clear
|
||||
return fields, rows
|
||||
end
|
||||
|
||||
# Returns the list of a table's column names, data types, and default values.
|
||||
#
|
||||
# The underlying query is roughly:
|
||||
# SELECT column.name, column.type, default.value
|
||||
# FROM column LEFT JOIN default
|
||||
# ON column.table_id = default.table_id
|
||||
# AND column.num = default.column_num
|
||||
# WHERE column.table_id = get_table_id('table_name')
|
||||
# AND column.num > 0
|
||||
# AND NOT column.is_dropped
|
||||
# ORDER BY column.num
|
||||
#
|
||||
# If the table name is not prefixed with a schema, the database will
|
||||
# take the first match from the schema search path.
|
||||
#
|
||||
# Query implementation notes:
|
||||
# - format_type includes the column size constraint, e.g. varchar(50)
|
||||
# - ::regclass is a function that gives the id for a table name
|
||||
def column_definitions(table_name) #:nodoc:
|
||||
query <<-end_sql
|
||||
SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
|
||||
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
||||
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
||||
WHERE a.attrelid = '#{table_name}'::regclass
|
||||
AND a.attnum > 0 AND NOT a.attisdropped
|
||||
ORDER BY a.attnum
|
||||
end_sql
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
34
vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
vendored
Normal file
34
vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
require 'active_record/connection_adapters/sqlite_adapter'
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
# sqlite3 adapter reuses sqlite_connection.
|
||||
def self.sqlite3_connection(config) # :nodoc:
|
||||
parse_sqlite_config!(config)
|
||||
|
||||
unless self.class.const_defined?(:SQLite3)
|
||||
require_library_or_gem(config[:adapter])
|
||||
end
|
||||
|
||||
db = SQLite3::Database.new(
|
||||
config[:database],
|
||||
:results_as_hash => true,
|
||||
:type_translation => false
|
||||
)
|
||||
|
||||
db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
|
||||
|
||||
ConnectionAdapters::SQLite3Adapter.new(db, logger)
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionAdapters #:nodoc:
|
||||
class SQLite3Adapter < SQLiteAdapter # :nodoc:
|
||||
def table_structure(table_name)
|
||||
returning structure = @connection.table_info(table_name) do
|
||||
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
391
vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
vendored
Normal file
391
vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
vendored
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
class << self
|
||||
# Establishes a connection to the database that's used by all Active Record objects
|
||||
def sqlite_connection(config) # :nodoc:
|
||||
parse_sqlite_config!(config)
|
||||
|
||||
unless self.class.const_defined?(:SQLite)
|
||||
require_library_or_gem(config[:adapter])
|
||||
|
||||
db = SQLite::Database.new(config[:database], 0)
|
||||
db.show_datatypes = "ON" if !defined? SQLite::Version
|
||||
db.results_as_hash = true if defined? SQLite::Version
|
||||
db.type_translation = false
|
||||
|
||||
# "Downgrade" deprecated sqlite API
|
||||
if SQLite.const_defined?(:Version)
|
||||
ConnectionAdapters::SQLite2Adapter.new(db, logger)
|
||||
else
|
||||
ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def parse_sqlite_config!(config)
|
||||
config[:database] ||= config[:dbfile]
|
||||
# Require database.
|
||||
unless config[:database]
|
||||
raise ArgumentError, "No database file specified. Missing argument: database"
|
||||
end
|
||||
|
||||
# Allow database path relative to RAILS_ROOT, but only if
|
||||
# the database path is not the special path that tells
|
||||
# Sqlite to build a database only in memory.
|
||||
if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
|
||||
config[:database] = File.expand_path(config[:database], RAILS_ROOT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionAdapters #:nodoc:
|
||||
class SQLiteColumn < Column #:nodoc:
|
||||
class << self
|
||||
def string_to_binary(value)
|
||||
value.gsub(/\0|\%/n) do |b|
|
||||
case b
|
||||
when "\0" then "%00"
|
||||
when "%" then "%25"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def binary_to_string(value)
|
||||
value.gsub(/%00|%25/n) do |b|
|
||||
case b
|
||||
when "%00" then "\0"
|
||||
when "%25" then "%"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
|
||||
# from http://rubyforge.org/projects/sqlite-ruby/).
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * <tt>:database</tt> -- Path to the database file.
|
||||
class SQLiteAdapter < AbstractAdapter
|
||||
def adapter_name #:nodoc:
|
||||
'SQLite'
|
||||
end
|
||||
|
||||
def supports_migrations? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def requires_reloading?
|
||||
true
|
||||
end
|
||||
|
||||
def disconnect!
|
||||
super
|
||||
@connection.close rescue nil
|
||||
end
|
||||
|
||||
def supports_count_distinct? #:nodoc:
|
||||
sqlite_version >= '3.2.6'
|
||||
end
|
||||
|
||||
def supports_autoincrement? #:nodoc:
|
||||
sqlite_version >= '3.1.0'
|
||||
end
|
||||
|
||||
def native_database_types #:nodoc:
|
||||
{
|
||||
:primary_key => default_primary_key_type,
|
||||
:string => { :name => "varchar", :limit => 255 },
|
||||
:text => { :name => "text" },
|
||||
:integer => { :name => "integer" },
|
||||
:float => { :name => "float" },
|
||||
:decimal => { :name => "decimal" },
|
||||
:datetime => { :name => "datetime" },
|
||||
:timestamp => { :name => "datetime" },
|
||||
:time => { :name => "datetime" },
|
||||
:date => { :name => "date" },
|
||||
:binary => { :name => "blob" },
|
||||
:boolean => { :name => "boolean" }
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
def quote_string(s) #:nodoc:
|
||||
@connection.class.quote(s)
|
||||
end
|
||||
|
||||
def quote_column_name(name) #:nodoc:
|
||||
%Q("#{name}")
|
||||
end
|
||||
|
||||
|
||||
# DATABASE STATEMENTS ======================================
|
||||
|
||||
def execute(sql, name = nil) #:nodoc:
|
||||
catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
|
||||
end
|
||||
|
||||
def update_sql(sql, name = nil) #:nodoc:
|
||||
super
|
||||
@connection.changes
|
||||
end
|
||||
|
||||
def delete_sql(sql, name = nil) #:nodoc:
|
||||
sql += " WHERE 1=1" unless sql =~ /WHERE/i
|
||||
super sql, name
|
||||
end
|
||||
|
||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
||||
super || @connection.last_insert_row_id
|
||||
end
|
||||
|
||||
def select_rows(sql, name = nil)
|
||||
execute(sql, name).map do |row|
|
||||
(0...(row.size / 2)).map { |i| row[i] }
|
||||
end
|
||||
end
|
||||
|
||||
def begin_db_transaction #:nodoc:
|
||||
catch_schema_changes { @connection.transaction }
|
||||
end
|
||||
|
||||
def commit_db_transaction #:nodoc:
|
||||
catch_schema_changes { @connection.commit }
|
||||
end
|
||||
|
||||
def rollback_db_transaction #:nodoc:
|
||||
catch_schema_changes { @connection.rollback }
|
||||
end
|
||||
|
||||
|
||||
# SELECT ... FOR UPDATE is redundant since the table is locked.
|
||||
def add_lock!(sql, options) #:nodoc:
|
||||
sql
|
||||
end
|
||||
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
def tables(name = nil) #:nodoc:
|
||||
sql = <<-SQL
|
||||
SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
|
||||
SQL
|
||||
|
||||
execute(sql, name).map do |row|
|
||||
row[0]
|
||||
end
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil) #:nodoc:
|
||||
table_structure(table_name).map do |field|
|
||||
SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
|
||||
end
|
||||
end
|
||||
|
||||
def indexes(table_name, name = nil) #:nodoc:
|
||||
execute("PRAGMA index_list(#{table_name})", name).map do |row|
|
||||
index = IndexDefinition.new(table_name, row['name'])
|
||||
index.unique = row['unique'] != '0'
|
||||
index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
|
||||
index
|
||||
end
|
||||
end
|
||||
|
||||
def primary_key(table_name) #:nodoc:
|
||||
column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
|
||||
column ? column['name'] : nil
|
||||
end
|
||||
|
||||
def remove_index(table_name, options={}) #:nodoc:
|
||||
execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
|
||||
end
|
||||
|
||||
def rename_table(name, new_name)
|
||||
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
super(table_name, column_name, type, options)
|
||||
# See last paragraph on http://www.sqlite.org/lang_altertable.html
|
||||
execute "VACUUM"
|
||||
end
|
||||
|
||||
def remove_column(table_name, column_name) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition.columns.delete(definition[column_name])
|
||||
end
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition[column_name].default = default
|
||||
end
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
include_default = options_include_default?(options)
|
||||
definition[column_name].instance_eval do
|
||||
self.type = type
|
||||
self.limit = options[:limit] if options.include?(:limit)
|
||||
self.default = options[:default] if include_default
|
||||
self.null = options[:null] if options.include?(:null)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
|
||||
end
|
||||
|
||||
def empty_insert_statement(table_name)
|
||||
"INSERT INTO #{table_name} VALUES(NULL)"
|
||||
end
|
||||
|
||||
protected
|
||||
def select(sql, name = nil) #:nodoc:
|
||||
execute(sql, name).map do |row|
|
||||
record = {}
|
||||
row.each_key do |key|
|
||||
if key.is_a?(String)
|
||||
record[key.sub(/^\w+\./, '')] = row[key]
|
||||
end
|
||||
end
|
||||
record
|
||||
end
|
||||
end
|
||||
|
||||
def table_structure(table_name)
|
||||
returning structure = execute("PRAGMA table_info(#{table_name})") do
|
||||
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
||||
end
|
||||
end
|
||||
|
||||
def alter_table(table_name, options = {}) #:nodoc:
|
||||
altered_table_name = "altered_#{table_name}"
|
||||
caller = lambda {|definition| yield definition if block_given?}
|
||||
|
||||
transaction do
|
||||
move_table(table_name, altered_table_name,
|
||||
options.merge(:temporary => true))
|
||||
move_table(altered_table_name, table_name, &caller)
|
||||
end
|
||||
end
|
||||
|
||||
def move_table(from, to, options = {}, &block) #:nodoc:
|
||||
copy_table(from, to, options, &block)
|
||||
drop_table(from)
|
||||
end
|
||||
|
||||
def copy_table(from, to, options = {}) #:nodoc:
|
||||
options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
|
||||
create_table(to, options) do |definition|
|
||||
@definition = definition
|
||||
columns(from).each do |column|
|
||||
column_name = options[:rename] ?
|
||||
(options[:rename][column.name] ||
|
||||
options[:rename][column.name.to_sym] ||
|
||||
column.name) : column.name
|
||||
|
||||
@definition.column(column_name, column.type,
|
||||
:limit => column.limit, :default => column.default,
|
||||
:null => column.null)
|
||||
end
|
||||
@definition.primary_key(primary_key(from)) if primary_key(from)
|
||||
yield @definition if block_given?
|
||||
end
|
||||
|
||||
copy_table_indexes(from, to)
|
||||
copy_table_contents(from, to,
|
||||
@definition.columns.map {|column| column.name},
|
||||
options[:rename] || {})
|
||||
end
|
||||
|
||||
def copy_table_indexes(from, to) #:nodoc:
|
||||
indexes(from).each do |index|
|
||||
name = index.name
|
||||
if to == "altered_#{from}"
|
||||
name = "temp_#{name}"
|
||||
elsif from == "altered_#{to}"
|
||||
name = name[5..-1]
|
||||
end
|
||||
|
||||
# index name can't be the same
|
||||
opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
|
||||
opts[:unique] = true if index.unique
|
||||
add_index(to, index.columns, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
||||
column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
|
||||
rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
|
||||
from_columns = columns(from).collect {|col| col.name}
|
||||
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
||||
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
||||
|
||||
@connection.execute "SELECT * FROM #{from}" do |row|
|
||||
sql = "INSERT INTO #{to} (#{quoted_columns}) VALUES ("
|
||||
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
|
||||
sql << ')'
|
||||
@connection.execute sql
|
||||
end
|
||||
end
|
||||
|
||||
def catch_schema_changes
|
||||
return yield
|
||||
rescue ActiveRecord::StatementInvalid => exception
|
||||
if exception.message =~ /database schema has changed/
|
||||
reconnect!
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def sqlite_version
|
||||
@sqlite_version ||= select_value('select sqlite_version(*)')
|
||||
end
|
||||
|
||||
def default_primary_key_type
|
||||
if supports_autoincrement?
|
||||
'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
|
||||
else
|
||||
'INTEGER PRIMARY KEY NOT NULL'.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class SQLite2Adapter < SQLiteAdapter # :nodoc:
|
||||
def supports_count_distinct? #:nodoc:
|
||||
false
|
||||
end
|
||||
|
||||
def rename_table(name, new_name)
|
||||
move_table(name, new_name)
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition.column(column_name, type, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil)
|
||||
execute(sql, name = nil)
|
||||
id_value || @connection.last_insert_rowid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
1023
vendor/rails/activerecord/lib/active_record/fixtures.rb
vendored
Executable file
1023
vendor/rails/activerecord/lib/active_record/fixtures.rb
vendored
Executable file
File diff suppressed because it is too large
Load diff
144
vendor/rails/activerecord/lib/active_record/locking/optimistic.rb
vendored
Normal file
144
vendor/rails/activerecord/lib/active_record/locking/optimistic.rb
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
module ActiveRecord
|
||||
module Locking
|
||||
# == What is Optimistic Locking
|
||||
#
|
||||
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
|
||||
# conflicts with the data. It does this by checking whether another process has made changes to a record since
|
||||
# it was opened, an ActiveRecord::StaleObjectError is thrown if that has occurred and the update is ignored.
|
||||
#
|
||||
# Check out ActiveRecord::Locking::Pessimistic for an alternative.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
|
||||
# record increments the lock_version column and the locking facilities ensure that records instantiated twice
|
||||
# will let the last one saved raise a StaleObjectError if the first was also updated. Example:
|
||||
#
|
||||
# p1 = Person.find(1)
|
||||
# p2 = Person.find(1)
|
||||
#
|
||||
# p1.first_name = "Michael"
|
||||
# p1.save
|
||||
#
|
||||
# p2.first_name = "should fail"
|
||||
# p2.save # Raises a ActiveRecord::StaleObjectError
|
||||
#
|
||||
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
||||
# or otherwise apply the business logic needed to resolve the conflict.
|
||||
#
|
||||
# You must ensure that your database schema defaults the lock_version column to 0.
|
||||
#
|
||||
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
|
||||
# To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
|
||||
# This method uses the same syntax as <tt>set_table_name</tt>
|
||||
module Optimistic
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend ClassMethods
|
||||
|
||||
base.cattr_accessor :lock_optimistically, :instance_writer => false
|
||||
base.lock_optimistically = true
|
||||
|
||||
base.alias_method_chain :update, :lock
|
||||
base.alias_method_chain :attributes_from_column_definition, :lock
|
||||
|
||||
class << base
|
||||
alias_method :locking_column=, :set_locking_column
|
||||
end
|
||||
end
|
||||
|
||||
def locking_enabled? #:nodoc:
|
||||
self.class.locking_enabled?
|
||||
end
|
||||
|
||||
private
|
||||
def attributes_from_column_definition_with_lock
|
||||
result = attributes_from_column_definition_without_lock
|
||||
|
||||
# If the locking column has no default value set,
|
||||
# start the lock version at zero. Note we can't use
|
||||
# locking_enabled? at this point as @attributes may
|
||||
# not have been initialized yet
|
||||
|
||||
if lock_optimistically && result.include?(self.class.locking_column)
|
||||
result[self.class.locking_column] ||= 0
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
def update_with_lock #:nodoc:
|
||||
return update_without_lock unless locking_enabled?
|
||||
|
||||
lock_col = self.class.locking_column
|
||||
previous_value = send(lock_col).to_i
|
||||
send(lock_col + '=', previous_value + 1)
|
||||
|
||||
begin
|
||||
affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
|
||||
UPDATE #{self.class.table_name}
|
||||
SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false))}
|
||||
WHERE #{self.class.primary_key} = #{quote_value(id)}
|
||||
AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
|
||||
end_sql
|
||||
|
||||
unless affected_rows == 1
|
||||
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
|
||||
end
|
||||
|
||||
affected_rows
|
||||
|
||||
# If something went wrong, revert the version.
|
||||
rescue Exception
|
||||
send(lock_col + '=', previous_value)
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
DEFAULT_LOCKING_COLUMN = 'lock_version'
|
||||
|
||||
def self.extended(base)
|
||||
class <<base
|
||||
alias_method_chain :update_counters, :lock
|
||||
end
|
||||
end
|
||||
|
||||
# Is optimistic locking enabled for this table? Returns true if the
|
||||
# #lock_optimistically flag is set to true (which it is, by default)
|
||||
# and the table includes the #locking_column column (defaults to
|
||||
# lock_version).
|
||||
def locking_enabled?
|
||||
lock_optimistically && columns_hash[locking_column]
|
||||
end
|
||||
|
||||
# Set the column to use for optimistic locking. Defaults to lock_version.
|
||||
def set_locking_column(value = nil, &block)
|
||||
define_attr_method :locking_column, value, &block
|
||||
value
|
||||
end
|
||||
|
||||
# The version column used for optimistic locking. Defaults to lock_version.
|
||||
def locking_column
|
||||
reset_locking_column
|
||||
end
|
||||
|
||||
# Quote the column name used for optimistic locking.
|
||||
def quoted_locking_column
|
||||
connection.quote_column_name(locking_column)
|
||||
end
|
||||
|
||||
# Reset the column used for optimistic locking back to the lock_version default.
|
||||
def reset_locking_column
|
||||
set_locking_column DEFAULT_LOCKING_COLUMN
|
||||
end
|
||||
|
||||
# make sure the lock version column gets updated when counters are
|
||||
# updated.
|
||||
def update_counters_with_lock(id, counters)
|
||||
counters = counters.merge(locking_column => 1) if locking_enabled?
|
||||
update_counters_without_lock(id, counters)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
77
vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb
vendored
Normal file
77
vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Copyright (c) 2006 Shugo Maeda <shugo@ruby-lang.org>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject
|
||||
# to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
module ActiveRecord
|
||||
module Locking
|
||||
# Locking::Pessimistic provides support for row-level locking using
|
||||
# SELECT ... FOR UPDATE and other lock types.
|
||||
#
|
||||
# Pass :lock => true to ActiveRecord::Base.find to obtain an exclusive
|
||||
# lock on the selected rows:
|
||||
# # select * from accounts where id=1 for update
|
||||
# Account.find(1, :lock => true)
|
||||
#
|
||||
# Pass :lock => 'some locking clause' to give a database-specific locking clause
|
||||
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
|
||||
#
|
||||
# Example:
|
||||
# Account.transaction do
|
||||
# # select * from accounts where name = 'shugo' limit 1 for update
|
||||
# shugo = Account.find(:first, :conditions => "name = 'shugo'", :lock => true)
|
||||
# yuko = Account.find(:first, :conditions => "name = 'yuko'", :lock => true)
|
||||
# shugo.balance -= 100
|
||||
# shugo.save!
|
||||
# yuko.balance += 100
|
||||
# yuko.save!
|
||||
# end
|
||||
#
|
||||
# You can also use ActiveRecord::Base#lock! method to lock one record by id.
|
||||
# This may be better if you don't need to lock every row. Example:
|
||||
# Account.transaction do
|
||||
# # select * from accounts where ...
|
||||
# accounts = Account.find(:all, :conditions => ...)
|
||||
# account1 = accounts.detect { |account| ... }
|
||||
# account2 = accounts.detect { |account| ... }
|
||||
# # select * from accounts where id=? for update
|
||||
# account1.lock!
|
||||
# account2.lock!
|
||||
# account1.balance -= 100
|
||||
# account1.save!
|
||||
# account2.balance += 100
|
||||
# account2.save!
|
||||
# end
|
||||
#
|
||||
# Database-specific information on row locking:
|
||||
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
|
||||
# PostgreSQL: http://www.postgresql.org/docs/8.1/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
|
||||
module Pessimistic
|
||||
# Obtain a row lock on this record. Reloads the record to obtain the requested
|
||||
# lock. Pass an SQL locking clause to append the end of the SELECT statement
|
||||
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
|
||||
# the locked record.
|
||||
def lock!(lock = true)
|
||||
reload(:lock => lock) unless new_record?
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
417
vendor/rails/activerecord/lib/active_record/migration.rb
vendored
Normal file
417
vendor/rails/activerecord/lib/active_record/migration.rb
vendored
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
module ActiveRecord
|
||||
class IrreversibleMigration < ActiveRecordError#:nodoc:
|
||||
end
|
||||
|
||||
class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
|
||||
def initialize(version)
|
||||
super("Multiple migrations have the version number #{version}")
|
||||
end
|
||||
end
|
||||
|
||||
class IllegalMigrationNameError < ActiveRecordError#:nodoc:
|
||||
def initialize(name)
|
||||
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
||||
end
|
||||
end
|
||||
|
||||
# Migrations can manage the evolution of a schema used by several physical databases. It's a solution
|
||||
# to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
|
||||
# push that change to other developers and to the production server. With migrations, you can describe the transformations
|
||||
# in self-contained classes that can be checked into version control systems and executed against another database that
|
||||
# might be one, two, or five versions behind.
|
||||
#
|
||||
# Example of a simple migration:
|
||||
#
|
||||
# class AddSsl < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# add_column :accounts, :ssl_enabled, :boolean, :default => 1
|
||||
# end
|
||||
#
|
||||
# def self.down
|
||||
# remove_column :accounts, :ssl_enabled
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This migration will add a boolean flag to the accounts table and remove it if you're backing out of the migration.
|
||||
# It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
|
||||
# or remove the migration. These methods can consist of both the migration specific methods like add_column and remove_column,
|
||||
# but may also contain regular Ruby code for generating data needed for the transformations.
|
||||
#
|
||||
# Example of a more complex migration that also needs to initialize data:
|
||||
#
|
||||
# class AddSystemSettings < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# create_table :system_settings do |t|
|
||||
# t.string :name
|
||||
# t.string :label
|
||||
# t.text :value
|
||||
# t.string :type
|
||||
# t.integer :position
|
||||
# end
|
||||
#
|
||||
# SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
|
||||
# end
|
||||
#
|
||||
# def self.down
|
||||
# drop_table :system_settings
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
|
||||
# that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
|
||||
# in one block call.
|
||||
#
|
||||
# == Available transformations
|
||||
#
|
||||
# * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
|
||||
# that can then add columns to it, following the same format as add_column. See example above. The options hash is for
|
||||
# fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
|
||||
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
|
||||
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
|
||||
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
|
||||
# named +column_name+ specified to be one of the following types:
|
||||
# :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time,
|
||||
# :date, :binary, :boolean. A default value can be specified by passing an
|
||||
# +options+ hash like { :default => 11 }. Other options include :limit and :null (e.g. { :limit => 50, :null => false })
|
||||
# -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
|
||||
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
|
||||
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
|
||||
# parameters as add_column.
|
||||
# * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
|
||||
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index with the name of the column. Other options include
|
||||
# :name and :unique (e.g. { :name => "users_name_index", :unique => true }).
|
||||
# * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified by +index_name+.
|
||||
#
|
||||
# == Irreversible transformations
|
||||
#
|
||||
# Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
|
||||
# an <tt>ActiveRecord::IrreversibleMigration</tt> exception in their +down+ method.
|
||||
#
|
||||
# == Running migrations from within Rails
|
||||
#
|
||||
# The Rails package has several tools to help create and apply migrations.
|
||||
#
|
||||
# To generate a new migration, use <tt>script/generate migration MyNewMigration</tt>
|
||||
# where MyNewMigration is the name of your migration. The generator will
|
||||
# create a file <tt>nnn_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
|
||||
# directory where <tt>nnn</tt> is the next largest migration number.
|
||||
# You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
|
||||
# MyNewMigration.
|
||||
#
|
||||
# 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_info</tt> table if missing.
|
||||
#
|
||||
# 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
|
||||
# you wish to downgrade. If any of the migrations throw an
|
||||
# <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
|
||||
# have some manual work to do.
|
||||
#
|
||||
# == Database support
|
||||
#
|
||||
# Migrations are currently supported in MySQL, PostgreSQL, SQLite,
|
||||
# SQL Server, Sybase, and Oracle (all supported databases except DB2).
|
||||
#
|
||||
# == More examples
|
||||
#
|
||||
# Not all migrations change the schema. Some just fix the data:
|
||||
#
|
||||
# class RemoveEmptyTags < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
|
||||
# end
|
||||
#
|
||||
# def self.down
|
||||
# # not much we can do to restore deleted data
|
||||
# raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Others remove columns when they migrate up instead of down:
|
||||
#
|
||||
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# remove_column :items, :incomplete_items_count
|
||||
# remove_column :items, :completed_items_count
|
||||
# end
|
||||
#
|
||||
# def self.down
|
||||
# add_column :items, :incomplete_items_count
|
||||
# add_column :items, :completed_items_count
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# And sometimes you need to do something in SQL not abstracted directly by migrations:
|
||||
#
|
||||
# class MakeJoinUnique < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
|
||||
# end
|
||||
#
|
||||
# def self.down
|
||||
# execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == Using a model after changing its table
|
||||
#
|
||||
# Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
|
||||
# to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
|
||||
# after the new column was added. Example:
|
||||
#
|
||||
# class AddPeopleSalary < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# add_column :people, :salary, :integer
|
||||
# Person.reset_column_information
|
||||
# Person.find(:all).each do |p|
|
||||
# p.update_attribute :salary, SalaryCalculator.compute(p)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == Controlling verbosity
|
||||
#
|
||||
# By default, migrations will describe the actions they are taking, writing
|
||||
# them to the console as they happen, along with benchmarks describing how
|
||||
# long each step took.
|
||||
#
|
||||
# You can quiet them down by setting ActiveRecord::Migration.verbose = false.
|
||||
#
|
||||
# You can also insert your own messages and benchmarks by using the #say_with_time
|
||||
# method:
|
||||
#
|
||||
# def self.up
|
||||
# ...
|
||||
# say_with_time "Updating salaries..." do
|
||||
# Person.find(:all).each do |p|
|
||||
# p.update_attribute :salary, SalaryCalculator.compute(p)
|
||||
# end
|
||||
# end
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# The phrase "Updating salaries..." would then be printed, along with the
|
||||
# benchmark for the block when the block completes.
|
||||
class Migration
|
||||
@@verbose = true
|
||||
cattr_accessor :verbose
|
||||
|
||||
class << self
|
||||
def up_with_benchmarks #:nodoc:
|
||||
migrate(:up)
|
||||
end
|
||||
|
||||
def down_with_benchmarks #:nodoc:
|
||||
migrate(:down)
|
||||
end
|
||||
|
||||
# Execute this migration in the named direction
|
||||
def migrate(direction)
|
||||
return unless respond_to?(direction)
|
||||
|
||||
case direction
|
||||
when :up then announce "migrating"
|
||||
when :down then announce "reverting"
|
||||
end
|
||||
|
||||
result = nil
|
||||
time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
|
||||
|
||||
case direction
|
||||
when :up then announce "migrated (%.4fs)" % time.real; write
|
||||
when :down then announce "reverted (%.4fs)" % time.real; write
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Because the method added may do an alias_method, it can be invoked
|
||||
# recursively. We use @ignore_new_methods as a guard to indicate whether
|
||||
# it is safe for the call to proceed.
|
||||
def singleton_method_added(sym) #:nodoc:
|
||||
return if @ignore_new_methods
|
||||
|
||||
begin
|
||||
@ignore_new_methods = true
|
||||
|
||||
case sym
|
||||
when :up, :down
|
||||
klass = (class << self; self; end)
|
||||
klass.send(:alias_method_chain, sym, "benchmarks")
|
||||
end
|
||||
ensure
|
||||
@ignore_new_methods = false
|
||||
end
|
||||
end
|
||||
|
||||
def write(text="")
|
||||
puts(text) if verbose
|
||||
end
|
||||
|
||||
def announce(message)
|
||||
text = "#{@version} #{name}: #{message}"
|
||||
length = [0, 75 - text.length].max
|
||||
write "== %s %s" % [text, "=" * length]
|
||||
end
|
||||
|
||||
def say(message, subitem=false)
|
||||
write "#{subitem ? " ->" : "--"} #{message}"
|
||||
end
|
||||
|
||||
def say_with_time(message)
|
||||
say(message)
|
||||
result = nil
|
||||
time = Benchmark.measure { result = yield }
|
||||
say "%.4fs" % time.real, :subitem
|
||||
say("#{result} rows", :subitem) if result.is_a?(Integer)
|
||||
result
|
||||
end
|
||||
|
||||
def suppress_messages
|
||||
save, self.verbose = verbose, false
|
||||
yield
|
||||
ensure
|
||||
self.verbose = save
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments, &block)
|
||||
arg_list = arguments.map(&:inspect) * ', '
|
||||
|
||||
say_with_time "#{method}(#{arg_list})" do
|
||||
unless arguments.empty? || method == :execute
|
||||
arguments[0] = Migrator.proper_table_name(arguments.first)
|
||||
end
|
||||
ActiveRecord::Base.connection.send(method, *arguments, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Migrator#:nodoc:
|
||||
class << self
|
||||
def migrate(migrations_path, target_version = nil)
|
||||
Base.connection.initialize_schema_information
|
||||
|
||||
case
|
||||
when target_version.nil?, current_version < target_version
|
||||
up(migrations_path, target_version)
|
||||
when current_version > target_version
|
||||
down(migrations_path, target_version)
|
||||
when current_version == target_version
|
||||
return # You're on the right version
|
||||
end
|
||||
end
|
||||
|
||||
def up(migrations_path, target_version = nil)
|
||||
self.new(:up, migrations_path, target_version).migrate
|
||||
end
|
||||
|
||||
def down(migrations_path, target_version = nil)
|
||||
self.new(:down, migrations_path, target_version).migrate
|
||||
end
|
||||
|
||||
def schema_info_table_name
|
||||
Base.table_name_prefix + "schema_info" + Base.table_name_suffix
|
||||
end
|
||||
|
||||
def current_version
|
||||
Base.connection.select_value("SELECT version FROM #{schema_info_table_name}").to_i
|
||||
end
|
||||
|
||||
def proper_table_name(name)
|
||||
# Use the ActiveRecord objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
|
||||
name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(direction, migrations_path, target_version = nil)
|
||||
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
|
||||
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
||||
Base.connection.initialize_schema_information
|
||||
end
|
||||
|
||||
def current_version
|
||||
self.class.current_version
|
||||
end
|
||||
|
||||
def migrate
|
||||
migration_classes.each do |migration_class|
|
||||
if reached_target_version?(migration_class.version)
|
||||
Base.logger.info("Reached target version: #{@target_version}")
|
||||
break
|
||||
end
|
||||
|
||||
next if irrelevant_migration?(migration_class.version)
|
||||
|
||||
Base.logger.info "Migrating to #{migration_class} (#{migration_class.version})"
|
||||
migration_class.migrate(@direction)
|
||||
set_schema_version(migration_class.version)
|
||||
end
|
||||
end
|
||||
|
||||
def pending_migrations
|
||||
migration_classes.select { |m| m.version > current_version }
|
||||
end
|
||||
|
||||
private
|
||||
def migration_classes
|
||||
migrations = migration_files.inject([]) do |migrations, migration_file|
|
||||
load(migration_file)
|
||||
version, name = migration_version_and_name(migration_file)
|
||||
assert_unique_migration_version(migrations, version.to_i)
|
||||
migrations << migration_class(name, version.to_i)
|
||||
end
|
||||
|
||||
sorted = migrations.sort_by { |m| m.version }
|
||||
down? ? sorted.reverse : sorted
|
||||
end
|
||||
|
||||
def assert_unique_migration_version(migrations, version)
|
||||
if !migrations.empty? && migrations.find { |m| m.version == version }
|
||||
raise DuplicateMigrationVersionError.new(version)
|
||||
end
|
||||
end
|
||||
|
||||
def migration_files
|
||||
files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f|
|
||||
m = migration_version_and_name(f)
|
||||
raise IllegalMigrationNameError.new(f) unless m
|
||||
m.first.to_i
|
||||
end
|
||||
down? ? files.reverse : files
|
||||
end
|
||||
|
||||
def migration_class(migration_name, version)
|
||||
klass = migration_name.camelize.constantize
|
||||
class << klass; attr_accessor :version end
|
||||
klass.version = version
|
||||
klass
|
||||
end
|
||||
|
||||
def migration_version_and_name(migration_file)
|
||||
return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
||||
end
|
||||
|
||||
def set_schema_version(version)
|
||||
Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
|
||||
end
|
||||
|
||||
def up?
|
||||
@direction == :up
|
||||
end
|
||||
|
||||
def down?
|
||||
@direction == :down
|
||||
end
|
||||
|
||||
def reached_target_version?(version)
|
||||
return false if @target_version == nil
|
||||
(up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version)
|
||||
end
|
||||
|
||||
def irrelevant_migration?(version)
|
||||
(up? && version.to_i <= current_version) || (down? && version.to_i > current_version)
|
||||
end
|
||||
end
|
||||
end
|
||||
181
vendor/rails/activerecord/lib/active_record/observer.rb
vendored
Normal file
181
vendor/rails/activerecord/lib/active_record/observer.rb
vendored
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
require 'singleton'
|
||||
require 'set'
|
||||
|
||||
module ActiveRecord
|
||||
module Observing # :nodoc:
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Activates the observers assigned. Examples:
|
||||
#
|
||||
# # Calls PersonObserver.instance
|
||||
# ActiveRecord::Base.observers = :person_observer
|
||||
#
|
||||
# # Calls Cacher.instance and GarbageCollector.instance
|
||||
# ActiveRecord::Base.observers = :cacher, :garbage_collector
|
||||
#
|
||||
# # Same as above, just using explicit class references
|
||||
# ActiveRecord::Base.observers = Cacher, GarbageCollector
|
||||
#
|
||||
# Note: Setting this does not instantiate the observers yet. #instantiate_observers is
|
||||
# called during startup, and before each development request.
|
||||
def observers=(*observers)
|
||||
@observers = observers.flatten
|
||||
end
|
||||
|
||||
# Gets the current observers.
|
||||
def observers
|
||||
@observers ||= []
|
||||
end
|
||||
|
||||
# Instantiate the global ActiveRecord observers
|
||||
def instantiate_observers
|
||||
return if @observers.blank?
|
||||
@observers.each do |observer|
|
||||
if observer.respond_to?(:to_sym) # Symbol or String
|
||||
observer.to_s.camelize.constantize.instance
|
||||
elsif observer.respond_to?(:instance)
|
||||
observer.instance
|
||||
else
|
||||
raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# Notify observers when the observed class is subclassed.
|
||||
def inherited(subclass)
|
||||
super
|
||||
changed
|
||||
notify_observers :observed_class_inherited, subclass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Observer classes respond to lifecycle callbacks to implement trigger-like
|
||||
# behavior outside the original class. This is a great way to reduce the
|
||||
# clutter that normally comes when the model class is burdened with
|
||||
# functionality that doesn't pertain to the core responsibility of the
|
||||
# class. Example:
|
||||
#
|
||||
# class CommentObserver < ActiveRecord::Observer
|
||||
# def after_save(comment)
|
||||
# Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This Observer sends an email when a Comment#save is finished.
|
||||
#
|
||||
# class ContactObserver < ActiveRecord::Observer
|
||||
# def after_create(contact)
|
||||
# contact.logger.info('New contact added!')
|
||||
# end
|
||||
#
|
||||
# def after_destroy(contact)
|
||||
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This Observer uses logger to log when specific callbacks are triggered.
|
||||
#
|
||||
# == Observing a class that can't be inferred
|
||||
#
|
||||
# Observers will by default be mapped to the class with which they share a name. So CommentObserver will
|
||||
# be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
|
||||
# differently than the class you're interested in observing, you can use the Observer.observe class method which takes
|
||||
# either the concrete class (Product) or a symbol for that class (:product):
|
||||
#
|
||||
# class AuditObserver < ActiveRecord::Observer
|
||||
# observe :account
|
||||
#
|
||||
# def after_update(account)
|
||||
# AuditTrail.new(account, "UPDATED")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
|
||||
#
|
||||
# class AuditObserver < ActiveRecord::Observer
|
||||
# observe :account, :balance
|
||||
#
|
||||
# def after_update(record)
|
||||
# AuditTrail.new(record, "UPDATED")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
|
||||
#
|
||||
# == Available callback methods
|
||||
#
|
||||
# The observer can implement callback methods for each of the methods described in the Callbacks module.
|
||||
#
|
||||
# == Storing Observers in Rails
|
||||
#
|
||||
# If you're using Active Record within Rails, observer classes are usually stored in app/models with the
|
||||
# naming convention of app/models/audit_observer.rb.
|
||||
#
|
||||
# == Configuration
|
||||
#
|
||||
# In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
|
||||
# <tt>config/environment.rb</tt> file.
|
||||
#
|
||||
# config.active_record.observers = :comment_observer, :signup_observer
|
||||
#
|
||||
# Observers will not be invoked unless you define these in your application configuration.
|
||||
#
|
||||
class Observer
|
||||
include Singleton
|
||||
|
||||
class << self
|
||||
# Attaches the observer to the supplied model classes.
|
||||
def observe(*models)
|
||||
models.flatten!
|
||||
models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
|
||||
define_method(:observed_classes) { Set.new(models) }
|
||||
end
|
||||
|
||||
# The class observed by default is inferred from the observer's class name:
|
||||
# assert_equal [Person], PersonObserver.observed_class
|
||||
def observed_class
|
||||
if observed_class_name = name.scan(/(.*)Observer/)[0]
|
||||
observed_class_name[0].constantize
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Start observing the declared classes and their subclasses.
|
||||
def initialize
|
||||
Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
|
||||
end
|
||||
|
||||
# Send observed_method(object) if the method exists.
|
||||
def update(observed_method, object) #:nodoc:
|
||||
send(observed_method, object) if respond_to?(observed_method)
|
||||
end
|
||||
|
||||
# Special method sent by the observed class when it is inherited.
|
||||
# Passes the new subclass.
|
||||
def observed_class_inherited(subclass) #:nodoc:
|
||||
self.class.observe(observed_classes + [subclass])
|
||||
add_observer!(subclass)
|
||||
end
|
||||
|
||||
protected
|
||||
def observed_classes
|
||||
Set.new([self.class.observed_class].compact.flatten)
|
||||
end
|
||||
|
||||
def observed_subclasses
|
||||
observed_classes.collect(&:subclasses).flatten
|
||||
end
|
||||
|
||||
def add_observer!(klass)
|
||||
klass.add_observer(self)
|
||||
klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
|
||||
end
|
||||
end
|
||||
end
|
||||
21
vendor/rails/activerecord/lib/active_record/query_cache.rb
vendored
Normal file
21
vendor/rails/activerecord/lib/active_record/query_cache.rb
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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)
|
||||
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
|
||||
end
|
||||
219
vendor/rails/activerecord/lib/active_record/reflection.rb
vendored
Normal file
219
vendor/rails/activerecord/lib/active_record/reflection.rb
vendored
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
module ActiveRecord
|
||||
module Reflection # :nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
|
||||
# This information can, for example, be used in a form builder that took an Active Record object and created input
|
||||
# fields for all of the attributes depending on their type and displayed the associations to other objects.
|
||||
#
|
||||
# You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
|
||||
module ClassMethods
|
||||
def create_reflection(macro, name, options, active_record)
|
||||
case macro
|
||||
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
||||
reflection = AssociationReflection.new(macro, name, options, active_record)
|
||||
when :composed_of
|
||||
reflection = AggregateReflection.new(macro, name, options, active_record)
|
||||
end
|
||||
write_inheritable_hash :reflections, name => reflection
|
||||
reflection
|
||||
end
|
||||
|
||||
# Returns a hash containing all AssociationReflection objects for the current class
|
||||
# Example:
|
||||
#
|
||||
# Invoice.reflections
|
||||
# Account.reflections
|
||||
#
|
||||
def reflections
|
||||
read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
|
||||
end
|
||||
|
||||
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
||||
def reflect_on_all_aggregations
|
||||
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
|
||||
end
|
||||
|
||||
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
|
||||
#
|
||||
# Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
|
||||
#
|
||||
def reflect_on_aggregation(aggregation)
|
||||
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
|
||||
end
|
||||
|
||||
# Returns an array of AssociationReflection objects for all the associations in the class. If you only want to reflect on a
|
||||
# certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter.
|
||||
# Example:
|
||||
#
|
||||
# Account.reflect_on_all_associations # returns an array of all associations
|
||||
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
||||
#
|
||||
def reflect_on_all_associations(macro = nil)
|
||||
association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
|
||||
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
||||
end
|
||||
|
||||
# Returns the AssociationReflection object for the named +association+ (use the symbol). Example:
|
||||
#
|
||||
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
||||
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
||||
#
|
||||
def reflect_on_association(association)
|
||||
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
|
||||
# those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
||||
class MacroReflection
|
||||
attr_reader :active_record
|
||||
|
||||
def initialize(macro, name, options, active_record)
|
||||
@macro, @name, @options, @active_record = macro, name, options, active_record
|
||||
end
|
||||
|
||||
# Returns the name of the macro, so it would return :balance for "composed_of :balance, :class_name => 'Money'" or
|
||||
# :clients for "has_many :clients".
|
||||
def name
|
||||
@name
|
||||
end
|
||||
|
||||
# Returns the type of the macro, so it would return :composed_of for
|
||||
# "composed_of :balance, :class_name => 'Money'" or :has_many for "has_many :clients".
|
||||
def macro
|
||||
@macro
|
||||
end
|
||||
|
||||
# Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
|
||||
# "composed_of :balance, :class_name => 'Money'" or {} for "has_many :clients".
|
||||
def options
|
||||
@options
|
||||
end
|
||||
|
||||
# Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" returns the Money class and
|
||||
# "has_many :clients" returns the Client class.
|
||||
def klass
|
||||
@klass ||= class_name.constantize
|
||||
end
|
||||
|
||||
def class_name
|
||||
@class_name ||= options[:class_name] || derive_class_name
|
||||
end
|
||||
|
||||
def ==(other_aggregation)
|
||||
name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
|
||||
end
|
||||
|
||||
private
|
||||
def derive_class_name
|
||||
name.to_s.camelize
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Holds all the meta-data about an aggregation as it was specified in the Active Record class.
|
||||
class AggregateReflection < MacroReflection #:nodoc:
|
||||
end
|
||||
|
||||
# Holds all the meta-data about an association as it was specified in the Active Record class.
|
||||
class AssociationReflection < MacroReflection #:nodoc:
|
||||
def klass
|
||||
@klass ||= active_record.send(:compute_type, class_name)
|
||||
end
|
||||
|
||||
def table_name
|
||||
@table_name ||= klass.table_name
|
||||
end
|
||||
|
||||
def primary_key_name
|
||||
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
|
||||
end
|
||||
|
||||
def association_foreign_key
|
||||
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
|
||||
end
|
||||
|
||||
def counter_cache_column
|
||||
if options[:counter_cache] == true
|
||||
"#{active_record.name.underscore.pluralize}_count"
|
||||
elsif options[:counter_cache]
|
||||
options[:counter_cache]
|
||||
end
|
||||
end
|
||||
|
||||
def through_reflection
|
||||
@through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
|
||||
end
|
||||
|
||||
# Gets an array of possible :through source reflection names
|
||||
#
|
||||
# [singularized, pluralized]
|
||||
#
|
||||
def source_reflection_names
|
||||
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
|
||||
end
|
||||
|
||||
# Gets the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many.
|
||||
# (The :tags association on Tagging below)
|
||||
#
|
||||
# class Post
|
||||
# has_many :tags, :through => :taggings
|
||||
# end
|
||||
#
|
||||
def source_reflection
|
||||
return nil unless through_reflection
|
||||
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
|
||||
end
|
||||
|
||||
def check_validity!
|
||||
if options[:through]
|
||||
if through_reflection.nil?
|
||||
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
||||
end
|
||||
|
||||
if source_reflection.nil?
|
||||
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
||||
end
|
||||
|
||||
if options[:source_type] && source_reflection.options[:polymorphic].nil?
|
||||
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
||||
end
|
||||
|
||||
if source_reflection.options[:polymorphic] && options[:source_type].nil?
|
||||
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
|
||||
end
|
||||
|
||||
unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
||||
raise HasManyThroughSourceAssociationMacroError.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def derive_class_name
|
||||
# get the class_name of the belongs_to association of the through reflection
|
||||
if through_reflection
|
||||
options[:source_type] || source_reflection.class_name
|
||||
else
|
||||
class_name = name.to_s.camelize
|
||||
class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
|
||||
class_name
|
||||
end
|
||||
end
|
||||
|
||||
def derive_primary_key_name
|
||||
if macro == :belongs_to
|
||||
"#{name}_id"
|
||||
elsif options[:as]
|
||||
"#{options[:as]}_id"
|
||||
else
|
||||
active_record.name.foreign_key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
58
vendor/rails/activerecord/lib/active_record/schema.rb
vendored
Normal file
58
vendor/rails/activerecord/lib/active_record/schema.rb
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
module ActiveRecord
|
||||
# Allows programmers to programmatically define a schema in a portable
|
||||
# DSL. This means you can define tables, indexes, etc. without using SQL
|
||||
# directly, so your applications can more easily support multiple
|
||||
# databases.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# ActiveRecord::Schema.define do
|
||||
# create_table :authors do |t|
|
||||
# t.string :name, :null => false
|
||||
# end
|
||||
#
|
||||
# add_index :authors, :name, :unique
|
||||
#
|
||||
# create_table :posts do |t|
|
||||
# t.integer :author_id, :null => false
|
||||
# t.string :subject
|
||||
# t.text :body
|
||||
# t.boolean :private, :default => false
|
||||
# end
|
||||
#
|
||||
# add_index :posts, :author_id
|
||||
# end
|
||||
#
|
||||
# ActiveRecord::Schema is only supported by database adapters that also
|
||||
# support migrations, the two features being very similar.
|
||||
class Schema < Migration
|
||||
private_class_method :new
|
||||
|
||||
# Eval the given block. All methods available to the current connection
|
||||
# adapter are available within the block, so you can easily use the
|
||||
# database definition DSL to build up your schema (#create_table,
|
||||
# #add_index, etc.).
|
||||
#
|
||||
# The +info+ hash is optional, and if given is used to define metadata
|
||||
# about the current schema (like the schema's version):
|
||||
#
|
||||
# ActiveRecord::Schema.define(:version => 15) do
|
||||
# ...
|
||||
# end
|
||||
def self.define(info={}, &block)
|
||||
instance_eval(&block)
|
||||
|
||||
unless info.empty?
|
||||
initialize_schema_information
|
||||
cols = columns('schema_info')
|
||||
|
||||
info = info.map do |k,v|
|
||||
v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s })
|
||||
"#{k} = #{v}"
|
||||
end
|
||||
|
||||
Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
171
vendor/rails/activerecord/lib/active_record/schema_dumper.rb
vendored
Normal file
171
vendor/rails/activerecord/lib/active_record/schema_dumper.rb
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
require 'stringio'
|
||||
require 'bigdecimal'
|
||||
|
||||
module ActiveRecord
|
||||
# This class is used to dump the database schema for some connection to some
|
||||
# output format (i.e., ActiveRecord::Schema).
|
||||
class SchemaDumper #:nodoc:
|
||||
private_class_method :new
|
||||
|
||||
# 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
|
||||
cattr_accessor :ignore_tables
|
||||
@@ignore_tables = []
|
||||
|
||||
def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
|
||||
new(connection).dump(stream)
|
||||
stream
|
||||
end
|
||||
|
||||
def dump(stream)
|
||||
header(stream)
|
||||
tables(stream)
|
||||
trailer(stream)
|
||||
stream
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(connection)
|
||||
@connection = connection
|
||||
@types = @connection.native_database_types
|
||||
@info = @connection.select_one("SELECT * FROM schema_info") rescue nil
|
||||
end
|
||||
|
||||
def header(stream)
|
||||
define_params = @info ? ":version => #{@info['version']}" : ""
|
||||
|
||||
stream.puts <<HEADER
|
||||
# This file is auto-generated from the current state of the database. Instead of editing this file,
|
||||
# please use the migrations feature of ActiveRecord to incrementally modify your database, and
|
||||
# then regenerate this schema definition.
|
||||
#
|
||||
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
|
||||
# to create the application database on another system, you should be using db:schema:load, not running
|
||||
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
||||
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
||||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(#{define_params}) do
|
||||
|
||||
HEADER
|
||||
end
|
||||
|
||||
def trailer(stream)
|
||||
stream.puts "end"
|
||||
end
|
||||
|
||||
def tables(stream)
|
||||
@connection.tables.sort.each do |tbl|
|
||||
next if ["schema_info", ignore_tables].flatten.any? do |ignored|
|
||||
case ignored
|
||||
when String; tbl == ignored
|
||||
when Regexp; tbl =~ ignored
|
||||
else
|
||||
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
||||
end
|
||||
end
|
||||
table(tbl, stream)
|
||||
end
|
||||
end
|
||||
|
||||
def table(table, stream)
|
||||
columns = @connection.columns(table)
|
||||
begin
|
||||
tbl = StringIO.new
|
||||
|
||||
if @connection.respond_to?(:pk_and_sequence_for)
|
||||
pk, pk_seq = @connection.pk_and_sequence_for(table)
|
||||
end
|
||||
pk ||= 'id'
|
||||
|
||||
tbl.print " create_table #{table.inspect}"
|
||||
if columns.detect { |c| c.name == pk }
|
||||
if pk != 'id'
|
||||
tbl.print %Q(, :primary_key => "#{pk}")
|
||||
end
|
||||
else
|
||||
tbl.print ", :id => false"
|
||||
end
|
||||
tbl.print ", :force => true"
|
||||
tbl.puts " do |t|"
|
||||
|
||||
column_specs = columns.map do |column|
|
||||
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
|
||||
next if column.name == pk
|
||||
spec = {}
|
||||
spec[:name] = column.name.inspect
|
||||
spec[:type] = column.type.to_s
|
||||
spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
|
||||
spec[:precision] = column.precision.inspect if !column.precision.nil?
|
||||
spec[:scale] = column.scale.inspect if !column.scale.nil?
|
||||
spec[:null] = 'false' if !column.null
|
||||
spec[:default] = default_string(column.default) if !column.default.nil?
|
||||
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
|
||||
spec
|
||||
end.compact
|
||||
|
||||
# find all migration keys used in this table
|
||||
keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map(&:keys).flatten
|
||||
|
||||
# figure out the lengths for each column based on above keys
|
||||
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
|
||||
|
||||
# the string we're going to sprintf our values against, with standardized column widths
|
||||
format_string = lengths.map{ |len| "%-#{len}s" }
|
||||
|
||||
# find the max length for the 'type' column, which is special
|
||||
type_length = column_specs.map{ |column| column[:type].length }.max
|
||||
|
||||
# add column type definition to our format string
|
||||
format_string.unshift " t.%-#{type_length}s "
|
||||
|
||||
format_string *= ''
|
||||
|
||||
column_specs.each do |colspec|
|
||||
values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
|
||||
values.unshift colspec[:type]
|
||||
tbl.print((format_string % values).gsub(/,\s*$/, ''))
|
||||
tbl.puts
|
||||
end
|
||||
|
||||
tbl.puts " end"
|
||||
tbl.puts
|
||||
|
||||
indexes(table, tbl)
|
||||
|
||||
tbl.rewind
|
||||
stream.print tbl.read
|
||||
rescue => e
|
||||
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
||||
stream.puts "# #{e.message}"
|
||||
stream.puts
|
||||
end
|
||||
|
||||
stream
|
||||
end
|
||||
|
||||
def default_string(value)
|
||||
case value
|
||||
when BigDecimal
|
||||
value.to_s
|
||||
when Date, DateTime, Time
|
||||
"'" + value.to_s(:db) + "'"
|
||||
else
|
||||
value.inspect
|
||||
end
|
||||
end
|
||||
|
||||
def indexes(table, stream)
|
||||
indexes = @connection.indexes(table)
|
||||
indexes.each do |index|
|
||||
stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
|
||||
stream.print ", :unique => true" if index.unique
|
||||
stream.puts
|
||||
end
|
||||
stream.puts unless indexes.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
98
vendor/rails/activerecord/lib/active_record/serialization.rb
vendored
Normal file
98
vendor/rails/activerecord/lib/active_record/serialization.rb
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
class Serializer #:nodoc:
|
||||
attr_reader :options
|
||||
|
||||
def initialize(record, options = {})
|
||||
@record, @options = record, options.dup
|
||||
end
|
||||
|
||||
# To replicate the behavior in ActiveRecord#attributes,
|
||||
# :except takes precedence over :only. If :only is not set
|
||||
# for a N level model but is set for the N+1 level models,
|
||||
# then because :except is set to a default value, the second
|
||||
# level model can have both :except and :only set. So if
|
||||
# :only is set, always delete :except.
|
||||
def serializable_attribute_names
|
||||
attribute_names = @record.attribute_names
|
||||
|
||||
if options[:only]
|
||||
options.delete(:except)
|
||||
attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
|
||||
else
|
||||
options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
|
||||
attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
|
||||
end
|
||||
|
||||
attribute_names
|
||||
end
|
||||
|
||||
def serializable_method_names
|
||||
Array(options[:methods]).inject([]) do |method_attributes, name|
|
||||
method_attributes << name if @record.respond_to?(name.to_s)
|
||||
method_attributes
|
||||
end
|
||||
end
|
||||
|
||||
def serializable_names
|
||||
serializable_attribute_names + serializable_method_names
|
||||
end
|
||||
|
||||
# Add associations specified via the :includes option.
|
||||
# Expects a block that takes as arguments:
|
||||
# +association+ - name of the association
|
||||
# +records+ - the association record(s) to be serialized
|
||||
# +opts+ - options for the association records
|
||||
def add_includes(&block)
|
||||
if include_associations = options.delete(:include)
|
||||
base_only_or_except = { :except => options[:except],
|
||||
:only => options[:only] }
|
||||
|
||||
include_has_options = include_associations.is_a?(Hash)
|
||||
associations = include_has_options ? include_associations.keys : Array(include_associations)
|
||||
|
||||
for association in associations
|
||||
records = case @record.class.reflect_on_association(association).macro
|
||||
when :has_many, :has_and_belongs_to_many
|
||||
@record.send(association).to_a
|
||||
when :has_one, :belongs_to
|
||||
@record.send(association)
|
||||
end
|
||||
|
||||
unless records.nil?
|
||||
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
||||
opts = options.merge(association_options)
|
||||
yield(association, records, opts)
|
||||
end
|
||||
end
|
||||
|
||||
options[:include] = include_associations
|
||||
end
|
||||
end
|
||||
|
||||
def serializable_record
|
||||
returning(serializable_record = {}) do
|
||||
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
|
||||
add_includes do |association, records, opts|
|
||||
if records.is_a?(Enumerable)
|
||||
serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record }
|
||||
else
|
||||
serializable_record[association] = self.class.new(records, opts).serializable_record
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
# overwrite to implement
|
||||
end
|
||||
|
||||
def to_s(&block)
|
||||
serialize(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_record/serializers/xml_serializer'
|
||||
require 'active_record/serializers/json_serializer'
|
||||
71
vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb
vendored
Normal file
71
vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
# Returns a JSON string representing the model. Some configuration is
|
||||
# available through +options+.
|
||||
#
|
||||
# Without any +options+, the returned JSON string will include all
|
||||
# the model's attributes. For example:
|
||||
#
|
||||
# konata = User.find(1)
|
||||
# konata.to_json
|
||||
#
|
||||
# {"id": 1, "name": "Konata Izumi", "age": 16,
|
||||
# "created_at": "2006/08/01", "awesome": true}
|
||||
#
|
||||
# The :only and :except options can be used to limit the attributes
|
||||
# included, and work similar to the #attributes method. For example:
|
||||
#
|
||||
# konata.to_json(:only => [ :id, :name ])
|
||||
#
|
||||
# {"id": 1, "name": "Konata Izumi"}
|
||||
#
|
||||
# konata.to_json(:except => [ :id, :created_at, :age ])
|
||||
#
|
||||
# {"name": "Konata Izumi", "awesome": true}
|
||||
#
|
||||
# To include any methods on the model, use :methods.
|
||||
#
|
||||
# konata.to_json(:methods => :permalink)
|
||||
#
|
||||
# {"id": 1, "name": "Konata Izumi", "age": 16,
|
||||
# "created_at": "2006/08/01", "awesome": true,
|
||||
# "permalink": "1-konata-izumi"}
|
||||
#
|
||||
# To include associations, use :include.
|
||||
#
|
||||
# konata.to_json(:include => :posts)
|
||||
#
|
||||
# {"id": 1, "name": "Konata Izumi", "age": 16,
|
||||
# "created_at": "2006/08/01", "awesome": true,
|
||||
# "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
|
||||
# {"id": 2, author_id: 1, "title": "So I was thinking"}]}
|
||||
#
|
||||
# 2nd level and higher order associations work as well:
|
||||
#
|
||||
# konata.to_json(:include => { :posts => {
|
||||
# :include => { :comments => {
|
||||
# :only => :body } },
|
||||
# :only => :title } })
|
||||
#
|
||||
# {"id": 1, "name": "Konata Izumi", "age": 16,
|
||||
# "created_at": "2006/08/01", "awesome": true,
|
||||
# "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
|
||||
# "title": "Welcome to the weblog"},
|
||||
# {"comments": [{"body": "Don't think too hard"}],
|
||||
# "title": "So I was thinking"}]}
|
||||
def to_json(options = {})
|
||||
JsonSerializer.new(self, options).to_s
|
||||
end
|
||||
|
||||
def from_json(json)
|
||||
self.attributes = ActiveSupport::JSON.decode(json)
|
||||
self
|
||||
end
|
||||
|
||||
class JsonSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
|
||||
def serialize
|
||||
serializable_record.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
315
vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb
vendored
Normal file
315
vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb
vendored
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
# Builds an XML document to represent the model. Some configuration is
|
||||
# available through +options+, however more complicated cases should
|
||||
# override ActiveRecord's to_xml.
|
||||
#
|
||||
# By default the generated XML document will include the processing
|
||||
# instruction and all object's attributes. For example:
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <topic>
|
||||
# <title>The First Topic</title>
|
||||
# <author-name>David</author-name>
|
||||
# <id type="integer">1</id>
|
||||
# <approved type="boolean">false</approved>
|
||||
# <replies-count type="integer">0</replies-count>
|
||||
# <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
|
||||
# <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
|
||||
# <content>Have a nice day</content>
|
||||
# <author-email-address>david@loudthinking.com</author-email-address>
|
||||
# <parent-id></parent-id>
|
||||
# <last-read type="date">2004-04-15</last-read>
|
||||
# </topic>
|
||||
#
|
||||
# This behavior can be controlled with :only, :except,
|
||||
# :skip_instruct, :skip_types and :dasherize. The :only and
|
||||
# :except options are the same as for the #attributes method.
|
||||
# The default is to dasherize all column names, to disable this,
|
||||
# set :dasherize to false. To not have the column type included
|
||||
# in the XML output, set :skip_types to true.
|
||||
#
|
||||
# For instance:
|
||||
#
|
||||
# topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
|
||||
#
|
||||
# <topic>
|
||||
# <title>The First Topic</title>
|
||||
# <author-name>David</author-name>
|
||||
# <approved type="boolean">false</approved>
|
||||
# <content>Have a nice day</content>
|
||||
# <author-email-address>david@loudthinking.com</author-email-address>
|
||||
# <parent-id></parent-id>
|
||||
# <last-read type="date">2004-04-15</last-read>
|
||||
# </topic>
|
||||
#
|
||||
# To include first level associations use :include
|
||||
#
|
||||
# firm.to_xml :include => [ :account, :clients ]
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <firm>
|
||||
# <id type="integer">1</id>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>37signals</name>
|
||||
# <clients type="array">
|
||||
# <client>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>Summit</name>
|
||||
# </client>
|
||||
# <client>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>Microsoft</name>
|
||||
# </client>
|
||||
# </clients>
|
||||
# <account>
|
||||
# <id type="integer">1</id>
|
||||
# <credit-limit type="integer">50</credit-limit>
|
||||
# </account>
|
||||
# </firm>
|
||||
#
|
||||
# To include any methods on the object(s) being called use :methods
|
||||
#
|
||||
# firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
|
||||
#
|
||||
# <firm>
|
||||
# # ... normal attributes as shown above ...
|
||||
# <calculated-earnings>100000000000000000</calculated-earnings>
|
||||
# <real-earnings>5</real-earnings>
|
||||
# </firm>
|
||||
#
|
||||
# To call any Proc's on the object(s) use :procs. The Proc's
|
||||
# are passed a modified version of the options hash that was
|
||||
# given to #to_xml.
|
||||
#
|
||||
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
|
||||
# firm.to_xml :procs => [ proc ]
|
||||
#
|
||||
# <firm>
|
||||
# # ... normal attributes as shown above ...
|
||||
# <abc>def</abc>
|
||||
# </firm>
|
||||
#
|
||||
# Alternatively, you can also just yield the builder object as part of the to_xml call:
|
||||
#
|
||||
# firm.to_xml do |xml|
|
||||
# xml.creator do
|
||||
# xml.first_name "David"
|
||||
# xml.last_name "Heinemeier Hansson"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# <firm>
|
||||
# # ... normal attributes as shown above ...
|
||||
# <creator>
|
||||
# <first_name>David</first_name>
|
||||
# <last_name>Heinemeier Hansson</last_name>
|
||||
# </creator>
|
||||
# </firm>
|
||||
#
|
||||
# You can override the to_xml method in your ActiveRecord::Base
|
||||
# subclasses if you need to. The general form of doing this is
|
||||
#
|
||||
# class IHaveMyOwnXML < ActiveRecord::Base
|
||||
# def to_xml(options = {})
|
||||
# options[:indent] ||= 2
|
||||
# xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
||||
# xml.instruct! unless options[:skip_instruct]
|
||||
# xml.level_one do
|
||||
# xml.tag!(:second_level, 'content')
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
def to_xml(options = {}, &block)
|
||||
serializer = XmlSerializer.new(self, options)
|
||||
block_given? ? serializer.to_s(&block) : serializer.to_s
|
||||
end
|
||||
|
||||
def from_xml(xml)
|
||||
self.attributes = Hash.from_xml(xml).values.first
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
|
||||
def builder
|
||||
@builder ||= begin
|
||||
options[:indent] ||= 2
|
||||
builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
||||
|
||||
unless options[:skip_instruct]
|
||||
builder.instruct!
|
||||
options[:skip_instruct] = true
|
||||
end
|
||||
|
||||
builder
|
||||
end
|
||||
end
|
||||
|
||||
def root
|
||||
root = (options[:root] || @record.class.to_s.underscore).to_s
|
||||
dasherize? ? root.dasherize : root
|
||||
end
|
||||
|
||||
def dasherize?
|
||||
!options.has_key?(:dasherize) || options[:dasherize]
|
||||
end
|
||||
|
||||
|
||||
# To replicate the behavior in ActiveRecord#attributes,
|
||||
# :except takes precedence over :only. If :only is not set
|
||||
# for a N level model but is set for the N+1 level models,
|
||||
# then because :except is set to a default value, the second
|
||||
# level model can have both :except and :only set. So if
|
||||
# :only is set, always delete :except.
|
||||
def serializable_attributes
|
||||
serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
|
||||
end
|
||||
|
||||
def serializable_method_attributes
|
||||
Array(options[:methods]).inject([]) do |method_attributes, name|
|
||||
method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
|
||||
method_attributes
|
||||
end
|
||||
end
|
||||
|
||||
def add_attributes
|
||||
(serializable_attributes + serializable_method_attributes).each do |attribute|
|
||||
add_tag(attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def add_procs
|
||||
if procs = options.delete(:procs)
|
||||
[ *procs ].each do |proc|
|
||||
proc.call(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_tag(attribute)
|
||||
builder.tag!(
|
||||
dasherize? ? attribute.name.dasherize : attribute.name,
|
||||
attribute.value.to_s,
|
||||
attribute.decorations(!options[:skip_types])
|
||||
)
|
||||
end
|
||||
|
||||
def add_associations(association, records, opts)
|
||||
if records.is_a?(Enumerable)
|
||||
tag = association.to_s
|
||||
tag = tag.dasherize if dasherize?
|
||||
if records.empty?
|
||||
builder.tag!(tag, :type => :array)
|
||||
else
|
||||
builder.tag!(tag, :type => :array) do
|
||||
association_name = association.to_s.singularize
|
||||
records.each do |record|
|
||||
record.to_xml opts.merge(
|
||||
:root => association_name,
|
||||
:type => (record.class.to_s.underscore == association_name ? nil : record.class.name)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if record = @record.send(association)
|
||||
record.to_xml(opts.merge(:root => association))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
args = [root]
|
||||
if options[:namespace]
|
||||
args << {:xmlns=>options[:namespace]}
|
||||
end
|
||||
|
||||
if options[:type]
|
||||
args << {:type=>options[:type]}
|
||||
end
|
||||
|
||||
builder.tag!(*args) do
|
||||
add_attributes
|
||||
procs = options.delete(:procs)
|
||||
add_includes { |association, records, opts| add_associations(association, records, opts) }
|
||||
options[:procs] = procs
|
||||
add_procs
|
||||
yield builder if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
class Attribute #:nodoc:
|
||||
attr_reader :name, :value, :type
|
||||
|
||||
def initialize(name, record)
|
||||
@name, @record = name, record
|
||||
|
||||
@type = compute_type
|
||||
@value = compute_value
|
||||
end
|
||||
|
||||
# There is a significant speed improvement if the value
|
||||
# does not need to be escaped, as #tag! escapes all values
|
||||
# to ensure that valid XML is generated. For known binary
|
||||
# values, it is at least an order of magnitude faster to
|
||||
# Base64 encode binary values and directly put them in the
|
||||
# output XML than to pass the original value or the Base64
|
||||
# encoded value to the #tag! method. It definitely makes
|
||||
# no sense to Base64 encode the value and then give it to
|
||||
# #tag!, since that just adds additional overhead.
|
||||
def needs_encoding?
|
||||
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
|
||||
end
|
||||
|
||||
def decorations(include_types = true)
|
||||
decorations = {}
|
||||
|
||||
if type == :binary
|
||||
decorations[:encoding] = 'base64'
|
||||
end
|
||||
|
||||
if include_types && type != :string
|
||||
decorations[:type] = type
|
||||
end
|
||||
|
||||
if value.nil?
|
||||
decorations[:nil] = true
|
||||
end
|
||||
|
||||
decorations
|
||||
end
|
||||
|
||||
protected
|
||||
def compute_type
|
||||
type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
|
||||
|
||||
case type
|
||||
when :text
|
||||
:string
|
||||
when :time
|
||||
:datetime
|
||||
else
|
||||
type
|
||||
end
|
||||
end
|
||||
|
||||
def compute_value
|
||||
value = @record.send(name)
|
||||
|
||||
if formatter = Hash::XML_FORMATTING[type.to_s]
|
||||
value ? formatter.call(value) : nil
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MethodAttribute < Attribute #:nodoc:
|
||||
protected
|
||||
def compute_type
|
||||
Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
vendor/rails/activerecord/lib/active_record/timestamp.rb
vendored
Normal file
41
vendor/rails/activerecord/lib/active_record/timestamp.rb
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
module ActiveRecord
|
||||
# Active Record automatically timestamps create and update operations if the table has fields
|
||||
# named created_at/created_on or updated_at/updated_on.
|
||||
#
|
||||
# Timestamping can be turned off by setting
|
||||
# <tt>ActiveRecord::Base.record_timestamps = false</tt>
|
||||
#
|
||||
# Timestamps are in the local timezone by default but can use UTC by setting
|
||||
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
|
||||
module Timestamp
|
||||
def self.included(base) #:nodoc:
|
||||
base.alias_method_chain :create, :timestamps
|
||||
base.alias_method_chain :update, :timestamps
|
||||
|
||||
base.class_inheritable_accessor :record_timestamps, :instance_writer => false
|
||||
base.record_timestamps = true
|
||||
end
|
||||
|
||||
private
|
||||
def create_with_timestamps #:nodoc:
|
||||
if record_timestamps
|
||||
t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
|
||||
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)
|
||||
end
|
||||
create_without_timestamps
|
||||
end
|
||||
|
||||
def update_with_timestamps #:nodoc:
|
||||
if record_timestamps
|
||||
t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
|
||||
write_attribute('updated_at', t) if respond_to?(:updated_at)
|
||||
write_attribute('updated_on', t) if respond_to?(:updated_on)
|
||||
end
|
||||
update_without_timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
132
vendor/rails/activerecord/lib/active_record/transactions.rb
vendored
Normal file
132
vendor/rails/activerecord/lib/active_record/transactions.rb
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
require 'thread'
|
||||
|
||||
module ActiveRecord
|
||||
module Transactions # :nodoc:
|
||||
class TransactionError < ActiveRecordError # :nodoc:
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.class_eval do
|
||||
[:destroy, :save, :save!].each do |method|
|
||||
alias_method_chain method, :transactions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
|
||||
# The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
|
||||
# vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
|
||||
# So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
|
||||
# not at all. Example:
|
||||
#
|
||||
# transaction do
|
||||
# david.withdrawal(100)
|
||||
# mary.deposit(100)
|
||||
# end
|
||||
#
|
||||
# This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
|
||||
# Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
|
||||
# that the objects by default will _not_ have their instance data returned to their pre-transactional state.
|
||||
#
|
||||
# == Different ActiveRecord classes in a single transaction
|
||||
#
|
||||
# Though the transaction class method is called on some ActiveRecord class,
|
||||
# the objects within the transaction block need not all be instances of
|
||||
# that class.
|
||||
# In this example a <tt>Balance</tt> record is transactionally saved even
|
||||
# though <tt>transaction</tt> is called on the <tt>Account</tt> class:
|
||||
#
|
||||
# Account.transaction do
|
||||
# balance.save!
|
||||
# account.save!
|
||||
# end
|
||||
#
|
||||
# == Transactions are not distributed across database connections
|
||||
#
|
||||
# A transaction acts on a single database connection. If you have
|
||||
# multiple class-specific databases, the transaction will not protect
|
||||
# interaction among them. One workaround is to begin a transaction
|
||||
# on each class whose models you alter:
|
||||
#
|
||||
# Student.transaction do
|
||||
# Course.transaction do
|
||||
# course.enroll(student)
|
||||
# student.units += course.units
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This is a poor solution, but full distributed transactions are beyond
|
||||
# the scope of Active Record.
|
||||
#
|
||||
# == Save and destroy are automatically wrapped in a transaction
|
||||
#
|
||||
# Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
|
||||
# will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
|
||||
# depends on or you can raise exceptions in the callbacks to rollback.
|
||||
#
|
||||
# == Exception handling
|
||||
#
|
||||
# Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
|
||||
# should be ready to catch those in your application code. One exception is the ActiveRecord::Rollback exception, which will
|
||||
# trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
|
||||
module ClassMethods
|
||||
def transaction(&block)
|
||||
previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
|
||||
increment_open_transactions
|
||||
|
||||
begin
|
||||
connection.transaction(Thread.current['start_db_transaction'], &block)
|
||||
ensure
|
||||
decrement_open_transactions
|
||||
trap('TERM', previous_handler)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def increment_open_transactions #:nodoc:
|
||||
open = Thread.current['open_transactions'] ||= 0
|
||||
Thread.current['start_db_transaction'] = open.zero?
|
||||
Thread.current['open_transactions'] = open + 1
|
||||
end
|
||||
|
||||
def decrement_open_transactions #:nodoc:
|
||||
Thread.current['open_transactions'] -= 1
|
||||
end
|
||||
end
|
||||
|
||||
def transaction(&block)
|
||||
self.class.transaction(&block)
|
||||
end
|
||||
|
||||
def destroy_with_transactions #:nodoc:
|
||||
transaction { destroy_without_transactions }
|
||||
end
|
||||
|
||||
def save_with_transactions(perform_validation = true) #:nodoc:
|
||||
rollback_active_record_state! { transaction { save_without_transactions(perform_validation) } }
|
||||
end
|
||||
|
||||
def save_with_transactions! #:nodoc:
|
||||
rollback_active_record_state! { transaction { save_without_transactions! } }
|
||||
end
|
||||
|
||||
# Reset id and @new_record if the transaction rolls back.
|
||||
def rollback_active_record_state!
|
||||
id_present = has_attribute?(self.class.primary_key)
|
||||
previous_id = id
|
||||
previous_new_record = @new_record
|
||||
yield
|
||||
rescue Exception
|
||||
@new_record = previous_new_record
|
||||
if id_present
|
||||
self.id = previous_id
|
||||
else
|
||||
@attributes.delete(self.class.primary_key)
|
||||
@attributes_cache.delete(self.class.primary_key)
|
||||
end
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
1025
vendor/rails/activerecord/lib/active_record/validations.rb
vendored
Executable file
1025
vendor/rails/activerecord/lib/active_record/validations.rb
vendored
Executable file
File diff suppressed because it is too large
Load diff
362
vendor/rails/activerecord/lib/active_record/vendor/db2.rb
vendored
Normal file
362
vendor/rails/activerecord/lib/active_record/vendor/db2.rb
vendored
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
require 'db2/db2cli.rb'
|
||||
|
||||
module DB2
|
||||
module DB2Util
|
||||
include DB2CLI
|
||||
|
||||
def free() SQLFreeHandle(@handle_type, @handle); end
|
||||
def handle() @handle; end
|
||||
|
||||
def check_rc(rc)
|
||||
if ![SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA_FOUND].include?(rc)
|
||||
rec = 1
|
||||
msg = ''
|
||||
loop do
|
||||
a = SQLGetDiagRec(@handle_type, @handle, rec, 500)
|
||||
break if a[0] != SQL_SUCCESS
|
||||
msg << a[3] if !a[3].nil? and a[3] != '' # Create message.
|
||||
rec += 1
|
||||
end
|
||||
raise "DB2 error: #{msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Environment
|
||||
include DB2Util
|
||||
|
||||
def initialize
|
||||
@handle_type = SQL_HANDLE_ENV
|
||||
rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE)
|
||||
check_rc(rc)
|
||||
end
|
||||
|
||||
def data_sources(buffer_length = 1024)
|
||||
retval = []
|
||||
max_buffer_length = buffer_length
|
||||
|
||||
a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length)
|
||||
retval << [a[1], a[3]]
|
||||
max_buffer_length = [max_buffer_length, a[4]].max
|
||||
|
||||
loop do
|
||||
a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length)
|
||||
break if a[0] == SQL_NO_DATA_FOUND
|
||||
|
||||
retval << [a[1], a[3]]
|
||||
max_buffer_length = [max_buffer_length, a[4]].max
|
||||
end
|
||||
|
||||
if max_buffer_length > buffer_length
|
||||
get_data_sources(max_buffer_length)
|
||||
else
|
||||
retval
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Connection
|
||||
include DB2Util
|
||||
|
||||
def initialize(environment)
|
||||
@env = environment
|
||||
@handle_type = SQL_HANDLE_DBC
|
||||
rc, @handle = SQLAllocHandle(@handle_type, @env.handle)
|
||||
check_rc(rc)
|
||||
end
|
||||
|
||||
def connect(server_name, user_name = '', auth = '')
|
||||
check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s))
|
||||
end
|
||||
|
||||
def set_connect_attr(attr, value)
|
||||
value += "\0" if value.class == String
|
||||
check_rc(SQLSetConnectAttr(@handle, attr, value))
|
||||
end
|
||||
|
||||
def set_auto_commit_on
|
||||
set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON)
|
||||
end
|
||||
|
||||
def set_auto_commit_off
|
||||
set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
|
||||
end
|
||||
|
||||
def disconnect
|
||||
check_rc(SQLDisconnect(@handle))
|
||||
end
|
||||
|
||||
def rollback
|
||||
check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK))
|
||||
end
|
||||
|
||||
def commit
|
||||
check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT))
|
||||
end
|
||||
end
|
||||
|
||||
class Statement
|
||||
include DB2Util
|
||||
|
||||
def initialize(connection)
|
||||
@conn = connection
|
||||
@handle_type = SQL_HANDLE_STMT
|
||||
@parms = [] #yun
|
||||
@sql = '' #yun
|
||||
@numParms = 0 #yun
|
||||
@prepared = false #yun
|
||||
@parmArray = [] #yun. attributes of the parameter markers
|
||||
rc, @handle = SQLAllocHandle(@handle_type, @conn.handle)
|
||||
check_rc(rc)
|
||||
end
|
||||
|
||||
def columns(table_name, schema_name = '%')
|
||||
check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%'))
|
||||
fetch_all
|
||||
end
|
||||
|
||||
def tables(schema_name = '%')
|
||||
check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE'))
|
||||
fetch_all
|
||||
end
|
||||
|
||||
def indexes(table_name, schema_name = '')
|
||||
check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE))
|
||||
fetch_all
|
||||
end
|
||||
|
||||
def prepare(sql)
|
||||
@sql = sql
|
||||
check_rc(SQLPrepare(@handle, sql))
|
||||
rc, @numParms = SQLNumParams(@handle) #number of question marks
|
||||
check_rc(rc)
|
||||
#--------------------------------------------------------------------------
|
||||
# parameter attributes are stored in instance variable @parmArray so that
|
||||
# they are available when execute method is called.
|
||||
#--------------------------------------------------------------------------
|
||||
if @numParms > 0 # get parameter marker attributes
|
||||
1.upto(@numParms) do |i| # parameter number starts from 1
|
||||
rc, type, size, decimalDigits = SQLDescribeParam(@handle, i)
|
||||
check_rc(rc)
|
||||
@parmArray << Parameter.new(type, size, decimalDigits)
|
||||
end
|
||||
end
|
||||
@prepared = true
|
||||
self
|
||||
end
|
||||
|
||||
def execute(*parms)
|
||||
raise "The statement was not prepared" if @prepared == false
|
||||
|
||||
if parms.size == 1 and parms[0].class == Array
|
||||
parms = parms[0]
|
||||
end
|
||||
|
||||
if @numParms != parms.size
|
||||
raise "Number of parameters supplied does not match with the SQL statement"
|
||||
end
|
||||
|
||||
if @numParms > 0 #need to bind parameters
|
||||
#--------------------------------------------------------------------
|
||||
#calling bindParms may not be safe. Look comment below.
|
||||
#--------------------------------------------------------------------
|
||||
#bindParms(parms)
|
||||
|
||||
valueArray = []
|
||||
1.upto(@numParms) do |i| # parameter number starts from 1
|
||||
type = @parmArray[i - 1].class
|
||||
size = @parmArray[i - 1].size
|
||||
decimalDigits = @parmArray[i - 1].decimalDigits
|
||||
|
||||
if parms[i - 1].class == String
|
||||
valueArray << parms[i - 1]
|
||||
else
|
||||
valueArray << parms[i - 1].to_s
|
||||
end
|
||||
|
||||
rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1])
|
||||
check_rc(rc)
|
||||
end
|
||||
end
|
||||
|
||||
check_rc(SQLExecute(@handle))
|
||||
|
||||
if @numParms != 0
|
||||
check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# The last argument(value) to SQLBindParameter is a deferred argument, that is,
|
||||
# it should be available when SQLExecute is called. Even though "value" is
|
||||
# local to bindParms method, it seems that it is available when SQLExecute
|
||||
# is called. I am not sure whether it would still work if garbage collection
|
||||
# is done between bindParms call and SQLExecute call inside the execute method
|
||||
# above.
|
||||
#-------------------------------------------------------------------------------
|
||||
def bindParms(parms) # This is the real thing. It uses SQLBindParms
|
||||
1.upto(@numParms) do |i| # parameter number starts from 1
|
||||
rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i)
|
||||
check_rc(rc)
|
||||
if parms[i - 1].class == String
|
||||
value = parms[i - 1]
|
||||
else
|
||||
value = parms[i - 1].to_s
|
||||
end
|
||||
rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value)
|
||||
check_rc(rc)
|
||||
end
|
||||
end
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# bind method does not use DB2's SQLBindParams, but replaces "?" in the
|
||||
# SQL statement with the value before passing the SQL statement to DB2.
|
||||
# It is not efficient and can handle only strings since it puts everything in
|
||||
# quotes.
|
||||
#------------------------------------------------------------------------------
|
||||
def bind(sql, args) #does not use SQLBindParams
|
||||
arg_index = 0
|
||||
result = ""
|
||||
tokens(sql).each do |part|
|
||||
case part
|
||||
when '?'
|
||||
result << "'" + (args[arg_index]) + "'" #put it into quotes
|
||||
arg_index += 1
|
||||
when '??'
|
||||
result << "?"
|
||||
else
|
||||
result << part
|
||||
end
|
||||
end
|
||||
if arg_index < args.size
|
||||
raise "Too many SQL parameters"
|
||||
elsif arg_index > args.size
|
||||
raise "Not enough SQL parameters"
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
## Break the sql string into parts.
|
||||
#
|
||||
# This is NOT a full lexer for SQL. It just breaks up the SQL
|
||||
# string enough so that question marks, double question marks and
|
||||
# quoted strings are separated. This is used when binding
|
||||
# arguments to "?" in the SQL string. Note: comments are not
|
||||
# handled.
|
||||
#
|
||||
def tokens(sql)
|
||||
toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/)
|
||||
toks.collect { |t| t[0] }
|
||||
end
|
||||
|
||||
def exec_direct(sql)
|
||||
check_rc(SQLExecDirect(@handle, sql))
|
||||
self
|
||||
end
|
||||
|
||||
def set_cursor_name(name)
|
||||
check_rc(SQLSetCursorName(@handle, name))
|
||||
self
|
||||
end
|
||||
|
||||
def get_cursor_name
|
||||
rc, name = SQLGetCursorName(@handle)
|
||||
check_rc(rc)
|
||||
name
|
||||
end
|
||||
|
||||
def row_count
|
||||
rc, rowcount = SQLRowCount(@handle)
|
||||
check_rc(rc)
|
||||
rowcount
|
||||
end
|
||||
|
||||
def num_result_cols
|
||||
rc, cols = SQLNumResultCols(@handle)
|
||||
check_rc(rc)
|
||||
cols
|
||||
end
|
||||
|
||||
def fetch_all
|
||||
if block_given?
|
||||
while row = fetch do
|
||||
yield row
|
||||
end
|
||||
else
|
||||
res = []
|
||||
while row = fetch do
|
||||
res << row
|
||||
end
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
def fetch
|
||||
cols = get_col_desc
|
||||
rc = SQLFetch(@handle)
|
||||
if rc == SQL_NO_DATA_FOUND
|
||||
SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
|
||||
SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
|
||||
return nil
|
||||
end
|
||||
raise "ERROR" unless rc == SQL_SUCCESS
|
||||
|
||||
retval = []
|
||||
cols.each_with_index do |c, i|
|
||||
rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
|
||||
retval << adjust_content(content)
|
||||
end
|
||||
retval
|
||||
end
|
||||
|
||||
def fetch_as_hash
|
||||
cols = get_col_desc
|
||||
rc = SQLFetch(@handle)
|
||||
if rc == SQL_NO_DATA_FOUND
|
||||
SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
|
||||
SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
|
||||
return nil
|
||||
end
|
||||
raise "ERROR" unless rc == SQL_SUCCESS
|
||||
|
||||
retval = {}
|
||||
cols.each_with_index do |c, i|
|
||||
rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
|
||||
retval[c[0]] = adjust_content(content)
|
||||
end
|
||||
retval
|
||||
end
|
||||
|
||||
def get_col_desc
|
||||
rc, nr_cols = SQLNumResultCols(@handle)
|
||||
cols = (1..nr_cols).collect do |c|
|
||||
rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
|
||||
[name.downcase, type, col_sz]
|
||||
end
|
||||
end
|
||||
|
||||
def adjust_content(c)
|
||||
case c.class.to_s
|
||||
when 'DB2CLI::NullClass'
|
||||
return nil
|
||||
when 'DB2CLI::Time'
|
||||
"%02d:%02d:%02d" % [c.hour, c.minute, c.second]
|
||||
when 'DB2CLI::Date'
|
||||
"%04d-%02d-%02d" % [c.year, c.month, c.day]
|
||||
when 'DB2CLI::Timestamp'
|
||||
"%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second]
|
||||
else
|
||||
return c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Parameter
|
||||
attr_reader :type, :size, :decimalDigits
|
||||
def initialize(type, size, decimalDigits)
|
||||
@type, @size, @decimalDigits = type, size, decimalDigits
|
||||
end
|
||||
end
|
||||
end
|
||||
1214
vendor/rails/activerecord/lib/active_record/vendor/mysql.rb
vendored
Normal file
1214
vendor/rails/activerecord/lib/active_record/vendor/mysql.rb
vendored
Normal file
File diff suppressed because it is too large
Load diff
9
vendor/rails/activerecord/lib/active_record/version.rb
vendored
Normal file
9
vendor/rails/activerecord/lib/active_record/version.rb
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
module ActiveRecord
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 0
|
||||
TINY = 2
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
end
|
||||
1
vendor/rails/activerecord/lib/activerecord.rb
vendored
Normal file
1
vendor/rails/activerecord/lib/activerecord.rb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
require 'active_record'
|
||||
72
vendor/rails/activerecord/test/aaa_create_tables_test.rb
vendored
Normal file
72
vendor/rails/activerecord/test/aaa_create_tables_test.rb
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# The filename begins with "aaa" to ensure this is the first test.
|
||||
require 'abstract_unit'
|
||||
|
||||
class AAACreateTablesTest < Test::Unit::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@base_path = "#{File.dirname(__FILE__)}/fixtures/db_definitions"
|
||||
end
|
||||
|
||||
def test_drop_and_create_main_tables
|
||||
recreate ActiveRecord::Base unless use_migrations?
|
||||
assert true
|
||||
end
|
||||
|
||||
def test_load_schema
|
||||
if ActiveRecord::Base.connection.supports_migrations?
|
||||
eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb"))
|
||||
else
|
||||
recreate ActiveRecord::Base, '3'
|
||||
end
|
||||
assert true
|
||||
end
|
||||
|
||||
def test_drop_and_create_courses_table
|
||||
if Course.connection.supports_migrations?
|
||||
eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema2.rb"))
|
||||
end
|
||||
recreate Course, '2' unless use_migrations_for_courses?
|
||||
assert true
|
||||
end
|
||||
|
||||
private
|
||||
def use_migrations?
|
||||
unittest_sql_filename = ActiveRecord::Base.connection.adapter_name.downcase + ".sql"
|
||||
not File.exist? "#{@base_path}/#{unittest_sql_filename}"
|
||||
end
|
||||
|
||||
def use_migrations_for_courses?
|
||||
unittest2_sql_filename = ActiveRecord::Base.connection.adapter_name.downcase + "2.sql"
|
||||
not File.exist? "#{@base_path}/#{unittest2_sql_filename}"
|
||||
end
|
||||
|
||||
def recreate(base, suffix = nil)
|
||||
connection = base.connection
|
||||
adapter_name = connection.adapter_name.downcase + suffix.to_s
|
||||
execute_sql_file "#{@base_path}/#{adapter_name}.drop.sql", connection
|
||||
execute_sql_file "#{@base_path}/#{adapter_name}.sql", connection
|
||||
end
|
||||
|
||||
def execute_sql_file(path, connection)
|
||||
# OpenBase has a different format for sql files
|
||||
if current_adapter?(:OpenBaseAdapter) then
|
||||
File.read(path).split("go").each_with_index do |sql, i|
|
||||
begin
|
||||
# OpenBase does not support comments embedded in sql
|
||||
connection.execute(sql,"SQL statement ##{i}") unless sql.blank?
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
#$stderr.puts "warning: #{$!}"
|
||||
end
|
||||
end
|
||||
else
|
||||
File.read(path).split(';').each_with_index do |sql, i|
|
||||
begin
|
||||
connection.execute("\n\n-- statement ##{i}\n#{sql}\n") unless sql.blank?
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
#$stderr.puts "warning: #{$!}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
84
vendor/rails/activerecord/test/abstract_unit.rb
vendored
Executable file
84
vendor/rails/activerecord/test/abstract_unit.rb
vendored
Executable file
|
|
@ -0,0 +1,84 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
|
||||
|
||||
require 'test/unit'
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
require 'active_support/test_case'
|
||||
require 'connection'
|
||||
|
||||
# Show backtraces for deprecated behavior for quicker cleanup.
|
||||
ActiveSupport::Deprecation.debug = true
|
||||
|
||||
|
||||
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)
|
||||
|
||||
class Test::Unit::TestCase #:nodoc:
|
||||
self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
||||
self.use_instantiated_fixtures = false
|
||||
self.use_transactional_fixtures = (ENV['AR_NO_TX_FIXTURES'] != "yes")
|
||||
|
||||
def create_fixtures(*table_names, &block)
|
||||
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block)
|
||||
end
|
||||
|
||||
def assert_date_from_db(expected, actual, message = nil)
|
||||
# SQL Server doesn't have a separate column type just for dates,
|
||||
# so the time is in the string and incorrectly formatted
|
||||
if current_adapter?(:SQLServerAdapter)
|
||||
assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
|
||||
elsif current_adapter?(:SybaseAdapter)
|
||||
assert_equal expected.to_s, actual.to_date.to_s, message
|
||||
else
|
||||
assert_equal expected.to_s, actual.to_s, message
|
||||
end
|
||||
end
|
||||
|
||||
def assert_queries(num = 1)
|
||||
$query_count = 0
|
||||
yield
|
||||
ensure
|
||||
assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
|
||||
end
|
||||
|
||||
def assert_no_queries(&block)
|
||||
assert_queries(0, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def current_adapter?(*types)
|
||||
types.any? do |type|
|
||||
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
|
||||
ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters.const_get(type))
|
||||
end
|
||||
end
|
||||
|
||||
def uses_mocha(test_name)
|
||||
require 'rubygems'
|
||||
require 'mocha'
|
||||
yield
|
||||
rescue LoadError
|
||||
$stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again."
|
||||
end
|
||||
|
||||
ActiveRecord::Base.connection.class.class_eval do
|
||||
unless defined? IGNORED_SQL
|
||||
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/]
|
||||
|
||||
def execute_with_counting(sql, name = nil, &block)
|
||||
$query_count ||= 0
|
||||
$query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
|
||||
execute_without_counting(sql, name, &block)
|
||||
end
|
||||
|
||||
alias_method_chain :execute, :counting
|
||||
end
|
||||
end
|
||||
|
||||
# Make with_scope public for tests
|
||||
class << ActiveRecord::Base
|
||||
public :with_scope, :with_exclusive_scope
|
||||
end
|
||||
|
||||
#ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
#ActiveRecord::Base.colorize_logging = false
|
||||
43
vendor/rails/activerecord/test/active_schema_test_mysql.rb
vendored
Normal file
43
vendor/rails/activerecord/test/active_schema_test_mysql.rb
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ActiveSchemaTest < Test::Unit::TestCase
|
||||
def setup
|
||||
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
||||
alias_method :real_execute, :execute
|
||||
def execute(sql, name = nil) return sql end
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:alias_method, :execute, :real_execute)
|
||||
end
|
||||
|
||||
def test_drop_table
|
||||
assert_equal "DROP TABLE `people`", drop_table(:people)
|
||||
end
|
||||
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
def test_create_mysql_database_with_encoding
|
||||
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
|
||||
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
|
||||
assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_column
|
||||
assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
|
||||
end
|
||||
|
||||
def test_add_column_with_limit
|
||||
assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
|
||||
end
|
||||
|
||||
def test_drop_table_with_specific_database
|
||||
assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method_symbol, *arguments)
|
||||
ActiveRecord::Base.connection.send(method_symbol, *arguments)
|
||||
end
|
||||
end
|
||||
105
vendor/rails/activerecord/test/adapter_test.rb
vendored
Normal file
105
vendor/rails/activerecord/test/adapter_test.rb
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class AdapterTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
def test_tables
|
||||
if @connection.respond_to?(:tables)
|
||||
tables = @connection.tables
|
||||
assert tables.include?("accounts")
|
||||
assert tables.include?("authors")
|
||||
assert tables.include?("tasks")
|
||||
assert tables.include?("topics")
|
||||
else
|
||||
warn "#{@connection.class} does not respond to #tables"
|
||||
end
|
||||
end
|
||||
|
||||
def test_indexes
|
||||
idx_name = "accounts_idx"
|
||||
|
||||
if @connection.respond_to?(:indexes)
|
||||
indexes = @connection.indexes("accounts")
|
||||
assert indexes.empty?
|
||||
|
||||
@connection.add_index :accounts, :firm_id, :name => idx_name
|
||||
indexes = @connection.indexes("accounts")
|
||||
assert_equal "accounts", indexes.first.table
|
||||
# OpenBase does not have the concept of a named index
|
||||
# Indexes are merely properties of columns.
|
||||
assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter)
|
||||
assert !indexes.first.unique
|
||||
assert_equal ["firm_id"], indexes.first.columns
|
||||
else
|
||||
warn "#{@connection.class} does not respond to #indexes"
|
||||
end
|
||||
|
||||
ensure
|
||||
@connection.remove_index(:accounts, :name => idx_name) rescue nil
|
||||
end
|
||||
|
||||
def test_current_database
|
||||
if @connection.respond_to?(:current_database)
|
||||
assert_equal ENV['ARUNIT_DB_NAME'] || "activerecord_unittest", @connection.current_database
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
def test_charset
|
||||
assert_not_nil @connection.charset
|
||||
assert_not_equal 'character_set_database', @connection.charset
|
||||
assert_equal @connection.show_variable('character_set_database'), @connection.charset
|
||||
end
|
||||
|
||||
def test_collation
|
||||
assert_not_nil @connection.collation
|
||||
assert_not_equal 'collation_database', @connection.collation
|
||||
assert_equal @connection.show_variable('collation_database'), @connection.collation
|
||||
end
|
||||
|
||||
def test_show_nonexistent_variable_returns_nil
|
||||
assert_nil @connection.show_variable('foo_bar_baz')
|
||||
end
|
||||
end
|
||||
|
||||
def test_table_alias
|
||||
def @connection.test_table_alias_length() 10; end
|
||||
class << @connection
|
||||
alias_method :old_table_alias_length, :table_alias_length
|
||||
alias_method :table_alias_length, :test_table_alias_length
|
||||
end
|
||||
|
||||
assert_equal 'posts', @connection.table_alias_for('posts')
|
||||
assert_equal 'posts_comm', @connection.table_alias_for('posts_comments')
|
||||
assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts')
|
||||
|
||||
class << @connection
|
||||
alias_method :table_alias_length, :old_table_alias_length
|
||||
end
|
||||
end
|
||||
|
||||
# test resetting sequences in odd tables in postgreSQL
|
||||
if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
|
||||
require 'fixtures/movie'
|
||||
require 'fixtures/subscriber'
|
||||
|
||||
def test_reset_empty_table_with_custom_pk
|
||||
Movie.delete_all
|
||||
Movie.connection.reset_pk_sequence! 'movies'
|
||||
assert_equal 1, Movie.create(:name => 'fight club').id
|
||||
end
|
||||
|
||||
if ActiveRecord::Base.connection.adapter_name != "FrontBase"
|
||||
def test_reset_table_with_non_integer_pk
|
||||
Subscriber.delete_all
|
||||
Subscriber.connection.reset_pk_sequence! 'subscribers'
|
||||
sub = Subscriber.new(:name => 'robert drake')
|
||||
sub.id = 'bob drake'
|
||||
assert_nothing_raised { sub.save! }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
95
vendor/rails/activerecord/test/adapter_test_sqlserver.rb
vendored
Normal file
95
vendor/rails/activerecord/test/adapter_test_sqlserver.rb
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/default'
|
||||
require 'fixtures/post'
|
||||
require 'fixtures/task'
|
||||
|
||||
class SqlServerAdapterTest < Test::Unit::TestCase
|
||||
class TableWithRealColumn < ActiveRecord::Base; end
|
||||
|
||||
fixtures :posts, :tasks
|
||||
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
def teardown
|
||||
@connection.execute("SET LANGUAGE us_english") rescue nil
|
||||
end
|
||||
|
||||
def test_real_column_has_float_type
|
||||
assert_equal :float, TableWithRealColumn.columns_hash["real_number"].type
|
||||
end
|
||||
|
||||
# SQL Server 2000 has a bug where some unambiguous date formats are not
|
||||
# correctly identified if the session language is set to german
|
||||
def test_date_insertion_when_language_is_german
|
||||
@connection.execute("SET LANGUAGE deutsch")
|
||||
|
||||
assert_nothing_raised do
|
||||
Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
|
||||
end
|
||||
end
|
||||
|
||||
def test_indexes_with_descending_order
|
||||
# Make sure we have an index with descending order
|
||||
@connection.execute "CREATE INDEX idx_credit_limit ON accounts (credit_limit DESC)" rescue nil
|
||||
assert_equal ["credit_limit"], @connection.indexes('accounts').first.columns
|
||||
ensure
|
||||
@connection.execute "DROP INDEX accounts.idx_credit_limit"
|
||||
end
|
||||
|
||||
def test_execute_without_block_closes_statement
|
||||
assert_all_statements_used_are_closed do
|
||||
@connection.execute("SELECT 1")
|
||||
end
|
||||
end
|
||||
|
||||
def test_execute_with_block_closes_statement
|
||||
assert_all_statements_used_are_closed do
|
||||
@connection.execute("SELECT 1") do |sth|
|
||||
assert !sth.finished?, "Statement should still be alive within block"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_insert_with_identity_closes_statement
|
||||
assert_all_statements_used_are_closed do
|
||||
@connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
|
||||
end
|
||||
end
|
||||
|
||||
def test_insert_without_identity_closes_statement
|
||||
assert_all_statements_used_are_closed do
|
||||
@connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
|
||||
end
|
||||
end
|
||||
|
||||
def test_active_closes_statement
|
||||
assert_all_statements_used_are_closed do
|
||||
@connection.active?
|
||||
end
|
||||
end
|
||||
|
||||
def assert_all_statements_used_are_closed(&block)
|
||||
existing_handles = []
|
||||
ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle}
|
||||
GC.disable
|
||||
|
||||
yield
|
||||
|
||||
used_handles = []
|
||||
ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle}
|
||||
|
||||
assert_block "No statements were used within given block" do
|
||||
used_handles.size > 0
|
||||
end
|
||||
|
||||
ObjectSpace.each_object(DBI::StatementHandle) do |handle|
|
||||
assert_block "Statement should have been closed within given block" do
|
||||
handle.finished?
|
||||
end
|
||||
end
|
||||
ensure
|
||||
GC.enable
|
||||
end
|
||||
end
|
||||
128
vendor/rails/activerecord/test/aggregations_test.rb
vendored
Normal file
128
vendor/rails/activerecord/test/aggregations_test.rb
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/customer'
|
||||
|
||||
class AggregationsTest < Test::Unit::TestCase
|
||||
fixtures :customers
|
||||
|
||||
def test_find_single_value_object
|
||||
assert_equal 50, customers(:david).balance.amount
|
||||
assert_kind_of Money, customers(:david).balance
|
||||
assert_equal 300, customers(:david).balance.exchange_to("DKK").amount
|
||||
end
|
||||
|
||||
def test_find_multiple_value_object
|
||||
assert_equal customers(:david).address_street, customers(:david).address.street
|
||||
assert(
|
||||
customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country))
|
||||
)
|
||||
end
|
||||
|
||||
def test_change_single_value_object
|
||||
customers(:david).balance = Money.new(100)
|
||||
customers(:david).save
|
||||
assert_equal 100, customers(:david).reload.balance.amount
|
||||
end
|
||||
|
||||
def test_immutable_value_objects
|
||||
customers(:david).balance = Money.new(100)
|
||||
assert_raises(TypeError) { customers(:david).balance.instance_eval { @amount = 20 } }
|
||||
end
|
||||
|
||||
def test_inferred_mapping
|
||||
assert_equal "35.544623640962634", customers(:david).gps_location.latitude
|
||||
assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
|
||||
|
||||
customers(:david).gps_location = GpsLocation.new("39x-110")
|
||||
|
||||
assert_equal "39", customers(:david).gps_location.latitude
|
||||
assert_equal "-110", customers(:david).gps_location.longitude
|
||||
|
||||
customers(:david).save
|
||||
|
||||
customers(:david).reload
|
||||
|
||||
assert_equal "39", customers(:david).gps_location.latitude
|
||||
assert_equal "-110", customers(:david).gps_location.longitude
|
||||
end
|
||||
|
||||
def test_reloaded_instance_refreshes_aggregations
|
||||
assert_equal "35.544623640962634", customers(:david).gps_location.latitude
|
||||
assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
|
||||
|
||||
Customer.update_all("gps_location = '24x113'")
|
||||
customers(:david).reload
|
||||
assert_equal '24x113', customers(:david)['gps_location']
|
||||
|
||||
assert_equal GpsLocation.new('24x113'), customers(:david).gps_location
|
||||
end
|
||||
|
||||
def test_gps_equality
|
||||
assert GpsLocation.new('39x110') == GpsLocation.new('39x110')
|
||||
end
|
||||
|
||||
def test_gps_inequality
|
||||
assert GpsLocation.new('39x110') != GpsLocation.new('39x111')
|
||||
end
|
||||
|
||||
def test_allow_nil_gps_is_nil
|
||||
assert_equal nil, customers(:zaphod).gps_location
|
||||
end
|
||||
|
||||
def test_allow_nil_gps_set_to_nil
|
||||
customers(:david).gps_location = nil
|
||||
customers(:david).save
|
||||
customers(:david).reload
|
||||
assert_equal nil, customers(:david).gps_location
|
||||
end
|
||||
|
||||
def test_allow_nil_set_address_attributes_to_nil
|
||||
customers(:zaphod).address = nil
|
||||
assert_equal nil, customers(:zaphod).attributes[:address_street]
|
||||
assert_equal nil, customers(:zaphod).attributes[:address_city]
|
||||
assert_equal nil, customers(:zaphod).attributes[:address_country]
|
||||
end
|
||||
|
||||
def test_allow_nil_address_set_to_nil
|
||||
customers(:zaphod).address = nil
|
||||
customers(:zaphod).save
|
||||
customers(:zaphod).reload
|
||||
assert_equal nil, customers(:zaphod).address
|
||||
end
|
||||
|
||||
def test_nil_raises_error_when_allow_nil_is_false
|
||||
assert_raises(NoMethodError) { customers(:david).balance = nil }
|
||||
end
|
||||
|
||||
def test_allow_nil_address_loaded_when_only_some_attributes_are_nil
|
||||
customers(:zaphod).address_street = nil
|
||||
customers(:zaphod).save
|
||||
customers(:zaphod).reload
|
||||
assert_kind_of Address, customers(:zaphod).address
|
||||
assert customers(:zaphod).address.street.nil?
|
||||
end
|
||||
|
||||
def test_nil_assignment_results_in_nil
|
||||
customers(:david).gps_location = GpsLocation.new('39x111')
|
||||
assert_not_equal nil, customers(:david).gps_location
|
||||
customers(:david).gps_location = nil
|
||||
assert_equal nil, customers(:david).gps_location
|
||||
end
|
||||
end
|
||||
|
||||
class OverridingAggregationsTest < Test::Unit::TestCase
|
||||
class Name; end
|
||||
class DifferentName; end
|
||||
|
||||
class Person < ActiveRecord::Base
|
||||
composed_of :composed_of, :mapping => %w(person_first_name first_name)
|
||||
end
|
||||
|
||||
class DifferentPerson < Person
|
||||
composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name)
|
||||
end
|
||||
|
||||
def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited
|
||||
assert_not_equal Person.reflect_on_aggregation(:composed_of),
|
||||
DifferentPerson.reflect_on_aggregation(:composed_of)
|
||||
end
|
||||
end
|
||||
8
vendor/rails/activerecord/test/all.sh
vendored
Executable file
8
vendor/rails/activerecord/test/all.sh
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <database>" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ruby -I connections/native_$1 -e 'Dir["**/*_test.rb"].each { |path| require path }'
|
||||
33
vendor/rails/activerecord/test/ar_schema_test.rb
vendored
Normal file
33
vendor/rails/activerecord/test/ar_schema_test.rb
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
require 'abstract_unit'
|
||||
require "#{File.dirname(__FILE__)}/../lib/active_record/schema"
|
||||
|
||||
if ActiveRecord::Base.connection.supports_migrations?
|
||||
|
||||
class ActiveRecordSchemaTest < Test::Unit::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
def teardown
|
||||
@connection.drop_table :fruits rescue nil
|
||||
end
|
||||
|
||||
def test_schema_define
|
||||
ActiveRecord::Schema.define(:version => 7) do
|
||||
create_table :fruits do |t|
|
||||
t.column :color, :string
|
||||
t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
|
||||
t.column :texture, :string
|
||||
t.column :flavor, :string
|
||||
end
|
||||
end
|
||||
|
||||
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
|
||||
assert_nothing_raised { @connection.select_all "SELECT * FROM schema_info" }
|
||||
assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
14
vendor/rails/activerecord/test/association_inheritance_reload.rb
vendored
Normal file
14
vendor/rails/activerecord/test/association_inheritance_reload.rb
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/company'
|
||||
|
||||
class AssociationInheritanceReloadTest < Test::Unit::TestCase
|
||||
fixtures :companies
|
||||
|
||||
def test_set_attributes
|
||||
assert_equal ["errors.add_on_empty('name', \"can't be empty\")"], Firm.read_inheritable_attribute("validate"), "Second run"
|
||||
# ActiveRecord::Base.reset_column_information_and_inheritable_attributes_for_all_subclasses
|
||||
remove_subclass_of(ActiveRecord::Base)
|
||||
load 'fixtures/company.rb'
|
||||
assert_equal ["errors.add_on_empty('name', \"can't be empty\")"], Firm.read_inheritable_attribute("validate"), "Second run"
|
||||
end
|
||||
end
|
||||
147
vendor/rails/activerecord/test/associations/callbacks_test.rb
vendored
Normal file
147
vendor/rails/activerecord/test/associations/callbacks_test.rb
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/post'
|
||||
require 'fixtures/comment'
|
||||
require 'fixtures/author'
|
||||
require 'fixtures/category'
|
||||
require 'fixtures/project'
|
||||
require 'fixtures/developer'
|
||||
|
||||
class AssociationCallbacksTest < Test::Unit::TestCase
|
||||
fixtures :posts, :authors, :projects, :developers
|
||||
|
||||
def setup
|
||||
@david = authors(:david)
|
||||
@thinking = posts(:thinking)
|
||||
@authorless = posts(:authorless)
|
||||
assert @david.post_log.empty?
|
||||
end
|
||||
|
||||
def test_adding_macro_callbacks
|
||||
@david.posts_with_callbacks << @thinking
|
||||
assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
|
||||
@david.posts_with_callbacks << @thinking
|
||||
assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}",
|
||||
"after_adding#{@thinking.id}"], @david.post_log
|
||||
end
|
||||
|
||||
def test_adding_with_proc_callbacks
|
||||
@david.posts_with_proc_callbacks << @thinking
|
||||
assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
|
||||
@david.posts_with_proc_callbacks << @thinking
|
||||
assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}",
|
||||
"after_adding#{@thinking.id}"], @david.post_log
|
||||
end
|
||||
|
||||
def test_removing_with_macro_callbacks
|
||||
first_post, second_post = @david.posts_with_callbacks[0, 2]
|
||||
@david.posts_with_callbacks.delete(first_post)
|
||||
assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
|
||||
@david.posts_with_callbacks.delete(second_post)
|
||||
assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}",
|
||||
"after_removing#{second_post.id}"], @david.post_log
|
||||
end
|
||||
|
||||
def test_removing_with_proc_callbacks
|
||||
first_post, second_post = @david.posts_with_callbacks[0, 2]
|
||||
@david.posts_with_proc_callbacks.delete(first_post)
|
||||
assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
|
||||
@david.posts_with_proc_callbacks.delete(second_post)
|
||||
assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}",
|
||||
"after_removing#{second_post.id}"], @david.post_log
|
||||
end
|
||||
|
||||
def test_multiple_callbacks
|
||||
@david.posts_with_multiple_callbacks << @thinking
|
||||
assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}",
|
||||
"after_adding_proc#{@thinking.id}"], @david.post_log
|
||||
@david.posts_with_multiple_callbacks << @thinking
|
||||
assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}",
|
||||
"after_adding_proc#{@thinking.id}", "before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}",
|
||||
"after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log
|
||||
end
|
||||
|
||||
def test_has_many_callbacks_with_create
|
||||
morten = Author.create :name => "Morten"
|
||||
post = morten.posts_with_proc_callbacks.create! :title => "Hello", :body => "How are you doing?"
|
||||
assert_equal ["before_adding<new>", "after_adding#{post.id}"], morten.post_log
|
||||
end
|
||||
|
||||
def test_has_many_callbacks_with_create!
|
||||
morten = Author.create! :name => "Morten"
|
||||
post = morten.posts_with_proc_callbacks.create :title => "Hello", :body => "How are you doing?"
|
||||
assert_equal ["before_adding<new>", "after_adding#{post.id}"], morten.post_log
|
||||
end
|
||||
|
||||
def test_has_many_callbacks_for_save_on_parent
|
||||
jack = Author.new :name => "Jack"
|
||||
post = jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep"
|
||||
|
||||
callback_log = ["before_adding<new>", "after_adding#{jack.posts_with_callbacks.first.id}"]
|
||||
assert_equal callback_log, jack.post_log
|
||||
assert jack.save
|
||||
assert_equal 1, jack.posts_with_callbacks.count
|
||||
assert_equal callback_log, jack.post_log
|
||||
end
|
||||
|
||||
def test_has_and_belongs_to_many_add_callback
|
||||
david = developers(:david)
|
||||
ar = projects(:active_record)
|
||||
assert ar.developers_log.empty?
|
||||
ar.developers_with_callbacks << david
|
||||
assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log
|
||||
ar.developers_with_callbacks << david
|
||||
assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}",
|
||||
"after_adding#{david.id}"], ar.developers_log
|
||||
end
|
||||
|
||||
def test_has_and_belongs_to_many_remove_callback
|
||||
david = developers(:david)
|
||||
jamis = developers(:jamis)
|
||||
activerecord = projects(:active_record)
|
||||
assert activerecord.developers_log.empty?
|
||||
activerecord.developers_with_callbacks.delete(david)
|
||||
assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log
|
||||
|
||||
activerecord.developers_with_callbacks.delete(jamis)
|
||||
assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}",
|
||||
"after_removing#{jamis.id}"], activerecord.developers_log
|
||||
end
|
||||
|
||||
def test_has_and_belongs_to_many_remove_callback_on_clear
|
||||
activerecord = projects(:active_record)
|
||||
assert activerecord.developers_log.empty?
|
||||
if activerecord.developers_with_callbacks.size == 0
|
||||
activerecord.developers << developers(:david)
|
||||
activerecord.developers << developers(:jamis)
|
||||
activerecord.reload
|
||||
assert activerecord.developers_with_callbacks.size == 2
|
||||
end
|
||||
log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort
|
||||
assert activerecord.developers_with_callbacks.clear
|
||||
assert_equal log_array, activerecord.developers_log.sort
|
||||
end
|
||||
|
||||
def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent
|
||||
project = Project.new :name => "Callbacks"
|
||||
project.developers_with_callbacks.build :name => "Jack", :salary => 95000
|
||||
|
||||
callback_log = ["before_adding<new>", "after_adding<new>"]
|
||||
assert_equal callback_log, project.developers_log
|
||||
assert project.save
|
||||
assert_equal 1, project.developers_with_callbacks.count
|
||||
assert_equal callback_log, project.developers_log
|
||||
end
|
||||
|
||||
def test_dont_add_if_before_callback_raises_exception
|
||||
assert !@david.unchangable_posts.include?(@authorless)
|
||||
begin
|
||||
@david.unchangable_posts << @authorless
|
||||
rescue Exception => e
|
||||
end
|
||||
assert @david.post_log.empty?
|
||||
assert !@david.unchangable_posts.include?(@authorless)
|
||||
@david.reload
|
||||
assert !@david.unchangable_posts.include?(@authorless)
|
||||
end
|
||||
end
|
||||
|
||||
110
vendor/rails/activerecord/test/associations/cascaded_eager_loading_test.rb
vendored
Normal file
110
vendor/rails/activerecord/test/associations/cascaded_eager_loading_test.rb
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/post'
|
||||
require 'fixtures/comment'
|
||||
require 'fixtures/author'
|
||||
require 'fixtures/category'
|
||||
require 'fixtures/categorization'
|
||||
require 'fixtures/company'
|
||||
require 'fixtures/topic'
|
||||
require 'fixtures/reply'
|
||||
|
||||
class CascadedEagerLoadingTest < Test::Unit::TestCase
|
||||
fixtures :authors, :mixins, :companies, :posts, :topics
|
||||
|
||||
def test_eager_association_loading_with_cascaded_two_levels
|
||||
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
|
||||
assert_equal 2, authors.size
|
||||
assert_equal 5, authors[0].posts.size
|
||||
assert_equal 1, authors[1].posts.size
|
||||
assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_cascaded_two_levels_and_one_level
|
||||
authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
|
||||
assert_equal 2, authors.size
|
||||
assert_equal 5, authors[0].posts.size
|
||||
assert_equal 1, authors[1].posts.size
|
||||
assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
|
||||
assert_equal 1, authors[0].categorizations.size
|
||||
assert_equal 2, authors[1].categorizations.size
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
|
||||
authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
|
||||
assert_equal 2, authors.size
|
||||
assert_equal 5, authors[0].posts.size
|
||||
assert_equal 1, authors[1].posts.size
|
||||
assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
|
||||
authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
|
||||
assert_equal 2, authors.size
|
||||
assert_equal 5, authors[0].posts.size
|
||||
assert_equal authors(:david).name, authors[0].name
|
||||
assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_cascaded_two_levels_with_condition
|
||||
authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id")
|
||||
assert_equal 1, authors.size
|
||||
assert_equal 5, authors[0].posts.size
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
|
||||
firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id")
|
||||
assert_equal 2, firms.size
|
||||
assert_equal firms.first.account, firms.first.account.firm.account
|
||||
assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
|
||||
assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_has_many_sti
|
||||
topics = Topic.find(:all, :include => :replies, :order => 'topics.id')
|
||||
assert_equal topics(:first, :second), topics
|
||||
assert_no_queries do
|
||||
assert_equal 1, topics[0].replies.size
|
||||
assert_equal 0, topics[1].replies.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_sti
|
||||
replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
|
||||
assert_equal [topics(:second)], replies
|
||||
assert_equal topics(:first), assert_no_queries { replies.first.topic }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_multiple_stis_and_order
|
||||
author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
|
||||
assert_equal authors(:david), author
|
||||
assert_no_queries do
|
||||
author.posts.first.special_comments
|
||||
author.posts.first.very_special_comment
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_association_loading_of_stis_with_multiple_references
|
||||
authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
|
||||
assert_equal [authors(:david)], authors
|
||||
assert_no_queries do
|
||||
authors.first.posts.first.special_comments.first.post.special_comments
|
||||
authors.first.posts.first.special_comments.first.post.very_special_comment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'fixtures/vertex'
|
||||
require 'fixtures/edge'
|
||||
class CascadedEagerLoadingTest < Test::Unit::TestCase
|
||||
fixtures :edges, :vertices
|
||||
|
||||
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
|
||||
source = Vertex.find(:first, :include=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id')
|
||||
assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
|
||||
sink = Vertex.find(:first, :include=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC')
|
||||
assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
|
||||
end
|
||||
end
|
||||
145
vendor/rails/activerecord/test/associations/eager_singularization_test.rb
vendored
Normal file
145
vendor/rails/activerecord/test/associations/eager_singularization_test.rb
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class Virus < ActiveRecord::Base
|
||||
belongs_to :octopus
|
||||
end
|
||||
class Octopus < ActiveRecord::Base
|
||||
has_one :virus
|
||||
end
|
||||
class Pass < ActiveRecord::Base
|
||||
belongs_to :bus
|
||||
end
|
||||
class Bus < ActiveRecord::Base
|
||||
has_many :passes
|
||||
end
|
||||
class Mess < ActiveRecord::Base
|
||||
has_and_belongs_to_many :crises
|
||||
end
|
||||
class Crisis < ActiveRecord::Base
|
||||
has_and_belongs_to_many :messes
|
||||
has_many :analyses, :dependent => :destroy
|
||||
has_many :successes, :through => :analyses
|
||||
has_many :dresses, :dependent => :destroy
|
||||
has_many :compresses, :through => :dresses
|
||||
end
|
||||
class Analysis < ActiveRecord::Base
|
||||
belongs_to :crisis
|
||||
belongs_to :success
|
||||
end
|
||||
class Success < ActiveRecord::Base
|
||||
has_many :analyses, :dependent => :destroy
|
||||
has_many :crises, :through => :analyses
|
||||
end
|
||||
class Dress < ActiveRecord::Base
|
||||
belongs_to :crisis
|
||||
has_many :compresses
|
||||
end
|
||||
class Compress < ActiveRecord::Base
|
||||
belongs_to :dress
|
||||
end
|
||||
|
||||
|
||||
class EagerSingularizationTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
if ActiveRecord::Base.connection.supports_migrations?
|
||||
ActiveRecord::Base.connection.create_table :viri do |t|
|
||||
t.column :octopus_id, :integer
|
||||
t.column :species, :string
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :octopi do |t|
|
||||
t.column :species, :string
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :passes do |t|
|
||||
t.column :bus_id, :integer
|
||||
t.column :rides, :integer
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :buses do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :crises_messes, :id => false do |t|
|
||||
t.column :crisis_id, :integer
|
||||
t.column :mess_id, :integer
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :messes do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :crises do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :successes do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :analyses do |t|
|
||||
t.column :crisis_id, :integer
|
||||
t.column :success_id, :integer
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :dresses do |t|
|
||||
t.column :crisis_id, :integer
|
||||
end
|
||||
ActiveRecord::Base.connection.create_table :compresses do |t|
|
||||
t.column :dress_id, :integer
|
||||
end
|
||||
@have_tables = true
|
||||
else
|
||||
@have_tables = false
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActiveRecord::Base.connection.drop_table :viri
|
||||
ActiveRecord::Base.connection.drop_table :octopi
|
||||
ActiveRecord::Base.connection.drop_table :passes
|
||||
ActiveRecord::Base.connection.drop_table :buses
|
||||
ActiveRecord::Base.connection.drop_table :crises_messes
|
||||
ActiveRecord::Base.connection.drop_table :messes
|
||||
ActiveRecord::Base.connection.drop_table :crises
|
||||
ActiveRecord::Base.connection.drop_table :successes
|
||||
ActiveRecord::Base.connection.drop_table :analyses
|
||||
ActiveRecord::Base.connection.drop_table :dresses
|
||||
ActiveRecord::Base.connection.drop_table :compresses
|
||||
end
|
||||
|
||||
def test_eager_no_extra_singularization_belongs_to
|
||||
return unless @have_tables
|
||||
assert_nothing_raised do
|
||||
Virus.find(:all, :include => :octopus)
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_no_extra_singularization_has_one
|
||||
return unless @have_tables
|
||||
assert_nothing_raised do
|
||||
Octopus.find(:all, :include => :virus)
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_no_extra_singularization_has_many
|
||||
return unless @have_tables
|
||||
assert_nothing_raised do
|
||||
Bus.find(:all, :include => :passes)
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_no_extra_singularization_has_and_belongs_to_many
|
||||
return unless @have_tables
|
||||
assert_nothing_raised do
|
||||
Crisis.find(:all, :include => :messes)
|
||||
Mess.find(:all, :include => :crises)
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_no_extra_singularization_has_many_through_belongs_to
|
||||
return unless @have_tables
|
||||
assert_nothing_raised do
|
||||
Crisis.find(:all, :include => :successes)
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_no_extra_singularization_has_many_through_has_many
|
||||
return unless @have_tables
|
||||
assert_nothing_raised do
|
||||
Crisis.find(:all, :include => :compresses)
|
||||
end
|
||||
end
|
||||
end
|
||||
442
vendor/rails/activerecord/test/associations/eager_test.rb
vendored
Normal file
442
vendor/rails/activerecord/test/associations/eager_test.rb
vendored
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/post'
|
||||
require 'fixtures/comment'
|
||||
require 'fixtures/author'
|
||||
require 'fixtures/category'
|
||||
require 'fixtures/company'
|
||||
require 'fixtures/person'
|
||||
require 'fixtures/reader'
|
||||
|
||||
class EagerAssociationTest < Test::Unit::TestCase
|
||||
fixtures :posts, :comments, :authors, :categories, :categories_posts,
|
||||
:companies, :accounts, :tags, :people, :readers
|
||||
|
||||
def test_loading_with_one_association
|
||||
posts = Post.find(:all, :include => :comments)
|
||||
post = posts.find { |p| p.id == 1 }
|
||||
assert_equal 2, post.comments.size
|
||||
assert post.comments.include?(comments(:greetings))
|
||||
|
||||
post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'")
|
||||
assert_equal 2, post.comments.size
|
||||
assert post.comments.include?(comments(:greetings))
|
||||
end
|
||||
|
||||
def test_loading_conditions_with_or
|
||||
posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
|
||||
assert_nil posts.detect { |p| p.author_id != authors(:david).id },
|
||||
"expected to find only david's posts"
|
||||
end
|
||||
|
||||
def test_with_ordering
|
||||
list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
|
||||
[:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments,
|
||||
:authorless, :thinking, :welcome
|
||||
].each_with_index do |post, index|
|
||||
assert_equal posts(post), list[index]
|
||||
end
|
||||
end
|
||||
|
||||
def test_with_two_tables_in_from_without_getting_double_quoted
|
||||
posts = Post.find(:all,
|
||||
:select => "posts.*",
|
||||
:from => "authors, posts",
|
||||
:include => :comments,
|
||||
:conditions => "posts.author_id = authors.id",
|
||||
:order => "posts.id"
|
||||
)
|
||||
|
||||
assert_equal 2, posts.first.comments.size
|
||||
end
|
||||
|
||||
def test_loading_with_multiple_associations
|
||||
posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id")
|
||||
assert_equal 2, posts.first.comments.size
|
||||
assert_equal 2, posts.first.categories.size
|
||||
assert posts.first.comments.include?(comments(:greetings))
|
||||
end
|
||||
|
||||
def test_loading_from_an_association
|
||||
posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id")
|
||||
assert_equal 2, posts.first.comments.size
|
||||
end
|
||||
|
||||
def test_loading_with_no_associations
|
||||
assert_nil Post.find(posts(:authorless).id, :include => :author).author
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to
|
||||
comments = Comment.find(:all, :include => :post)
|
||||
assert_equal 10, comments.length
|
||||
titles = comments.map { |c| c.post.title }
|
||||
assert titles.include?(posts(:welcome).title)
|
||||
assert titles.include?(posts(:sti_post_and_comments).title)
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_and_limit
|
||||
comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id')
|
||||
assert_equal 5, comments.length
|
||||
assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
|
||||
comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id')
|
||||
assert_equal 3, comments.length
|
||||
assert_equal [5,6,7], comments.collect { |c| c.id }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_and_limit_and_offset
|
||||
comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id')
|
||||
assert_equal 3, comments.length
|
||||
assert_equal [3,5,6], comments.collect { |c| c.id }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
|
||||
comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id')
|
||||
assert_equal 3, comments.length
|
||||
assert_equal [6,7,8], comments.collect { |c| c.id }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
|
||||
comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id')
|
||||
assert_equal 3, comments.length
|
||||
assert_equal [6,7,8], comments.collect { |c| c.id }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
|
||||
posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id')
|
||||
assert_equal 1, posts.length
|
||||
assert_equal [1], posts.collect { |p| p.id }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
|
||||
posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id')
|
||||
assert_equal 1, posts.length
|
||||
assert_equal [2], posts.collect { |p| p.id }
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_explicit_join
|
||||
posts = Post.find(:all, :include => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id')
|
||||
assert_equal 1, posts.length
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_through
|
||||
posts_with_comments = people(:michael).posts.find(:all, :include => :comments)
|
||||
posts_with_author = people(:michael).posts.find(:all, :include => :author )
|
||||
posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ])
|
||||
assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
|
||||
assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
|
||||
assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_through_an_sti_join_model
|
||||
author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id')
|
||||
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
|
||||
author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id')
|
||||
assert_equal [], author.special_nonexistant_post_comments
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_through_join_model_with_conditions
|
||||
assert_equal Author.find(:first, :include => :hello_post_comments,
|
||||
:order => 'authors.id').hello_post_comments.sort_by(&:id),
|
||||
Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id)
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit
|
||||
posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
|
||||
assert_equal 2, posts.size
|
||||
assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_and_conditions
|
||||
if current_adapter?(:OpenBaseAdapter)
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id")
|
||||
else
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id")
|
||||
end
|
||||
assert_equal 2, posts.size
|
||||
assert_equal [4,5], posts.collect { |p| p.id }
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_and_conditions_array
|
||||
if current_adapter?(:OpenBaseAdapter)
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id")
|
||||
else
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id")
|
||||
end
|
||||
assert_equal 2, posts.size
|
||||
assert_equal [4,5], posts.collect { |p| p.id }
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
|
||||
assert_equal 2, posts.size
|
||||
|
||||
count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
|
||||
assert_equal count, posts.size
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_ond_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
|
||||
posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
|
||||
assert_equal 0, posts
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_with_no_results
|
||||
posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'")
|
||||
assert_equal 0, posts.size
|
||||
end
|
||||
|
||||
def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional
|
||||
author = authors(:david)
|
||||
author_posts_without_comments = author.posts.select { |post| post.comments.blank? }
|
||||
assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null')
|
||||
end
|
||||
|
||||
def test_eager_with_has_and_belongs_to_many_and_limit
|
||||
posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
|
||||
assert_equal 3, posts.size
|
||||
assert_equal 2, posts[0].categories.size
|
||||
assert_equal 1, posts[1].categories.size
|
||||
assert_equal 0, posts[2].categories.size
|
||||
assert posts[0].categories.include?(categories(:technology))
|
||||
assert posts[1].categories.include?(categories(:general))
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
|
||||
posts = authors(:david).posts.find(:all,
|
||||
:include => :comments,
|
||||
:conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
|
||||
:limit => 2
|
||||
)
|
||||
assert_equal 2, posts.size
|
||||
|
||||
count = Post.count(
|
||||
:include => [ :comments, :author ],
|
||||
:conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
|
||||
:limit => 2
|
||||
)
|
||||
assert_equal count, posts.size
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
|
||||
posts = nil
|
||||
Post.with_scope(:find => {
|
||||
:include => :comments,
|
||||
:conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'"
|
||||
}) do
|
||||
posts = authors(:david).posts.find(:all, :limit => 2)
|
||||
assert_equal 2, posts.size
|
||||
end
|
||||
|
||||
Post.with_scope(:find => {
|
||||
:include => [ :comments, :author ],
|
||||
:conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')"
|
||||
}) do
|
||||
count = Post.count(:limit => 2)
|
||||
assert_equal count, posts.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
|
||||
Post.with_scope(:find => { :conditions => "1=1" }) do
|
||||
posts = authors(:david).posts.find(:all,
|
||||
:include => :comments,
|
||||
:conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
|
||||
:limit => 2
|
||||
)
|
||||
assert_equal 2, posts.size
|
||||
|
||||
count = Post.count(
|
||||
:include => [ :comments, :author ],
|
||||
:conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
|
||||
:limit => 2
|
||||
)
|
||||
assert_equal count, posts.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope
|
||||
posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :order => 'posts.id DESC', :limit => 2)
|
||||
posts_with_scoped_order = Post.with_scope(:find => {:order => 'posts.id DESC'}) do
|
||||
Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :limit => 2)
|
||||
end
|
||||
assert_equal posts_with_explicit_order, posts_with_scoped_order
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_habtm
|
||||
posts = Post.find(:all, :include => :categories, :order => "posts.id")
|
||||
assert_equal 2, posts[0].categories.size
|
||||
assert_equal 1, posts[1].categories.size
|
||||
assert_equal 0, posts[2].categories.size
|
||||
assert posts[0].categories.include?(categories(:technology))
|
||||
assert posts[1].categories.include?(categories(:general))
|
||||
end
|
||||
|
||||
def test_eager_with_inheritance
|
||||
posts = SpecialPost.find(:all, :include => [ :comments ])
|
||||
end
|
||||
|
||||
def test_eager_has_one_with_association_inheritance
|
||||
post = Post.find(4, :include => [ :very_special_comment ])
|
||||
assert_equal "VerySpecialComment", post.very_special_comment.class.to_s
|
||||
end
|
||||
|
||||
def test_eager_has_many_with_association_inheritance
|
||||
post = Post.find(4, :include => [ :special_comments ])
|
||||
post.special_comments.each do |special_comment|
|
||||
assert_equal "SpecialComment", special_comment.class.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_habtm_with_association_inheritance
|
||||
post = Post.find(6, :include => [ :special_categories ])
|
||||
assert_equal 1, post.special_categories.size
|
||||
post.special_categories.each do |special_category|
|
||||
assert_equal "SpecialCategory", special_category.class.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_with_has_one_dependent_does_not_destroy_dependent
|
||||
assert_not_nil companies(:first_firm).account
|
||||
f = Firm.find(:first, :include => :account,
|
||||
:conditions => ["companies.name = ?", "37signals"])
|
||||
assert_not_nil f.account
|
||||
assert_equal companies(:first_firm, :reload).account, f.account
|
||||
end
|
||||
|
||||
def test_eager_with_multi_table_conditional_properly_counts_the_records_when_using_size
|
||||
author = authors(:david)
|
||||
posts_with_no_comments = author.posts.select { |post| post.comments.blank? }
|
||||
assert_equal posts_with_no_comments.size, author.posts_with_no_comments.size
|
||||
assert_equal posts_with_no_comments, author.posts_with_no_comments
|
||||
end
|
||||
|
||||
def test_eager_with_invalid_association_reference
|
||||
assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
|
||||
post = Post.find(6, :include=> :monkeys )
|
||||
}
|
||||
assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
|
||||
post = Post.find(6, :include=>[ :monkeys ])
|
||||
}
|
||||
assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
|
||||
post = Post.find(6, :include=>[ 'monkeys' ])
|
||||
}
|
||||
assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
|
||||
post = Post.find(6, :include=>[ :monkeys, :elephants ])
|
||||
}
|
||||
end
|
||||
|
||||
def find_all_ordered(className, include=nil)
|
||||
className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include)
|
||||
end
|
||||
|
||||
def test_limited_eager_with_order
|
||||
assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title)', :limit => 2, :offset => 1)
|
||||
assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1)
|
||||
end
|
||||
|
||||
def test_limited_eager_with_multiple_order_columns
|
||||
assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title), posts.id', :limit => 2, :offset => 1)
|
||||
assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC, posts.id', :limit => 2, :offset => 1)
|
||||
end
|
||||
|
||||
def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm
|
||||
# Eager includes of has many and habtm associations aren't necessarily sorted in the same way
|
||||
def assert_equal_after_sort(item1, item2, item3 = nil)
|
||||
assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id})
|
||||
assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3
|
||||
end
|
||||
# Test regular association, association with conditions, association with
|
||||
# STI, and association with conditions assured not to be true
|
||||
post_types = [:posts, :other_posts, :special_posts]
|
||||
# test both has_many and has_and_belongs_to_many
|
||||
[Author, Category].each do |className|
|
||||
d1 = find_all_ordered(className)
|
||||
# test including all post types at once
|
||||
d2 = find_all_ordered(className, post_types)
|
||||
d1.each_index do |i|
|
||||
assert_equal(d1[i], d2[i])
|
||||
assert_equal_after_sort(d1[i].posts, d2[i].posts)
|
||||
post_types[1..-1].each do |post_type|
|
||||
# test including post_types together
|
||||
d3 = find_all_ordered(className, [:posts, post_type])
|
||||
assert_equal(d1[i], d3[i])
|
||||
assert_equal_after_sort(d1[i].posts, d3[i].posts)
|
||||
assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_with_multiple_associations_with_same_table_has_one
|
||||
d1 = find_all_ordered(Firm)
|
||||
d2 = find_all_ordered(Firm, :account)
|
||||
d1.each_index do |i|
|
||||
assert_equal(d1[i], d2[i])
|
||||
assert_equal(d1[i].account, d2[i].account)
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_with_multiple_associations_with_same_table_belongs_to
|
||||
firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition]
|
||||
d1 = find_all_ordered(Client)
|
||||
d2 = find_all_ordered(Client, firm_types)
|
||||
d1.each_index do |i|
|
||||
assert_equal(d1[i], d2[i])
|
||||
firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) }
|
||||
end
|
||||
end
|
||||
def test_eager_with_valid_association_as_string_not_symbol
|
||||
assert_nothing_raised { Post.find(:all, :include => 'comments') }
|
||||
end
|
||||
|
||||
def test_preconfigured_includes_with_belongs_to
|
||||
author = posts(:welcome).author_with_posts
|
||||
assert_equal 5, author.posts.size
|
||||
end
|
||||
|
||||
def test_preconfigured_includes_with_has_one
|
||||
comment = posts(:sti_comments).very_special_comment_with_post
|
||||
assert_equal posts(:sti_comments), comment.post
|
||||
end
|
||||
|
||||
def test_preconfigured_includes_with_has_many
|
||||
posts = authors(:david).posts_with_comments
|
||||
one = posts.detect { |p| p.id == 1 }
|
||||
assert_equal 5, posts.size
|
||||
assert_equal 2, one.comments.size
|
||||
end
|
||||
|
||||
def test_preconfigured_includes_with_habtm
|
||||
posts = authors(:david).posts_with_categories
|
||||
one = posts.detect { |p| p.id == 1 }
|
||||
assert_equal 5, posts.size
|
||||
assert_equal 2, one.categories.size
|
||||
end
|
||||
|
||||
def test_preconfigured_includes_with_has_many_and_habtm
|
||||
posts = authors(:david).posts_with_comments_and_categories
|
||||
one = posts.detect { |p| p.id == 1 }
|
||||
assert_equal 5, posts.size
|
||||
assert_equal 2, one.comments.size
|
||||
assert_equal 2, one.categories.size
|
||||
end
|
||||
|
||||
def test_count_with_include
|
||||
if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
|
||||
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
|
||||
elsif current_adapter?(:OpenBaseAdapter)
|
||||
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15")
|
||||
else
|
||||
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15")
|
||||
end
|
||||
end
|
||||
end
|
||||
47
vendor/rails/activerecord/test/associations/extension_test.rb
vendored
Normal file
47
vendor/rails/activerecord/test/associations/extension_test.rb
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/post'
|
||||
require 'fixtures/comment'
|
||||
require 'fixtures/project'
|
||||
require 'fixtures/developer'
|
||||
|
||||
class AssociationsExtensionsTest < Test::Unit::TestCase
|
||||
fixtures :projects, :developers, :developers_projects, :comments, :posts
|
||||
|
||||
def test_extension_on_has_many
|
||||
assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent
|
||||
end
|
||||
|
||||
def test_extension_on_habtm
|
||||
assert_equal projects(:action_controller), developers(:david).projects.find_most_recent
|
||||
end
|
||||
|
||||
def test_named_extension_on_habtm
|
||||
assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent
|
||||
end
|
||||
|
||||
def test_named_two_extensions_on_habtm
|
||||
assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_twice.find_most_recent
|
||||
assert_equal projects(:active_record), developers(:david).projects_extended_by_name_twice.find_least_recent
|
||||
end
|
||||
|
||||
def test_named_extension_and_block_on_habtm
|
||||
assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_and_block.find_most_recent
|
||||
assert_equal projects(:active_record), developers(:david).projects_extended_by_name_and_block.find_least_recent
|
||||
end
|
||||
|
||||
def test_marshalling_extensions
|
||||
david = developers(:david)
|
||||
assert_equal projects(:action_controller), david.projects.find_most_recent
|
||||
|
||||
david = Marshal.load(Marshal.dump(david))
|
||||
assert_equal projects(:action_controller), david.projects.find_most_recent
|
||||
end
|
||||
|
||||
def test_marshalling_named_extensions
|
||||
david = developers(:david)
|
||||
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
|
||||
|
||||
david = Marshal.load(Marshal.dump(david))
|
||||
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
|
||||
end
|
||||
end
|
||||
88
vendor/rails/activerecord/test/associations/inner_join_association_test.rb
vendored
Normal file
88
vendor/rails/activerecord/test/associations/inner_join_association_test.rb
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/post'
|
||||
require 'fixtures/comment'
|
||||
require 'fixtures/author'
|
||||
require 'fixtures/category'
|
||||
require 'fixtures/categorization'
|
||||
|
||||
class InnerJoinAssociationTest < Test::Unit::TestCase
|
||||
fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations
|
||||
|
||||
def test_construct_finder_sql_creates_inner_joins
|
||||
sql = Author.send(:construct_finder_sql, :joins => :posts)
|
||||
assert_match /INNER JOIN `?posts`? ON `?posts`?.author_id = authors.id/, sql
|
||||
end
|
||||
|
||||
def test_construct_finder_sql_cascades_inner_joins
|
||||
sql = Author.send(:construct_finder_sql, :joins => {:posts => :comments})
|
||||
assert_match /INNER JOIN `?posts`? ON `?posts`?.author_id = authors.id/, sql
|
||||
assert_match /INNER JOIN `?comments`? ON `?comments`?.post_id = posts.id/, sql
|
||||
end
|
||||
|
||||
def test_construct_finder_sql_inner_joins_through_associations
|
||||
sql = Author.send(:construct_finder_sql, :joins => :categorized_posts)
|
||||
assert_match /INNER JOIN `?categorizations`?.*INNER JOIN `?posts`?/, sql
|
||||
end
|
||||
|
||||
def test_construct_finder_sql_applies_association_conditions
|
||||
sql = Author.send(:construct_finder_sql, :joins => :categories_like_general, :conditions => "TERMINATING_MARKER")
|
||||
assert_match /INNER JOIN `?categories`? ON.*AND.*`?General`?.*TERMINATING_MARKER/, sql
|
||||
end
|
||||
|
||||
def test_construct_finder_sql_unpacks_nested_joins
|
||||
sql = Author.send(:construct_finder_sql, :joins => {:posts => [[:comments]]})
|
||||
assert_no_match /inner join.*inner join.*inner join/i, sql, "only two join clauses should be present"
|
||||
assert_match /INNER JOIN `?posts`? ON `?posts`?.author_id = authors.id/, sql
|
||||
assert_match /INNER JOIN `?comments`? ON `?comments`?.post_id = `?posts`?.id/, sql
|
||||
end
|
||||
|
||||
def test_construct_finder_sql_ignores_empty_joins_hash
|
||||
sql = Author.send(:construct_finder_sql, :joins => {})
|
||||
assert_no_match /JOIN/i, sql
|
||||
end
|
||||
|
||||
def test_construct_finder_sql_ignores_empty_joins_array
|
||||
sql = Author.send(:construct_finder_sql, :joins => [])
|
||||
assert_no_match /JOIN/i, sql
|
||||
end
|
||||
|
||||
def test_find_with_implicit_inner_joins_honors_readonly_without_select
|
||||
authors = Author.find(:all, :joins => :posts)
|
||||
assert !authors.empty?, "expected authors to be non-empty"
|
||||
assert authors.all? {|a| a.readonly? }, "expected all authors to be readonly"
|
||||
end
|
||||
|
||||
def test_find_with_implicit_inner_joins_honors_readonly_with_select
|
||||
authors = Author.find(:all, :select => 'authors.*', :joins => :posts)
|
||||
assert !authors.empty?, "expected authors to be non-empty"
|
||||
assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly"
|
||||
end
|
||||
|
||||
def test_find_with_implicit_inner_joins_honors_readonly_false
|
||||
authors = Author.find(:all, :joins => :posts, :readonly => false)
|
||||
assert !authors.empty?, "expected authors to be non-empty"
|
||||
assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly"
|
||||
end
|
||||
|
||||
def test_find_with_implicit_inner_joins_does_not_set_associations
|
||||
authors = Author.find(:all, :select => 'authors.*', :joins => :posts)
|
||||
assert !authors.empty?, "expected authors to be non-empty"
|
||||
assert authors.all? {|a| !a.send(:instance_variables).include?("@posts")}, "expected no authors to have the @posts association loaded"
|
||||
end
|
||||
|
||||
def test_count_honors_implicit_inner_joins
|
||||
real_count = Author.find(:all).sum{|a| a.posts.count }
|
||||
assert_equal real_count, Author.count(:joins => :posts), "plain inner join count should match the number of referenced posts records"
|
||||
end
|
||||
|
||||
def test_calculate_honors_implicit_inner_joins
|
||||
real_count = Author.find(:all).sum{|a| a.posts.count }
|
||||
assert_equal real_count, Author.calculate(:count, 'authors.id', :joins => :posts), "plain inner join count should match the number of referenced posts records"
|
||||
end
|
||||
|
||||
def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions
|
||||
real_count = Author.find(:all).select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length
|
||||
authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'")
|
||||
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
|
||||
end
|
||||
end
|
||||
559
vendor/rails/activerecord/test/associations/join_model_test.rb
vendored
Normal file
559
vendor/rails/activerecord/test/associations/join_model_test.rb
vendored
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/tag'
|
||||
require 'fixtures/tagging'
|
||||
require 'fixtures/post'
|
||||
require 'fixtures/item'
|
||||
require 'fixtures/comment'
|
||||
require 'fixtures/author'
|
||||
require 'fixtures/category'
|
||||
require 'fixtures/categorization'
|
||||
require 'fixtures/vertex'
|
||||
require 'fixtures/edge'
|
||||
require 'fixtures/book'
|
||||
require 'fixtures/citation'
|
||||
|
||||
class AssociationsJoinModelTest < Test::Unit::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books
|
||||
|
||||
def test_has_many
|
||||
assert authors(:david).categories.include?(categories(:general))
|
||||
end
|
||||
|
||||
def test_has_many_inherited
|
||||
assert authors(:mary).categories.include?(categories(:sti_test))
|
||||
end
|
||||
|
||||
def test_inherited_has_many
|
||||
assert categories(:sti_test).authors.include?(authors(:mary))
|
||||
end
|
||||
|
||||
def test_has_many_uniq_through_join_model
|
||||
assert_equal 2, authors(:mary).categorized_posts.size
|
||||
assert_equal 1, authors(:mary).unique_categorized_posts.size
|
||||
end
|
||||
|
||||
def test_has_many_uniq_through_count
|
||||
author = authors(:mary)
|
||||
assert !authors(:mary).unique_categorized_posts.loaded?
|
||||
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count }
|
||||
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) }
|
||||
assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, :conditions => "title is NULL") }
|
||||
assert !authors(:mary).unique_categorized_posts.loaded?
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many
|
||||
assert posts(:welcome).taggings.include?(taggings(:welcome_general))
|
||||
end
|
||||
|
||||
def test_polymorphic_has_one
|
||||
assert_equal taggings(:welcome_general), posts(:welcome).tagging
|
||||
end
|
||||
|
||||
def test_polymorphic_belongs_to
|
||||
assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model
|
||||
assert_equal tags(:general), tag = posts(:welcome).tags.first
|
||||
assert_no_queries do
|
||||
tag.tagging
|
||||
end
|
||||
end
|
||||
|
||||
def test_count_polymorphic_has_many
|
||||
assert_equal 1, posts(:welcome).taggings.count
|
||||
assert_equal 1, posts(:welcome).tags.count
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_find
|
||||
assert_equal tags(:general), tag = posts(:welcome).tags.find(:first)
|
||||
assert_no_queries do
|
||||
tag.tagging
|
||||
end
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection
|
||||
assert_equal tags(:general), tag = posts(:welcome).funky_tags.first
|
||||
assert_no_queries do
|
||||
tag.tagging
|
||||
end
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find
|
||||
assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first)
|
||||
assert_no_queries do
|
||||
tag.tagging
|
||||
end
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_disabled_include
|
||||
assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
|
||||
assert_queries 1 do
|
||||
tag.tagging
|
||||
end
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
|
||||
assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
|
||||
tag.author_id
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key
|
||||
assert_equal tags(:misc), taggings(:welcome_general).super_tag
|
||||
assert_equal tags(:misc), posts(:welcome).super_tags.first
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class
|
||||
post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body'
|
||||
assert_instance_of SubStiPost, post
|
||||
|
||||
tagging = tags(:misc).taggings.create(:taggable => post)
|
||||
assert_equal "SubStiPost", tagging.taggable_type
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_inheritance
|
||||
assert_equal tags(:general), posts(:thinking).tags.first
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name
|
||||
assert_equal tags(:general), posts(:thinking).funky_tags.first
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_create_model_with_inheritance
|
||||
post = posts(:thinking)
|
||||
assert_instance_of SpecialPost, post
|
||||
|
||||
tagging = tags(:misc).taggings.create(:taggable => post)
|
||||
assert_equal "Post", tagging.taggable_type
|
||||
end
|
||||
|
||||
def test_polymorphic_has_one_create_model_with_inheritance
|
||||
tagging = tags(:misc).create_tagging(:taggable => posts(:thinking))
|
||||
assert_equal "Post", tagging.taggable_type
|
||||
end
|
||||
|
||||
def test_set_polymorphic_has_many
|
||||
tagging = tags(:misc).taggings.create
|
||||
posts(:thinking).taggings << tagging
|
||||
assert_equal "Post", tagging.taggable_type
|
||||
end
|
||||
|
||||
def test_set_polymorphic_has_one
|
||||
tagging = tags(:misc).taggings.create
|
||||
posts(:thinking).tagging = tagging
|
||||
assert_equal "Post", tagging.taggable_type
|
||||
end
|
||||
|
||||
def test_create_polymorphic_has_many_with_scope
|
||||
old_count = posts(:welcome).taggings.count
|
||||
tagging = posts(:welcome).taggings.create(:tag => tags(:misc))
|
||||
assert_equal "Post", tagging.taggable_type
|
||||
assert_equal old_count+1, posts(:welcome).taggings.count
|
||||
end
|
||||
|
||||
def test_create_bang_polymorphic_with_has_many_scope
|
||||
old_count = posts(:welcome).taggings.count
|
||||
tagging = posts(:welcome).taggings.create!(:tag => tags(:misc))
|
||||
assert_equal "Post", tagging.taggable_type
|
||||
assert_equal old_count+1, posts(:welcome).taggings.count
|
||||
end
|
||||
|
||||
def test_create_polymorphic_has_one_with_scope
|
||||
old_count = Tagging.count
|
||||
tagging = posts(:welcome).tagging.create(:tag => tags(:misc))
|
||||
assert_equal "Post", tagging.taggable_type
|
||||
assert_equal old_count+1, Tagging.count
|
||||
end
|
||||
|
||||
def test_delete_polymorphic_has_many_with_delete_all
|
||||
assert_equal 1, posts(:welcome).taggings.count
|
||||
posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll'
|
||||
post = find_post_with_dependency(1, :has_many, :taggings, :delete_all)
|
||||
|
||||
old_count = Tagging.count
|
||||
post.destroy
|
||||
assert_equal old_count-1, Tagging.count
|
||||
assert_equal 0, posts(:welcome).taggings.count
|
||||
end
|
||||
|
||||
def test_delete_polymorphic_has_many_with_destroy
|
||||
assert_equal 1, posts(:welcome).taggings.count
|
||||
posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy'
|
||||
post = find_post_with_dependency(1, :has_many, :taggings, :destroy)
|
||||
|
||||
old_count = Tagging.count
|
||||
post.destroy
|
||||
assert_equal old_count-1, Tagging.count
|
||||
assert_equal 0, posts(:welcome).taggings.count
|
||||
end
|
||||
|
||||
def test_delete_polymorphic_has_many_with_nullify
|
||||
assert_equal 1, posts(:welcome).taggings.count
|
||||
posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify'
|
||||
post = find_post_with_dependency(1, :has_many, :taggings, :nullify)
|
||||
|
||||
old_count = Tagging.count
|
||||
post.destroy
|
||||
assert_equal old_count, Tagging.count
|
||||
assert_equal 0, posts(:welcome).taggings.count
|
||||
end
|
||||
|
||||
def test_delete_polymorphic_has_one_with_destroy
|
||||
assert posts(:welcome).tagging
|
||||
posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy'
|
||||
post = find_post_with_dependency(1, :has_one, :tagging, :destroy)
|
||||
|
||||
old_count = Tagging.count
|
||||
post.destroy
|
||||
assert_equal old_count-1, Tagging.count
|
||||
assert_nil posts(:welcome).tagging(true)
|
||||
end
|
||||
|
||||
def test_delete_polymorphic_has_one_with_nullify
|
||||
assert posts(:welcome).tagging
|
||||
posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify'
|
||||
post = find_post_with_dependency(1, :has_one, :tagging, :nullify)
|
||||
|
||||
old_count = Tagging.count
|
||||
post.destroy
|
||||
assert_equal old_count, Tagging.count
|
||||
assert_nil posts(:welcome).tagging(true)
|
||||
end
|
||||
|
||||
def test_has_many_with_piggyback
|
||||
assert_equal "2", categories(:sti_test).authors.first.post_id.to_s
|
||||
end
|
||||
|
||||
def test_include_has_many_through
|
||||
posts = Post.find(:all, :order => 'posts.id')
|
||||
posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id')
|
||||
assert_equal posts.length, posts_with_authors.length
|
||||
posts.length.times do |i|
|
||||
assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length }
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_polymorphic_has_one
|
||||
post = Post.find_by_id(posts(:welcome).id, :include => :tagging)
|
||||
tagging = taggings(:welcome_general)
|
||||
assert_no_queries do
|
||||
assert_equal tagging, post.tagging
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_polymorphic_has_one_defined_in_abstract_parent
|
||||
item = Item.find_by_id(items(:dvd).id, :include => :tagging)
|
||||
tagging = taggings(:godfather)
|
||||
assert_no_queries do
|
||||
assert_equal tagging, item.tagging
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_polymorphic_has_many_through
|
||||
posts = Post.find(:all, :order => 'posts.id')
|
||||
posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
|
||||
assert_equal posts.length, posts_with_tags.length
|
||||
posts.length.times do |i|
|
||||
assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_polymorphic_has_many
|
||||
posts = Post.find(:all, :order => 'posts.id')
|
||||
posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
|
||||
assert_equal posts.length, posts_with_taggings.length
|
||||
posts.length.times do |i|
|
||||
assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_many_find_all
|
||||
assert_equal [categories(:general)], authors(:david).categories.find(:all)
|
||||
end
|
||||
|
||||
def test_has_many_find_first
|
||||
assert_equal categories(:general), authors(:david).categories.find(:first)
|
||||
end
|
||||
|
||||
def test_has_many_with_hash_conditions
|
||||
assert_equal categories(:general), authors(:david).categories_like_general.find(:first)
|
||||
end
|
||||
|
||||
def test_has_many_find_conditions
|
||||
assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'")
|
||||
assert_equal nil, authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'")
|
||||
end
|
||||
|
||||
def test_has_many_class_methods_called_by_method_missing
|
||||
assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first
|
||||
assert_equal nil, authors(:david).categories.find_by_name('Technology')
|
||||
end
|
||||
|
||||
def test_has_many_going_through_join_model_with_custom_foreign_key
|
||||
assert_equal [], posts(:thinking).authors
|
||||
assert_equal [authors(:mary)], posts(:authorless).authors
|
||||
end
|
||||
|
||||
def test_belongs_to_polymorphic_with_counter_cache
|
||||
assert_equal 0, posts(:welcome)[:taggings_count]
|
||||
tagging = posts(:welcome).taggings.create(:tag => tags(:general))
|
||||
assert_equal 1, posts(:welcome, :reload)[:taggings_count]
|
||||
tagging.destroy
|
||||
assert posts(:welcome, :reload)[:taggings_count].zero?
|
||||
end
|
||||
|
||||
def test_unavailable_through_reflection
|
||||
assert_raises (ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings }
|
||||
end
|
||||
|
||||
def test_has_many_through_join_model_with_conditions
|
||||
assert_equal [], posts(:welcome).invalid_taggings
|
||||
assert_equal [], posts(:welcome).invalid_tags
|
||||
end
|
||||
|
||||
def test_has_many_polymorphic
|
||||
assert_raises ActiveRecord::HasManyThroughAssociationPolymorphicError do
|
||||
assert_equal posts(:welcome, :thinking), tags(:general).taggables
|
||||
end
|
||||
assert_raises ActiveRecord::EagerLoadPolymorphicError do
|
||||
assert_equal posts(:welcome, :thinking), tags(:general).taggings.find(:all, :include => :taggable)
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_many_polymorphic_with_source_type
|
||||
assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts
|
||||
end
|
||||
|
||||
def test_eager_has_many_polymorphic_with_source_type
|
||||
tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts)
|
||||
desired = posts(:welcome, :thinking)
|
||||
assert_no_queries do
|
||||
assert_equal desired, tag_with_include.tagged_posts
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_many_through_has_many_find_all
|
||||
assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first
|
||||
end
|
||||
|
||||
def test_has_many_through_has_many_find_all_with_custom_class
|
||||
assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first
|
||||
end
|
||||
|
||||
def test_has_many_through_has_many_find_first
|
||||
assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id')
|
||||
end
|
||||
|
||||
def test_has_many_through_has_many_find_conditions
|
||||
options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' }
|
||||
assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options)
|
||||
end
|
||||
|
||||
def test_has_many_through_has_many_find_by_id
|
||||
assert_equal comments(:more_greetings), authors(:david).comments.find(2)
|
||||
end
|
||||
|
||||
def test_has_many_through_polymorphic_has_one
|
||||
assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging }
|
||||
end
|
||||
|
||||
def test_has_many_through_polymorphic_has_many
|
||||
assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.uniq.sort_by { |t| t.id }
|
||||
end
|
||||
|
||||
def test_include_has_many_through_polymorphic_has_many
|
||||
author = Author.find_by_id(authors(:david).id, :include => :taggings)
|
||||
expected_taggings = taggings(:welcome_general, :thinking_general)
|
||||
assert_no_queries do
|
||||
assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_many_through_has_many_through
|
||||
assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags }
|
||||
end
|
||||
|
||||
def test_has_many_through_habtm
|
||||
assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories }
|
||||
end
|
||||
|
||||
def test_eager_load_has_many_through_has_many
|
||||
author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
|
||||
SpecialComment.new; VerySpecialComment.new
|
||||
assert_no_queries do
|
||||
assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_load_has_many_through_has_many_with_conditions
|
||||
post = Post.find(:first, :include => :invalid_tags)
|
||||
assert_no_queries do
|
||||
post.invalid_tags
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_belongs_to_and_has_one_not_singularized
|
||||
assert_nothing_raised do
|
||||
Author.find(:first, :include => :author_address)
|
||||
AuthorAddress.find(:first, :include => :author)
|
||||
end
|
||||
end
|
||||
|
||||
def test_self_referential_has_many_through
|
||||
assert_equal [authors(:mary)], authors(:david).favorite_authors
|
||||
assert_equal [], authors(:mary).favorite_authors
|
||||
end
|
||||
|
||||
def test_add_to_self_referential_has_many_through
|
||||
new_author = Author.create(:name => "Bob")
|
||||
authors(:david).author_favorites.create :favorite_author => new_author
|
||||
assert_equal new_author, authors(:david).reload.favorite_authors.first
|
||||
end
|
||||
|
||||
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
|
||||
author = Author.find(:first)
|
||||
assert !author.comments.blank?
|
||||
assert author.nonexistant_comments.blank?
|
||||
end
|
||||
|
||||
def test_has_many_through_uses_correct_attributes
|
||||
assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"]
|
||||
end
|
||||
|
||||
def test_raise_error_when_adding_new_record_to_has_many_through
|
||||
assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags << tags(:general).clone }
|
||||
assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).clone.tags << tags(:general) }
|
||||
assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags.build }
|
||||
assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags.new }
|
||||
end
|
||||
|
||||
def test_create_associate_when_adding_to_has_many_through
|
||||
count = posts(:thinking).tags.count
|
||||
push = Tag.create!(:name => 'pushme')
|
||||
post_thinking = posts(:thinking)
|
||||
assert_nothing_raised { post_thinking.tags << push }
|
||||
assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
|
||||
message = "Expected a Tag in tags collection, got #{wrong.class}.")
|
||||
assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
|
||||
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
|
||||
assert_equal(count + 1, post_thinking.tags.size)
|
||||
assert_equal(count + 1, post_thinking.tags(true).size)
|
||||
|
||||
assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo')
|
||||
assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
|
||||
message = "Expected a Tag in tags collection, got #{wrong.class}.")
|
||||
assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
|
||||
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
|
||||
assert_equal(count + 2, post_thinking.tags.size)
|
||||
assert_equal(count + 2, post_thinking.tags(true).size)
|
||||
|
||||
assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) }
|
||||
assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
|
||||
message = "Expected a Tag in tags collection, got #{wrong.class}.")
|
||||
assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
|
||||
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
|
||||
assert_equal(count + 4, post_thinking.tags.size)
|
||||
assert_equal(count + 4, post_thinking.tags(true).size)
|
||||
|
||||
# Raises if the wrong reflection name is used to set the Edge belongs_to
|
||||
assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) }
|
||||
end
|
||||
|
||||
def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded
|
||||
author = authors(:david)
|
||||
assert_equal 9, author.comments.size
|
||||
assert !author.comments.loaded?
|
||||
end
|
||||
|
||||
uses_mocha('has_many_through_collection_size_uses_counter_cache_if_it_exists') do
|
||||
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
|
||||
author = authors(:david)
|
||||
author.stubs(:read_attribute).with('comments_count').returns(100)
|
||||
assert_equal 100, author.comments.size
|
||||
assert !author.comments.loaded?
|
||||
end
|
||||
end
|
||||
|
||||
def test_adding_junk_to_has_many_through_should_raise_type_mismatch
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags << "Uhh what now?" }
|
||||
end
|
||||
|
||||
def test_adding_to_has_many_through_should_return_self
|
||||
tags = posts(:thinking).tags
|
||||
assert_equal tags, posts(:thinking).tags.push(tags(:general))
|
||||
end
|
||||
|
||||
def test_delete_associate_when_deleting_from_has_many_through_with_nonstandard_id
|
||||
count = books(:awdr).references.count
|
||||
references_before = books(:awdr).references
|
||||
book = Book.create!(:name => 'Getting Real')
|
||||
book_awdr = books(:awdr)
|
||||
book_awdr.references << book
|
||||
assert_equal(count + 1, book_awdr.references(true).size)
|
||||
|
||||
assert_nothing_raised { book_awdr.references.delete(book) }
|
||||
assert_equal(count, book_awdr.references.size)
|
||||
assert_equal(count, book_awdr.references(true).size)
|
||||
assert_equal(references_before.sort, book_awdr.references.sort)
|
||||
end
|
||||
|
||||
def test_delete_associate_when_deleting_from_has_many_through
|
||||
count = posts(:thinking).tags.count
|
||||
tags_before = posts(:thinking).tags
|
||||
tag = Tag.create!(:name => 'doomed')
|
||||
post_thinking = posts(:thinking)
|
||||
post_thinking.tags << tag
|
||||
assert_equal(count + 1, post_thinking.tags(true).size)
|
||||
|
||||
assert_nothing_raised { post_thinking.tags.delete(tag) }
|
||||
assert_equal(count, post_thinking.tags.size)
|
||||
assert_equal(count, post_thinking.tags(true).size)
|
||||
assert_equal(tags_before.sort, post_thinking.tags.sort)
|
||||
end
|
||||
|
||||
def test_delete_associate_when_deleting_from_has_many_through_with_multiple_tags
|
||||
count = posts(:thinking).tags.count
|
||||
tags_before = posts(:thinking).tags
|
||||
doomed = Tag.create!(:name => 'doomed')
|
||||
doomed2 = Tag.create!(:name => 'doomed2')
|
||||
quaked = Tag.create!(:name => 'quaked')
|
||||
post_thinking = posts(:thinking)
|
||||
post_thinking.tags << doomed << doomed2
|
||||
assert_equal(count + 2, post_thinking.tags(true).size)
|
||||
|
||||
assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) }
|
||||
assert_equal(count, post_thinking.tags.size)
|
||||
assert_equal(count, post_thinking.tags(true).size)
|
||||
assert_equal(tags_before.sort, post_thinking.tags.sort)
|
||||
end
|
||||
|
||||
def test_deleting_junk_from_has_many_through_should_raise_type_mismatch
|
||||
assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") }
|
||||
end
|
||||
|
||||
def test_has_many_through_sum_uses_calculations
|
||||
assert_nothing_raised { authors(:david).comments.sum(:post_id) }
|
||||
end
|
||||
|
||||
def test_has_many_through_has_many_with_sti
|
||||
assert_equal [comments(:does_it_hurt)], authors(:david).special_post_comments
|
||||
end
|
||||
|
||||
def test_uniq_has_many_through_should_retain_order
|
||||
comment_ids = authors(:david).comments.map(&:id)
|
||||
assert_equal comment_ids.sort, authors(:david).ordered_uniq_comments.map(&:id)
|
||||
assert_equal comment_ids.sort.reverse, authors(:david).ordered_uniq_comments_desc.map(&:id)
|
||||
end
|
||||
|
||||
private
|
||||
# create dynamic Post models to allow different dependency options
|
||||
def find_post_with_dependency(post_id, association, association_name, dependency)
|
||||
class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
|
||||
Post.find(post_id).update_attribute :type, class_name
|
||||
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
|
||||
klass.set_table_name 'posts'
|
||||
klass.send(association, association_name, :as => :taggable, :dependent => dependency)
|
||||
klass.find(post_id)
|
||||
end
|
||||
end
|
||||
2147
vendor/rails/activerecord/test/associations_test.rb
vendored
Executable file
2147
vendor/rails/activerecord/test/associations_test.rb
vendored
Executable file
File diff suppressed because it is too large
Load diff
146
vendor/rails/activerecord/test/attribute_methods_test.rb
vendored
Executable file
146
vendor/rails/activerecord/test/attribute_methods_test.rb
vendored
Executable file
|
|
@ -0,0 +1,146 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/topic'
|
||||
|
||||
class AttributeMethodsTest < Test::Unit::TestCase
|
||||
fixtures :topics
|
||||
def setup
|
||||
@old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup
|
||||
@target = Class.new(ActiveRecord::Base)
|
||||
@target.table_name = 'topics'
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActiveRecord::Base.send(:attribute_method_suffixes).clear
|
||||
ActiveRecord::Base.attribute_method_suffix *@old_suffixes
|
||||
end
|
||||
|
||||
|
||||
def test_match_attribute_method_query_returns_match_data
|
||||
assert_not_nil md = @target.match_attribute_method?('title=')
|
||||
assert_equal 'title', md.pre_match
|
||||
assert_equal ['='], md.captures
|
||||
|
||||
%w(_hello_world ist! _maybe?).each do |suffix|
|
||||
@target.class_eval "def attribute#{suffix}(*args) args end"
|
||||
@target.attribute_method_suffix suffix
|
||||
|
||||
assert_not_nil md = @target.match_attribute_method?("title#{suffix}")
|
||||
assert_equal 'title', md.pre_match
|
||||
assert_equal [suffix], md.captures
|
||||
end
|
||||
end
|
||||
|
||||
def test_declared_attribute_method_affects_respond_to_and_method_missing
|
||||
topic = @target.new(:title => 'Budget')
|
||||
assert topic.respond_to?('title')
|
||||
assert_equal 'Budget', topic.title
|
||||
assert !topic.respond_to?('title_hello_world')
|
||||
assert_raise(NoMethodError) { topic.title_hello_world }
|
||||
|
||||
%w(_hello_world _it! _candidate= able?).each do |suffix|
|
||||
@target.class_eval "def attribute#{suffix}(*args) args end"
|
||||
@target.attribute_method_suffix suffix
|
||||
|
||||
meth = "title#{suffix}"
|
||||
assert topic.respond_to?(meth)
|
||||
assert_equal ['title'], topic.send(meth)
|
||||
assert_equal ['title', 'a'], topic.send(meth, 'a')
|
||||
assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3)
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_unserialize_attributes_for_frozen_records
|
||||
myobj = {:value1 => :value2}
|
||||
topic = Topic.create("content" => myobj)
|
||||
topic.freeze
|
||||
assert_equal myobj, topic.content
|
||||
end
|
||||
|
||||
def test_kernel_methods_not_implemented_in_activerecord
|
||||
%w(test name display y).each do |method|
|
||||
assert_equal false, ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined"
|
||||
end
|
||||
end
|
||||
|
||||
def test_primary_key_implemented
|
||||
assert_equal true, Class.new(ActiveRecord::Base).instance_method_already_implemented?('id')
|
||||
end
|
||||
|
||||
def test_defined_kernel_methods_implemented_in_model
|
||||
%w(test name display y).each do |method|
|
||||
klass = Class.new ActiveRecord::Base
|
||||
klass.class_eval "def #{method}() 'defined #{method}' end"
|
||||
assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined"
|
||||
end
|
||||
end
|
||||
|
||||
def test_defined_kernel_methods_implemented_in_model_abstract_subclass
|
||||
%w(test name display y).each do |method|
|
||||
abstract = Class.new ActiveRecord::Base
|
||||
abstract.class_eval "def #{method}() 'defined #{method}' end"
|
||||
abstract.abstract_class = true
|
||||
klass = Class.new abstract
|
||||
assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined"
|
||||
end
|
||||
end
|
||||
|
||||
def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model
|
||||
%w(save create_or_update).each do |method|
|
||||
klass = Class.new ActiveRecord::Base
|
||||
klass.class_eval "def #{method}() 'defined #{method}' end"
|
||||
assert_raises ActiveRecord::DangerousAttributeError do
|
||||
klass.instance_method_already_implemented?(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_only_time_related_columns_are_meant_to_be_cached_by_default
|
||||
expected = %w(datetime timestamp time date).sort
|
||||
assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort
|
||||
end
|
||||
|
||||
def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default
|
||||
default_attributes = Topic.cached_attributes
|
||||
Topic.cache_attributes :replies_count
|
||||
expected = default_attributes + ["replies_count"]
|
||||
assert_equal expected.sort, Topic.cached_attributes.sort
|
||||
Topic.instance_variable_set "@cached_attributes", nil
|
||||
end
|
||||
|
||||
def test_time_related_columns_are_actually_cached
|
||||
column_types = %w(datetime timestamp time date).map(&:to_sym)
|
||||
column_names = Topic.columns.select{|c| column_types.include?(c.type) }.map(&:name)
|
||||
|
||||
assert_equal column_names.sort, Topic.cached_attributes.sort
|
||||
assert_equal time_related_columns_on_topic.sort, Topic.cached_attributes.sort
|
||||
end
|
||||
|
||||
def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else
|
||||
t = topics(:first)
|
||||
cache = t.instance_variable_get "@attributes_cache"
|
||||
|
||||
assert_not_nil cache
|
||||
assert cache.empty?
|
||||
|
||||
all_columns = Topic.columns.map(&:name)
|
||||
cached_columns = time_related_columns_on_topic
|
||||
uncached_columns = all_columns - cached_columns
|
||||
|
||||
all_columns.each do |attr_name|
|
||||
attribute_gets_cached = Topic.cache_attribute?(attr_name)
|
||||
val = t.send attr_name unless attr_name == "type"
|
||||
if attribute_gets_cached
|
||||
assert cached_columns.include?(attr_name)
|
||||
assert_equal val, cache[attr_name]
|
||||
else
|
||||
assert uncached_columns.include?(attr_name)
|
||||
assert !cache.include?(attr_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def time_related_columns_on_topic
|
||||
Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name)
|
||||
end
|
||||
end
|
||||
1745
vendor/rails/activerecord/test/base_test.rb
vendored
Executable file
1745
vendor/rails/activerecord/test/base_test.rb
vendored
Executable file
File diff suppressed because it is too large
Load diff
32
vendor/rails/activerecord/test/binary_test.rb
vendored
Normal file
32
vendor/rails/activerecord/test/binary_test.rb
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
# Without using prepared statements, it makes no sense to test
|
||||
# BLOB data with SQL Server, because the length of a statement is
|
||||
# limited to 8KB.
|
||||
#
|
||||
# Without using prepared statements, it makes no sense to test
|
||||
# BLOB data with DB2 or Firebird, because the length of a statement
|
||||
# is limited to 32KB.
|
||||
unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
|
||||
require 'fixtures/binary'
|
||||
|
||||
class BinaryTest < Test::Unit::TestCase
|
||||
FIXTURES = %w(flowers.jpg example.log)
|
||||
|
||||
def test_load_save
|
||||
Binary.delete_all
|
||||
|
||||
FIXTURES.each do |filename|
|
||||
data = File.read("#{File.dirname(__FILE__)}/fixtures/#{filename}").freeze
|
||||
|
||||
bin = Binary.new(:data => data)
|
||||
assert_equal data, bin.data, 'Newly assigned data differs from original'
|
||||
|
||||
bin.save!
|
||||
assert_equal data, bin.data, 'Data differs from original after save'
|
||||
|
||||
assert_equal data, bin.reload.data, 'Reloaded data differs from original'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
251
vendor/rails/activerecord/test/calculations_test.rb
vendored
Normal file
251
vendor/rails/activerecord/test/calculations_test.rb
vendored
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/company'
|
||||
require 'fixtures/topic'
|
||||
|
||||
Company.has_many :accounts
|
||||
|
||||
class NumericData < ActiveRecord::Base
|
||||
self.table_name = 'numeric_data'
|
||||
end
|
||||
|
||||
class CalculationsTest < Test::Unit::TestCase
|
||||
fixtures :companies, :accounts, :topics
|
||||
|
||||
def test_should_sum_field
|
||||
assert_equal 318, Account.sum(:credit_limit)
|
||||
end
|
||||
|
||||
def test_should_average_field
|
||||
value = Account.average(:credit_limit)
|
||||
assert_kind_of Float, value
|
||||
assert_in_delta 53.0, value, 0.001
|
||||
end
|
||||
|
||||
def test_should_return_nil_as_average
|
||||
assert_nil NumericData.average(:bank_balance)
|
||||
end
|
||||
|
||||
def test_should_get_maximum_of_field
|
||||
assert_equal 60, Account.maximum(:credit_limit)
|
||||
end
|
||||
|
||||
def test_should_get_maximum_of_field_with_include
|
||||
assert_equal 50, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'")
|
||||
end
|
||||
|
||||
def test_should_get_maximum_of_field_with_scoped_include
|
||||
Account.with_scope :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do
|
||||
assert_equal 50, Account.maximum(:credit_limit)
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_get_minimum_of_field
|
||||
assert_equal 50, Account.minimum(:credit_limit)
|
||||
end
|
||||
|
||||
def test_should_group_by_field
|
||||
c = Account.sum(:credit_limit, :group => :firm_id)
|
||||
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_field
|
||||
c = Account.sum(:credit_limit, :group => :firm_id)
|
||||
assert_equal 50, c[1]
|
||||
assert_equal 105, c[6]
|
||||
assert_equal 60, c[2]
|
||||
end
|
||||
|
||||
def test_should_order_by_grouped_field
|
||||
c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
|
||||
assert_equal [1, 2, 6, 9], c.keys.compact
|
||||
end
|
||||
|
||||
def test_should_order_by_calculation
|
||||
c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
|
||||
assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
|
||||
assert_equal [6, 2, 9, 1], c.keys.compact
|
||||
end
|
||||
|
||||
def test_should_limit_calculation
|
||||
c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
|
||||
:group => :firm_id, :order => "firm_id", :limit => 2)
|
||||
assert_equal [1, 2], c.keys.compact
|
||||
end
|
||||
|
||||
def test_should_limit_calculation_with_offset
|
||||
c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
|
||||
:group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1)
|
||||
assert_equal [2, 6], c.keys.compact
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_field_having_condition
|
||||
c = Account.sum(:credit_limit, :group => :firm_id,
|
||||
:having => 'sum(credit_limit) > 50')
|
||||
assert_nil c[1]
|
||||
assert_equal 105, c[6]
|
||||
assert_equal 60, c[2]
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_association
|
||||
c = Account.sum(:credit_limit, :group => :firm)
|
||||
assert_equal 50, c[companies(:first_firm)]
|
||||
assert_equal 105, c[companies(:rails_core)]
|
||||
assert_equal 60, c[companies(:first_client)]
|
||||
end
|
||||
|
||||
def test_should_sum_field_with_conditions
|
||||
assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6')
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_field_with_conditions
|
||||
c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
|
||||
:group => :firm_id)
|
||||
assert_nil c[1]
|
||||
assert_equal 105, c[6]
|
||||
assert_equal 60, c[2]
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_field_with_conditions_and_having
|
||||
c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
|
||||
:group => :firm_id,
|
||||
:having => 'sum(credit_limit) > 60')
|
||||
assert_nil c[1]
|
||||
assert_equal 105, c[6]
|
||||
assert_nil c[2]
|
||||
end
|
||||
|
||||
def test_should_group_by_fields_with_table_alias
|
||||
c = Account.sum(:credit_limit, :group => 'accounts.firm_id')
|
||||
assert_equal 50, c[1]
|
||||
assert_equal 105, c[6]
|
||||
assert_equal 60, c[2]
|
||||
end
|
||||
|
||||
def test_should_calculate_with_invalid_field
|
||||
assert_equal 6, Account.calculate(:count, '*')
|
||||
assert_equal 6, Account.calculate(:count, :all)
|
||||
end
|
||||
|
||||
def test_should_calculate_grouped_with_invalid_field
|
||||
c = Account.count(:all, :group => 'accounts.firm_id')
|
||||
assert_equal 1, c[1]
|
||||
assert_equal 2, c[6]
|
||||
assert_equal 1, c[2]
|
||||
end
|
||||
|
||||
def test_should_calculate_grouped_association_with_invalid_field
|
||||
c = Account.count(:all, :group => :firm)
|
||||
assert_equal 1, c[companies(:first_firm)]
|
||||
assert_equal 2, c[companies(:rails_core)]
|
||||
assert_equal 1, c[companies(:first_client)]
|
||||
end
|
||||
|
||||
uses_mocha 'group_by_non_numeric_foreign_key_association' do
|
||||
def test_should_group_by_association_with_non_numeric_foreign_key
|
||||
ActiveRecord::Base.connection.expects(:select_all).returns([{"count_all" => 1, "firm_id" => "ABC"}])
|
||||
|
||||
firm = mock()
|
||||
firm.expects(:id).returns("ABC")
|
||||
firm.expects(:class).returns(Firm)
|
||||
Company.expects(:find).with(["ABC"]).returns([firm])
|
||||
|
||||
column = mock()
|
||||
column.expects(:name).at_least_once.returns(:firm_id)
|
||||
column.expects(:type_cast).with("ABC").returns("ABC")
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_modify_options_when_using_includes
|
||||
options = {:conditions => 'companies.id > 1', :include => :firm}
|
||||
options_copy = options.dup
|
||||
|
||||
Account.count(:all, options)
|
||||
assert_equal options_copy, options
|
||||
end
|
||||
|
||||
def test_should_calculate_grouped_by_function
|
||||
c = Company.count(:all, :group => "UPPER(#{QUOTED_TYPE})")
|
||||
assert_equal 2, c[nil]
|
||||
assert_equal 1, c['DEPENDENTFIRM']
|
||||
assert_equal 3, c['CLIENT']
|
||||
assert_equal 2, c['FIRM']
|
||||
end
|
||||
|
||||
def test_should_calculate_grouped_by_function_with_table_alias
|
||||
c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})")
|
||||
assert_equal 2, c[nil]
|
||||
assert_equal 1, c['DEPENDENTFIRM']
|
||||
assert_equal 3, c['CLIENT']
|
||||
assert_equal 2, c['FIRM']
|
||||
end
|
||||
|
||||
def test_should_not_overshadow_enumerable_sum
|
||||
assert_equal 6, [1, 2, 3].sum(&:abs)
|
||||
end
|
||||
|
||||
def test_should_sum_scoped_field
|
||||
assert_equal 15, companies(:rails_core).companies.sum(:id)
|
||||
end
|
||||
|
||||
def test_should_sum_scoped_field_with_conditions
|
||||
assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
|
||||
end
|
||||
|
||||
def test_should_group_by_scoped_field
|
||||
c = companies(:rails_core).companies.sum(:id, :group => :name)
|
||||
assert_equal 7, c['Leetsoft']
|
||||
assert_equal 8, c['Jadedpixel']
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_field_with_conditions_and_having
|
||||
c = companies(:rails_core).companies.sum(:id, :group => :name,
|
||||
:having => 'sum(id) > 7')
|
||||
assert_nil c['Leetsoft']
|
||||
assert_equal 8, c['Jadedpixel']
|
||||
end
|
||||
|
||||
def test_should_reject_invalid_options
|
||||
assert_nothing_raised do
|
||||
[:count, :sum].each do |func|
|
||||
# empty options are valid
|
||||
Company.send(:validate_calculation_options, func)
|
||||
# these options are valid for all calculations
|
||||
[:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt|
|
||||
Company.send(:validate_calculation_options, func, opt => true)
|
||||
end
|
||||
end
|
||||
|
||||
# :include is only valid on :count
|
||||
Company.send(:validate_calculation_options, :count, :include => true)
|
||||
end
|
||||
|
||||
assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) }
|
||||
assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) }
|
||||
end
|
||||
|
||||
def test_should_count_selected_field_with_include
|
||||
assert_equal 6, Account.count(:distinct => true, :include => :firm)
|
||||
assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit)
|
||||
end
|
||||
|
||||
def test_count_with_column_parameter
|
||||
assert_equal 5, Account.count(:firm_id)
|
||||
end
|
||||
|
||||
def test_count_with_column_and_options_parameter
|
||||
assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50")
|
||||
end
|
||||
|
||||
def test_count_with_no_parameters_isnt_deprecated
|
||||
assert_not_deprecated { Account.count }
|
||||
end
|
||||
|
||||
def test_count_with_too_many_parameters_raises
|
||||
assert_raise(ArgumentError) { Account.count(1, 2, 3) }
|
||||
end
|
||||
end
|
||||
400
vendor/rails/activerecord/test/callbacks_test.rb
vendored
Normal file
400
vendor/rails/activerecord/test/callbacks_test.rb
vendored
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class CallbackDeveloper < ActiveRecord::Base
|
||||
set_table_name 'developers'
|
||||
|
||||
class << self
|
||||
def callback_string(callback_method)
|
||||
"history << [#{callback_method.to_sym.inspect}, :string]"
|
||||
end
|
||||
|
||||
def callback_proc(callback_method)
|
||||
Proc.new { |model| model.history << [callback_method, :proc] }
|
||||
end
|
||||
|
||||
def define_callback_method(callback_method)
|
||||
define_method("#{callback_method}_method") do |model|
|
||||
model.history << [callback_method, :method]
|
||||
end
|
||||
end
|
||||
|
||||
def callback_object(callback_method)
|
||||
klass = Class.new
|
||||
klass.send(:define_method, callback_method) do |model|
|
||||
model.history << [callback_method, :object]
|
||||
end
|
||||
klass.new
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
|
||||
callback_method_sym = callback_method.to_sym
|
||||
define_callback_method(callback_method_sym)
|
||||
send(callback_method, callback_method_sym)
|
||||
send(callback_method, callback_string(callback_method_sym))
|
||||
send(callback_method, callback_proc(callback_method_sym))
|
||||
send(callback_method, callback_object(callback_method_sym))
|
||||
send(callback_method) { |model| model.history << [callback_method_sym, :block] }
|
||||
end
|
||||
|
||||
def history
|
||||
@history ||= []
|
||||
end
|
||||
|
||||
# after_initialize and after_find are invoked only if instance methods have been defined.
|
||||
def after_initialize
|
||||
end
|
||||
|
||||
def after_find
|
||||
end
|
||||
end
|
||||
|
||||
class ParentDeveloper < ActiveRecord::Base
|
||||
set_table_name 'developers'
|
||||
attr_accessor :after_save_called
|
||||
before_validation {|record| record.after_save_called = true}
|
||||
end
|
||||
|
||||
class ChildDeveloper < ParentDeveloper
|
||||
|
||||
end
|
||||
|
||||
class RecursiveCallbackDeveloper < ActiveRecord::Base
|
||||
set_table_name 'developers'
|
||||
|
||||
before_save :on_before_save
|
||||
after_save :on_after_save
|
||||
|
||||
attr_reader :on_before_save_called, :on_after_save_called
|
||||
|
||||
def on_before_save
|
||||
@on_before_save_called ||= 0
|
||||
@on_before_save_called += 1
|
||||
save unless @on_before_save_called > 1
|
||||
end
|
||||
|
||||
def on_after_save
|
||||
@on_after_save_called ||= 0
|
||||
@on_after_save_called += 1
|
||||
save unless @on_after_save_called > 1
|
||||
end
|
||||
end
|
||||
|
||||
class ImmutableDeveloper < ActiveRecord::Base
|
||||
set_table_name 'developers'
|
||||
|
||||
validates_inclusion_of :salary, :in => 50000..200000
|
||||
|
||||
before_save :cancel
|
||||
before_destroy :cancel
|
||||
|
||||
def cancelled?
|
||||
@cancelled == true
|
||||
end
|
||||
|
||||
private
|
||||
def cancel
|
||||
@cancelled = true
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class ImmutableMethodDeveloper < ActiveRecord::Base
|
||||
set_table_name 'developers'
|
||||
|
||||
validates_inclusion_of :salary, :in => 50000..200000
|
||||
|
||||
def cancelled?
|
||||
@cancelled == true
|
||||
end
|
||||
|
||||
def before_save
|
||||
@cancelled = true
|
||||
false
|
||||
end
|
||||
|
||||
def before_destroy
|
||||
@cancelled = true
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class CallbackCancellationDeveloper < ActiveRecord::Base
|
||||
set_table_name 'developers'
|
||||
def before_create
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class CallbacksTest < Test::Unit::TestCase
|
||||
fixtures :developers
|
||||
|
||||
def test_initialize
|
||||
david = CallbackDeveloper.new
|
||||
assert_equal [
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_find
|
||||
david = CallbackDeveloper.find(1)
|
||||
assert_equal [
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_new_valid?
|
||||
david = CallbackDeveloper.new
|
||||
david.valid?
|
||||
assert_equal [
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_create, :string ],
|
||||
[ :before_validation_on_create, :proc ],
|
||||
[ :before_validation_on_create, :object ],
|
||||
[ :before_validation_on_create, :block ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_create, :string ],
|
||||
[ :after_validation_on_create, :proc ],
|
||||
[ :after_validation_on_create, :object ],
|
||||
[ :after_validation_on_create, :block ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_existing_valid?
|
||||
david = CallbackDeveloper.find(1)
|
||||
david.valid?
|
||||
assert_equal [
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_update, :string ],
|
||||
[ :before_validation_on_update, :proc ],
|
||||
[ :before_validation_on_update, :object ],
|
||||
[ :before_validation_on_update, :block ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_update, :string ],
|
||||
[ :after_validation_on_update, :proc ],
|
||||
[ :after_validation_on_update, :object ],
|
||||
[ :after_validation_on_update, :block ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_create
|
||||
david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000)
|
||||
assert_equal [
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_create, :string ],
|
||||
[ :before_validation_on_create, :proc ],
|
||||
[ :before_validation_on_create, :object ],
|
||||
[ :before_validation_on_create, :block ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_create, :string ],
|
||||
[ :after_validation_on_create, :proc ],
|
||||
[ :after_validation_on_create, :object ],
|
||||
[ :after_validation_on_create, :block ],
|
||||
[ :before_save, :string ],
|
||||
[ :before_save, :proc ],
|
||||
[ :before_save, :object ],
|
||||
[ :before_save, :block ],
|
||||
[ :before_create, :string ],
|
||||
[ :before_create, :proc ],
|
||||
[ :before_create, :object ],
|
||||
[ :before_create, :block ],
|
||||
[ :after_create, :string ],
|
||||
[ :after_create, :proc ],
|
||||
[ :after_create, :object ],
|
||||
[ :after_create, :block ],
|
||||
[ :after_save, :string ],
|
||||
[ :after_save, :proc ],
|
||||
[ :after_save, :object ],
|
||||
[ :after_save, :block ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_save
|
||||
david = CallbackDeveloper.find(1)
|
||||
david.save
|
||||
assert_equal [
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_update, :string ],
|
||||
[ :before_validation_on_update, :proc ],
|
||||
[ :before_validation_on_update, :object ],
|
||||
[ :before_validation_on_update, :block ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_update, :string ],
|
||||
[ :after_validation_on_update, :proc ],
|
||||
[ :after_validation_on_update, :object ],
|
||||
[ :after_validation_on_update, :block ],
|
||||
[ :before_save, :string ],
|
||||
[ :before_save, :proc ],
|
||||
[ :before_save, :object ],
|
||||
[ :before_save, :block ],
|
||||
[ :before_update, :string ],
|
||||
[ :before_update, :proc ],
|
||||
[ :before_update, :object ],
|
||||
[ :before_update, :block ],
|
||||
[ :after_update, :string ],
|
||||
[ :after_update, :proc ],
|
||||
[ :after_update, :object ],
|
||||
[ :after_update, :block ],
|
||||
[ :after_save, :string ],
|
||||
[ :after_save, :proc ],
|
||||
[ :after_save, :object ],
|
||||
[ :after_save, :block ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_destroy
|
||||
david = CallbackDeveloper.find(1)
|
||||
david.destroy
|
||||
assert_equal [
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_destroy, :string ],
|
||||
[ :before_destroy, :proc ],
|
||||
[ :before_destroy, :object ],
|
||||
[ :before_destroy, :block ],
|
||||
[ :after_destroy, :string ],
|
||||
[ :after_destroy, :proc ],
|
||||
[ :after_destroy, :object ],
|
||||
[ :after_destroy, :block ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_delete
|
||||
david = CallbackDeveloper.find(1)
|
||||
CallbackDeveloper.delete(david.id)
|
||||
assert_equal [
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_before_save_returning_false
|
||||
david = ImmutableDeveloper.find(1)
|
||||
assert david.valid?
|
||||
assert !david.save
|
||||
assert_raises(ActiveRecord::RecordNotSaved) { david.save! }
|
||||
|
||||
david = ImmutableDeveloper.find(1)
|
||||
david.salary = 10_000_000
|
||||
assert !david.valid?
|
||||
assert !david.save
|
||||
assert_raises(ActiveRecord::RecordInvalid) { david.save! }
|
||||
end
|
||||
|
||||
def test_before_create_returning_false
|
||||
someone = CallbackCancellationDeveloper.new
|
||||
assert someone.valid?
|
||||
assert !someone.save
|
||||
end
|
||||
|
||||
def test_before_destroy_returning_false
|
||||
david = ImmutableDeveloper.find(1)
|
||||
assert !david.destroy
|
||||
assert_not_nil ImmutableDeveloper.find_by_id(1)
|
||||
end
|
||||
|
||||
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 }
|
||||
CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
|
||||
david.save
|
||||
assert_equal [
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation, :returning_false ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_inheritence_of_callbacks
|
||||
parent = ParentDeveloper.new
|
||||
assert !parent.after_save_called
|
||||
parent.save
|
||||
assert parent.after_save_called
|
||||
|
||||
child = ChildDeveloper.new
|
||||
assert !child.after_save_called
|
||||
child.save
|
||||
assert child.after_save_called
|
||||
end
|
||||
|
||||
end
|
||||
32
vendor/rails/activerecord/test/class_inheritable_attributes_test.rb
vendored
Normal file
32
vendor/rails/activerecord/test/class_inheritable_attributes_test.rb
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
require 'test/unit'
|
||||
require 'abstract_unit'
|
||||
require 'active_support/core_ext/class/inheritable_attributes'
|
||||
|
||||
class A
|
||||
include ClassInheritableAttributes
|
||||
end
|
||||
|
||||
class B < A
|
||||
write_inheritable_array "first", [ :one, :two ]
|
||||
end
|
||||
|
||||
class C < A
|
||||
write_inheritable_array "first", [ :three ]
|
||||
end
|
||||
|
||||
class D < B
|
||||
write_inheritable_array "first", [ :four ]
|
||||
end
|
||||
|
||||
|
||||
class ClassInheritableAttributesTest < Test::Unit::TestCase
|
||||
def test_first_level
|
||||
assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
|
||||
assert_equal [ :three ], C.read_inheritable_attribute("first")
|
||||
end
|
||||
|
||||
def test_second_level
|
||||
assert_equal [ :one, :two, :four ], D.read_inheritable_attribute("first")
|
||||
assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
|
||||
end
|
||||
end
|
||||
17
vendor/rails/activerecord/test/column_alias_test.rb
vendored
Normal file
17
vendor/rails/activerecord/test/column_alias_test.rb
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/topic'
|
||||
|
||||
class TestColumnAlias < Test::Unit::TestCase
|
||||
fixtures :topics
|
||||
|
||||
QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name
|
||||
'SELECT id AS pk FROM topics WHERE ROWNUM < 2'
|
||||
else
|
||||
'SELECT id AS pk FROM topics'
|
||||
end
|
||||
|
||||
def test_column_alias
|
||||
records = Topic.connection.select_all(QUERY)
|
||||
assert_equal 'pk', records[0].keys[0]
|
||||
end
|
||||
end
|
||||
8
vendor/rails/activerecord/test/connection_test_firebird.rb
vendored
Normal file
8
vendor/rails/activerecord/test/connection_test_firebird.rb
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
|
||||
class FirebirdConnectionTest < Test::Unit::TestCase
|
||||
def test_charset_properly_set
|
||||
fb_conn = ActiveRecord::Base.connection.instance_variable_get(:@connection)
|
||||
assert_equal 'UTF8', fb_conn.database.character_set
|
||||
end
|
||||
end
|
||||
30
vendor/rails/activerecord/test/connection_test_mysql.rb
vendored
Normal file
30
vendor/rails/activerecord/test/connection_test_mysql.rb
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
|
||||
class MysqlConnectionTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
def test_no_automatic_reconnection_after_timeout
|
||||
assert @connection.active?
|
||||
@connection.update('set @@wait_timeout=1')
|
||||
sleep 2
|
||||
assert !@connection.active?
|
||||
end
|
||||
|
||||
def test_successful_reconnection_after_timeout_with_manual_reconnect
|
||||
assert @connection.active?
|
||||
@connection.update('set @@wait_timeout=1')
|
||||
sleep 2
|
||||
@connection.reconnect!
|
||||
assert @connection.active?
|
||||
end
|
||||
|
||||
def test_successful_reconnection_after_timeout_with_verify
|
||||
assert @connection.active?
|
||||
@connection.update('set @@wait_timeout=1')
|
||||
sleep 2
|
||||
@connection.verify!(0)
|
||||
assert @connection.active?
|
||||
end
|
||||
end
|
||||
25
vendor/rails/activerecord/test/connections/native_db2/connection.rb
vendored
Normal file
25
vendor/rails/activerecord/test/connections/native_db2/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
print "Using native DB2\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'db2',
|
||||
:host => 'localhost',
|
||||
:username => 'arunit',
|
||||
:password => 'arunit',
|
||||
:database => 'arunit'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'db2',
|
||||
:host => 'localhost',
|
||||
:username => 'arunit',
|
||||
:password => 'arunit',
|
||||
:database => 'arunit2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
26
vendor/rails/activerecord/test/connections/native_firebird/connection.rb
vendored
Normal file
26
vendor/rails/activerecord/test/connections/native_firebird/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
print "Using native Firebird\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'firebird',
|
||||
:host => 'localhost',
|
||||
:username => 'rails',
|
||||
:password => 'rails',
|
||||
:database => 'activerecord_unittest',
|
||||
:charset => 'UTF8'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'firebird',
|
||||
:host => 'localhost',
|
||||
:username => 'rails',
|
||||
:password => 'rails',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
27
vendor/rails/activerecord/test/connections/native_frontbase/connection.rb
vendored
Normal file
27
vendor/rails/activerecord/test/connections/native_frontbase/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
puts 'Using native Frontbase'
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'frontbase',
|
||||
:host => 'localhost',
|
||||
:username => 'rails',
|
||||
:password => '',
|
||||
:database => 'activerecord_unittest',
|
||||
:session_name => "unittest-#{$$}"
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'frontbase',
|
||||
:host => 'localhost',
|
||||
:username => 'rails',
|
||||
:password => '',
|
||||
:database => 'activerecord_unittest2',
|
||||
:session_name => "unittest-#{$$}"
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
27
vendor/rails/activerecord/test/connections/native_mysql/connection.rb
vendored
Normal file
27
vendor/rails/activerecord/test/connections/native_mysql/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
print "Using native MySQL\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
RAILS_DEFAULT_LOGGER = Logger.new('debug.log')
|
||||
RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
|
||||
ActiveRecord::Base.logger = RAILS_DEFAULT_LOGGER
|
||||
|
||||
# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
|
||||
# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'mysql',
|
||||
:username => 'rails',
|
||||
:encoding => 'utf8',
|
||||
:database => 'activerecord_unittest',
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'mysql',
|
||||
:username => 'rails',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
21
vendor/rails/activerecord/test/connections/native_openbase/connection.rb
vendored
Normal file
21
vendor/rails/activerecord/test/connections/native_openbase/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
print "Using native OpenBase\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'openbase',
|
||||
:username => 'admin',
|
||||
:database => 'activerecord_unittest',
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'openbase',
|
||||
:username => 'admin',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
27
vendor/rails/activerecord/test/connections/native_oracle/connection.rb
vendored
Normal file
27
vendor/rails/activerecord/test/connections/native_oracle/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
print "Using Oracle\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new STDOUT
|
||||
ActiveRecord::Base.logger.level = Logger::WARN
|
||||
|
||||
# Set these to your database connection strings
|
||||
db = ENV['ARUNIT_DB'] || 'activerecord_unittest'
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'oracle',
|
||||
:username => 'arunit',
|
||||
:password => 'arunit',
|
||||
:database => db,
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'oracle',
|
||||
:username => 'arunit2',
|
||||
:password => 'arunit2',
|
||||
:database => db
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
23
vendor/rails/activerecord/test/connections/native_postgresql/connection.rb
vendored
Normal file
23
vendor/rails/activerecord/test/connections/native_postgresql/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
print "Using native PostgreSQL\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'postgresql',
|
||||
:username => 'postgres',
|
||||
:database => 'activerecord_unittest',
|
||||
:min_messages => 'warning'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'postgresql',
|
||||
:username => 'postgres',
|
||||
:database => 'activerecord_unittest2',
|
||||
:min_messages => 'warning'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
25
vendor/rails/activerecord/test/connections/native_sqlite/connection.rb
vendored
Normal file
25
vendor/rails/activerecord/test/connections/native_sqlite/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
print "Using native SQlite\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
class SqliteError < StandardError
|
||||
end
|
||||
|
||||
BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../fixtures')
|
||||
sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite"
|
||||
sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite"
|
||||
|
||||
def make_connection(clazz, db_file)
|
||||
ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite', :database => db_file } }
|
||||
unless File.exist?(db_file)
|
||||
puts "SQLite database not found at #{db_file}. Rebuilding it."
|
||||
sqlite_command = %Q{sqlite "#{db_file}" "create table a (a integer); drop table a;"}
|
||||
puts "Executing '#{sqlite_command}'"
|
||||
raise SqliteError.new("Seems that there is no sqlite 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)
|
||||
25
vendor/rails/activerecord/test/connections/native_sqlite3/connection.rb
vendored
Normal file
25
vendor/rails/activerecord/test/connections/native_sqlite3/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
print "Using native SQLite3\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
class SqliteError < StandardError
|
||||
end
|
||||
|
||||
BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../fixtures')
|
||||
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 => 'sqlite3', :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)
|
||||
18
vendor/rails/activerecord/test/connections/native_sqlite3/in_memory_connection.rb
vendored
Normal file
18
vendor/rails/activerecord/test/connections/native_sqlite3/in_memory_connection.rb
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
print "Using native SQLite3\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
class SqliteError < StandardError
|
||||
end
|
||||
|
||||
def make_connection(clazz, db_definitions_file)
|
||||
clazz.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
||||
File.read("#{File.dirname(__FILE__)}/../../fixtures/db_definitions/#{db_definitions_file}").split(';').each do |command|
|
||||
clazz.connection.execute(command) unless command.strip.empty?
|
||||
end
|
||||
end
|
||||
|
||||
make_connection(ActiveRecord::Base, 'sqlite.sql')
|
||||
make_connection(Course, 'sqlite2.sql')
|
||||
load("#{File.dirname(__FILE__)}/../../fixtures/db_definitions/schema.rb")
|
||||
23
vendor/rails/activerecord/test/connections/native_sybase/connection.rb
vendored
Normal file
23
vendor/rails/activerecord/test/connections/native_sybase/connection.rb
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
print "Using native Sybase Open Client\n"
|
||||
require_dependency 'fixtures/course'
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.configurations = {
|
||||
'arunit' => {
|
||||
:adapter => 'sybase',
|
||||
:host => 'database_ASE',
|
||||
:username => 'sa',
|
||||
:database => 'activerecord_unittest'
|
||||
},
|
||||
'arunit2' => {
|
||||
:adapter => 'sybase',
|
||||
:host => 'database_ASE',
|
||||
:username => 'sa',
|
||||
:database => 'activerecord_unittest2'
|
||||
}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
Course.establish_connection 'arunit2'
|
||||
69
vendor/rails/activerecord/test/copy_table_test_sqlite.rb
vendored
Normal file
69
vendor/rails/activerecord/test/copy_table_test_sqlite.rb
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class CopyTableTest < Test::Unit::TestCase
|
||||
fixtures :companies, :comments
|
||||
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
class << @connection
|
||||
public :copy_table, :table_structure, :indexes
|
||||
end
|
||||
end
|
||||
|
||||
def test_copy_table(from = 'companies', to = 'companies2', options = {})
|
||||
assert_nothing_raised {copy_table(from, to, options)}
|
||||
assert_equal row_count(from), row_count(to)
|
||||
|
||||
if block_given?
|
||||
yield from, to, options
|
||||
else
|
||||
assert_equal column_names(from), column_names(to)
|
||||
end
|
||||
|
||||
@connection.drop_table(to) rescue nil
|
||||
end
|
||||
|
||||
def test_copy_table_renaming_column
|
||||
test_copy_table('companies', 'companies2',
|
||||
:rename => {'client_of' => 'fan_of'}) do |from, to, options|
|
||||
expected = column_values(from, 'client_of')
|
||||
assert expected.any?, 'only nils in resultset; real values are needed'
|
||||
assert_equal expected, column_values(to, 'fan_of')
|
||||
end
|
||||
end
|
||||
|
||||
def test_copy_table_with_index
|
||||
test_copy_table('comments', 'comments_with_index') do
|
||||
@connection.add_index('comments_with_index', ['post_id', 'type'])
|
||||
test_copy_table('comments_with_index', 'comments_with_index2') do
|
||||
assert_equal table_indexes_without_name('comments_with_index'),
|
||||
table_indexes_without_name('comments_with_index2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_copy_table_without_primary_key
|
||||
test_copy_table('developers_projects', 'programmers_projects')
|
||||
end
|
||||
|
||||
protected
|
||||
def copy_table(from, to, options = {})
|
||||
@connection.copy_table(from, to, {:temporary => true}.merge(options))
|
||||
end
|
||||
|
||||
def column_names(table)
|
||||
@connection.table_structure(table).map {|column| column['name']}
|
||||
end
|
||||
|
||||
def column_values(table, column)
|
||||
@connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map {|row| row[column]}
|
||||
end
|
||||
|
||||
def table_indexes_without_name(table)
|
||||
@connection.indexes('comments_with_index').delete(:name)
|
||||
end
|
||||
|
||||
def row_count(table)
|
||||
@connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count']
|
||||
end
|
||||
end
|
||||
203
vendor/rails/activerecord/test/datatype_test_postgresql.rb
vendored
Normal file
203
vendor/rails/activerecord/test/datatype_test_postgresql.rb
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class PostgresqlArray < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class PostgresqlMoney < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class PostgresqlNumber < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class PostgresqlTime < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class PostgresqlNetworkAddress < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class PostgresqlBitString < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class PostgresqlOid < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class PostgresqlDataTypeTest < Test::Unit::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
|
||||
@connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )")
|
||||
@first_array = PostgresqlArray.find(1)
|
||||
|
||||
@connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('$567.89')")
|
||||
@connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-$567.89')")
|
||||
@first_money = PostgresqlMoney.find(1)
|
||||
@second_money = PostgresqlMoney.find(2)
|
||||
|
||||
@connection.execute("INSERT INTO postgresql_numbers (single, double) VALUES (123.456, 123456.789)")
|
||||
@first_number = PostgresqlNumber.find(1)
|
||||
|
||||
@connection.execute("INSERT INTO postgresql_times (time_interval) VALUES ('1 year 2 days ago')")
|
||||
@first_time = PostgresqlTime.find(1)
|
||||
|
||||
@connection.execute("INSERT INTO postgresql_network_addresses (cidr_address, inet_address, mac_address) VALUES('192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')")
|
||||
@first_network_address = PostgresqlNetworkAddress.find(1)
|
||||
|
||||
@connection.execute("INSERT INTO postgresql_bit_strings (bit_string, bit_string_varying) VALUES (B'00010101', X'15')")
|
||||
@first_bit_string = PostgresqlBitString.find(1)
|
||||
|
||||
@connection.execute("INSERT INTO postgresql_oids (obj_id) VALUES (1234)")
|
||||
@first_oid = PostgresqlOid.find(1)
|
||||
end
|
||||
|
||||
def test_data_type_of_array_types
|
||||
assert_equal :string, @first_array.column_for_attribute(:commission_by_quarter).type
|
||||
assert_equal :string, @first_array.column_for_attribute(:nicknames).type
|
||||
end
|
||||
|
||||
def test_data_type_of_money_types
|
||||
assert_equal :decimal, @first_money.column_for_attribute(:wealth).type
|
||||
end
|
||||
|
||||
def test_data_type_of_number_types
|
||||
assert_equal :float, @first_number.column_for_attribute(:single).type
|
||||
assert_equal :float, @first_number.column_for_attribute(:double).type
|
||||
end
|
||||
|
||||
def test_data_type_of_time_types
|
||||
assert_equal :string, @first_time.column_for_attribute(:time_interval).type
|
||||
end
|
||||
|
||||
def test_data_type_of_network_address_types
|
||||
assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type
|
||||
assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type
|
||||
assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type
|
||||
end
|
||||
|
||||
def test_data_type_of_bit_string_types
|
||||
assert_equal :string, @first_bit_string.column_for_attribute(:bit_string).type
|
||||
assert_equal :string, @first_bit_string.column_for_attribute(:bit_string_varying).type
|
||||
end
|
||||
|
||||
def test_data_type_of_oid_types
|
||||
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
|
||||
end
|
||||
|
||||
def test_array_values
|
||||
assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter
|
||||
assert_equal '{foo,bar,baz}', @first_array.nicknames
|
||||
end
|
||||
|
||||
def test_money_values
|
||||
assert_equal 567.89, @first_money.wealth
|
||||
assert_equal -567.89, @second_money.wealth
|
||||
end
|
||||
|
||||
def test_number_values
|
||||
assert_equal 123.456, @first_number.single
|
||||
assert_equal 123456.789, @first_number.double
|
||||
end
|
||||
|
||||
def test_time_values
|
||||
assert_equal '-1 years -2 days', @first_time.time_interval
|
||||
end
|
||||
|
||||
def test_network_address_values
|
||||
assert_equal '192.168.0.0/24', @first_network_address.cidr_address
|
||||
assert_equal '172.16.1.254', @first_network_address.inet_address
|
||||
assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
|
||||
end
|
||||
|
||||
def test_bit_string_values
|
||||
assert_equal '00010101', @first_bit_string.bit_string
|
||||
assert_equal '00010101', @first_bit_string.bit_string_varying
|
||||
end
|
||||
|
||||
def test_oid_values
|
||||
assert_equal 1234, @first_oid.obj_id
|
||||
end
|
||||
|
||||
def test_update_integer_array
|
||||
new_value = '{32800,95000,29350,17000}'
|
||||
assert @first_array.commission_by_quarter = new_value
|
||||
assert @first_array.save
|
||||
assert @first_array.reload
|
||||
assert_equal @first_array.commission_by_quarter, new_value
|
||||
assert @first_array.commission_by_quarter = new_value
|
||||
assert @first_array.save
|
||||
assert @first_array.reload
|
||||
assert_equal @first_array.commission_by_quarter, new_value
|
||||
end
|
||||
|
||||
def test_update_text_array
|
||||
new_value = '{robby,robert,rob,robbie}'
|
||||
assert @first_array.nicknames = new_value
|
||||
assert @first_array.save
|
||||
assert @first_array.reload
|
||||
assert_equal @first_array.nicknames, new_value
|
||||
assert @first_array.nicknames = new_value
|
||||
assert @first_array.save
|
||||
assert @first_array.reload
|
||||
assert_equal @first_array.nicknames, new_value
|
||||
end
|
||||
|
||||
def test_update_money
|
||||
new_value = 123.45
|
||||
assert @first_money.wealth = new_value
|
||||
assert @first_money.save
|
||||
assert @first_money.reload
|
||||
assert_equal @first_money.wealth, new_value
|
||||
end
|
||||
|
||||
def test_update_number
|
||||
new_single = 789.012
|
||||
new_double = 789012.345
|
||||
assert @first_number.single = new_single
|
||||
assert @first_number.double = new_double
|
||||
assert @first_number.save
|
||||
assert @first_number.reload
|
||||
assert_equal @first_number.single, new_single
|
||||
assert_equal @first_number.double, new_double
|
||||
end
|
||||
|
||||
def test_update_time
|
||||
assert @first_time.time_interval = '2 years 3 minutes'
|
||||
assert @first_time.save
|
||||
assert @first_time.reload
|
||||
assert_equal @first_time.time_interval, '2 years 00:03:00'
|
||||
end
|
||||
|
||||
def test_update_network_address
|
||||
new_cidr_address = '10.1.2.3/32'
|
||||
new_inet_address = '10.0.0.0/8'
|
||||
new_mac_address = 'bc:de:f0:12:34:56'
|
||||
assert @first_network_address.cidr_address = new_cidr_address
|
||||
assert @first_network_address.inet_address = new_inet_address
|
||||
assert @first_network_address.mac_address = new_mac_address
|
||||
assert @first_network_address.save
|
||||
assert @first_network_address.reload
|
||||
assert_equal @first_network_address.cidr_address, new_cidr_address
|
||||
assert_equal @first_network_address.inet_address, new_inet_address
|
||||
assert_equal @first_network_address.mac_address, new_mac_address
|
||||
end
|
||||
|
||||
def test_update_bit_string
|
||||
new_bit_string = '11111111'
|
||||
new_bit_string_varying = 'FF'
|
||||
assert @first_bit_string.bit_string = new_bit_string
|
||||
assert @first_bit_string.bit_string_varying = new_bit_string_varying
|
||||
assert @first_bit_string.save
|
||||
assert @first_bit_string.reload
|
||||
assert_equal @first_bit_string.bit_string, new_bit_string
|
||||
assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
|
||||
end
|
||||
|
||||
def test_update_oid
|
||||
new_value = 567890
|
||||
assert @first_oid.obj_id = new_value
|
||||
assert @first_oid.save
|
||||
assert @first_oid.reload
|
||||
assert_equal @first_oid.obj_id, new_value
|
||||
end
|
||||
end
|
||||
37
vendor/rails/activerecord/test/date_time_test.rb
vendored
Normal file
37
vendor/rails/activerecord/test/date_time_test.rb
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/topic'
|
||||
require 'fixtures/task'
|
||||
|
||||
class DateTimeTest < Test::Unit::TestCase
|
||||
def test_saves_both_date_and_time
|
||||
time_values = [1807, 2, 10, 15, 30, 45]
|
||||
now = DateTime.civil(*time_values)
|
||||
|
||||
task = Task.new
|
||||
task.starting = now
|
||||
task.save!
|
||||
|
||||
# check against Time.local_time, since some platforms will return a Time instead of a DateTime
|
||||
assert_equal Time.local_time(*time_values), Task.find(task.id).starting
|
||||
end
|
||||
|
||||
def test_assign_empty_date_time
|
||||
task = Task.new
|
||||
task.starting = ''
|
||||
task.ending = nil
|
||||
assert_nil task.starting
|
||||
assert_nil task.ending
|
||||
end
|
||||
|
||||
def test_assign_empty_date
|
||||
topic = Topic.new
|
||||
topic.last_read = ''
|
||||
assert_nil topic.last_read
|
||||
end
|
||||
|
||||
def test_assign_empty_time
|
||||
topic = Topic.new
|
||||
topic.bonus_time = ''
|
||||
assert_nil topic.bonus_time
|
||||
end
|
||||
end
|
||||
16
vendor/rails/activerecord/test/default_test_firebird.rb
vendored
Normal file
16
vendor/rails/activerecord/test/default_test_firebird.rb
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/default'
|
||||
|
||||
class DefaultTest < Test::Unit::TestCase
|
||||
def test_default_timestamp
|
||||
default = Default.new
|
||||
assert_instance_of(Time, default.default_timestamp)
|
||||
assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type)
|
||||
|
||||
# Variance should be small; increase if required -- e.g., if test db is on
|
||||
# remote host and clocks aren't synchronized.
|
||||
t1 = Time.new
|
||||
accepted_variance = 1.0
|
||||
assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance)
|
||||
end
|
||||
end
|
||||
67
vendor/rails/activerecord/test/defaults_test.rb
vendored
Normal file
67
vendor/rails/activerecord/test/defaults_test.rb
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/default'
|
||||
require 'fixtures/entrant'
|
||||
|
||||
class DefaultTest < Test::Unit::TestCase
|
||||
def test_nil_defaults_for_not_null_columns
|
||||
column_defaults =
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
{ 'id' => nil, 'name' => '', 'course_id' => nil }
|
||||
else
|
||||
{ 'id' => nil, 'name' => nil, 'course_id' => nil }
|
||||
end
|
||||
|
||||
column_defaults.each do |name, default|
|
||||
column = Entrant.columns_hash[name]
|
||||
assert !column.null, "#{name} column should be NOT NULL"
|
||||
assert_equal default, column.default, "#{name} column should be DEFAULT #{default.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:MysqlAdapter)
|
||||
# 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
|
||||
klass = Class.new(ActiveRecord::Base)
|
||||
klass.table_name = 'test_integer_not_null_default_zero'
|
||||
klass.connection.create_table klass.table_name do |t|
|
||||
t.column :zero, :integer, :null => false, :default => 0
|
||||
t.column :omit, :integer, :null => false
|
||||
end
|
||||
|
||||
assert_equal 0, klass.columns_hash['zero'].default
|
||||
assert !klass.columns_hash['zero'].null
|
||||
# 0 in MySQL 4, nil in 5.
|
||||
assert [0, nil].include?(klass.columns_hash['omit'].default)
|
||||
assert !klass.columns_hash['omit'].null
|
||||
|
||||
assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
|
||||
|
||||
assert_nothing_raised do
|
||||
instance = klass.create!(:omit => 1)
|
||||
assert_equal 0, instance.zero
|
||||
assert_equal 1, instance.omit
|
||||
end
|
||||
ensure
|
||||
klass.connection.drop_table(klass.table_name) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :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
|
||||
assert_equal "--- []\n\n", Default.columns_hash['multiline_default'].default
|
||||
end
|
||||
end
|
||||
end
|
||||
30
vendor/rails/activerecord/test/deprecated_finder_test.rb
vendored
Executable file
30
vendor/rails/activerecord/test/deprecated_finder_test.rb
vendored
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/entrant'
|
||||
|
||||
class DeprecatedFinderTest < Test::Unit::TestCase
|
||||
fixtures :entrants
|
||||
|
||||
def test_deprecated_find_all_was_removed
|
||||
assert_raise(NoMethodError) { Entrant.find_all }
|
||||
end
|
||||
|
||||
def test_deprecated_find_first_was_removed
|
||||
assert_raise(NoMethodError) { Entrant.find_first }
|
||||
end
|
||||
|
||||
def test_deprecated_find_on_conditions_was_removed
|
||||
assert_raise(NoMethodError) { Entrant.find_on_conditions }
|
||||
end
|
||||
|
||||
def test_count
|
||||
assert_equal(0, Entrant.count(:conditions => "id > 3"))
|
||||
assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
|
||||
assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
|
||||
end
|
||||
|
||||
def test_count_by_sql
|
||||
assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
|
||||
assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
|
||||
assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
|
||||
end
|
||||
end
|
||||
650
vendor/rails/activerecord/test/finder_test.rb
vendored
Normal file
650
vendor/rails/activerecord/test/finder_test.rb
vendored
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/author'
|
||||
require 'fixtures/comment'
|
||||
require 'fixtures/company'
|
||||
require 'fixtures/topic'
|
||||
require 'fixtures/reply'
|
||||
require 'fixtures/entrant'
|
||||
require 'fixtures/developer'
|
||||
require 'fixtures/post'
|
||||
|
||||
class FinderTest < Test::Unit::TestCase
|
||||
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors
|
||||
|
||||
def test_find
|
||||
assert_equal(topics(:first).title, Topic.find(1).title)
|
||||
end
|
||||
|
||||
# find should handle strings that come from URLs
|
||||
# (example: Category.find(params[:id]))
|
||||
def test_find_with_string
|
||||
assert_equal(Topic.find(1).title,Topic.find("1").title)
|
||||
end
|
||||
|
||||
def test_exists
|
||||
assert Topic.exists?(1)
|
||||
assert Topic.exists?("1")
|
||||
assert Topic.exists?(:author_name => "David")
|
||||
assert Topic.exists?(:author_name => "Mary", :approved => true)
|
||||
assert Topic.exists?(["parent_id = ?", 1])
|
||||
assert !Topic.exists?(45)
|
||||
|
||||
begin
|
||||
assert !Topic.exists?("foo")
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# PostgreSQL complains about string comparison with integer field
|
||||
rescue Exception
|
||||
flunk
|
||||
end
|
||||
|
||||
assert_raise(NoMethodError) { Topic.exists?([1,2]) }
|
||||
end
|
||||
|
||||
def test_find_by_array_of_one_id
|
||||
assert_kind_of(Array, Topic.find([ 1 ]))
|
||||
assert_equal(1, Topic.find([ 1 ]).length)
|
||||
end
|
||||
|
||||
def test_find_by_ids
|
||||
assert_equal 2, Topic.find(1, 2).size
|
||||
assert_equal topics(:second).title, Topic.find([2]).first.title
|
||||
end
|
||||
|
||||
def test_find_by_ids_with_limit_and_offset
|
||||
assert_equal 2, Entrant.find([1,3,2], :limit => 2).size
|
||||
assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size
|
||||
|
||||
# Also test an edge case: If you have 11 results, and you set a
|
||||
# limit of 3 and offset of 9, then you should find that there
|
||||
# will be only 2 results, regardless of the limit.
|
||||
devs = Developer.find :all
|
||||
last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9
|
||||
assert_equal 2, last_devs.size
|
||||
end
|
||||
|
||||
def test_find_an_empty_array
|
||||
assert_equal [], Topic.find([])
|
||||
end
|
||||
|
||||
def test_find_by_ids_missing_one
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
|
||||
end
|
||||
|
||||
def test_find_all_with_limit
|
||||
entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
|
||||
|
||||
assert_equal(2, entrants.size)
|
||||
assert_equal(entrants(:first).name, entrants.first.name)
|
||||
end
|
||||
|
||||
def test_find_all_with_prepared_limit_and_offset
|
||||
entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)
|
||||
|
||||
assert_equal(2, entrants.size)
|
||||
assert_equal(entrants(:second).name, entrants.first.name)
|
||||
|
||||
entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
|
||||
assert_equal(1, entrants.size)
|
||||
assert_equal(entrants(:third).name, entrants.first.name)
|
||||
end
|
||||
|
||||
def test_find_all_with_limit_and_offset_and_multiple_orderings
|
||||
developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
|
||||
assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
|
||||
end
|
||||
|
||||
def test_find_with_limit_and_condition
|
||||
developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
|
||||
assert_equal(1, developers.size)
|
||||
assert_equal("fixture_3", developers.first.name)
|
||||
end
|
||||
|
||||
def test_find_with_entire_select_statement
|
||||
topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
|
||||
|
||||
assert_equal(1, topics.size)
|
||||
assert_equal(topics(:second).title, topics.first.title)
|
||||
end
|
||||
|
||||
def test_find_with_prepared_select_statement
|
||||
topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"]
|
||||
|
||||
assert_equal(1, topics.size)
|
||||
assert_equal(topics(:second).title, topics.first.title)
|
||||
end
|
||||
|
||||
def test_find_by_sql_with_sti_on_joined_table
|
||||
accounts = Account.find_by_sql("SELECT * FROM accounts INNER JOIN companies ON companies.id = accounts.firm_id")
|
||||
assert_equal [Account], accounts.collect(&:class).uniq
|
||||
end
|
||||
|
||||
def test_find_first
|
||||
first = Topic.find(:first, :conditions => "title = 'The First Topic'")
|
||||
assert_equal(topics(:first).title, first.title)
|
||||
end
|
||||
|
||||
def test_find_first_failing
|
||||
first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
|
||||
assert_nil(first)
|
||||
end
|
||||
|
||||
def test_unexisting_record_exception_handling
|
||||
assert_raises(ActiveRecord::RecordNotFound) {
|
||||
Topic.find(1).parent
|
||||
}
|
||||
|
||||
Topic.find(2).topic
|
||||
end
|
||||
|
||||
def test_find_only_some_columns
|
||||
topic = Topic.find(1, :select => "author_name")
|
||||
assert_raises(ActiveRecord::MissingAttributeError) {topic.title}
|
||||
assert_equal "David", topic.author_name
|
||||
assert !topic.attribute_present?("title")
|
||||
#assert !topic.respond_to?("title")
|
||||
assert topic.attribute_present?("author_name")
|
||||
assert topic.respond_to?("author_name")
|
||||
end
|
||||
|
||||
def test_find_on_blank_conditions
|
||||
[nil, " ", [], {}].each do |blank|
|
||||
assert_nothing_raised { Topic.find(:first, :conditions => blank) }
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_on_blank_bind_conditions
|
||||
[ [""], ["",{}] ].each do |blank|
|
||||
assert_nothing_raised { Topic.find(:first, :conditions => blank) }
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_on_array_conditions
|
||||
assert Topic.find(1, :conditions => ["approved = ?", false])
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
|
||||
end
|
||||
|
||||
def test_find_on_hash_conditions
|
||||
assert Topic.find(1, :conditions => { :approved => false })
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) }
|
||||
end
|
||||
|
||||
def test_find_on_hash_conditions_with_explicit_table_name
|
||||
assert Topic.find(1, :conditions => { 'topics.approved' => false })
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
|
||||
end
|
||||
|
||||
def test_find_on_association_proxy_conditions
|
||||
assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
|
||||
end
|
||||
|
||||
def test_find_on_hash_conditions_with_range
|
||||
assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) }
|
||||
end
|
||||
|
||||
def test_find_on_hash_conditions_with_multiple_ranges
|
||||
assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort
|
||||
assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort
|
||||
end
|
||||
|
||||
def test_find_on_multiple_hash_conditions
|
||||
assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false })
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) }
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
|
||||
end
|
||||
|
||||
|
||||
def test_condition_interpolation
|
||||
assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
|
||||
assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
|
||||
assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
|
||||
assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
|
||||
end
|
||||
|
||||
def test_condition_array_interpolation
|
||||
assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
|
||||
assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
|
||||
assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
|
||||
assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
|
||||
end
|
||||
|
||||
def test_condition_hash_interpolation
|
||||
assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"})
|
||||
assert_nil Company.find(:first, :conditions => { :name => "37signals!"})
|
||||
assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on
|
||||
end
|
||||
|
||||
def test_hash_condition_find_malformed
|
||||
assert_raises(ActiveRecord::StatementInvalid) {
|
||||
Company.find(:first, :conditions => { :id => 2, :dhh => true })
|
||||
}
|
||||
end
|
||||
|
||||
def test_hash_condition_find_with_escaped_characters
|
||||
Company.create("name" => "Ain't noth'n like' \#stuff")
|
||||
assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })
|
||||
end
|
||||
|
||||
def test_hash_condition_find_with_array
|
||||
p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
|
||||
assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc')
|
||||
assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc')
|
||||
end
|
||||
|
||||
def test_hash_condition_find_with_nil
|
||||
topic = Topic.find(:first, :conditions => { :last_read => nil } )
|
||||
assert_not_nil topic
|
||||
assert_nil topic.last_read
|
||||
end
|
||||
|
||||
def test_bind_variables
|
||||
assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
|
||||
assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
|
||||
assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
|
||||
assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
|
||||
assert_raises(ActiveRecord::PreparedStatementInvalid) {
|
||||
Company.find(:first, :conditions => ["id=? AND name = ?", 2])
|
||||
}
|
||||
assert_raises(ActiveRecord::PreparedStatementInvalid) {
|
||||
Company.find(:first, :conditions => ["id=?", 2, 3, 4])
|
||||
}
|
||||
end
|
||||
|
||||
def test_bind_variables_with_quotes
|
||||
Company.create("name" => "37signals' go'es agains")
|
||||
assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
|
||||
end
|
||||
|
||||
def test_named_bind_variables_with_quotes
|
||||
Company.create("name" => "37signals' go'es agains")
|
||||
assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
|
||||
end
|
||||
|
||||
def test_bind_arity
|
||||
assert_nothing_raised { bind '' }
|
||||
assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
|
||||
|
||||
assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
|
||||
assert_nothing_raised { bind '?', 1 }
|
||||
assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
|
||||
end
|
||||
|
||||
def test_named_bind_variables
|
||||
assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
|
||||
assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
|
||||
|
||||
assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
|
||||
assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
|
||||
assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
|
||||
assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
|
||||
end
|
||||
|
||||
def test_bind_enumerable
|
||||
quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
|
||||
|
||||
assert_equal '1,2,3', bind('?', [1, 2, 3])
|
||||
assert_equal quoted_abc, bind('?', %w(a b c))
|
||||
|
||||
assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
|
||||
assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
|
||||
|
||||
require 'set'
|
||||
assert_equal '1,2,3', bind('?', Set.new([1, 2, 3]))
|
||||
assert_equal quoted_abc, bind('?', Set.new(%w(a b c)))
|
||||
|
||||
assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3]))
|
||||
assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # '
|
||||
end
|
||||
|
||||
def test_bind_empty_enumerable
|
||||
quoted_nil = ActiveRecord::Base.connection.quote(nil)
|
||||
assert_equal quoted_nil, bind('?', [])
|
||||
assert_equal " in (#{quoted_nil})", bind(' in (?)', [])
|
||||
assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
|
||||
end
|
||||
|
||||
def test_bind_string
|
||||
assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '')
|
||||
end
|
||||
|
||||
def test_bind_record
|
||||
o = Struct.new(:quoted_id).new(1)
|
||||
assert_equal '1', bind('?', o)
|
||||
|
||||
os = [o] * 3
|
||||
assert_equal '1,1,1', bind('?', os)
|
||||
end
|
||||
|
||||
def test_string_sanitation
|
||||
assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
|
||||
assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
|
||||
end
|
||||
|
||||
def test_count
|
||||
assert_equal(0, Entrant.count(:conditions => "id > 3"))
|
||||
assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
|
||||
assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
|
||||
end
|
||||
|
||||
def test_count_by_sql
|
||||
assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
|
||||
assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
|
||||
assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
|
||||
end
|
||||
|
||||
def test_find_by_one_attribute
|
||||
assert_equal topics(:first), Topic.find_by_title("The First Topic")
|
||||
assert_nil Topic.find_by_title("The First Topic!")
|
||||
end
|
||||
|
||||
def test_find_by_one_attribute_caches_dynamic_finder
|
||||
# ensure this test can run independently of order
|
||||
class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
|
||||
assert !Topic.respond_to?(:find_by_title)
|
||||
t = Topic.find_by_title("The First Topic")
|
||||
assert Topic.respond_to?(:find_by_title)
|
||||
end
|
||||
|
||||
def test_dynamic_finder_returns_same_results_after_caching
|
||||
# ensure this test can run independently of order
|
||||
class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
|
||||
t = Topic.find_by_title("The First Topic")
|
||||
assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached
|
||||
end
|
||||
|
||||
def test_find_by_one_attribute_with_order_option
|
||||
assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
|
||||
assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
|
||||
end
|
||||
|
||||
def test_find_by_one_attribute_with_conditions
|
||||
assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
|
||||
end
|
||||
|
||||
def test_dynamic_finder_on_one_attribute_with_conditions_caches_method
|
||||
# ensure this test can run independently of order
|
||||
class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
|
||||
assert !Account.respond_to?(:find_by_credit_limit)
|
||||
a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
|
||||
assert Account.respond_to?(:find_by_credit_limit)
|
||||
end
|
||||
|
||||
def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
|
||||
# ensure this test can run independently of order
|
||||
class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
|
||||
a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
|
||||
assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached
|
||||
end
|
||||
|
||||
def test_find_by_one_attribute_with_several_options
|
||||
assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
|
||||
end
|
||||
|
||||
def test_find_by_one_missing_attribute
|
||||
assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
|
||||
end
|
||||
|
||||
def test_find_by_invalid_method_syntax
|
||||
assert_raises(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") }
|
||||
assert_raises(NoMethodError) { Topic.find_by_title?("The First Topic") }
|
||||
assert_raises(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") }
|
||||
assert_raises(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") }
|
||||
end
|
||||
|
||||
def test_find_by_two_attributes
|
||||
assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
|
||||
assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
|
||||
end
|
||||
|
||||
def test_find_all_by_one_attribute
|
||||
topics = Topic.find_all_by_content("Have a nice day")
|
||||
assert_equal 2, topics.size
|
||||
assert topics.include?(topics(:first))
|
||||
|
||||
assert_equal [], Topic.find_all_by_title("The First Topic!!")
|
||||
end
|
||||
|
||||
def test_find_all_by_one_attribute_with_options
|
||||
topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
|
||||
assert topics(:first), topics.last
|
||||
|
||||
topics = Topic.find_all_by_content("Have a nice day", :order => "id")
|
||||
assert topics(:first), topics.first
|
||||
end
|
||||
|
||||
def test_find_all_by_array_attribute
|
||||
assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic's of the day"]).size
|
||||
end
|
||||
|
||||
def test_find_all_by_boolean_attribute
|
||||
topics = Topic.find_all_by_approved(false)
|
||||
assert_equal 1, topics.size
|
||||
assert topics.include?(topics(:first))
|
||||
|
||||
topics = Topic.find_all_by_approved(true)
|
||||
assert_equal 1, topics.size
|
||||
assert topics.include?(topics(:second))
|
||||
end
|
||||
|
||||
def test_find_by_nil_attribute
|
||||
topic = Topic.find_by_last_read nil
|
||||
assert_not_nil topic
|
||||
assert_nil topic.last_read
|
||||
end
|
||||
|
||||
def test_find_all_by_nil_attribute
|
||||
topics = Topic.find_all_by_last_read nil
|
||||
assert_equal 1, topics.size
|
||||
assert_nil topics[0].last_read
|
||||
end
|
||||
|
||||
def test_find_by_nil_and_not_nil_attributes
|
||||
topic = Topic.find_by_last_read_and_author_name nil, "Mary"
|
||||
assert_equal "Mary", topic.author_name
|
||||
end
|
||||
|
||||
def test_find_all_by_nil_and_not_nil_attributes
|
||||
topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
|
||||
assert_equal 1, topics.size
|
||||
assert_equal "Mary", topics[0].author_name
|
||||
end
|
||||
|
||||
def test_find_or_create_from_one_attribute
|
||||
number_of_companies = Company.count
|
||||
sig38 = Company.find_or_create_by_name("38signals")
|
||||
assert_equal number_of_companies + 1, Company.count
|
||||
assert_equal sig38, Company.find_or_create_by_name("38signals")
|
||||
assert !sig38.new_record?
|
||||
end
|
||||
|
||||
def test_find_or_create_from_two_attributes
|
||||
number_of_topics = Topic.count
|
||||
another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
|
||||
assert_equal number_of_topics + 1, Topic.count
|
||||
assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
|
||||
assert !another.new_record?
|
||||
end
|
||||
|
||||
def test_find_or_create_from_one_attribute_and_hash
|
||||
number_of_companies = Company.count
|
||||
sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
|
||||
assert_equal number_of_companies + 1, Company.count
|
||||
assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
|
||||
assert !sig38.new_record?
|
||||
assert_equal "38signals", sig38.name
|
||||
assert_equal 17, sig38.firm_id
|
||||
assert_equal 23, sig38.client_of
|
||||
end
|
||||
|
||||
def test_find_or_initialize_from_one_attribute
|
||||
sig38 = Company.find_or_initialize_by_name("38signals")
|
||||
assert_equal "38signals", sig38.name
|
||||
assert sig38.new_record?
|
||||
end
|
||||
|
||||
def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
|
||||
c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
|
||||
assert_equal "Fortune 1000", c.name
|
||||
assert_equal 1000, c.rating
|
||||
assert c.valid?
|
||||
assert c.new_record?
|
||||
end
|
||||
|
||||
def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
|
||||
c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
|
||||
assert_equal "Fortune 1000", c.name
|
||||
assert_equal 1000, c.rating
|
||||
assert c.valid?
|
||||
assert !c.new_record?
|
||||
end
|
||||
|
||||
def test_dynamic_find_or_initialize_from_one_attribute_caches_method
|
||||
class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.respond_to?(:find_or_initialize_by_name)
|
||||
assert !Company.respond_to?(:find_or_initialize_by_name)
|
||||
sig38 = Company.find_or_initialize_by_name("38signals")
|
||||
assert Company.respond_to?(:find_or_initialize_by_name)
|
||||
end
|
||||
|
||||
def test_find_or_initialize_from_two_attributes
|
||||
another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
|
||||
assert_equal "Another topic", another.title
|
||||
assert_equal "John", another.author_name
|
||||
assert another.new_record?
|
||||
end
|
||||
|
||||
def test_find_or_initialize_from_one_attribute_and_hash
|
||||
sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
|
||||
assert_equal "38signals", sig38.name
|
||||
assert_equal 17, sig38.firm_id
|
||||
assert_equal 23, sig38.client_of
|
||||
assert sig38.new_record?
|
||||
end
|
||||
|
||||
def test_find_with_bad_sql
|
||||
assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
|
||||
end
|
||||
|
||||
def test_find_with_invalid_params
|
||||
assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" }
|
||||
assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
|
||||
end
|
||||
|
||||
def test_dynamic_finder_with_invalid_params
|
||||
assert_raises(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
|
||||
end
|
||||
|
||||
def test_find_all_with_limit
|
||||
first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5
|
||||
assert_equal 5, first_five_developers.length
|
||||
assert_equal 'David', first_five_developers.first.name
|
||||
assert_equal 'fixture_5', first_five_developers.last.name
|
||||
|
||||
no_developers = Developer.find :all, :order => 'id ASC', :limit => 0
|
||||
assert_equal 0, no_developers.length
|
||||
end
|
||||
|
||||
def test_find_all_with_limit_and_offset
|
||||
first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0
|
||||
second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3
|
||||
last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8
|
||||
|
||||
assert_equal 3, first_three_developers.length
|
||||
assert_equal 3, second_three_developers.length
|
||||
assert_equal 2, last_two_developers.length
|
||||
|
||||
assert_equal 'David', first_three_developers.first.name
|
||||
assert_equal 'fixture_4', second_three_developers.first.name
|
||||
assert_equal 'fixture_9', last_two_developers.first.name
|
||||
end
|
||||
|
||||
def test_find_all_with_limit_and_offset_and_multiple_order_clauses
|
||||
first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
|
||||
second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
|
||||
last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
|
||||
|
||||
assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
|
||||
assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
|
||||
assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
|
||||
end
|
||||
|
||||
def test_find_all_with_join
|
||||
developers_on_project_one = Developer.find(
|
||||
:all,
|
||||
:joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
|
||||
:conditions => 'project_id=1'
|
||||
)
|
||||
assert_equal 3, developers_on_project_one.length
|
||||
developer_names = developers_on_project_one.map { |d| d.name }
|
||||
assert developer_names.include?('David')
|
||||
assert developer_names.include?('Jamis')
|
||||
end
|
||||
|
||||
def test_joins_dont_clobber_id
|
||||
first = Firm.find(
|
||||
:first,
|
||||
:joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id',
|
||||
:conditions => 'companies.id = 1'
|
||||
)
|
||||
assert_equal 1, first.id
|
||||
end
|
||||
|
||||
def test_find_by_id_with_conditions_with_or
|
||||
assert_nothing_raised do
|
||||
Post.find([1,2,3],
|
||||
:conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'")
|
||||
end
|
||||
end
|
||||
|
||||
# http://dev.rubyonrails.org/ticket/6778
|
||||
def test_find_ignores_previously_inserted_record
|
||||
post = Post.create!(:title => 'test', :body => 'it out')
|
||||
assert_equal [], Post.find_all_by_id(nil)
|
||||
end
|
||||
|
||||
def test_find_by_empty_ids
|
||||
assert_equal [], Post.find([])
|
||||
end
|
||||
|
||||
def test_find_by_empty_in_condition
|
||||
assert_equal [], Post.find(:all, :conditions => ['id in (?)', []])
|
||||
end
|
||||
|
||||
def test_find_by_records
|
||||
p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
|
||||
assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc')
|
||||
assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc')
|
||||
end
|
||||
|
||||
def test_select_value
|
||||
assert_equal "37signals", Company.connection.select_value("SELECT name FROM companies WHERE id = 1")
|
||||
assert_nil Company.connection.select_value("SELECT name FROM companies WHERE id = -1")
|
||||
# make sure we didn't break count...
|
||||
assert_equal 0, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = 'Halliburton'")
|
||||
assert_equal 1, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = '37signals'")
|
||||
end
|
||||
|
||||
def test_select_values
|
||||
assert_equal ["1","2","3","4","5","6","7","8","9"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
|
||||
assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
|
||||
end
|
||||
|
||||
def test_select_rows
|
||||
assert_equal(
|
||||
[["1", nil, nil, "37signals"],
|
||||
["2", "1", "2", "Summit"],
|
||||
["3", "1", "1", "Microsoft"]],
|
||||
Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}})
|
||||
assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
|
||||
Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}
|
||||
end
|
||||
|
||||
protected
|
||||
def bind(statement, *vars)
|
||||
if vars.first.is_a?(Hash)
|
||||
ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
|
||||
else
|
||||
ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
|
||||
end
|
||||
end
|
||||
end
|
||||
28
vendor/rails/activerecord/test/fixtures/accounts.yml
vendored
Normal file
28
vendor/rails/activerecord/test/fixtures/accounts.yml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
signals37:
|
||||
id: 1
|
||||
firm_id: 1
|
||||
credit_limit: 50
|
||||
|
||||
unknown:
|
||||
id: 2
|
||||
credit_limit: 50
|
||||
|
||||
rails_core_account:
|
||||
id: 3
|
||||
firm_id: 6
|
||||
credit_limit: 50
|
||||
|
||||
last_account:
|
||||
id: 4
|
||||
firm_id: 2
|
||||
credit_limit: 60
|
||||
|
||||
rails_core_account_2:
|
||||
id: 5
|
||||
firm_id: 6
|
||||
credit_limit: 55
|
||||
|
||||
odegy_account:
|
||||
id: 6
|
||||
firm_id: 9
|
||||
credit_limit: 53
|
||||
0
vendor/rails/activerecord/test/fixtures/all/developers.yml
vendored
Normal file
0
vendor/rails/activerecord/test/fixtures/all/developers.yml
vendored
Normal file
0
vendor/rails/activerecord/test/fixtures/all/people.csv
vendored
Normal file
0
vendor/rails/activerecord/test/fixtures/all/people.csv
vendored
Normal file
|
|
0
vendor/rails/activerecord/test/fixtures/all/tasks.yml
vendored
Normal file
0
vendor/rails/activerecord/test/fixtures/all/tasks.yml
vendored
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue