From b3420f5a2e3c38e5efc2b3d995354c39af09569e Mon Sep 17 00:00:00 2001 From: Jonathan Viney Date: Sun, 31 Aug 2008 21:09:16 +1200 Subject: Implement savepoints. --- .../connection_adapters/abstract/database_statements.rb | 16 ++++++++++------ .../connection_adapters/abstract_adapter.rb | 15 +++++++++++++++ .../active_record/connection_adapters/mysql_adapter.rb | 11 +++++++++++ .../connection_adapters/postgresql_adapter.rb | 11 +++++++++++ activerecord/lib/active_record/fixtures.rb | 2 ++ activerecord/lib/active_record/transactions.rb | 12 ++++-------- 6 files changed, 53 insertions(+), 14 deletions(-) (limited to 'activerecord/lib/active_record') 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 97c6cd4331..f23fe5a90c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -55,12 +55,14 @@ module ActiveRecord end # Wrap a block in a transaction. Returns result of block. - def transaction(start_db_transaction = true) + def transaction(start_db_transaction = false) + start_db_transaction ||= open_transactions.zero? || (open_transactions == 1 && transactional_fixtures) transaction_open = false begin if block_given? if start_db_transaction - begin_db_transaction + open_transactions.zero? ? begin_db_transaction : create_savepoint + increment_open_transactions transaction_open = true end yield @@ -68,21 +70,23 @@ module ActiveRecord rescue Exception => database_transaction_rollback if transaction_open transaction_open = false - rollback_db_transaction + decrement_open_transactions + open_transactions.zero? ? rollback_db_transaction : rollback_to_savepoint end raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback end ensure if transaction_open + decrement_open_transactions begin - commit_db_transaction + open_transactions.zero? ? commit_db_transaction : release_savepoint rescue Exception => database_transaction_rollback - rollback_db_transaction + open_transactions.zero? ? rollback_db_transaction : rollback_to_savepoint raise end end end - + # Begins the transaction (and turns off auto-committing). def begin_db_transaction() end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c5183357a1..cb5c5740c3 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -159,6 +159,21 @@ module ActiveRecord def decrement_open_transactions @open_transactions -= 1 end + + def create_savepoint + end + + def rollback_to_savepoint + end + + def release_savepoint + end + + def current_savepoint_name + "rails_savepoint_#{open_transactions}" + end + + attr_accessor :transactional_fixtures def log_info(sql, name, seconds) if @logger && @logger.debug? diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1e452ae88a..721b365bc7 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -343,6 +343,17 @@ module ActiveRecord # Transactions aren't supported end + def create_savepoint + execute("SAVEPOINT #{current_savepoint_name}") + end + + def rollback_to_savepoint + execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") + end + + def release_savepoint + execute("RELEASE SAVEPOINT #{current_savepoint_name}") + end def add_limit_offset!(sql, options) #:nodoc: if limit = options[:limit] diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 60ec01b95e..f34093f0ff 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -567,6 +567,17 @@ module ActiveRecord end end + def create_savepoint + execute("SAVEPOINT #{current_savepoint_name}") + end + + def rollback_to_savepoint + execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") + end + + def release_savepoint(savepoint_number) + execute("RELEASE SAVEPOINT #{current_savepoint_name}") + end # SCHEMA STATEMENTS ======================================== diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 114141a646..3d8b0e40a9 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -932,6 +932,7 @@ module Test #:nodoc: end ActiveRecord::Base.connection.increment_open_transactions ActiveRecord::Base.connection.begin_db_transaction + ActiveRecord::Base.connection.transactional_fixtures = true # Load fixtures for every test. else Fixtures.reset_cache @@ -954,6 +955,7 @@ module Test #:nodoc: if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0 ActiveRecord::Base.connection.rollback_db_transaction ActiveRecord::Base.connection.decrement_open_transactions + ActiveRecord::Base.connection.transactional_fixtures = false end ActiveRecord::Base.clear_active_connections! end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 27b5aca18f..56b62474d9 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -122,14 +122,10 @@ module ActiveRecord # One should restart the entire transaction if a StatementError occurred. module ClassMethods # See ActiveRecord::Transactions::ClassMethods for detailed documentation. - def transaction(&block) - connection.increment_open_transactions - - begin - connection.transaction(connection.open_transactions == 1, &block) - ensure - connection.decrement_open_transactions - end + def transaction(options = {}, &block) + options.assert_valid_keys :force + + connection.transaction(options[:force], &block) end end -- cgit v1.2.3 From 47b594cc5a2adc86e14a6a3d331583edde56a22f Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 14:31:23 +0200 Subject: Improve documentation for DatabaseStatements#transactions and AbstractAdapter#transactional_fixtures, especially with regard to support for nested transactions. --- .../abstract/database_statements.rb | 75 ++++++++++++++++++++-- .../connection_adapters/abstract_adapter.rb | 3 + activerecord/lib/active_record/transactions.rb | 2 + 3 files changed, 74 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') 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 f23fe5a90c..bd434f2efc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -54,14 +54,65 @@ module ActiveRecord delete_sql(sql, name) end - # Wrap a block in a transaction. Returns result of block. + # Runs the given block in a database transaction, and returns the result + # of the block. + # + # == Nested transactions support + # + # Most databases don't support true nested transactions. At the time of + # writing, the only database that supports true nested transactions that + # we're aware of, is MS-SQL. + # + # In order to get around this problem, #transaction will emulate the effect + # of nested transactions, by using savepoints: + # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html + # Savepoints are supported by MySQL and PostgreSQL, but not SQLite3. + # + # It is safe to call this method if a database transaction is already open, + # i.e. if #transaction is called within another #transaction block. In case + # of a nested call, #transaction will behave as follows: + # + # - The block will be run without doing anything. All database statements + # that happen within the block are effectively appended to the already + # open database transaction. + # - However, if +start_db_transaction+ is set to true, then the block will + # be run inside a new database savepoint, effectively making the block + # a sub-transaction. + # - If the #transactional_fixtures attribute is set to true, then the first + # nested call to #transaction will create a new savepoint instead of + # doing nothing. This makes it possible for toplevel transactions in unit + # tests to behave like real transactions, even though a database + # transaction has already been opened. + # + # === Caveats + # + # MySQL doesn't support DDL transactions. If you perform a DDL operation, + # then any created savepoints will be automatically released. For example, + # if you've created a savepoint, then you execute a CREATE TABLE statement, + # then the savepoint that was created will be automatically released. + # + # This means that, on MySQL, you shouldn't execute DDL operations inside + # a #transaction call that you know might create a savepoint. Otherwise, + # #transaction will raise exceptions when it tries to release the + # already-automatically-released savepoints: + # + # Model.connection.transaction do # BEGIN + # Model.connection.transaction(true) do # CREATE SAVEPOINT rails_savepoint_1 + # Model.connection.create_table(...) + # # rails_savepoint_1 now automatically released + # end # RELEASE savepoint rails_savepoint_1 <--- BOOM! database error! + # end def transaction(start_db_transaction = false) - start_db_transaction ||= open_transactions.zero? || (open_transactions == 1 && transactional_fixtures) + start_db_transaction ||= open_transactions == 0 || (open_transactions == 1 && transactional_fixtures) transaction_open = false begin if block_given? if start_db_transaction - open_transactions.zero? ? begin_db_transaction : create_savepoint + if open_transactions == 0 + begin_db_transaction + else + create_savepoint + end increment_open_transactions transaction_open = true end @@ -71,7 +122,11 @@ module ActiveRecord if transaction_open transaction_open = false decrement_open_transactions - open_transactions.zero? ? rollback_db_transaction : rollback_to_savepoint + if open_transactions == 0 + rollback_db_transaction + else + rollback_to_savepoint + end end raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback end @@ -79,9 +134,17 @@ module ActiveRecord if transaction_open decrement_open_transactions begin - open_transactions.zero? ? commit_db_transaction : release_savepoint + if open_transactions == 0 + commit_db_transaction + else + release_savepoint + end rescue Exception => database_transaction_rollback - open_transactions.zero? ? rollback_db_transaction : rollback_to_savepoint + if open_transactions == 0 + rollback_db_transaction + else + rollback_to_savepoint + end raise end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index cb5c5740c3..45a6cff346 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -173,6 +173,9 @@ module ActiveRecord "rails_savepoint_#{open_transactions}" end + # Whether this AbstractAdapter is currently being used inside a unit test + # with transactional fixtures turned on. See DatabaseStatements#transaction + # for more information about the effect of this option. attr_accessor :transactional_fixtures def log_info(sql, name, seconds) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 56b62474d9..4bac7b39d9 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -125,6 +125,8 @@ module ActiveRecord def transaction(options = {}, &block) options.assert_valid_keys :force + # See the API documentation for ConnectionAdapters::DatabaseStatements#transaction + # for useful information. connection.transaction(options[:force], &block) end end -- cgit v1.2.3 From e383835e738a30cd992c322eff126380bd15094f Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 14:47:43 +0200 Subject: Revert "PostgreSQL: introduce transaction_active? rather than tracking activity ourselves" This commit conflicts with savepoint support. This reverts commit 045713ee240fff815edb5962b25d668512649478. Conflicts: activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb --- .../connection_adapters/postgresql_adapter.rb | 38 ---------------------- 1 file changed, 38 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f34093f0ff..98501db816 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -529,44 +529,6 @@ module ActiveRecord execute "ROLLBACK" end - # ruby-pg defines Ruby constants for transaction status, - # ruby-postgres does not. - PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0 - - # Check whether a transaction is active. - def transaction_active? - @connection.transaction_status != PQTRANS_IDLE - end - - # Wrap a block in a transaction. Returns result of block. - def transaction(start_db_transaction = true) - transaction_open = false - begin - if block_given? - if start_db_transaction - begin_db_transaction - transaction_open = true - end - yield - end - rescue Exception => database_transaction_rollback - if transaction_open && transaction_active? - transaction_open = false - rollback_db_transaction - end - raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback - end - ensure - if transaction_open && transaction_active? - begin - commit_db_transaction - rescue Exception => database_transaction_rollback - rollback_db_transaction - raise - end - end - end - def create_savepoint execute("SAVEPOINT #{current_savepoint_name}") end -- cgit v1.2.3 From e981eaaf342d06e399b5138553c964adcfadd87c Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 14:52:02 +0200 Subject: Fix a stale typo in the PostgreSQL adapter. Fix a stale mock expection in transaction_test. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 98501db816..cfeef30d06 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -537,7 +537,7 @@ module ActiveRecord execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") end - def release_savepoint(savepoint_number) + def release_savepoint execute("RELEASE SAVEPOINT #{current_savepoint_name}") end -- cgit v1.2.3 From 885c11b8f9e18f34b12076023455e72166365f00 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 15:41:56 +0200 Subject: Make SQLite3 pass the unit tests for savepoints. --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 6 ++++++ activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 4 ++++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 4 ++++ 3 files changed, 14 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 45a6cff346..81260eeecc 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -65,6 +65,12 @@ module ActiveRecord def supports_ddl_transactions? false end + + # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite + # does not. + def supports_savepoints? + false + end # Should primary key values be selected from their corresponding # sequence before the insert statement? If true, next_sequence_value diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 721b365bc7..76ade2a980 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -205,6 +205,10 @@ module ActiveRecord def supports_migrations? #:nodoc: true end + + def supports_savepoints? #:nodoc: + true + end def native_database_types #:nodoc: NATIVE_DATABASE_TYPES diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index cfeef30d06..828f321767 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -272,6 +272,10 @@ module ActiveRecord def supports_ddl_transactions? true end + + def supports_savepoints? + true + end # Returns the configured supported identifier length supported by PostgreSQL, # or report the default of 63 on PostgreSQL 7.x. -- cgit v1.2.3 From e916aa7ea1613be966959c05ad41d13fee55a683 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 16:24:15 +0200 Subject: Rename ActiveRecord::Base#transaction's :force option to :nest. Improve documentation for nested transactions. --- activerecord/lib/active_record/transactions.rb | 59 +++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 4bac7b39d9..1e0185d39c 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -120,14 +120,69 @@ module ActiveRecord # end # # One should restart the entire transaction if a StatementError occurred. + # + # == Nested transactions + # + # #transaction calls can be nested. By default, this makes all database + # statements in the nested transaction block become part of the parent + # transaction. For example: + # + # User.transaction do + # User.create(:username => 'Kotori') + # User.transaction do + # User.create(:username => 'Nemu') + # raise ActiveRecord::Rollback + # end + # end + # + # User.find(:all) # => empty + # + # It is also possible to treat a certain #transaction call as its own + # sub-transaction, by passing :nest => true to #transaction. If + # anything goes wrong inside that transaction block, then the parent + # transaction will remain unaffected. For example: + # + # User.transaction do + # User.create(:username => 'Kotori') + # User.transaction(:nest => true) do + # User.create(:username => 'Nemu') + # raise ActiveRecord::Rollback + # end + # end + # + # User.find(:all) # => Returns only Kotori + # + # Most databases don't support true nested transactions. At the time of + # writing, the only database that we're aware of that supports true nested + # transactions, is MS-SQL. Because of this, Active Record emulates nested + # transactions by using savepoints. See + # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html + # for more information about savepoints. + # + # === Caveats + # + # If you're on MySQL, then do not use DDL operations in nested transactions + # blocks that are emulated with savepoints. That is, do not execute statements + # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically + # releases all savepoints upon executing a DDL operation. When #transaction + # is finished and tries to release the savepoint it created earlier, a + # database error will occur because the savepoint has already been + # automatically released. The following example demonstrates the problem: + # + # Model.connection.transaction do # BEGIN + # Model.connection.transaction(true) do # CREATE SAVEPOINT rails_savepoint_1 + # Model.connection.create_table(...) # rails_savepoint_1 now automatically released + # end # RELEASE savepoint rails_savepoint_1 + # # ^^^^ BOOM! database error! + # end module ClassMethods # See ActiveRecord::Transactions::ClassMethods for detailed documentation. def transaction(options = {}, &block) - options.assert_valid_keys :force + options.assert_valid_keys :nest # See the API documentation for ConnectionAdapters::DatabaseStatements#transaction # for useful information. - connection.transaction(options[:force], &block) + connection.transaction(options[:nest], &block) end end -- cgit v1.2.3 From fb2325e35855d62abd2c76ce03feaa3ca7992e4f Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 17:57:49 +0200 Subject: Reimplement Jeremy's PostgreSQL automatic transaction state introspection code. - Fixed compatibility with the old 'postgres' driver which doesn't support transaction state introspection. - Added unit tests for it. --- .../abstract/database_statements.rb | 20 ++++++++++++++++++-- .../connection_adapters/postgresql_adapter.rb | 10 ++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') 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 bd434f2efc..a9a63e5a9f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -53,6 +53,20 @@ module ActiveRecord def delete(sql, name = nil) delete_sql(sql, name) end + + # Checks whether there is currently no transaction active. This is done + # by querying the database driver, and does not use the transaction + # house-keeping information recorded by #increment_open_transactions and + # friends. + # + # Returns true if there is no transaction active, false if there is a + # transaction active, and nil if this information is unknown. + # + # Not all adapters supports transaction state introspection. Currently, + # only the PostgreSQL adapter supports this. + def outside_transaction? + nil + end # Runs the given block in a database transaction, and returns the result # of the block. @@ -119,7 +133,7 @@ module ActiveRecord yield end rescue Exception => database_transaction_rollback - if transaction_open + if transaction_open && !outside_transaction? transaction_open = false decrement_open_transactions if open_transactions == 0 @@ -131,7 +145,9 @@ module ActiveRecord raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback end ensure - if transaction_open + if outside_transaction? + @open_transactions = 0 + elsif transaction_open decrement_open_transactions begin if open_transactions == 0 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 828f321767..c4ef2be82e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -532,6 +532,16 @@ module ActiveRecord def rollback_db_transaction execute "ROLLBACK" end + + if PGconn.public_method_defined?(:transaction_status) + # ruby-pg defines Ruby constants for transaction status, + # ruby-postgres does not. + PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0 + + def outside_transaction? + @connection.transaction_status == PQTRANS_IDLE + end + end def create_savepoint execute("SAVEPOINT #{current_savepoint_name}") -- cgit v1.2.3 From eb457ceee13779ade67e1bdebd2919d476148277 Mon Sep 17 00:00:00 2001 From: Pivotal Labs Date: Wed, 24 Dec 2008 14:57:09 -0800 Subject: Association preloading no longer stops if it hits a nil object [#1630 state:resolved] Signed-off-by: Frederick Cheung --- activerecord/lib/active_record/association_preload.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 7b1b2d9ad9..0bcf50c167 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -94,8 +94,8 @@ module ActiveRecord raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol) preload_associations(records, parent, preload_options) reflection = reflections[parent] - parents = records.map {|record| record.send(reflection.name)}.flatten - unless parents.empty? || parents.first.nil? + parents = records.map {|record| record.send(reflection.name)}.flatten.compact + unless parents.empty? parents.first.class.preload_associations(parents, child) end end -- cgit v1.2.3 From 5cebe69e74d411c3c9e5f6ab9d4b2b16ee36177c Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Fri, 19 Dec 2008 01:02:21 +0000 Subject: Preload uses exclusive scope [#643 state:resolved] With self referential associations, the scope for the the top level should not affect fetching of associations, for example when doing Person.male.find :all, :include => :friends we should load all of the friends for each male, not just the male friends. --- .../lib/active_record/association_preload.rb | 31 +++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 0bcf50c167..9d0bf3a308 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -43,7 +43,7 @@ module ActiveRecord # loading in a more high-level (application developer-friendly) manner. module ClassMethods protected - + # Eager loads the named associations for the given ActiveRecord record(s). # # In this description, 'association name' shall refer to the name passed @@ -113,7 +113,7 @@ module ActiveRecord # unnecessarily records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records| raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection - + # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus, # the following could call 'preload_belongs_to_association', # 'preload_has_many_association', etc. @@ -128,7 +128,7 @@ module ActiveRecord association_proxy.target.push(*[associated_record].flatten) end end - + def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record) parent_records.each do |parent_record| parent_record.send("set_#{reflection_name}_target", associated_record) @@ -183,18 +183,19 @@ module ActiveRecord conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}" conditions << append_conditions(reflection, preload_options) - associated_records = reflection.klass.find(:all, :conditions => [conditions, ids], - :include => options[:include], - :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", - :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id", - :order => options[:order]) - + associated_records = reflection.klass.with_exclusive_scope do + reflection.klass.find(:all, :conditions => [conditions, ids], + :include => options[:include], + :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", + :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id", + :order => options[:order]) + end set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id') end def preload_has_one_association(records, reflection, preload_options={}) return if records.first.send("loaded_#{reflection.name}?") - id_to_record_map, ids = construct_id_map(records) + id_to_record_map, ids = construct_id_map(records) options = reflection.options records.each {|record| record.send("set_#{reflection.name}_target", nil)} if options[:through] @@ -248,7 +249,7 @@ module ActiveRecord reflection.primary_key_name) end end - + def preload_through_records(records, reflection, through_association) through_reflection = reflections[through_association] through_primary_key = through_reflection.primary_key_name @@ -333,11 +334,13 @@ module ActiveRecord end conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}" conditions << append_conditions(reflection, preload_options) - associated_records = klass.find(:all, :conditions => [conditions, ids], + associated_records = klass.with_exclusive_scope do + klass.find(:all, :conditions => [conditions, ids], :include => options[:include], :select => options[:select], :joins => options[:joins], :order => options[:order]) + end set_association_single_records(id_map, reflection.name, associated_records, primary_key) end end @@ -355,13 +358,15 @@ module ActiveRecord conditions << append_conditions(reflection, preload_options) - reflection.klass.find(:all, + reflection.klass.with_exclusive_scope do + reflection.klass.find(:all, :select => (preload_options[:select] || options[:select] || "#{table_name}.*"), :include => preload_options[:include] || options[:include], :conditions => [conditions, ids], :joins => options[:joins], :group => preload_options[:group] || options[:group], :order => preload_options[:order] || options[:order]) + end end -- cgit v1.2.3 From 7db1704068b86fb2212388b14b4963526bacfa5d Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Fri, 26 Dec 2008 22:53:07 +0000 Subject: Fix :include of has_many associations with :primary_key option --- activerecord/lib/active_record/association_preload.rb | 2 +- activerecord/lib/active_record/associations.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 9d0bf3a308..195d2fb4f8 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -229,7 +229,7 @@ module ActiveRecord options = reflection.options primary_key_name = reflection.through_reflection_primary_key_name - id_to_record_map, ids = construct_id_map(records, primary_key_name) + id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key]) records.each {|record| record.send(reflection.name).loaded} if options[:through] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5a60b13fd8..237e5c0e9d 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -2171,7 +2171,7 @@ module ActiveRecord aliased_table_name, foreign_key, parent.aliased_table_name, - parent.primary_key + reflection.options[:primary_key] || parent.primary_key ] end when :belongs_to -- cgit v1.2.3 From f9cab0e503a4721c9d0369f89bb85c6e658f778c Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Fri, 26 Dec 2008 23:26:37 +0000 Subject: Fix :include of has_one with :primary_key option --- activerecord/lib/active_record/association_preload.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 195d2fb4f8..e4ab69aa1b 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -195,7 +195,7 @@ module ActiveRecord def preload_has_one_association(records, reflection, preload_options={}) return if records.first.send("loaded_#{reflection.name}?") - id_to_record_map, ids = construct_id_map(records) + id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key]) options = reflection.options records.each {|record| record.send("set_#{reflection.name}_target", nil)} if options[:through] -- cgit v1.2.3 From 21efba464afa2ae6e5dfd938ac8a3ce446faf7e7 Mon Sep 17 00:00:00 2001 From: Roman Shterenzon Date: Sat, 27 Dec 2008 01:10:29 +0000 Subject: Fix HasManyAssociation#create ignoring the :primary_key option [#1633 state:resolved] Signed-off-by: Frederick Cheung --- activerecord/lib/active_record/associations/association_proxy.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 59f1d3b867..676c4ace61 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -180,7 +180,10 @@ module ActiveRecord record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record? record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s else - record[@reflection.primary_key_name] = @owner.id unless @owner.new_record? + unless @owner.new_record? + primary_key = @reflection.options[:primary_key] || :id + record[@reflection.primary_key_name] = @owner.send(primary_key) + end end end -- cgit v1.2.3 From 6e98adfc8e19a39fa45d4acd94145d318d151964 Mon Sep 17 00:00:00 2001 From: Yaroslav Markin Date: Sat, 27 Dec 2008 16:26:13 +0300 Subject: ActiveRecord::Base#new_record? now returns false for existing records (was nil) [#1219 state:committed] Signed-off-by: David Heinemeier Hansson --- activerecord/lib/active_record/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9746a46d47..80c109aea7 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2406,9 +2406,9 @@ module ActiveRecord #:nodoc: write_attribute(self.class.primary_key, value) end - # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet. + # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false. def new_record? - defined?(@new_record) && @new_record + (defined?(@new_record) && @new_record) || false end # :call-seq: -- cgit v1.2.3 From afdec83ed543e904b495d3225b6401101ea7ba6c Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sat, 27 Dec 2008 14:16:17 +0000 Subject: Fix to_sentence being used with options removed by 273c77 --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 237e5c0e9d..eba10b505e 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -22,7 +22,7 @@ module ActiveRecord through_reflection = reflection.through_reflection source_reflection_names = reflection.source_reflection_names source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect } - super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => '. Is it one of #{source_associations.to_sentence :connector => 'or'}?") + super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => '. Is it one of #{source_associations.to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '}?") end end -- cgit v1.2.3 From 28347d889bb4304e681bf885fea734067fdd8ff6 Mon Sep 17 00:00:00 2001 From: Yaroslav Markin Date: Sat, 27 Dec 2008 17:02:07 +0300 Subject: Refactor ActiveRecord::Base#new_record? [#1647 state:committed] Signed-off-by: David Heinemeier Hansson --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 80c109aea7..e5e94555eb 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2408,7 +2408,7 @@ module ActiveRecord #:nodoc: # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false. def new_record? - (defined?(@new_record) && @new_record) || false + @new_record || false end # :call-seq: -- cgit v1.2.3 From a2270ef2594b97891994848138614657363f2806 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 28 Dec 2008 19:48:05 +0000 Subject: Inline code comments for class_eval/module_eval [#1657 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record/associations.rb | 36 ++++++++-------- activerecord/lib/active_record/base.rb | 48 +++++++++++++++++++++- .../connection_adapters/abstract/query_cache.rb | 12 +++--- .../abstract/schema_definitions.rb | 48 +++++++++++----------- .../connection_adapters/mysql_adapter.rb | 24 ++++++----- .../connection_adapters/postgresql_adapter.rb | 14 +++---- activerecord/lib/active_record/dirty.rb | 2 +- 7 files changed, 116 insertions(+), 68 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5a60b13fd8..c154a5087c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1216,11 +1216,11 @@ module ActiveRecord # callbacks will be executed after the association is wiped out. old_method = "destroy_without_habtm_shim_for_#{reflection.name}" class_eval <<-end_eval unless method_defined?(old_method) - alias_method :#{old_method}, :destroy_without_callbacks - def destroy_without_callbacks - #{reflection.name}.clear - #{old_method} - end + alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks + def destroy_without_callbacks # def destroy_without_callbacks + #{reflection.name}.clear # posts.clear + #{old_method} # destroy_without_habtm_shim_for_posts + end # end end_eval add_association_callbacks(reflection.name, options) @@ -1463,22 +1463,22 @@ module ActiveRecord before_destroy method_name when :delete_all module_eval %Q{ - before_destroy do |record| - delete_all_has_many_dependencies(record, - "#{reflection.name}", - #{reflection.class_name}, - %@#{dependent_conditions}@) - end + before_destroy do |record| # before_destroy do |record| + delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record, + "#{reflection.name}", # "posts", + #{reflection.class_name}, # Post, + %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...) + end # end } when :nullify module_eval %Q{ - before_destroy do |record| - nullify_has_many_dependencies(record, - "#{reflection.name}", - #{reflection.class_name}, - "#{reflection.primary_key_name}", - %@#{dependent_conditions}@) - end + before_destroy do |record| # before_destroy do |record| + nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record, + "#{reflection.name}", # "posts", + #{reflection.class_name}, # Post, + "#{reflection.primary_key_name}", # "user_id", + %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...) + end # end } else raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})" diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e5e94555eb..4c9ad24ea3 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1818,10 +1818,31 @@ module ActiveRecord #:nodoc: if match.finder? finder = match.finder bang = match.bang? + # def self.find_by_login_and_activated(*args) + # options = args.extract_options! + # attributes = construct_attributes_from_arguments( + # [:login,:activated], + # args + # ) + # finder_options = { :conditions => attributes } + # validate_find_options(options) + # set_readonly_option!(options) + # + # if options[:conditions] + # with_scope(:find => finder_options) do + # find(:first, options) + # end + # else + # find(:first, options.merge(finder_options)) + # end + # end self.class_eval %{ def self.#{method_id}(*args) options = args.extract_options! - attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args) + attributes = construct_attributes_from_arguments( + [:#{attribute_names.join(',:')}], + args + ) finder_options = { :conditions => attributes } validate_find_options(options) set_readonly_option!(options) @@ -1839,6 +1860,31 @@ module ActiveRecord #:nodoc: send(method_id, *arguments) elsif match.instantiator? instantiator = match.instantiator + # def self.find_or_create_by_user_id(*args) + # guard_protected_attributes = false + # + # if args[0].is_a?(Hash) + # guard_protected_attributes = true + # attributes = args[0].with_indifferent_access + # find_attributes = attributes.slice(*[:user_id]) + # else + # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args) + # end + # + # options = { :conditions => find_attributes } + # set_readonly_option!(options) + # + # record = find(:first, options) + # + # if record.nil? + # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } + # yield(record) if block_given? + # record.save + # record + # else + # record + # end + # end self.class_eval %{ def self.#{method_id}(*args) guard_protected_attributes = false diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 950bd72101..00c71090f3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -14,12 +14,12 @@ module ActiveRecord def dirties_query_cache(base, *method_names) method_names.each do |method_name| base.class_eval <<-end_code, __FILE__, __LINE__ - def #{method_name}_with_query_dirty(*args) - clear_query_cache if @query_cache_enabled - #{method_name}_without_query_dirty(*args) - end - - alias_method_chain :#{method_name}, :query_dirty + def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args) + clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled + #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args) + end # end + # + alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty end_code end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index fe9cbcf024..273f823e7f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -476,12 +476,12 @@ module ActiveRecord %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| class_eval <<-EOV - def #{column_type}(*args) - options = args.extract_options! - column_names = args - - column_names.each { |name| column(name, '#{column_type}', options) } - end + def #{column_type}(*args) # def string(*args) + options = args.extract_options! # options = args.extract_options! + column_names = args # column_names = args + # + column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) } + end # end EOV end @@ -676,24 +676,24 @@ module ActiveRecord # t.string(:goat, :sheep) %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| class_eval <<-EOV - def #{column_type}(*args) - options = args.extract_options! - column_names = args - - column_names.each do |name| - column = ColumnDefinition.new(@base, name, '#{column_type}') - if options[:limit] - column.limit = options[:limit] - elsif native['#{column_type}'.to_sym].is_a?(Hash) - column.limit = native['#{column_type}'.to_sym][:limit] - end - column.precision = options[:precision] - column.scale = options[:scale] - column.default = options[:default] - column.null = options[:null] - @base.add_column(@table_name, name, column.sql_type, options) - end - end + def #{column_type}(*args) # def string(*args) + options = args.extract_options! # options = args.extract_options! + column_names = args # column_names = args + # + column_names.each do |name| # column_names.each do |name| + column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string') + if options[:limit] # if options[:limit] + column.limit = options[:limit] # column.limit = options[:limit] + elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash) + column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit] + end # end + column.precision = options[:precision] # column.precision = options[:precision] + column.scale = options[:scale] # column.scale = options[:scale] + column.default = options[:default] # column.default = options[:default] + column.null = options[:null] # column.null = options[:null] + @base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options) + end # end + end # end EOV end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 46d4b6c89c..60729c63db 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -13,23 +13,25 @@ module MysqlCompat #:nodoc: # C driver >= 2.7 returns null values in each_hash if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700) target.class_eval <<-'end_eval' - def all_hashes - rows = [] - each_hash { |row| rows << row } - rows - end + def all_hashes # def all_hashes + rows = [] # rows = [] + each_hash { |row| rows << row } # each_hash { |row| rows << row } + rows # rows + end # end end_eval # adapters before 2.7 don't have a version constant # and don't return null values in each_hash else target.class_eval <<-'end_eval' - def all_hashes - rows = [] - all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields } - each_hash { |row| rows << all_fields.dup.update(row) } - rows - end + def all_hashes # def all_hashes + rows = [] # rows = [] + all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| + fields[f.name] = nil; fields # fields[f.name] = nil; fields + } # } + each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } + rows # rows + end # end end_eval end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 60ec01b95e..6685cb8663 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -950,13 +950,13 @@ module ActiveRecord # should know about this but can't detect it there, so deal with it here. money_precision = (postgresql_version >= 80300) ? 19 : 10 PostgreSQLColumn.module_eval(<<-end_eval) - def extract_precision(sql_type) - if sql_type =~ /^money$/ - #{money_precision} - else - super - end - end + def extract_precision(sql_type) # def extract_precision(sql_type) + if sql_type =~ /^money$/ # if sql_type =~ /^money$/ + #{money_precision} # 19 + else # else + super # super + end # end + end # end end_eval configure_connection diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index a1760875ba..4c899f58e5 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -174,7 +174,7 @@ module ActiveRecord alias_attribute_without_dirty(new_name, old_name) DIRTY_SUFFIXES.each do |suffix| module_eval <<-STR, __FILE__, __LINE__+1 - def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end + def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end STR end end -- cgit v1.2.3 From 66ee5890c5f21995b7fe0c486547f1287afe2b55 Mon Sep 17 00:00:00 2001 From: Yaroslav Markin Date: Sun, 28 Dec 2008 22:25:55 +0300 Subject: Introduce dynamic scopes for ActiveRecord: you can now use class methods like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that will use the scoped method with attributes you supply. [#1648 state:committed] Signed-off-by: David Heinemeier Hansson --- activerecord/lib/active_record/base.rb | 25 +++++++++++++++++++++- .../lib/active_record/dynamic_scope_match.rb | 25 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 activerecord/lib/active_record/dynamic_scope_match.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e5e94555eb..5198dbfa9b 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1456,7 +1456,10 @@ module ActiveRecord #:nodoc: def respond_to?(method_id, include_private = false) if match = DynamicFinderMatch.match(method_id) return true if all_attributes_exists?(match.attribute_names) + elsif match = DynamicScopeMatch.match(method_id) + return true if all_attributes_exists?(match.attribute_names) end + super end @@ -1809,7 +1812,11 @@ module ActiveRecord #:nodoc: # This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount) # or find_or_create_by_user_and_password(user, password). # - # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future + # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that + # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password]) + # respectively. + # + # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future # attempts to use it do not run through method_missing. def method_missing(method_id, *arguments, &block) if match = DynamicFinderMatch.match(method_id) @@ -1868,6 +1875,22 @@ module ActiveRecord #:nodoc: }, __FILE__, __LINE__ send(method_id, *arguments, &block) end + elsif match = DynamicScopeMatch.match(method_id) + attribute_names = match.attribute_names + super unless all_attributes_exists?(attribute_names) + if match.scope? + self.class_eval %{ + def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) + options = args.extract_options! # options = args.extract_options! + attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments( + [:#{attribute_names.join(',:')}], args # [:user_name, :password], args + ) # ) + # + scoped(:conditions => attributes) # scoped(:conditions => attributes) + end # end + }, __FILE__, __LINE__ + send(method_id, *arguments) + end else super end diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb new file mode 100644 index 0000000000..f796ba669a --- /dev/null +++ b/activerecord/lib/active_record/dynamic_scope_match.rb @@ -0,0 +1,25 @@ +module ActiveRecord + class DynamicScopeMatch + def self.match(method) + ds_match = self.new(method) + ds_match.scope ? ds_match : nil + end + + def initialize(method) + @scope = true + case method.to_s + when /^scoped_by_([_a-zA-Z]\w*)$/ + names = $1 + else + @scope = nil + end + @attribute_names = names && names.split('_and_') + end + + attr_reader :scope, :attribute_names + + def scope? + !@scope.nil? + end + end +end -- cgit v1.2.3 From 220dff4c3b58b7becb587ee6f2434b2ca720f7c3 Mon Sep 17 00:00:00 2001 From: Mike Gunderloy Date: Mon, 29 Dec 2008 20:46:20 -0600 Subject: Add transaction check to SQLite2 adapter to fix test_sqlite_add_column_in_transaction_raises_statement_invalid [#1669 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 84f8c0284e..9387cf8827 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -402,6 +402,10 @@ module ActiveRecord end def add_column(table_name, column_name, type, options = {}) #:nodoc: + if @connection.respond_to?(:transaction_active?) && @connection.transaction_active? + raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction' + end + alter_table(table_name) do |definition| definition.column(column_name, type, options) end -- cgit v1.2.3 From ab0ce052ba23a4cce7a84ecade0d00d9cc518ebd Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 10 Jan 2009 13:36:09 -0800 Subject: Introduce transaction_joinable flag to mark that the fixtures transaction can't joined, a new savepoint is required even if :requires_new is not set. Use :requires_new option instead of :nest. Update changelog. [#383 state:committed] --- .../abstract/database_statements.rb | 31 +++++++++++----------- .../connection_adapters/abstract_adapter.rb | 19 +++++++------ activerecord/lib/active_record/fixtures.rb | 5 ++-- activerecord/lib/active_record/transactions.rb | 27 +++++++++---------- 4 files changed, 39 insertions(+), 43 deletions(-) (limited to 'activerecord/lib/active_record') 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 cecbc6b3ac..39118583bd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -89,14 +89,8 @@ module ActiveRecord # - The block will be run without doing anything. All database statements # that happen within the block are effectively appended to the already # open database transaction. - # - However, if +start_db_transaction+ is set to true, then the block will - # be run inside a new database savepoint, effectively making the block - # a sub-transaction. - # - If the #transactional_fixtures attribute is set to true, then the first - # nested call to #transaction will create a new savepoint instead of - # doing nothing. This makes it possible for toplevel transactions in unit - # tests to behave like real transactions, even though a database - # transaction has already been opened. + # - However, if +requires_new+ is set, the block will be wrapped in a + # database savepoint acting as a sub-transaction. # # === Caveats # @@ -111,20 +105,25 @@ module ActiveRecord # already-automatically-released savepoints: # # Model.connection.transaction do # BEGIN - # Model.connection.transaction(true) do # CREATE SAVEPOINT rails_savepoint_1 + # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1 # Model.connection.create_table(...) - # # rails_savepoint_1 now automatically released - # end # RELEASE savepoint rails_savepoint_1 <--- BOOM! database error! + # # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error! # end - def transaction(start_db_transaction = false) - start_db_transaction ||= open_transactions == 0 || (open_transactions == 1 && transactional_fixtures) + def transaction(options = {}) + options.assert_valid_keys :requires_new, :joinable + + last_transaction_joinable, @transaction_joinable = + @transaction_joinable, options[:joinable] || true + requires_new = options[:requires_new] || !last_transaction_joinable + transaction_open = false begin if block_given? - if start_db_transaction + if requires_new || open_transactions == 0 if open_transactions == 0 begin_db_transaction - else + elsif requires_new create_savepoint end increment_open_transactions @@ -145,6 +144,8 @@ module ActiveRecord raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback end ensure + @transaction_joinable = last_transaction_joinable + if outside_transaction? @open_transactions = 0 elsif transaction_open diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 5137b0f78c..a8cd9f033b 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -165,24 +165,23 @@ module ActiveRecord def decrement_open_transactions @open_transactions -= 1 end - + + def transaction_joinable=(joinable) + @transaction_joinable = joinable + end + def create_savepoint end - + def rollback_to_savepoint end - + def release_savepoint end - + def current_savepoint_name - "rails_savepoint_#{open_transactions}" + "active_record_#{open_transactions}" end - - # Whether this AbstractAdapter is currently being used inside a unit test - # with transactional fixtures turned on. See DatabaseStatements#transaction - # for more information about the effect of this option. - attr_accessor :transactional_fixtures def log_info(sql, name, ms) if @logger && @logger.debug? diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 039d5a4e8e..0131d9fac5 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -516,7 +516,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) all_loaded_fixtures.update(fixtures_map) - connection.transaction(connection.open_transactions.zero?) do + connection.transaction(:requires_new => true) do fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } fixtures.each { |fixture| fixture.insert_fixtures } @@ -937,8 +937,8 @@ module ActiveRecord @@already_loaded_fixtures[self.class] = @loaded_fixtures end ActiveRecord::Base.connection.increment_open_transactions + ActiveRecord::Base.connection.transaction_joinable = false ActiveRecord::Base.connection.begin_db_transaction - ActiveRecord::Base.connection.transactional_fixtures = true # Load fixtures for every test. else Fixtures.reset_cache @@ -961,7 +961,6 @@ module ActiveRecord if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0 ActiveRecord::Base.connection.rollback_db_transaction ActiveRecord::Base.connection.decrement_open_transactions - ActiveRecord::Base.connection.transactional_fixtures = false end ActiveRecord::Base.clear_active_connections! end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index aaa298dc49..0b6e52c79b 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -137,14 +137,14 @@ module ActiveRecord # # User.find(:all) # => empty # - # It is also possible to treat a certain #transaction call as its own - # sub-transaction, by passing :nest => true to #transaction. If - # anything goes wrong inside that transaction block, then the parent - # transaction will remain unaffected. For example: + # It is also possible to requires a sub-transaction by passing + # :requires_new => true. If anything goes wrong, the + # database rolls back to the beginning of the sub-transaction + # without rolling back the parent transaction. For example: # # User.transaction do # User.create(:username => 'Kotori') - # User.transaction(:nest => true) do + # User.transaction(:requires_new => true) do # User.create(:username => 'Nemu') # raise ActiveRecord::Rollback # end @@ -169,20 +169,17 @@ module ActiveRecord # database error will occur because the savepoint has already been # automatically released. The following example demonstrates the problem: # - # Model.connection.transaction do # BEGIN - # Model.connection.transaction(true) do # CREATE SAVEPOINT rails_savepoint_1 - # Model.connection.create_table(...) # rails_savepoint_1 now automatically released - # end # RELEASE savepoint rails_savepoint_1 - # # ^^^^ BOOM! database error! + # Model.connection.transaction do # BEGIN + # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1 + # Model.connection.create_table(...) # active_record_1 now automatically released + # end # RELEASE savepoint active_record_1 + # # ^^^^ BOOM! database error! # end module ClassMethods # See ActiveRecord::Transactions::ClassMethods for detailed documentation. def transaction(options = {}, &block) - options.assert_valid_keys :nest - - # See the API documentation for ConnectionAdapters::DatabaseStatements#transaction - # for useful information. - connection.transaction(options[:nest], &block) + # See the ConnectionAdapters::DatabaseStatements#transaction API docs. + connection.transaction(options, &block) end end -- cgit v1.2.3 From b6a94fc1c611216c6d1001bb6044973b01dbba38 Mon Sep 17 00:00:00 2001 From: Cody Fauser Date: Tue, 13 Jan 2009 13:45:10 -0600 Subject: Remove legacy reloadable? method from ActiveRecord::SessionStore [#1745 state:resolved] Signed-off-by: Joshua Peek --- activerecord/lib/active_record/session_store.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index bd198c03b2..5e45cf65ab 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -53,11 +53,6 @@ module ActiveRecord before_save :raise_on_session_data_overflow! class << self - # Don't try to reload ARStore::Session in dev mode. - def reloadable? #:nodoc: - false - end - def data_column_size_limit @data_column_size_limit ||= columns_hash[@@data_column_name].limit end -- cgit v1.2.3 From 9bcf01b23c25e640da7908ac8b1b49fbf7d2e51a Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Tue, 13 Jan 2009 16:01:44 +0100 Subject: Fix PostgreSQL unit test failures that only occur when using the old 'postgres' driver. [#1748 state:committed] Signed-off-by: Jeremy Kemper --- .../connection_adapters/abstract/database_statements.rb | 12 ++++++++---- .../active_record/connection_adapters/postgresql_adapter.rb | 10 ++++------ 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'activerecord/lib/active_record') 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 39118583bd..08601da00a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -89,7 +89,7 @@ module ActiveRecord # - The block will be run without doing anything. All database statements # that happen within the block are effectively appended to the already # open database transaction. - # - However, if +requires_new+ is set, the block will be wrapped in a + # - However, if +:requires_new+ is set, the block will be wrapped in a # database savepoint acting as a sub-transaction. # # === Caveats @@ -113,8 +113,12 @@ module ActiveRecord def transaction(options = {}) options.assert_valid_keys :requires_new, :joinable - last_transaction_joinable, @transaction_joinable = - @transaction_joinable, options[:joinable] || true + last_transaction_joinable = @transaction_joinable + if options.has_key?(:joinable) + @transaction_joinable = options[:joinable] + else + @transaction_joinable = true + end requires_new = options[:requires_new] || !last_transaction_joinable transaction_open = false @@ -141,7 +145,7 @@ module ActiveRecord rollback_to_savepoint end end - raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback + raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback) end ensure @transaction_joinable = last_transaction_joinable diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5a8d99924d..913bb521ca 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -533,13 +533,11 @@ module ActiveRecord execute "ROLLBACK" end - if PGconn.public_method_defined?(:transaction_status) - # ruby-pg defines Ruby constants for transaction status, - # ruby-postgres does not. - PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0 - + if defined?(PGconn::PQTRANS_IDLE) + # The ruby-pg driver supports inspecting the transaction status, + # while the ruby-postgres driver does not. def outside_transaction? - @connection.transaction_status == PQTRANS_IDLE + @connection.transaction_status == PGconn::PQTRANS_IDLE end end -- cgit v1.2.3 From c891d685de9a729332836751c1293770b86a1b52 Mon Sep 17 00:00:00 2001 From: Carlos Kozuszko Date: Sun, 4 Jan 2009 20:22:53 -0200 Subject: Fixing bug on ActiveRecord::Dirty#field_changed? for nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. Only integer columns were considered. Signed-off-by: Michael Koziarski [#1692 state:committed] --- activerecord/lib/active_record/dirty.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index 4c899f58e5..4a2510aa63 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -151,8 +151,8 @@ module ActiveRecord def field_changed?(attr, old, value) if column = column_for_attribute(attr) - if column.type == :integer && column.null && (old.nil? || old == 0) && value.blank? - # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values. + if column.number? && column.null && (old.nil? || old == 0) && value.blank? + # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. # Hence we don't record it as a change if the value changes from nil to ''. # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll # be typecast back to 0 (''.to_i => 0) -- cgit v1.2.3 From 7a0e7c7270548138a333bc39aab5aec80580174b Mon Sep 17 00:00:00 2001 From: Michael Lovitt Date: Fri, 9 Jan 2009 18:09:50 -0500 Subject: Fixed broken after_save callback; was being called when before_create was canceled or before_update was canceled Signed-off-by: Michael Koziarski [#1735 state:committed] --- activerecord/lib/active_record/callbacks.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 42bfe34505..9f5384d39a 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -219,8 +219,9 @@ module ActiveRecord def after_save() end def create_or_update_with_callbacks #:nodoc: return false if callback(:before_save) == false - result = create_or_update_without_callbacks - callback(:after_save) + if result = create_or_update_without_callbacks + callback(:after_save) + end result end private :create_or_update_with_callbacks -- cgit v1.2.3 From 0e92f67073079fe3d34acd141099cdad28b0ee00 Mon Sep 17 00:00:00 2001 From: Ben VandenBos Date: Mon, 5 Jan 2009 20:32:34 +0000 Subject: Make belongs_to :dependent => :destroy destroy self before associated object [#1079 state:resolved] If foreign key constraints are in place then deleteing the associated object first will cause a foreign key violation Signed-off-by: Frederick Cheung --- activerecord/lib/active_record/associations.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 86616abf52..8b51a38f48 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1531,14 +1531,14 @@ module ActiveRecord association = send(reflection.name) association.destroy unless association.nil? end - before_destroy method_name + after_destroy method_name when :delete method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym define_method(method_name) do association = send(reflection.name) association.delete unless association.nil? end - before_destroy method_name + after_destroy method_name else raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})" end -- cgit v1.2.3 From 3ee4e009185173aab78f6503ee45e3ef4482874e Mon Sep 17 00:00:00 2001 From: lukeludwig Date: Fri, 16 Jan 2009 13:04:19 -0600 Subject: Cache columns for has_and_belongs_to_many associations This avoids repeatedly calling SHOW COLUMNS when the association is queried [#1738 state:committed] --- .../associations/has_and_belongs_to_many_association.rb | 12 +++++++++--- activerecord/lib/active_record/reflection.rb | 8 ++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 3d689098b5..a5cc3bf091 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -9,6 +9,14 @@ module ActiveRecord create_record(attributes) { |record| insert_record(record, true) } end + def columns + @reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns") + end + + def reset_column_information + @reflection.reset_column_information + end + protected def construct_find_options!(options) options[:joins] = @join_sql @@ -32,8 +40,6 @@ module ActiveRecord if @reflection.options[:insert_sql] @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record)) else - columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns") - attributes = columns.inject({}) do |attrs, column| case column.name.to_s when @reflection.primary_key_name.to_s @@ -103,7 +109,7 @@ module ActiveRecord # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has # an id column. This will then overwrite the id column of the records coming back. def finding_with_ambiguous_select?(select_clause) - !select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2 + !select_clause && columns.size != 2 end private diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index dbff4f24d6..1937abdc83 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -198,6 +198,14 @@ module ActiveRecord end end + def columns(tbl_name, log_msg) + @columns ||= klass.connection.columns(tbl_name, log_msg) + end + + def reset_column_information + @columns = nil + end + def check_validity! end -- cgit v1.2.3 From 39e1ac658efc80e4c54abef4f1c7679e4b3dc2ac Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 18 Jan 2009 18:10:58 +0000 Subject: Merge docrails --- activerecord/lib/active_record/base.rb | 122 +++++++++++++++---------- activerecord/lib/active_record/calculations.rb | 24 +++-- activerecord/lib/active_record/callbacks.rb | 2 +- activerecord/lib/active_record/locale/en.yml | 2 +- activerecord/lib/active_record/named_scope.rb | 2 +- 5 files changed, 93 insertions(+), 59 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cca012ed55..ebc0b7783f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -327,7 +327,7 @@ module ActiveRecord #:nodoc: # User.find(user.id).preferences # => { "background" => "black", "display" => large } # # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a - # descendent of a class not in the hierarchy. Example: + # descendant of a class not in the hierarchy. Example: # # class User < ActiveRecord::Base # serialize :preferences, Hash @@ -544,8 +544,9 @@ module ActiveRecord #:nodoc: # * :having - Combined with +:group+ this can be used to filter the records that a GROUP BY returns. Uses the HAVING SQL-clause. # * :limit - An integer determining the limit on the number of rows that should be returned. # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4. - # * :joins - 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). + # * :joins - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed), + # named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s), + # or an array containing a mixture of both strings and named associations. # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns. # Pass :readonly => false to override. # * :include - Names associations that should be loaded alongside. The symbols named refer @@ -755,25 +756,26 @@ module ActiveRecord #:nodoc: end end - # Delete an object (or multiple objects) where the +id+ given matches the primary_key. A SQL +DELETE+ command - # is executed on the database which means that no callbacks are fired off running this. This is an efficient method - # of deleting records that don't need cleaning up after or other actions to be taken. + # Deletes the row with a primary key matching the +id+ argument, using a + # SQL +DELETE+ statement, and returns the number of rows deleted. Active + # Record objects are not instantiated, so the object's callbacks are not + # executed, including any :dependent association options or + # Observer methods. # - # Objects are _not_ instantiated with this method, and so +:dependent+ rules - # defined on associations are not honered. + # You can delete multiple rows at once by passing an Array of ids. # - # ==== Parameters - # - # * +id+ - Can be either an Integer or an Array of Integers. + # Note: Although it is often much faster than the alternative, + # #destroy, skipping callbacks might bypass business logic in + # your application that ensures referential integrity or performs other + # essential jobs. # # ==== Examples # - # # Delete a single object + # # Delete a single row # Todo.delete(1) # - # # Delete multiple objects - # todos = [1,2,3] - # Todo.delete(todos) + # # Delete multiple rows + # Todo.delete([2,3,4]) def delete(id) delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ]) end @@ -849,25 +851,32 @@ module ActiveRecord #:nodoc: connection.update(sql, "#{name} Update") end - # Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method. - # This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting - # many records. If you want to simply delete records without worrying about dependent associations or - # callbacks, use the much faster +delete_all+ method instead. + # Destroys the records matching +conditions+ by instantiating each + # record and calling its +destroy+ method. Each object's callbacks are + # executed (including :dependent association options and + # +before_destroy+/+after_destroy+ Observer methods). Returns the + # collection of objects that were destroyed; each will be frozen, to + # reflect that no changes should be made (since they can't be + # persisted). + # + # Note: Instantiation, callback execution, and deletion of each + # record can be time consuming when you're removing many records at + # once. It generates at least one SQL +DELETE+ query per record (or + # possibly more, to enforce your callbacks). If you want to delete many + # rows quickly, without concern for their associations or callbacks, use + # +delete_all+ instead. # # ==== Parameters # - # * +conditions+ - Conditions are specified the same way as with +find+ method. + # * +conditions+ - A string, array, or hash that specifies which records + # to destroy. If omitted, all records are destroyed. See the + # Conditions section in the introduction to ActiveRecord::Base for + # more information. # - # ==== Example + # ==== Examples # # Person.destroy_all("last_login < '2004-04-04'") - # - # This loads and destroys each person one by one, including its dependent associations and before_ and - # after_destroy callbacks. - # - # +conditions+ can be anything that +find+ also accepts: - # - # Person.destroy_all(:last_login => 6.hours.ago) + # Person.destroy_all(:status => "inactive") def destroy_all(conditions = nil) find(:all, :conditions => conditions).each { |object| object.destroy } end @@ -1802,15 +1811,13 @@ module ActiveRecord #:nodoc: table_name end - # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into - # find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password]) - # respectively. Also works for find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]). - # - # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount - # is actually find_all_by_amount(amount, options). + # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) + # that are turned into find(:first, :conditions => ["user_name = ?", user_name]) and + # find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password]) respectively. Also works for + # find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]). # - # This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount) - # or find_or_create_by_user_and_password(user, password). + # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+ + # is actually find_all_by_amount(amount, options). # # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password]) @@ -2032,7 +2039,11 @@ module ActiveRecord #:nodoc: # end # # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of - # :conditions and :include options in :find, which are merged. + # :conditions, :include, and :joins options in :find, which are merged. + # + # :joins options are uniqued so multiple scopes can join in the same table without table aliasing + # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the + # array of strings format for your joins. # # class Article < ActiveRecord::Base # def self.find_with_scope @@ -2156,7 +2167,7 @@ module ActiveRecord #:nodoc: scoped_methods.last end - # Returns the class type of the record using the current module as a prefix. So descendents of + # Returns the class type of the record using the current module as a prefix. So descendants of # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. def compute_type(type_name) modularized_name = type_name_with_module(type_name) @@ -2169,7 +2180,8 @@ module ActiveRecord #:nodoc: end end - # Returns the class descending directly from Active Record in the inheritance hierarchy. + # Returns the class descending directly from ActiveRecord::Base or an + # abstract class, if any, in the inheritance hierarchy. def class_of_active_record_descendant(klass) if klass.superclass == Base || klass.superclass.abstract_class? klass @@ -2518,14 +2530,16 @@ module ActiveRecord #:nodoc: create_or_update || raise(RecordNotSaved) end - # Deletes the record in the database and freezes this instance to reflect that no changes should - # be made (since they can't be persisted). + # Deletes the record in the database and freezes this instance to + # reflect that no changes should be made (since they can't be + # persisted). Returns the frozen instance. # - # Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+ - # callbacks, nor will it enforce any association +:dependent+ rules. - # - # In addition to deleting this record, any defined +before_delete+ and +after_delete+ - # callbacks are run, and +:dependent+ rules defined on associations are run. + # The row is simply removed with a SQL +DELETE+ statement on the + # record's primary key, and no callbacks are executed. + # + # To enforce the object's +before_destroy+ and +after_destroy+ + # callbacks, Observer methods, or any :dependent association + # options, use #destroy. def delete self.class.delete(id) unless new_record? freeze @@ -2726,7 +2740,19 @@ module ActiveRecord #:nodoc: end end - # Format attributes nicely for inspect. + # Returns an #inspect-like string for the value of the + # attribute +attr_name+. String attributes are elided after 50 + # characters, and Date and Time attributes are returned in the + # :db format. Other attributes return the value of + # #inspect without modification. + # + # person = Person.create!(:name => "David Heinemeier Hansson " * 3) + # + # person.attribute_for_inspect(:name) + # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."' + # + # person.attribute_for_inspect(:created_at) + # # => '"2009-01-12 04:48:57"' def attribute_for_inspect(attr_name) value = read_attribute(attr_name) @@ -2855,7 +2881,7 @@ module ActiveRecord #:nodoc: id end - # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent. + # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant. # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the # Message class in that example. diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 65512d534a..b239c03284 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -48,30 +48,38 @@ module ActiveRecord calculate(:count, *construct_count_options_from_args(*args)) end - # Calculates the average value on a given column. The value is returned as a float. See +calculate+ for examples with options. + # Calculates the average value on a given column. The value is returned as + # a float, or +nil+ if there's no row. See +calculate+ for examples with + # options. # - # Person.average('age') + # Person.average('age') # => 35.8 def average(column_name, options = {}) calculate(:avg, column_name, options) end - # Calculates the minimum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options. + # Calculates the minimum value on a given column. The value is returned + # with the same data type of the column, or +nil+ if there's no row. See + # +calculate+ for examples with options. # - # Person.minimum('age') + # Person.minimum('age') # => 7 def minimum(column_name, options = {}) calculate(:min, column_name, options) end - # Calculates the maximum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options. + # Calculates the maximum value on a given column. The value is returned + # with the same data type of the column, or +nil+ if there's no row. See + # +calculate+ for examples with options. # - # Person.maximum('age') + # Person.maximum('age') # => 93 def maximum(column_name, options = {}) calculate(:max, column_name, options) end - # Calculates the sum of values on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options. + # Calculates the sum of values on a given column. The value is returned + # with the same data type of the column, 0 if there's no row. See + # +calculate+ for examples with options. # - # Person.sum('age') + # Person.sum('age') # => 4562 def sum(column_name, options = {}) calculate(:sum, column_name, options) end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 9f5384d39a..88958f4583 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -77,7 +77,7 @@ module ActiveRecord # # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when # you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods - # when you want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks. + # when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks. # # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index 7e205435f7..bf8a71d236 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -37,7 +37,7 @@ en: # blank: "This is a custom blank message for User login" # Will define custom blank validation message for User model and # custom blank validation message for login attribute of User model. - models: + #models: # Translate model names. Used in Model.human_name(). #models: diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 83043c2c22..989b2a1ec5 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -39,7 +39,7 @@ module ActiveRecord # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count). # - # All \scopes are available as class methods on the ActiveRecord::Base descendent upon which the \scopes were defined. But they are also available to + # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to # has_many associations. If, # # class Person < ActiveRecord::Base -- cgit v1.2.3 From ccda96093a3bf3fb360f7c6d61bbbf341b2ae034 Mon Sep 17 00:00:00 2001 From: Josh Susser Date: Thu, 1 Jan 2009 21:30:42 -0800 Subject: Minor refactoring of validates_associated to replace #inject with #collect + #all? [#1686 state:committed] --- activerecord/lib/active_record/validations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 6a9690ba85..6d750accb0 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -904,7 +904,7 @@ module ActiveRecord configuration.update(attr_names.extract_options!) validates_each(attr_names, configuration) do |record, attr_name, value| - unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v } + unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all? record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) end end -- cgit v1.2.3