aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--actionpack/CHANGELOG10
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb37
-rw-r--r--actionpack/lib/action_view/test_case.rb14
-rw-r--r--actionpack/test/abstract_unit.rb3
-rw-r--r--actionpack/test/controller/filters_test.rb6
-rw-r--r--actionpack/test/dispatch/routing_test.rb61
-rw-r--r--actionpack/test/template/test_case_test.rb4
-rw-r--r--activerecord/CHANGELOG4
-rwxr-xr-xactiverecord/lib/active_record/base.rb4
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb56
-rw-r--r--activerecord/lib/active_record/fixtures.rb12
-rw-r--r--activerecord/lib/active_record/transactions.rb137
-rw-r--r--activerecord/test/cases/active_schema_test_mysql.rb8
-rw-r--r--activerecord/test/cases/active_schema_test_postgresql.rb1
-rw-r--r--activerecord/test/cases/adapter_test.rb8
-rwxr-xr-xactiverecord/test/cases/base_test.rb17
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb240
-rw-r--r--activerecord/test/cases/transactions_test.rb33
-rw-r--r--railties/guides/source/3_0_release_notes.textile2
-rw-r--r--railties/guides/source/active_support_core_extensions.textile117
-rw-r--r--railties/guides/source/getting_started.textile2
24 files changed, 667 insertions, 114 deletions
diff --git a/Gemfile b/Gemfile
index 6d878404d6..acee230d23 100644
--- a/Gemfile
+++ b/Gemfile
@@ -32,7 +32,7 @@ if mri || RUBY_ENGINE == "rbx"
gem "sqlite3-ruby", "~> 1.3.0", :require => 'sqlite3'
group :db do
- # gem "pg", ">= 0.9.0"
+ gem "pg", ">= 0.9.0"
gem "mysql", ">= 2.8.1"
end
elsif RUBY_ENGINE == "jruby"
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index c3609958bc..1796d11dcd 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,15 +1,5 @@
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
-* Add shallow routes back to the new router [Diego Carrion]
-
- resources :posts do
- shallow do
- resources :comments
- end
- end
-
- You can now use comment_path for /comments/1 instead of post_comment_path for /posts/1/comments/1.
-
* Remove middleware laziness [José Valim]
* Make session stores rely on request.cookie_jar and change set_session semantics to return the cookie value instead of a boolean. [José Valim]
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index cf16417e84..e9db0d97b6 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -57,6 +57,7 @@ module ActionController #:nodoc:
def before(controller)
self.controller = controller
callback(:before) if controller.perform_caching
+ true # before method from sweeper should always return true
end
def after(controller)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index e91a72cbe5..7b79b6bde3 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -350,10 +350,6 @@ module ActionDispatch
scope(:constraints => constraints) { yield }
end
- def shallow
- scope(:shallow => true) { yield }
- end
-
def defaults(defaults = {})
scope(:defaults => defaults) { yield }
end
@@ -378,21 +374,12 @@ module ActionDispatch
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
end
- def merge_shallow_scope(parent, child)
- parent or child
- end
-
def merge_path_scope(parent, child)
- parent_path = (@scope[:shallow] and child.eql?(':id')) ? parent.split('/').last : parent
- Mapper.normalize_path "#{parent_path}/#{child}"
+ Mapper.normalize_path("#{parent}/#{child}")
end
def merge_name_prefix_scope(parent, child)
- if @scope[:shallow]
- child
- else
- parent ? "#{parent}_#{child}" : child
- end
+ parent ? "#{parent}_#{child}" : child
end
def merge_module_scope(parent, child)
@@ -535,10 +522,6 @@ module ActionDispatch
options["#{singular}_id".to_sym] = id_constraint if id_constraint?
options
end
-
- def shallow?
- options[:shallow]
- end
end
class SingletonResource < Resource #:nodoc:
@@ -620,12 +603,8 @@ module ActionDispatch
resource = Resource.new(resources.pop, options)
- scope(:path => resource.path, :controller => resource.controller, :shallow => resource.shallow?) do
+ scope(:path => resource.path, :controller => resource.controller) do
with_scope_level(:resources, resource) do
- if @scope[:shallow] && @scope[:name_prefix]
- @scope[:path] = "/#{@scope[:name_prefix].pluralize}/:#{@scope[:name_prefix]}_id/#{resource.path}"
- end
-
yield if block_given?
with_scope_level(:collection) do
@@ -639,8 +618,6 @@ module ActionDispatch
with_scope_level(:member) do
scope(':id') do
scope(resource.options) do
- @scope[:name_prefix] = nil if @scope[:shallow]
-
get :show if resource.actions.include?(:show)
put :update if resource.actions.include?(:update)
delete :destroy if resource.actions.include?(:destroy)
@@ -702,6 +679,14 @@ module ActionDispatch
end
end
+ def namespace(path)
+ if resource_scope?
+ nested { super }
+ else
+ super
+ end
+ end
+
def match(*args)
options = args.extract_options!
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 4dbbd2eb6a..15d424be74 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -131,12 +131,14 @@ module ActionView
end
def _view
- view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller)
- view.singleton_class.send :include, _helpers
- view.singleton_class.send :include, @controller._router.url_helpers
- view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
- view.output_buffer = self.output_buffer
- view
+ @_view ||= begin
+ view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller)
+ view.singleton_class.send :include, _helpers
+ view.singleton_class.send :include, @controller._router.url_helpers
+ view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
+ view.output_buffer = self.output_buffer
+ view
+ end
end
EXCLUDE_IVARS = %w{
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index d2e5d2e965..84c5395610 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -24,6 +24,9 @@ require 'action_view/testing/resolvers'
require 'action_dispatch'
require 'active_support/dependencies'
require 'active_model'
+require 'active_record'
+require 'action_controller/caching'
+require 'action_controller/caching/sweeping'
begin
require 'ruby-debug'
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index d5704eba78..25b78124e3 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -445,6 +445,12 @@ class FilterTest < ActionController::TestCase
end
+
+ def test_before_method_of_sweeper_should_always_return_true
+ sweeper = ActionController::Caching::Sweeper.send(:new)
+ assert sweeper.before(TestController.new)
+ end
+
def test_non_yielding_around_filters_not_returning_false_do_not_raise
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", true
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index a294535e88..e13960e0dc 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -34,33 +34,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- resources :users do
- shallow do
- resources :photos do
- resources :types do
- member do
- post :preview
- end
- collection do
- delete :erase
- end
- end
- end
- end
- end
-
- shallow do
- resources :teams do
- resources :players
- end
-
- resources :countries do
- resources :cities do
- resources :places
- end
- end
- end
-
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
match 'account/login', :to => redirect("/login")
@@ -171,6 +144,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :sheep
+ resources :clients do
+ namespace :google do
+ resource :account do
+ namespace :secret do
+ resource :info
+ end
+ end
+ end
+ end
+
match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
match 'people/:id/update', :to => 'people#update', :as => :update_person
@@ -779,18 +762,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- def test_shallow_routes
- with_test_routes do
- assert_equal '/photos/4', photo_path(4)
- assert_equal '/types/10/edit', edit_type_path(10)
- assert_equal '/types/5/preview', preview_type_path(5)
- assert_equal '/photos/2/types', photo_types_path(2)
- assert_equal '/cities/1/places', url_for(:controller => :places, :action => :index, :city_id => 1, :only_path => true)
- assert_equal '/teams/new', url_for(:controller => :teams, :action => :new, :only_path => true)
- assert_equal '/photos/11/types/erase', url_for(:controller => :types, :action => :erase, :photo_id => 11, :only_path => true)
- end
- end
-
def test_update_project_person
with_test_routes do
get '/projects/1/people/2/update'
@@ -852,6 +823,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/account/admin/subscription', account_admin_subscription_path
end
end
+
+ def test_namespace_nested_in_resources
+ with_test_routes do
+ get '/clients/1/google/account'
+ assert_equal '/clients/1/google/account', client_google_account_path(1)
+ assert_equal 'google/accounts#show', @response.body
+
+ get '/clients/1/google/account/secret/info'
+ assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1)
+ assert_equal 'google/secret/infos#show', @response.body
+ end
+ end
def test_articles_with_id
with_test_routes do
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index 16e5ee4f72..9b50ea8a42 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -37,6 +37,10 @@ module ActionView
include SharedTests
test_case = self
+ test "memoizes the _view" do
+ assert_same _view, _view
+ end
+
test "works without testing a helper module" do
assert_equal 'Eloy', render('developers/developer', :developer => stub(:name => 'Eloy'))
end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 348248e849..7d5e550a7c 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
+* Fixed that ActiveRecord::Base.compute_type would swallow NoMethodError #4751 [Andrew Bloomgarden, Andrew White]
+
* Add index length support for MySQL. #1852 [Emili Parreno, Pratik Naik]
Example:
@@ -12,6 +14,8 @@
* find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune]
+* New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand]
+
* Serialized attributes are not converted to YAML if they are any of the formats that can be serialized to XML (like Hash, Array and Strings). [José Valim]
* Destroy uses optimistic locking. If lock_version on the record you're destroying doesn't match lock_version in the database, a StaleObjectError is raised. #1966 [Curtis Hawthorne]
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index aa2826fb33..7cff6d9f1a 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1219,7 +1219,9 @@ module ActiveRecord #:nodoc:
begin
constant = candidate.constantize
return constant if candidate == constant.to_s
- rescue NameError
+ rescue NameError => e
+ # We don't want to swallow NoMethodError < NameError errors
+ raise e unless e.instance_of?(NameError)
rescue ArgumentError
end
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 498836aca4..44fee12001 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -31,7 +31,7 @@ module ActiveRecord
# 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
+ # before_validation(:on => :create) do
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
# end
# end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 0c87e052c4..b9fb452eee 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -122,6 +122,8 @@ module ActiveRecord
requires_new = options[:requires_new] || !last_transaction_joinable
transaction_open = false
+ @_current_transaction_records ||= []
+
begin
if block_given?
if requires_new || open_transactions == 0
@@ -132,6 +134,7 @@ module ActiveRecord
end
increment_open_transactions
transaction_open = true
+ @_current_transaction_records.push([])
end
yield
end
@@ -141,8 +144,10 @@ module ActiveRecord
decrement_open_transactions
if open_transactions == 0
rollback_db_transaction
+ rollback_transaction_records(true)
else
rollback_to_savepoint
+ rollback_transaction_records(false)
end
end
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
@@ -157,20 +162,35 @@ module ActiveRecord
begin
if open_transactions == 0
commit_db_transaction
+ commit_transaction_records
else
release_savepoint
+ save_point_records = @_current_transaction_records.pop
+ unless save_point_records.blank?
+ @_current_transaction_records.push([]) if @_current_transaction_records.empty?
+ @_current_transaction_records.last.concat(save_point_records)
+ end
end
rescue Exception => database_transaction_rollback
if open_transactions == 0
rollback_db_transaction
+ rollback_transaction_records(true)
else
rollback_to_savepoint
+ rollback_transaction_records(false)
end
raise
end
end
end
+ # Register a record with the current transaction so that its after_commit and after_rollback callbacks
+ # can be called.
+ def add_transaction_record(record)
+ last_batch = @_current_transaction_records.last
+ last_batch << record if last_batch
+ end
+
# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
@@ -268,6 +288,42 @@ module ActiveRecord
limit.to_i
end
end
+
+ # Send a rollback message to all records after they have been rolled back. If rollback
+ # is false, only rollback records since the last save point.
+ def rollback_transaction_records(rollback) #:nodoc
+ if rollback
+ records = @_current_transaction_records.flatten
+ @_current_transaction_records.clear
+ else
+ records = @_current_transaction_records.pop
+ end
+
+ unless records.blank?
+ records.uniq.each do |record|
+ begin
+ record.rolledback!(rollback)
+ rescue Exception => e
+ record.logger.error(e) if record.respond_to?(:logger)
+ end
+ end
+ end
+ end
+
+ # Send a commit message to all records after they have been committed.
+ def commit_transaction_records #:nodoc
+ records = @_current_transaction_records.flatten
+ @_current_transaction_records.clear
+ unless records.blank?
+ records.uniq.each do |record|
+ begin
+ record.committed!
+ rescue Exception => e
+ record.logger.error(e) if record.respond_to?(:logger)
+ end
+ end
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 8099aaa7f7..82270c56b3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -787,16 +787,14 @@ class Fixture #:nodoc:
end
def key_list
- columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
- columns.join(", ")
+ @fixture.keys.map { |column_name| @connection.quote_column_name(column_name) }.join(', ')
end
def value_list
- list = @fixture.inject([]) do |fixtures, (key, value)|
- col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
- fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
- end
- list * ', '
+ cols = (model_class && model_class < ActiveRecord::Base) ? model_class.columns_hash : {}
+ @fixture.map do |key, value|
+ @connection.quote(value, cols[key]).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
+ end.join(', ')
end
def find
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 3f2c1911e7..620758f5af 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -8,6 +8,10 @@ module ActiveRecord
class TransactionError < ActiveRecordError # :nodoc:
end
+ included do
+ define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
+ 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
@@ -72,7 +76,7 @@ module ActiveRecord
#
# Both +save+ and +destroy+ come wrapped in a transaction that ensures
# that whatever you do in validations or callbacks will happen under its
- # protected cover. So you can use validations to check for values that
+ # protected cover. So you can use validations to check for values that
# the transaction depends on or you can raise exceptions in the callbacks
# to rollback, including <tt>after_*</tt> callbacks.
#
@@ -158,6 +162,21 @@ module ActiveRecord
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
# for more information about savepoints.
#
+ # === Callbacks
+ #
+ # There are two types of callbacks associated with committing and rolling back transactions:
+ # +after_commit+ and +after_rollback+.
+ #
+ # +after_commit+ callbacks are called on every record saved or destroyed within a
+ # transaction immediately after the transaction is committed. +after_rollback+ callbacks
+ # are called on every record saved or destroyed within a transaction immediately after the
+ # transaction or savepoint is rolled back.
+ #
+ # These callbacks are useful for interacting with other systems since you will be guaranteed
+ # that the callback is only executed when the database is in a permanent state. For example,
+ # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
+ # within a transaction could trigger the cache to be regenerated before the database is updated.
+ #
# === Caveats
#
# If you're on MySQL, then do not use DDL operations in nested transactions
@@ -182,6 +201,24 @@ module ActiveRecord
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
connection.transaction(options, &block)
end
+
+ def after_commit(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
+ end
+ set_callback(:commit, :after, *args, &block)
+ end
+
+ def after_rollback(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
+ end
+ set_callback(:rollback, :after, *args, &block)
+ end
end
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
@@ -205,19 +242,36 @@ module ActiveRecord
# 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?
+ remember_transaction_record_state
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
+ restore_transaction_record_state
raise
+ ensure
+ clear_transaction_record_state
+ end
+
+ # Call the after_commit callbacks
+ def committed! #:nodoc:
+ _run_commit_callbacks
+ ensure
+ clear_transaction_record_state
+ end
+
+ # Call the after rollback callbacks. The restore_state argument indicates if the record
+ # state should be rolled back to the beginning or just to the last savepoint.
+ def rolledback!(force_restore_state = false) #:nodoc:
+ _run_rollback_callbacks
+ ensure
+ restore_transaction_record_state(force_restore_state)
+ end
+
+ # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks
+ # can be called.
+ def add_to_transaction
+ if self.class.connection.add_transaction_record(self)
+ remember_transaction_record_state
+ end
end
# Executes +method+ within a transaction and captures its return value as a
@@ -229,10 +283,71 @@ module ActiveRecord
def with_transaction_returning_status
status = nil
self.class.transaction do
+ add_to_transaction
status = yield
raise ActiveRecord::Rollback unless status
end
status
end
+
+ protected
+
+ # Save the new record state and id of a record so it can be restored later if a transaction fails.
+ def remember_transaction_record_state #:nodoc
+ @_start_transaction_state ||= {}
+ unless @_start_transaction_state.include?(:new_record)
+ @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
+ @_start_transaction_state[:new_record] = @new_record
+ end
+ unless @_start_transaction_state.include?(:destroyed)
+ @_start_transaction_state[:destroyed] = @destroyed
+ end
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
+ end
+
+ # Clear the new record state and id of a record.
+ def clear_transaction_record_state #:nodoc
+ if defined?(@_start_transaction_state)
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
+ end
+ end
+
+ # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
+ def restore_transaction_record_state(force = false) #:nodoc
+ if defined?(@_start_transaction_state)
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ if @_start_transaction_state[:level] < 1
+ restore_state = remove_instance_variable(:@_start_transaction_state)
+ if restore_state
+ @new_record = restore_state[:new_record]
+ @destroyed = restore_state[:destroyed]
+ if restore_state[:id]
+ self.id = restore_state[:id]
+ else
+ @attributes.delete(self.class.primary_key)
+ @attributes_cache.delete(self.class.primary_key)
+ end
+ end
+ end
+ end
+ end
+
+ # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
+ def transaction_record_state(state) #:nodoc
+ @_start_transaction_state[state] if defined?(@_start_transaction_state)
+ end
+
+ # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
+ def transaction_include_action?(action) #:nodoc
+ case action
+ when :create
+ transaction_record_state(:new_record)
+ when :destroy
+ destroyed?
+ when :update
+ !(transaction_record_state(:new_record) || destroyed?)
+ end
+ end
end
end
diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb
index 3526f49afd..d7431e5158 100644
--- a/activerecord/test/cases/active_schema_test_mysql.rb
+++ b/activerecord/test/cases/active_schema_test_mysql.rb
@@ -4,6 +4,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
def setup
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
alias_method :execute_without_stub, :execute
+ remove_method :execute
def execute(sql, name = nil) return sql end
end
end
@@ -66,7 +67,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
end
- def test_add_timestamps
+ def test_add_timestamps
with_real_execute do
begin
ActiveRecord::Base.connection.create_table :delete_me do |t|
@@ -79,8 +80,8 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
end
-
- def test_remove_timestamps
+
+ def test_remove_timestamps
with_real_execute do
begin
ActiveRecord::Base.connection.create_table :delete_me do |t|
@@ -106,6 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ensure
#before finishing, we restore the alias to the mock-up method
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ remove_method :execute
alias_method :execute, :execute_with_stub
end
end
diff --git a/activerecord/test/cases/active_schema_test_postgresql.rb b/activerecord/test/cases/active_schema_test_postgresql.rb
index af80f724f2..4f04c6735c 100644
--- a/activerecord/test/cases/active_schema_test_postgresql.rb
+++ b/activerecord/test/cases/active_schema_test_postgresql.rb
@@ -4,6 +4,7 @@ class PostgresqlActiveSchemaTest < Test::Unit::TestCase
def setup
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
alias_method :real_execute, :execute
+ remove_method :execute
def execute(sql, name = nil) sql end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0152b7be2a..fc08c2178a 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -145,13 +145,13 @@ class AdapterTest < ActiveRecord::TestCase
def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
sql_inject = "1 select * from schema"
- assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject)
- assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ assert_no_match(/schema/, @connection.add_limit_offset!("", :limit=>sql_inject))
+ assert_no_match(/schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7))
end
def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
sql_inject = "1, 7 procedure help()"
- assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject)
- assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ assert_no_match(/procedure/, @connection.add_limit_offset!("", :limit=>sql_inject))
+ assert_no_match(/procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7))
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 36c572b5e7..5c175de6d4 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -2334,6 +2334,23 @@ class BasicsTest < ActiveRecord::TestCase
assert !Minimalistic.new.freeze.dup.frozen?
end
+ def test_compute_type_success
+ assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
+ end
+
+ def test_compute_type_nonexistent_constant
+ assert_raises NameError do
+ ActiveRecord::Base.send :compute_type, 'NonexistentModel'
+ end
+ end
+
+ def test_compute_type_no_method_error
+ String.any_instance.stubs(:constantize).raises(NoMethodError)
+ assert_raises NoMethodError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
new file mode 100644
index 0000000000..ebc16653cb
--- /dev/null
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -0,0 +1,240 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+
+class TransactionCallbacksTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+ fixtures :topics
+
+ class TopicWithCallbacks < ActiveRecord::Base
+ set_table_name :topics
+
+ after_commit{|record| record.send(:do_after_commit, nil)}
+ after_commit(:on => :create){|record| record.send(:do_after_commit, :create)}
+ after_commit(:on => :update){|record| record.send(:do_after_commit, :update)}
+ after_commit(:on => :destroy){|record| record.send(:do_after_commit, :destroy)}
+ after_rollback{|record| record.send(:do_after_rollback, nil)}
+ after_rollback(:on => :create){|record| record.send(:do_after_rollback, :create)}
+ after_rollback(:on => :update){|record| record.send(:do_after_rollback, :update)}
+ after_rollback(:on => :destroy){|record| record.send(:do_after_rollback, :destroy)}
+
+ def history
+ @history ||= []
+ end
+
+ def after_commit_block(on = nil, &block)
+ @after_commit ||= {}
+ @after_commit[on] ||= []
+ @after_commit[on] << block
+ end
+
+ def after_rollback_block(on = nil, &block)
+ @after_rollback ||= {}
+ @after_rollback[on] ||= []
+ @after_rollback[on] << block
+ end
+
+ def do_after_commit(on)
+ blocks = @after_commit[on] if defined?(@after_commit)
+ blocks.each{|b| b.call(self)} if blocks
+ end
+
+ def do_after_rollback(on)
+ blocks = @after_rollback[on] if defined?(@after_rollback)
+ blocks.each{|b| b.call(self)} if blocks
+ end
+ end
+
+ def setup
+ @first, @second = TopicWithCallbacks.find(1, 3).sort_by { |t| t.id }
+ end
+
+ def test_call_after_commit_after_transaction_commits
+ @first.after_commit_block{|r| r.history << :after_commit}
+ @first.after_rollback_block{|r| r.history << :after_rollback}
+
+ @first.save!
+ assert_equal [:after_commit], @first.history
+ end
+
+ def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record
+ @first.after_commit_block(:create){|r| r.history << :commit_on_create}
+ @first.after_commit_block(:update){|r| r.history << :commit_on_update}
+ @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
+ @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
+ @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
+ @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+
+ @first.save!
+ assert_equal [:commit_on_update], @first.history
+ end
+
+ def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record
+ @first.after_commit_block(:create){|r| r.history << :commit_on_create}
+ @first.after_commit_block(:update){|r| r.history << :commit_on_update}
+ @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
+ @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
+ @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
+ @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+
+ @first.destroy
+ assert_equal [:commit_on_destroy], @first.history
+ end
+
+ def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record
+ @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
+ @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
+ @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
+ @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
+ @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
+ @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
+ @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+
+ @new_record.save!
+ assert_equal [:commit_on_create], @new_record.history
+ end
+
+ def test_call_after_rollback_after_transaction_rollsback
+ @first.after_commit_block{|r| r.history << :after_commit}
+ @first.after_rollback_block{|r| r.history << :after_rollback}
+
+ Topic.transaction do
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal [:after_rollback], @first.history
+ end
+
+ def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record
+ @first.after_commit_block(:create){|r| r.history << :commit_on_create}
+ @first.after_commit_block(:update){|r| r.history << :commit_on_update}
+ @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
+ @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
+ @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
+ @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+
+ Topic.transaction do
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal [:rollback_on_update], @first.history
+ end
+
+ def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record
+ @first.after_commit_block(:create){|r| r.history << :commit_on_create}
+ @first.after_commit_block(:update){|r| r.history << :commit_on_update}
+ @first.after_commit_block(:destroy){|r| r.history << :commit_on_update}
+ @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
+ @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
+ @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+
+ Topic.transaction do
+ @first.destroy
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal [:rollback_on_destroy], @first.history
+ end
+
+ def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record
+ @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
+ @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
+ @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
+ @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
+ @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
+ @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
+ @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+
+ Topic.transaction do
+ @new_record.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal [:rollback_on_create], @new_record.history
+ end
+
+ def test_call_after_rollback_when_commit_fails
+ @first.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
+ begin
+ @first.connection.class.class_eval do
+ def commit_db_transaction; raise "boom!"; end
+ end
+
+ @first.after_commit_block{|r| r.history << :after_commit}
+ @first.after_rollback_block{|r| r.history << :after_rollback}
+
+ assert !@first.save rescue nil
+ assert_equal [:after_rollback], @first.history
+ ensure
+ @first.connection.class.send(:remove_method, :commit_db_transaction)
+ @first.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
+ end
+ end
+
+ def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint
+ def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
+ @first.after_rollback_block{|r| r.rollbacks(1)}
+ @first.after_commit_block{|r| r.commits(1)}
+
+ def @second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def @second.commits(i=0); @commits ||= 0; @commits += i if i; end
+ @second.after_rollback_block{|r| r.rollbacks(1)}
+ @second.after_commit_block{|r| r.commits(1)}
+
+ Topic.transaction do
+ @first.save!
+ Topic.transaction(:requires_new => true) do
+ @second.save!
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ assert_equal 1, @first.commits
+ assert_equal 0, @first.rollbacks
+ assert_equal 0, @second.commits
+ assert_equal 1, @second.rollbacks
+ end
+
+ def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails
+ def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
+
+ @first.after_rollback_block{|r| r.rollbacks(1)}
+ @first.after_commit_block{|r| r.commits(1)}
+
+ Topic.transaction do
+ @first.save
+ Topic.transaction(:requires_new => true) do
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+ Topic.transaction(:requires_new => true) do
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ assert_equal 1, @first.commits
+ assert_equal 2, @first.rollbacks
+ end
+
+ def test_after_transaction_callbacks_should_not_raise_errors
+ def @first.last_after_transaction_error=(e); @last_transaction_error = e; end
+ def @first.last_after_transaction_error; @last_transaction_error; end
+ @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
+ @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
+
+ @first.save!
+ assert_equal :commit, @first.last_after_transaction_error
+
+ Topic.transaction do
+ @first.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal :rollback, @first.last_after_transaction_error
+ end
+end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 00f3b527d7..958a4e4f94 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -320,6 +320,33 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_restore_active_record_state_for_all_records_in_a_transaction
+ topic_1 = Topic.new(:title => 'test_1')
+ topic_2 = Topic.new(:title => 'test_2')
+ Topic.transaction do
+ assert topic_1.save
+ assert topic_2.save
+ @first.save
+ @second.destroy
+ assert_equal false, topic_1.new_record?
+ assert_not_nil topic_1.id
+ assert_equal false, topic_2.new_record?
+ assert_not_nil topic_2.id
+ assert_equal false, @first.new_record?
+ assert_not_nil @first.id
+ assert_equal true, @second.destroyed?
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal true, topic_1.new_record?
+ assert_nil topic_1.id
+ assert_equal true, topic_2.new_record?
+ assert_nil topic_2.id
+ assert_equal false, @first.new_record?
+ assert_not_nil @first.id
+ assert_equal false, @second.destroyed?
+ end
+
if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE)
def test_outside_transaction_works
assert Topic.connection.outside_transaction?
@@ -382,6 +409,12 @@ class TransactionTest < ActiveRecord::TestCase
end
private
+ def define_callback_method(callback_method)
+ define_method(callback_method) do
+ self.history << [callback_method, :method]
+ end
+ end
+
def add_exception_raising_after_save_callback_to_topic
Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
remove_method(:after_save_for_transaction)
diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile
index 35a386613f..41ea5d5822 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/railties/guides/source/3_0_release_notes.textile
@@ -36,7 +36,7 @@ h4. Rails 3 requires Ruby 1.8.7+
Rails 3.0 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.0 is also compatible with Ruby 1.9.2.
-TIP: Note that Ruby 1.8.7 p248 and p249 has marshaling bugs that crash Rails 3.0.0. Ruby 1.9.1 outright segfaults on Rails 3.0.0, so if you want to use Rails 3 with 1.9.x, jump on 1.9.2 trunk for smooth sailing.
+TIP: Note that Ruby 1.8.7 p248 and p249 has marshaling bugs that crash Rails 3.0.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults on Rails 3.0.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 for smooth sailing.
h4. Rails Application object
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index ba5c443b34..06d1a801c8 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -2869,7 +2869,7 @@ Date.new(2010, 2, 28).advance(:days => 1).advance(:months => 1)
# => Thu, 01 Apr 2010
</ruby>
-h5. Changing Date Components
+h5. Changing Components
The method +change+ allows you to get a new date which is the same as the receiver except for the given year, month, or day:
@@ -2909,11 +2909,122 @@ date.end_of_day # => Sun Jun 06 23:59:59 +0200 2010
+beginning_of_day+ is aliased to +at_beginning_of_day+, +midnight+, +at_midnight+
-h4. Conversions
+h4(#date-conversions). Conversions
h3. Extensions to +DateTime+
-NOTE TO SELF: Since +DateTime+ is a subclass of +Date+, you get inherited methods that return +DateTime+ objects.
+NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+.
+
+WARNING: +DateTime+ is not aware of DST rules and so some of these methods have edge cases when a DST change is going on. For example +seconds_since_midnight+ might not return the real amount in such a day.
+
+h4(#calculations-datetime). Calculations
+
+The class +DateTime+ is a subclass of +Date+ so by loading +active_support/core_ext/date/calculations.rb+ you inherit these methods and their aliases, except that they will always return datetimes:
+
+<ruby>
+yesterday
+tomorrow
+beginning_of_week
+end_on_week
+next_week
+months_ago
+months_since
+beginning_of_month
+end_of_month
+prev_month
+next_month
+beginning_of_quarter
+end_of_quarter
+beginning_of_year
+end_of_year
+years_ago
+years_since
+prev_year
+next_year
+</ruby>
+
+The following methods are reimplemented so you do *not* need to load +active_support/core_ext/date/calculations.rb+ for these ones:
+
+<ruby>
+beginning_of_day
+end_of_day
+ago
+since
+</ruby>
+
+On the other hand, +advance+ and +change+ are also defined and support more options, they are documented below.
+
+h5. Named Datetimes
+
+h6. +DateTime.current+
+
+Active Support defines +DateTime.current+ to be like +Time.now.to_datetime+, except that it honors the user time zone, if defined. It also defines instance predicates +past?+, and +future?+ relative to +DateTime.current+.
+
+h5. Other Extensions
+
+h6. +seconds_since_midnight+
+
+The method +seconds_since_midnight+ returns the number of seconds since midnight:
+
+<ruby>
+now = DateTime.current # => Mon, 07 Jun 2010 20:26:36 +0000
+now.seconds_since_midnight # => 73596
+</ruby>
+
+h6(#utc-datetime). +utc+
+
+The method +utc+ gives you the same datetime in the receiver expressed in UTC.
+
+<ruby>
+now = DateTime.current # => Mon, 07 Jun 2010 19:27:52 -0400
+now.utc # => Mon, 07 Jun 2010 23:27:52 +0000
+</ruby>
+
+This method is also aliased as +getutc+.
+
+h6. +utc?+
+
+The predicate +utc?+ says whether the receiver has UTC as its time zone:
+
+<ruby>
+now = DateTime.now # => Mon, 07 Jun 2010 19:30:47 -0400
+now.utc? # => false
+now.utc.utc? # => true
+</ruby>
+
+h5(#datetime-changing-components). Changing Components
+
+The method +change+ allows you to get a new datetime which is the same as the receiver except for the given options, which may include +:year+, +:month+, +:day+, +:hour+, +:min+, +:sec+, +:offset+, +:start+:
+
+<ruby>
+now = DateTime.current
+# => Tue, 08 Jun 2010 01:56:22 +0000
+now.change(:year => 2011, :offset => Rational(-6, 24))
+# => Wed, 08 Jun 2011 01:56:22 -0600
+</ruby>
+
+If hours are zeroed, then minutes and seconds are too (unless they have given values):
+
+<ruby>
+now.change(:hour => 0)
+# => Tue, 08 Jun 2010 00:00:00 +0000
+</ruby>
+
+Similarly, if minutes are zeroed, then seconds are too (unless it has given a value):
+
+<ruby>
+now.change(:min => 0)
+# => Tue, 08 Jun 2010 01:00:00 +0000
+</ruby>
+
+This method is not tolerant to non-existing dates, if the change is invalid +ArgumentError+ is raised:
+
+<ruby>
+DateTime.current.change(:month => 2, :day => 30)
+# => ArgumentError: invalid date
+</ruby>
+
+h4(#datetime-conversions). Conversions
h3. Extensions to +Time+
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 89551a223d..6edf07fd65 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -17,7 +17,7 @@ This guide is designed for beginners who want to get started with a Rails applic
* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher
-TIP: Note that Ruby 1.8.7 p248 and p249 has marshaling bugs that crash Rails 3.0.0. Ruby 1.9.1 outright segfaults on Rails 3.0.0, so if you want to use Rails 3 with 1.9.x, jump on 1.9.2 trunk for smooth sailing.
+TIP: Note that Ruby 1.8.7 p248 and p249 has marshaling bugs that crash Rails 3.0.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults on Rails 3.0.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 for smooth sailing.
* The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system
* A working installation of the "SQLite3 Database":http://www.sqlite.org