diff options
51 files changed, 716 insertions, 431 deletions
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 1167387def..0b418c4ea1 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -11,7 +11,7 @@ end require 'minitest/autorun' require 'action_mailer' require 'action_mailer/test_case' -require 'rails/queueing' +require 'active_support/queueing' silence_warnings do # These external dependencies have warnings :/ @@ -27,7 +27,7 @@ ActionView::Template.register_template_handler :bak, lambda { |template| "Lame b FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__)) ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH -ActionMailer::Base.queue = Rails::Queueing::SynchronousQueue.new +ActionMailer::Base.queue = ActiveSupport::SynchronousQueue.new class MockSMTP def self.deliveries diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 862b954f9e..6a06cec041 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -3,13 +3,13 @@ require 'abstract_unit' require 'set' require 'action_dispatch' +require 'active_support/queueing' require 'active_support/time' require 'mailers/base_mailer' require 'mailers/proc_mailer' require 'mailers/asset_mailer' require 'mailers/async_mailer' -require 'rails/queueing' class BaseTest < ActiveSupport::TestCase def teardown @@ -433,7 +433,7 @@ class BaseTest < ActiveSupport::TestCase end test "delivering message asynchronously" do - testing_queue = Rails::Queueing::TestQueue.new + testing_queue = ActiveSupport::TestQueue.new AsyncMailer.delivery_method = :test AsyncMailer.deliveries.clear stub_queue(AsyncMailer, testing_queue).welcome.deliver diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 36e8479441..239e4445d3 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,23 @@ ## Rails 4.0.0 (unreleased) ## +* Support multiple etags in If-None-Match header. *Travis Warlick* + +* Allow to configure how unverified request will be handled using `:with` + option in `protect_from_forgery` method. + + Valid unverified request handling methods are: + + - `:exception` - Raises ActionController::InvalidAuthenticityToken exception. + - `:reset_session` - Resets the session. + - `:null_session` - Provides an empty session during request but doesn't + reset it completely. Used as default if `:with` option is not specified. + + New applications are generated with: + + protect_from_forgery :with => :exception + + *Sergey Nartimov* + * Add .rb template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran* * Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`: diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index a7f93b780e..0d6015d993 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -17,12 +17,21 @@ module ActionDispatch env[HTTP_IF_NONE_MATCH] end + def if_none_match_etags + (if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag| + etag.gsub(/^\"|\"$/, "") + end + end + def not_modified?(modified_at) if_modified_since && modified_at && if_modified_since >= modified_at end def etag_matches?(etag) - if_none_match && if_none_match == etag + if etag + etag = etag.gsub(/^\"|\"$/, "") + if_none_match_etags.include?(etag) + end end # Check response freshness (Last-Modified and ETag) against request diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index a434e49dbd..a2b9571660 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -746,6 +746,45 @@ class RequestTest < ActiveSupport::TestCase assert_equal "/foo?bar", path end + test "if_none_match_etags none" do + request = stub_request + + assert_equal nil, request.if_none_match + assert_equal [], request.if_none_match_etags + assert !request.etag_matches?("foo") + assert !request.etag_matches?(nil) + end + + test "if_none_match_etags single" do + header = 'the-etag' + request = stub_request('HTTP_IF_NONE_MATCH' => header) + + assert_equal header, request.if_none_match + assert_equal [header], request.if_none_match_etags + assert request.etag_matches?("the-etag") + end + + test "if_none_match_etags quoted single" do + header = '"the-etag"' + request = stub_request('HTTP_IF_NONE_MATCH' => header) + + assert_equal header, request.if_none_match + assert_equal ['the-etag'], request.if_none_match_etags + assert request.etag_matches?("the-etag") + end + + test "if_none_match_etags multiple" do + header = 'etag1, etag2, "third etag", "etag4"' + expected = ['etag1', 'etag2', 'third etag', 'etag4'] + request = stub_request('HTTP_IF_NONE_MATCH' => header) + + assert_equal header, request.if_none_match + assert_equal expected, request.if_none_match_etags + expected.each do |etag| + assert request.etag_matches?(etag), etag + end + end + protected def stub_request(env = {}) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 475c237009..8b72529aff 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,25 @@ ## Rails 4.0.0 (unreleased) ## +* Explain only normal CRUD sql (select / update / insert / delete). + Fix problem that explains unexplainable sql. Closes #7544 #6458. + + *kennyj* + +* Fix `find_in_batches` when primary_key is set other than id. + You can now use this method with the primary key which is not integer-based. + + Example: + + class Post < ActiveRecord::Base + self.primary_key = :title + end + + Post.find_in_batches(:start => 'My First Post') do |batch| + batch.each { |post| post.author.greeting } + end + + *Toshiyuki Kawanishi* + * You can now override the generated accessor methods for stored attributes and reuse the original behavior with `read_store_attribute` and `write_store_attribute`, which are counterparts to `read_attribute` and `write_attribute`. diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index b15df4f308..424c1a5b79 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -373,7 +373,7 @@ module ActiveRecord # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */ interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do count_with = $2.to_s - count_with = '*' if count_with.blank? || count_with =~ /,/ + count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/ "SELECT #{$1}COUNT(#{count_with}) FROM" 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 11e4d34de2..32e3c7f5d8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -3,8 +3,7 @@ module ActiveRecord module DatabaseStatements def initialize super - @_current_transaction_records = [] - @transaction_joinable = nil + reset_transaction end # Converts an arel AST to SQL @@ -108,20 +107,6 @@ module ActiveRecord exec_delete(to_sql(arel, binds), name, binds) 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 - # Returns +true+ when the connection adapter supports prepared statement # caching, otherwise returns +false+ def supports_statement_cache? @@ -173,76 +158,60 @@ module ActiveRecord def transaction(options = {}) options.assert_valid_keys :requires_new, :joinable - last_transaction_joinable = @transaction_joinable - @transaction_joinable = options.fetch(:joinable, true) - requires_new = options[:requires_new] || !last_transaction_joinable - transaction_open = false - - begin - if requires_new || open_transactions == 0 - if open_transactions == 0 - begin_db_transaction - elsif requires_new - create_savepoint - end - increment_open_transactions - transaction_open = true - @_current_transaction_records.push([]) - end + if !options[:requires_new] && current_transaction.joinable? yield - rescue Exception => database_transaction_rollback - if transaction_open && !outside_transaction? - transaction_open = false - txn = decrement_open_transactions - txn.aborted! - 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) + else + within_new_transaction(options) { yield } end + rescue ActiveRecord::Rollback + # rollbacks are silently swallowed + end + + def within_new_transaction(options = {}) #:nodoc: + begin_transaction(options) + yield + rescue Exception => error + rollback_transaction + raise ensure - @transaction_joinable = last_transaction_joinable - - if outside_transaction? - @current_transaction = nil - elsif transaction_open - txn = decrement_open_transactions - txn.committed! - 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 - if open_transactions == 0 - rollback_db_transaction - rollback_transaction_records(true) - else - rollback_to_savepoint - rollback_transaction_records(false) - end - raise - end + begin + commit_transaction unless error + rescue Exception + rollback_transaction + raise end end + def current_transaction #:nodoc: + @transaction + end + + def transaction_open? + @transaction.open? + end + + def begin_transaction(options = {}) #:nodoc: + @transaction = @transaction.begin + @transaction.joinable = options.fetch(:joinable, true) + @transaction + end + + def commit_transaction #:nodoc: + @transaction = @transaction.commit + end + + def rollback_transaction #:nodoc: + @transaction = @transaction.rollback + end + + def reset_transaction #:nodoc: + @transaction = ClosedTransaction.new(self) + 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 + @transaction.add_record(record) end # Begins the transaction (and turns off auto-committing). @@ -356,42 +325,6 @@ module ActiveRecord update_sql(sql, name) 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) - 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 => e - record.logger.error(e) if record.respond_to?(:logger) && record.logger - end - end - end - end - - # Send a commit message to all records after they have been committed. - def commit_transaction_records - records = @_current_transaction_records.flatten - @_current_transaction_records.clear - unless records.blank? - records.uniq.each do |record| - begin - record.committed! - rescue => e - record.logger.error(e) if record.respond_to?(:logger) && record.logger - end - end - end - end - def sql_for_insert(sql, pk, id_value, sequence_name, binds) [sql, binds] end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb new file mode 100644 index 0000000000..2117eae5cb --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -0,0 +1,156 @@ +module ActiveRecord + module ConnectionAdapters + class Transaction #:nodoc: + attr_reader :connection + + def initialize(connection) + @connection = connection + end + end + + class ClosedTransaction < Transaction #:nodoc: + def number + 0 + end + + def begin + RealTransaction.new(connection, self) + end + + def closed? + true + end + + def open? + false + end + + def joinable? + false + end + + # This is a noop when there are no open transactions + def add_record(record) + end + end + + class OpenTransaction < Transaction #:nodoc: + attr_reader :parent, :records + attr_writer :joinable + + def initialize(connection, parent) + super connection + + @parent = parent + @records = [] + @finishing = false + @joinable = true + end + + # This state is necesarry so that we correctly handle stuff that might + # happen in a commit/rollback. But it's kinda distasteful. Maybe we can + # find a better way to structure it in the future. + def finishing? + @finishing + end + + def joinable? + @joinable && !finishing? + end + + def number + if finishing? + parent.number + else + parent.number + 1 + end + end + + def begin + if finishing? + parent.begin + else + SavepointTransaction.new(connection, self) + end + end + + def rollback + @finishing = true + perform_rollback + parent + end + + def commit + @finishing = true + perform_commit + parent + end + + def add_record(record) + records << record + end + + def rollback_records + records.uniq.each do |record| + begin + record.rolledback!(parent.closed?) + rescue => e + record.logger.error(e) if record.respond_to?(:logger) && record.logger + end + end + end + + def commit_records + records.uniq.each do |record| + begin + record.committed! + rescue => e + record.logger.error(e) if record.respond_to?(:logger) && record.logger + end + end + end + + def closed? + false + end + + def open? + true + end + end + + class RealTransaction < OpenTransaction #:nodoc: + def initialize(connection, parent) + super + connection.begin_db_transaction + end + + def perform_rollback + connection.rollback_db_transaction + rollback_records + end + + def perform_commit + connection.commit_db_transaction + commit_records + end + end + + class SavepointTransaction < OpenTransaction #:nodoc: + def initialize(connection, parent) + super + connection.create_savepoint + end + + def perform_rollback + connection.rollback_to_savepoint + rollback_records + end + + def perform_commit + connection.release_savepoint + records.each { |r| parent.add_record(r) } + end + end + 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 d5c7caad2e..3a8fbcf93f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/benchmark' require 'active_record/connection_adapters/schema_cache' require 'active_record/connection_adapters/abstract/schema_dumper' require 'monitor' +require 'active_support/deprecation' module ActiveRecord module ConnectionAdapters # :nodoc: @@ -34,6 +35,12 @@ module ActiveRecord autoload :QueryCache end + autoload_at 'active_record/connection_adapters/abstract/transaction' do + autoload :ClosedTransaction + autoload :RealTransaction + autoload :SavepointTransaction + end + # Active Record supports multiple database systems. AbstractAdapter and # related classes form the abstraction layer which makes this possible. # An AbstractAdapter represents a connection to a database, and provides an @@ -64,14 +71,11 @@ module ActiveRecord def initialize(connection, logger = nil, pool = nil) #:nodoc: super() - @active = nil @connection = connection @in_use = false @instrumenter = ActiveSupport::Notifications.instrumenter @last_use = false @logger = logger - @open_transactions = 0 - @current_transaction = nil @pool = pool @query_cache = Hash.new { |h,sql| h[sql] = {} } @query_cache_enabled = false @@ -184,19 +188,21 @@ module ActiveRecord # checking whether the database is actually capable of responding, i.e. whether # the connection isn't stale. def active? - @active != false end # Disconnects from the database if already connected, and establishes a - # new connection with the database. + # new connection with the database. Implementors should call super if they + # override the default implementation. def reconnect! - @active = true + clear_cache! + reset_transaction end # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! - @active = false + clear_cache! + reset_transaction end # Reset the state of this connection, directing the DBMS to clear @@ -240,33 +246,20 @@ module ActiveRecord end def open_transactions - count = 0 - txn = current_transaction - - while txn - count += 1 - txn = txn.next - end - - count + @transaction.number end - attr_reader :current_transaction - def increment_open_transactions - @current_transaction = Transaction.new(current_transaction) + ActiveSupport::Deprecation.warn "#increment_open_transactions is deprecated and has no effect" end def decrement_open_transactions - return unless current_transaction - - txn = current_transaction - @current_transaction = txn.next - txn + ActiveSupport::Deprecation.warn "#decrement_open_transactions is deprecated and has no effect" end def transaction_joinable=(joinable) - @transaction_joinable = joinable + ActiveSupport::Deprecation.warn "#transaction_joinable= is deprecated. Please pass the :joinable option to #begin_transaction instead." + @transaction.joinable = joinable end def create_savepoint diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 8fc172f6e8..328d080687 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -74,6 +74,7 @@ module ActiveRecord end def reconnect! + super disconnect! connect end @@ -82,6 +83,7 @@ module ActiveRecord # Disconnects from the database if already connected. # Otherwise, this method does nothing. def disconnect! + super unless @connection.nil? @connection.close @connection = nil diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index bb63fddf9b..0b936bbf39 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -189,14 +189,15 @@ module ActiveRecord end def reconnect! + super disconnect! - clear_cache! connect end # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! + super @connection.close rescue nil end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index eb3084e066..c8437c18cc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' + module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter < AbstractAdapter @@ -214,6 +216,10 @@ module ActiveRecord end def outside_transaction? + ActiveSupport::Deprecation.warn( + "#outside_transaction? is deprecated. This method was only really used " \ + "internally, but you can use #transaction_open? instead." + ) @connection.transaction_status == PGconn::PQTRANS_IDLE end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 60f01c297e..8a073bf878 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -111,7 +111,7 @@ module ActiveRecord inddef = row[3] oid = row[4] - columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")] + columns = Hash[query(<<-SQL, "SCHEMA")] SELECT a.attnum, a.attname FROM pg_attribute a WHERE a.attrelid = #{oid} @@ -252,7 +252,7 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) - select_value <<-end_sql, 'Reset sequence' + select_value <<-end_sql, 'SCHEMA' SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql end @@ -262,7 +262,7 @@ module ActiveRecord def pk_and_sequence_for(table) #:nodoc: # First try looking for a sequence with a dependency on the # given table's primary key. - result = query(<<-end_sql, 'PK and serial sequence')[0] + result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, seq.relname FROM pg_class seq, pg_attribute attr, @@ -283,7 +283,7 @@ module ActiveRecord # If that fails, try parsing the primary key's default value. # Support the 7.x and 8.0 nextval('foo'::text) as well as # the 8.1+ nextval('foo'::regclass). - result = query(<<-end_sql, 'PK and custom sequence')[0] + result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, CASE WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 1a727f9aa6..761052a788 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -473,9 +473,8 @@ module ActiveRecord # Close then reopen the connection. def reconnect! - clear_cache! + super @connection.reset - @open_transactions = 0 configure_connection end @@ -487,7 +486,7 @@ module ActiveRecord # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! - clear_cache! + super @connection.close rescue nil end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 4fe0013f0f..4a48812807 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -104,6 +104,8 @@ module ActiveRecord def initialize(connection, logger, config) super(connection, logger) + + @active = nil @statements = StatementPool.new(@connection, config.fetch(:statement_limit) { 1000 }) @config = config @@ -154,11 +156,15 @@ module ActiveRecord true end + def active? + @active != false + end + # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! super - clear_cache! + @active = false @connection.close rescue nil end @@ -397,7 +403,7 @@ module ActiveRecord table_name, row['name'], row['unique'] != 0, - exec_query("PRAGMA index_info('#{row['name']}')", "Columns for index #{row['name']} on #{table_name}").map { |col| + exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col| col['name'] }) end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index cf64985ddb..43ffca0227 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -387,7 +387,6 @@ module ActiveRecord @marked_for_destruction = false @new_record = true @mass_assignment_options = nil - @txn = nil @_start_transaction_state = {} end end diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index d5ba343b4c..0f927496fb 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -18,8 +18,9 @@ module ActiveRecord # On the other hand, we want to monitor the performance of our real database # queries, not the performance of the access to the query cache. IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE) + EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)/i def ignore_payload?(payload) - payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) + payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS end ActiveSupport::Notifications.subscribe("sql.active_record", new) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index b1db5f6f9f..60fc653735 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -843,9 +843,7 @@ module ActiveRecord end @fixture_connections = enlist_fixture_connections @fixture_connections.each do |connection| - connection.increment_open_transactions - connection.transaction_joinable = false - connection.begin_db_transaction + connection.begin_transaction joinable: false end # Load fixtures for every test. else @@ -868,10 +866,7 @@ module ActiveRecord # Rollback changes if a transaction is active. if run_in_transaction? @fixture_connections.each do |connection| - if connection.open_transactions != 0 - connection.rollback_db_transaction - connection.decrement_open_transactions - end + connection.rollback_transaction if connection.transaction_open? end @fixture_connections.clear end diff --git a/activerecord/lib/active_record/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb index 65a3d68619..90b462fad6 100644 --- a/activerecord/lib/active_record/railties/console_sandbox.rb +++ b/activerecord/lib/active_record/railties/console_sandbox.rb @@ -1,6 +1,4 @@ -ActiveRecord::Base.connection.increment_open_transactions ActiveRecord::Base.connection.begin_db_transaction at_exit do ActiveRecord::Base.connection.rollback_db_transaction - ActiveRecord::Base.connection.decrement_open_transactions end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 4d14506965..d32048cce1 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -36,12 +36,12 @@ module ActiveRecord # want multiple workers dealing with the same processing queue. You can # make worker 1 handle all the records between id 0 and 10,000 and # worker 2 handle from 10,000 and beyond (by setting the +:start+ - # option on that worker). + # option on that worker). You can also use non-integer-based primary keys + # if start point is set. # # It's not possible to set the order. That is automatically set to - # ascending on the primary key ("id ASC") to make the batch ordering - # work. This also mean that this method only works with integer-based - # primary keys. You can't set the limit either, that's used to control + # ascending on the primary key (e.g. "id ASC") to make the batch ordering + # work. You can't set the limit either, that's used to control # the batch sizes. # # Person.where("age > 21").find_in_batches do |group| @@ -62,7 +62,8 @@ module ActiveRecord ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") end - start = options.delete(:start).to_i + start = options.delete(:start) + start ||= 0 batch_size = options.delete(:batch_size) || 1000 relation = relation.reorder(batch_order).limit(batch_size) @@ -70,7 +71,7 @@ module ActiveRecord while records.any? records_size = records.size - primary_key_offset = records.last.id + primary_key_offset = records.last.send(primary_key) yield records diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index e008b32170..09318879d5 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -1,32 +1,6 @@ require 'thread' module ActiveRecord - class Transaction - attr_reader :next - - def initialize(txn = nil) - @next = txn - @committed = false - @aborted = false - end - - def committed! - @committed = true - end - - def aborted! - @aborted = true - end - - def committed? - @committed - end - - def aborted? - @aborted - end - end - # See ActiveRecord::Transactions::ClassMethods for documentation. module Transactions extend ActiveSupport::Concern @@ -333,11 +307,11 @@ module ActiveRecord def with_transaction_returning_status status = nil self.class.transaction do - @txn = self.class.connection.current_transaction add_to_transaction begin status = yield rescue ActiveRecord::Rollback + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 status = nil end @@ -353,17 +327,20 @@ module ActiveRecord @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key) @_start_transaction_state[:new_record] = @new_record @_start_transaction_state[:destroyed] = @destroyed + @_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: - @_start_transaction_state.clear if @txn.committed? + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + @_start_transaction_state.clear if @_start_transaction_state[:level] < 1 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: unless @_start_transaction_state.empty? - if @txn.aborted? || force + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + if @_start_transaction_state[:level] < 1 || force restore_state = @_start_transaction_state was_frozen = @attributes.frozen? @attributes = @attributes.dup if was_frozen diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb index 1199be68eb..59324c4857 100644 --- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb +++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb @@ -36,6 +36,10 @@ module ActiveRecord def columns(table_name) @columns[table_name] end + + def active? + true + end end end end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 852fc0e26e..93b01a3934 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -160,4 +160,36 @@ module ActiveRecord end end end + + class AdapterTestWithoutTransaction < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def setup + @klass = Class.new(ActiveRecord::Base) + @klass.establish_connection 'arunit' + @connection = @klass.connection + end + + def teardown + @klass.remove_connection + end + + test "transaction state is reset after a reconnect" do + skip "in-memory db doesn't allow reconnect" if in_memory_db? + + @connection.begin_transaction + assert @connection.transaction_open? + @connection.reconnect! + assert !@connection.transaction_open? + end + + test "transaction state is reset after a disconnect" do + skip "in-memory db doesn't allow disconnect" if in_memory_db? + + @connection.begin_transaction + assert @connection.transaction_open? + @connection.disconnect! + assert !@connection.transaction_open? + end + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 04714f42e9..5edabb4790 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -46,10 +46,13 @@ class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase end end -class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestCase +class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase class Invoice < ActiveRecord::Base ActiveSupport::Deprecation.silence do has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items" + has_many :custom_full_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.invoice_id, line_items.amount from line_items" + has_many :custom_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT * from line_items" + has_many :custom_qualified_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items" end end @@ -61,6 +64,33 @@ class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestC assert_equal 1, invoice.custom_line_items.count end + + def test_should_count_results_with_multiple_fields + invoice = Invoice.new + invoice.custom_full_line_items << LineItem.new(:amount => 0) + invoice.custom_full_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 2, invoice.custom_full_line_items.count + end + + def test_should_count_results_with_star + invoice = Invoice.new + invoice.custom_star_line_items << LineItem.new(:amount => 0) + invoice.custom_star_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 2, invoice.custom_star_line_items.count + end + + def test_should_count_results_with_qualified_star + invoice = Invoice.new + invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0) + invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 2, invoice.custom_qualified_star_line_items.count + end end class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index b9d480d9ce..b25d169038 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1619,26 +1619,32 @@ class BasicsTest < ActiveRecord::TestCase def test_silence_sets_log_level_to_error_in_block original_logger = ActiveRecord::Base.logger - log = StringIO.new - ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) - ActiveRecord::Base.logger.level = Logger::DEBUG - ActiveRecord::Base.silence do - ActiveRecord::Base.logger.warn "warn" - ActiveRecord::Base.logger.error "error" + + assert_deprecated do + log = StringIO.new + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) + ActiveRecord::Base.logger.level = Logger::DEBUG + ActiveRecord::Base.silence do + ActiveRecord::Base.logger.warn "warn" + ActiveRecord::Base.logger.error "error" + end + assert_equal "error\n", log.string end - assert_equal "error\n", log.string ensure ActiveRecord::Base.logger = original_logger end def test_silence_sets_log_level_back_to_level_before_yield original_logger = ActiveRecord::Base.logger - log = StringIO.new - ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) - ActiveRecord::Base.logger.level = Logger::WARN - ActiveRecord::Base.silence do + + assert_deprecated do + log = StringIO.new + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) + ActiveRecord::Base.logger.level = Logger::WARN + ActiveRecord::Base.silence do + end + assert_equal Logger::WARN, ActiveRecord::Base.logger.level end - assert_equal Logger::WARN, ActiveRecord::Base.logger.level ensure ActiveRecord::Base.logger = original_logger end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index cdd4b49042..3b4ff83725 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -124,4 +124,15 @@ class EachTest < ActiveRecord::TestCase assert_equal special_posts_ids, posts.map(&:id) end + def test_find_in_batches_should_use_any_column_as_primary_key + title_order_posts = Post.order('title asc') + start_title = title_order_posts.first.title + + posts = [] + PostWithTitlePrimaryKey.find_in_batches(:batch_size => 1, :start => start_title) do |batch| + posts.concat(batch) + end + + assert_equal title_order_posts.map(&:id), posts.map(&:id) + end end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index 91e1df91cd..b425967678 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -38,6 +38,13 @@ if ActiveRecord::Base.connection.supports_explain? end end + def test_collects_nothing_if_unexplained_sqls + with_queries([]) do |queries| + SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length') + assert queries.empty? + end + end + def with_queries(queries) Thread.current[:available_queries_for_explain] = queries yield queries diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 0d0de455b3..0b5fda3817 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -36,6 +36,7 @@ class TransactionTest < ActiveRecord::TestCase end end + # FIXME: Get rid of this fucking global variable! def test_successful_with_return class << Topic.connection alias :real_commit_db_transaction :commit_db_transaction @@ -348,7 +349,6 @@ class TransactionTest < ActiveRecord::TestCase def test_rollback_when_commit_raises Topic.connection.expects(:begin_db_transaction) Topic.connection.expects(:commit_db_transaction).raises('OH NOES') - Topic.connection.expects(:outside_transaction?).returns(false) Topic.connection.expects(:rollback_db_transaction) assert_raise RuntimeError do @@ -397,31 +397,11 @@ class TransactionTest < ActiveRecord::TestCase if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE) def test_outside_transaction_works - assert Topic.connection.outside_transaction? + assert assert_deprecated { Topic.connection.outside_transaction? } Topic.connection.begin_db_transaction - assert !Topic.connection.outside_transaction? + assert assert_deprecated { !Topic.connection.outside_transaction? } Topic.connection.rollback_db_transaction - assert Topic.connection.outside_transaction? - end - - def test_rollback_wont_be_executed_if_no_transaction_active - assert_raise RuntimeError do - Topic.transaction do - Topic.connection.rollback_db_transaction - Topic.connection.expects(:rollback_db_transaction).never - raise "Rails doesn't scale!" - end - end - end - - def test_open_transactions_count_is_reset_to_zero_if_no_transaction_active - Topic.transaction do - Topic.transaction do - Topic.connection.rollback_db_transaction - end - assert_equal 0, Topic.connection.open_transactions - end - assert_equal 0, Topic.connection.open_transactions + assert assert_deprecated { Topic.connection.outside_transaction? } end end @@ -580,5 +560,14 @@ if current_adapter?(:PostgreSQLAdapter) assert_equal original_salary, Developer.find(1).salary end + + test "#transaction_joinable= is deprecated" do + Developer.transaction do + conn = Developer.connection + assert conn.current_transaction.joinable? + assert_deprecated { conn.transaction_joinable = false } + assert !conn.current_transaction.joinable? + end + end end end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index c995f59a15..9858f68c4a 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -186,3 +186,8 @@ class SpecialPostWithDefaultScope < ActiveRecord::Base self.table_name = 'posts' default_scope { where(:id => [1, 5,6]) } end + +class PostWithTitlePrimaryKey < ActiveRecord::Base + self.table_name = 'posts' + self.primary_key = :title +end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index c36df796a8..59e112e3f9 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,22 @@ ## Rails 4.0.0 (unreleased) ## +* An optional block can be passed to `config_accessor` to set its default value + + class User + include ActiveSupport::Configurable + config_accessor :hair_colors do + [:brown, :black, :blonde, :red] + end + end + + User.hair_colors # => [:brown, :black, :blonde, :red] + + *Larry Lv* + +* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of + thread safety. It will be removed without replacement in Rails 4.1. *Steve + Klabnik* + * An optional block can be passed to `Hash#deep_merge`. The block will be invoked for each duplicated key and used to resolve the conflict. *Pranas Kiziela* diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index f149a7f0ed..1b391697e6 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -46,6 +46,7 @@ module ActiveSupport # Silence the logger during the execution of the block. # def silence + ActiveSupport::Deprecation.warn "ActiveSupport::Benchmarkable#silence is deprecated. It will be removed from Rails 4.1." old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger yield ensure diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 307ae40398..15a5b98d56 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -39,7 +39,7 @@ module ActiveSupport # Allows you to add shortcut so that you don't have to refer to attribute # through config. Also look at the example for config to contrast. - # + # # Defines both class and instance config accessors. # # class User @@ -47,16 +47,16 @@ module ActiveSupport # config_accessor :allowed_access # end # - # User.allowed_access # => nil + # User.allowed_access # => nil # User.allowed_access = false - # User.allowed_access # => false - # + # User.allowed_access # => false + # # user = User.new # user.allowed_access # => false # user.allowed_access = true # user.allowed_access # => true # - # User.allowed_access # => false + # User.allowed_access # => false # # The attribute name must be a valid method name in Ruby. # @@ -91,7 +91,18 @@ module ActiveSupport # User.allowed_access # => false # # User.new.allowed_access = true # => NoMethodError - # User.new.allowed_access # => NoMethodError + # User.new.allowed_access # => NoMethodError + # + # Also you can pass a block to set up the attribute with a default value. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # User.hair_colors # => [:brown, :black, :blonde, :red] def config_accessor(*names) options = names.extract_options! @@ -108,6 +119,7 @@ module ActiveSupport class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false end + send("#{name}=", yield) if block_given? end end end diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb index e7dc60a612..83cc8066e7 100644 --- a/activesupport/lib/active_support/core_ext/object/to_json.rb +++ b/activesupport/lib/active_support/core_ext/object/to_json.rb @@ -17,3 +17,11 @@ end end end end + +module Process + class Status + def as_json(options = nil) + { :exitstatus => exitstatus, :pid => pid } + end + end +end diff --git a/activesupport/lib/active_support/queueing.rb b/activesupport/lib/active_support/queueing.rb new file mode 100644 index 0000000000..f397e1c0c5 --- /dev/null +++ b/activesupport/lib/active_support/queueing.rb @@ -0,0 +1,116 @@ +require 'delegate' +require 'thread' + +module ActiveSupport + # A Queue that simply inherits from STDLIB's Queue. Everytime this + # queue is used, Rails automatically sets up a ThreadedConsumer + # to consume it. + class Queue < ::Queue + end + + class SynchronousQueue < ::Queue + def push(job) + result = nil + Thread.new { result = job.run }.join + result + end + alias << push + alias enq push + end + + # In test mode, the Rails queue is backed by an Array so that assertions + # can be made about its contents. The test queue provides a +jobs+ + # method to make assertions about the queue's contents and a +drain+ + # method to drain the queue and run the jobs. + # + # Jobs are run in a separate thread to catch mistakes where code + # assumes that the job is run in the same thread. + class TestQueue < ::Queue + # Get a list of the jobs off this queue. This method may not be + # available on production queues. + def jobs + @que.dup + end + + # Marshal and unmarshal job before pushing it onto the queue. This will + # raise an exception on any attempts in tests to push jobs that can't (or + # shouldn't) be marshalled. + def push(job) + super Marshal.load(Marshal.dump(job)) + end + + # Drain the queue, running all jobs in a different thread. This method + # may not be available on production queues. + def drain + # run the jobs in a separate thread so assumptions of synchronous + # jobs are caught in test mode. + Thread.new { pop.run until empty? }.join + end + end + + # A container for multiple queues. This class delegates to a default Queue + # so that <tt>Rails.queue.push</tt> and friends will Just Work. To use this class + # with multiple queues: + # + # # In your configuration: + # Rails.queue[:image_queue] = SomeQueue.new + # Rails.queue[:mail_queue] = SomeQueue.new + # + # # In your app code: + # Rails.queue[:mail_queue].push SomeJob.new + # + class QueueContainer < DelegateClass(::Queue) + def initialize(default_queue) + @queues = { :default => default_queue } + super(default_queue) + end + + def [](queue_name) + @queues[queue_name] + end + + def []=(queue_name, queue) + @queues[queue_name] = queue + end + end + + # The threaded consumer will run jobs in a background thread in + # development mode or in a VM where running jobs on a thread in + # production mode makes sense. + # + # When the process exits, the consumer pushes a nil onto the + # queue and joins the thread, which will ensure that all jobs + # are executed before the process finally dies. + class ThreadedQueueConsumer + def self.start(queue, logger=nil) + new(queue, logger).start + end + + def initialize(queue, logger=nil) + @queue = queue + @logger = logger + end + + def start + @thread = Thread.new do + while job = @queue.pop + begin + job.run + rescue Exception => e + handle_exception e + end + end + end + self + end + + def shutdown + @queue.push nil + @thread.join + end + + def handle_exception(e) + @logger.error "Job Error: #{e.message}\n#{e.backtrace.join("\n")}" if @logger + end + end +end diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index da7729d066..215a6e06b0 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -48,6 +48,18 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase assert !instance.respond_to?(:baz=) end + test "configuration accessors can take a default value" do + parent = Class.new do + include ActiveSupport::Configurable + config_accessor :hair_colors, :tshirt_colors do + [:black, :blue, :white] + end + end + + assert_equal [:black, :blue, :white], parent.hair_colors + assert_equal [:black, :blue, :white], parent.tshirt_colors + end + test "configuration hash is available on instance" do instance = Parent.new assert_equal :bar, instance.config.foo @@ -78,7 +90,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase test "should raise name error if attribute name is invalid" do assert_raises NameError do - Class.new do + Class.new do include ActiveSupport::Configurable config_accessor "invalid attribute name" end @@ -94,4 +106,4 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase methods = object.public_methods.map(&:to_s) assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" end -end
\ No newline at end of file +end diff --git a/activesupport/test/queueing/container_test.rb b/activesupport/test/queueing/container_test.rb new file mode 100644 index 0000000000..7afc11e7a9 --- /dev/null +++ b/activesupport/test/queueing/container_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'active_support/queueing' + +module ActiveSupport + class ContainerTest < ActiveSupport::TestCase + def test_delegates_to_default + q = Queue.new + container = QueueContainer.new q + job = Object.new + + container.push job + assert_equal job, q.pop + end + + def test_access_default + q = Queue.new + container = QueueContainer.new q + assert_equal q, container[:default] + end + + def test_assign_queue + container = QueueContainer.new Object.new + q = Object.new + container[:foo] = q + assert_equal q, container[:foo] + end + end +end diff --git a/railties/test/queueing/test_queue_test.rb b/activesupport/test/queueing/test_queue_test.rb index 2f0f507adb..4c08314366 100644 --- a/railties/test/queueing/test_queue_test.rb +++ b/activesupport/test/queueing/test_queue_test.rb @@ -1,9 +1,9 @@ require 'abstract_unit' -require 'rails/queueing' +require 'active_support/queueing' class TestQueueTest < ActiveSupport::TestCase def setup - @queue = Rails::Queueing::TestQueue.new + @queue = ActiveSupport::TestQueue.new end class ExceptionRaisingJob diff --git a/railties/test/queueing/threaded_consumer_test.rb b/activesupport/test/queueing/threaded_consumer_test.rb index c34a966d6e..20a1cc4e8e 100644 --- a/railties/test/queueing/threaded_consumer_test.rb +++ b/activesupport/test/queueing/threaded_consumer_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' -require 'rails/queueing' +require 'active_support/queueing' +require "active_support/log_subscriber/test_helper" class TestThreadConsumer < ActiveSupport::TestCase class Job @@ -15,8 +16,9 @@ class TestThreadConsumer < ActiveSupport::TestCase end def setup - @queue = Rails::Queueing::Queue.new - @consumer = Rails::Queueing::ThreadedConsumer.start(@queue) + @queue = ActiveSupport::Queue.new + @logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + @consumer = ActiveSupport::ThreadedQueueConsumer.start(@queue, @logger) end def teardown @@ -64,10 +66,6 @@ class TestThreadConsumer < ActiveSupport::TestCase end test "log job that raises an exception" do - require "active_support/log_subscriber/test_helper" - logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new - Rails.logger = logger - job = Job.new(1) do raise "RuntimeError: Error!" end @@ -75,13 +73,13 @@ class TestThreadConsumer < ActiveSupport::TestCase @queue.push job sleep 0.1 - assert_equal 1, logger.logged(:error).size - assert_match(/Job Error: RuntimeError: Error!/, logger.logged(:error).last) + assert_equal 1, @logger.logged(:error).size + assert_match(/Job Error: RuntimeError: Error!/, @logger.logged(:error).last) end test "test overriding exception handling" do @consumer.shutdown - @consumer = Class.new(Rails::Queueing::ThreadedConsumer) do + @consumer = Class.new(ActiveSupport::ThreadedQueueConsumer) do attr_reader :last_error def handle_exception(e) @last_error = e.message diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile index 73d6102eac..9c35738bf8 100644 --- a/guides/source/configuring.textile +++ b/guides/source/configuring.textile @@ -111,9 +111,9 @@ end * +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":#configuring-middleware section below. -* +config.queue+ configures a different queue implementation for the application. Defaults to +Rails::Queueing::Queue+. Note that, if the default queue is changed, the default +queue_consumer+ is not going to be initialized, it is up to the new queue implementation to handle starting and shutting down its own consumer(s). +* +config.queue+ configures a different queue implementation for the application. Defaults to +ActiveSupport::SynchronousQueue+. Note that, if the default queue is changed, the default +queue_consumer+ is not going to be initialized, it is up to the new queue implementation to handle starting and shutting down its own consumer(s). -* +config.queue_consumer+ configures a different consumer implementation for the default queue. Defaults to +Rails::Queueing::ThreadedConsumer+. +* +config.queue_consumer+ configures a different consumer implementation for the default queue. Defaults to +ActiveSupport::ThreadedQueueConsumer+. * +config.reload_classes_only_on_change+ enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If +config.cache_classes+ is true, this option is ignored. diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 670477f91a..a15965a9da 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -22,7 +22,6 @@ end module Rails autoload :Info, 'rails/info' autoload :InfoController, 'rails/info_controller' - autoload :Queueing, 'rails/queueing' class << self def application diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 80b8e4b9ba..050190cba6 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -1,4 +1,5 @@ require 'fileutils' +require 'active_support/queueing' require 'rails/engine' module Rails @@ -88,8 +89,8 @@ module Rails @initialized end - # Implements call according to the Rack API. It simples - # dispatch the request to the underlying middleware stack. + # Implements call according to the Rack API. It simply + # dispatches the request to the underlying middleware stack. def call(env) env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) super(env) @@ -188,7 +189,7 @@ module Rails end def queue #:nodoc: - @queue ||= Queueing::Container.new(build_queue) + @queue ||= ActiveSupport::QueueContainer.new(build_queue) end def build_queue #:nodoc: diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 7a0bb21043..c4c1fcca40 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/kernel/reporting' require 'active_support/file_update_checker' +require 'active_support/queueing' require 'rails/engine/configuration' module Rails @@ -41,8 +42,8 @@ module Rails @exceptions_app = nil @autoflush_log = true @log_formatter = ActiveSupport::Logger::SimpleFormatter.new - @queue = Rails::Queueing::SynchronousQueue - @queue_consumer = Rails::Queueing::ThreadedConsumer + @queue = ActiveSupport::SynchronousQueue + @queue_consumer = ActiveSupport::ThreadedQueueConsumer @eager_load = nil @assets = ActiveSupport::OrderedOptions.new diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 777f0d269e..f9a3c00946 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -97,8 +97,8 @@ module Rails end initializer :activate_queue_consumer do |app| - if config.queue == Rails::Queueing::Queue - app.queue_consumer = config.queue_consumer.start(app.queue) + if config.queue == ActiveSupport::Queue + app.queue_consumer = config.queue_consumer.start(app.queue, Rails.logger) at_exit { app.queue_consumer.shutdown } end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index fdf011a510..cb3e8b123e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -82,5 +82,5 @@ # Default the production mode queue to an synchronous queue. You will probably # want to replace this with an out-of-process queueing solution. - # config.queue = Rails::Queueing::SynchronousQueue + # config.queue = ActiveSupport::SynchronousQueue end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 8ab27eb6e1..75897ba8cd 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -40,5 +40,5 @@ config.active_support.deprecation = :stderr # Use the testing queue. - config.queue = Rails::Queueing::TestQueue + config.queue = ActiveSupport::TestQueue end diff --git a/railties/lib/rails/queueing.rb b/railties/lib/rails/queueing.rb deleted file mode 100644 index 7cd755b0f7..0000000000 --- a/railties/lib/rails/queueing.rb +++ /dev/null @@ -1,115 +0,0 @@ -require "thread" -require 'delegate' - -module Rails - module Queueing - # A container for multiple queues. This class delegates to a default Queue - # so that <tt>Rails.queue.push</tt> and friends will Just Work. To use this class - # with multiple queues: - # - # # In your configuration: - # Rails.queue[:image_queue] = SomeQueue.new - # Rails.queue[:mail_queue] = SomeQueue.new - # - # # In your app code: - # Rails.queue[:mail_queue].push SomeJob.new - # - class Container < DelegateClass(::Queue) - def initialize(default_queue) - @queues = { :default => default_queue } - super(default_queue) - end - - def [](queue_name) - @queues[queue_name] - end - - def []=(queue_name, queue) - @queues[queue_name] = queue - end - end - - # A Queue that simply inherits from STDLIB's Queue. Everytime this - # queue is used, Rails automatically sets up a ThreadedConsumer - # to consume it. - class Queue < ::Queue - end - - class SynchronousQueue < ::Queue - def push(job) - job.run - end - alias << push - alias enq push - end - - # In test mode, the Rails queue is backed by an Array so that assertions - # can be made about its contents. The test queue provides a +jobs+ - # method to make assertions about the queue's contents and a +drain+ - # method to drain the queue and run the jobs. - # - # Jobs are run in a separate thread to catch mistakes where code - # assumes that the job is run in the same thread. - class TestQueue < ::Queue - # Get a list of the jobs off this queue. This method may not be - # available on production queues. - def jobs - @que.dup - end - - # Marshal and unmarshal job before pushing it onto the queue. This will - # raise an exception on any attempts in tests to push jobs that can't (or - # shouldn't) be marshalled. - def push(job) - super Marshal.load(Marshal.dump(job)) - end - - # Drain the queue, running all jobs in a different thread. This method - # may not be available on production queues. - def drain - # run the jobs in a separate thread so assumptions of synchronous - # jobs are caught in test mode. - Thread.new { pop.run until empty? }.join - end - end - - # The threaded consumer will run jobs in a background thread in - # development mode or in a VM where running jobs on a thread in - # production mode makes sense. - # - # When the process exits, the consumer pushes a nil onto the - # queue and joins the thread, which will ensure that all jobs - # are executed before the process finally dies. - class ThreadedConsumer - def self.start(queue) - new(queue).start - end - - def initialize(queue) - @queue = queue - end - - def start - @thread = Thread.new do - while job = @queue.pop - begin - job.run - rescue Exception => e - handle_exception e - end - end - end - self - end - - def shutdown - @queue.push nil - @thread.join - end - - def handle_exception(e) - Rails.logger.error "Job Error: #{e.message}\n#{e.backtrace.join("\n")}" - end - end - end -end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 1eb5fce384..5268257d62 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -52,19 +52,19 @@ module ApplicationTests test "uses the default queue for ActionMailer" do require "#{app_path}/config/environment" - assert_kind_of Rails::Queueing::Container, ActionMailer::Base.queue + assert_kind_of ActiveSupport::QueueContainer, ActionMailer::Base.queue end test "allows me to configure queue for ActionMailer" do app_file "config/environments/development.rb", <<-RUBY AppTemplate::Application.configure do - Rails.queue[:mailer] = Rails::Queueing::TestQueue.new + Rails.queue[:mailer] = ActiveSupport::TestQueue.new config.action_mailer.queue = Rails.queue[:mailer] end RUBY require "#{app_path}/config/environment" - assert_kind_of Rails::Queueing::TestQueue, ActionMailer::Base.queue + assert_kind_of ActiveSupport::TestQueue, ActionMailer::Base.queue end test "does not include url helpers as action methods" do diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb index f4c11c5f4f..664001ca69 100644 --- a/railties/test/application/queue_test.rb +++ b/railties/test/application/queue_test.rb @@ -19,14 +19,14 @@ module ApplicationTests test "the queue is a TestQueue in test mode" do app("test") - assert_kind_of Rails::Queueing::TestQueue, Rails.application.queue[:default] - assert_kind_of Rails::Queueing::TestQueue, Rails.queue[:default] + assert_kind_of ActiveSupport::TestQueue, Rails.application.queue[:default] + assert_kind_of ActiveSupport::TestQueue, Rails.queue[:default] end test "the queue is a SynchronousQueue in development mode" do app("development") - assert_kind_of Rails::Queueing::SynchronousQueue, Rails.application.queue[:default] - assert_kind_of Rails::Queueing::SynchronousQueue, Rails.queue[:default] + assert_kind_of ActiveSupport::SynchronousQueue, Rails.application.queue[:default] + assert_kind_of ActiveSupport::SynchronousQueue, Rails.queue[:default] end class ThreadTrackingJob @@ -47,7 +47,7 @@ module ApplicationTests end end - test "in development mode, an enqueued job will be processed in the same thread" do + test "in development mode, an enqueued job will be processed in a separate thread" do app("development") job = ThreadTrackingJob.new @@ -55,7 +55,7 @@ module ApplicationTests sleep 0.1 assert job.ran?, "Expected job to be run" - refute job.ran_in_different_thread?, "Expected job to run in the same thread" + assert job.ran_in_different_thread?, "Expected job to run in the same thread" end test "in test mode, explicitly draining the queue will process it in a separate thread" do @@ -160,12 +160,12 @@ module ApplicationTests test "a custom consumer implementation can be provided" do add_to_env_config "production", <<-RUBY require "my_queue_consumer" - config.queue = Rails::Queueing::Queue + config.queue = ActiveSupport::Queue config.queue_consumer = MyQueueConsumer RUBY app_file "lib/my_queue_consumer.rb", <<-RUBY - class MyQueueConsumer < Rails::Queueing::ThreadedConsumer + class MyQueueConsumer < ActiveSupport::ThreadedQueueConsumer attr_reader :started def start diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 52c07cee9f..03798d572a 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -100,8 +100,8 @@ module ApplicationTests `rails generate model book title:string` `bundle exec rake db:migrate` `bundle exec rake db:fixtures:load` - assert_match /#{expected[:database]}/, - ActiveRecord::Base.connection_config[:database] + assert_match(/#{expected[:database]}/, + ActiveRecord::Base.connection_config[:database]) require "#{app_path}/app/models/book" assert_equal 2, Book.count end @@ -129,8 +129,8 @@ module ApplicationTests assert_match(/CREATE TABLE \"books\"/, structure_dump) `bundle exec rake db:drop` `bundle exec rake db:structure:load` - assert_match /#{expected[:database]}/, - ActiveRecord::Base.connection_config[:database] + assert_match(/#{expected[:database]}/, + ActiveRecord::Base.connection_config[:database]) require "#{app_path}/app/models/book" #if structure is not loaded correctly, exception would be raised assert Book.count, 0 @@ -161,8 +161,8 @@ module ApplicationTests require "#{app_path}/app/models/book" #if structure is not loaded correctly, exception would be raised assert Book.count, 0 - assert_match /#{ActiveRecord::Base.configurations['test']['database']}/, - ActiveRecord::Base.connection_config[:database] + assert_match(/#{ActiveRecord::Base.configurations['test']['database']}/, + ActiveRecord::Base.connection_config[:database]) end end diff --git a/railties/test/queueing/container_test.rb b/railties/test/queueing/container_test.rb deleted file mode 100644 index 69e59a3871..0000000000 --- a/railties/test/queueing/container_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'abstract_unit' -require 'rails/queueing' - -module Rails - module Queueing - class ContainerTest < ActiveSupport::TestCase - def test_delegates_to_default - q = Queue.new - container = Container.new q - job = Object.new - - container.push job - assert_equal job, q.pop - end - - def test_access_default - q = Queue.new - container = Container.new q - assert_equal q, container[:default] - end - - def test_assign_queue - container = Container.new Object.new - q = Object.new - container[:foo] = q - assert_equal q, container[:foo] - end - end - end -end |