diff options
72 files changed, 797 insertions, 543 deletions
@@ -2,7 +2,11 @@ source 'https://rubygems.org' gemspec -gem 'mocha', '~> 0.13.0', require: false +# This needs to be with require false as it is +# loaded after loading the test library to +# ensure correct loading order +gem 'mocha', '~> 0.14', require: false + gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.0.0' gem 'jquery-rails', '~> 2.2.0' diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 67ec0d1097..c56b6979ef 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -21,5 +21,5 @@ Gem::Specification.new do |s| s.add_dependency 'actionpack', version - s.add_dependency 'mail', '~> 2.5.3' + s.add_dependency 'mail', '~> 2.5.4' end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 599fff81c2..19cfd7dae1 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -8,7 +8,9 @@ module AbstractController include ActiveSupport::Callbacks included do - define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true + define_callbacks :process_action, + terminator: ->(controller,_) { controller.response_body }, + skip_after_callbacks_if_terminated: true end # Override AbstractController::Base's process_action to run the diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 7318c8b7ec..9279d8bcea 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -33,7 +33,7 @@ module ActionController end def halted_callback(event) - info("Filter chain halted as #{event.payload[:filter]} rendered or redirected") + info("Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected") end def send_file(event) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index e5f1ad63c2..0cbbbbe1fd 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -499,12 +499,6 @@ module ActionController process(action, "HEAD", *args) end - # Simulate a OPTIONS request with the given parameters and set/volley the response. - # See +get+ for more details. - def options(action, *args) - process(action, "OPTIONS", *args) - end - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index f8f9cf7c9f..89003e7a5e 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -243,13 +243,13 @@ module ActionDispatch session = Request::Session.find(env) || {} flash_hash = env[KEY] - if flash_hash.present? || session.key?('flash') + if flash_hash && (flash_hash.present? || session.key?('flash')) session["flash"] = flash_hash.to_session_value env[KEY] = flash_hash.dup end if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) - session.key?('flash') && session['flash'].nil? + session.key?('flash') && session['flash'].nil? session.delete('flash') end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 342b6ec23d..3ae9f92c0b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -218,6 +218,7 @@ module ActionDispatch keys -= t.url_options.keys if t.respond_to?(:url_options) keys -= options.keys end + keys -= inner_options.keys result.merge!(Hash[keys.zip(args)]) end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 56c31255f3..70b42ccd1b 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -62,12 +62,6 @@ module ActionDispatch process :head, path, parameters, headers_or_env end - # Performs a OPTIONS request with the given parameters. See +#get+ for - # more details. - def options(path, parameters = nil, headers_or_env = nil) - process :options, path, parameters, headers_or_env - end - # Performs an XMLHttpRequest request with the given parameters, mirroring # a request from the Prototype library. # @@ -342,7 +336,7 @@ module ActionDispatch @integration_session = Integration::Session.new(app) end - %w(get post patch put head delete options cookies assigns + %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| reset! unless integration_session diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb index 71b78cf0b5..b5f2df76ab 100644 --- a/actionpack/lib/action_view/helpers/asset_url_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_url_helper.rb @@ -193,7 +193,6 @@ module ActionView request = self.request if respond_to?(:request) host = config.asset_host if defined? config.asset_host host ||= request.base_url if request && options[:protocol] == :request - return unless host if host.respond_to?(:call) arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity @@ -204,6 +203,8 @@ module ActionView host = host % (Zlib.crc32(source) % 4) end + return unless host + if host =~ URI_REGEXP host else diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 10b487f37a..3145446114 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -211,7 +211,9 @@ module ActionView alias_method :_view, :view INTERNAL_IVARS = [ - :@__name__, + :@NAME, + :@failures, + :@assertions, :@__io__, :@_assertion_wrapped, :@_assertions, diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 3b79161ad3..4c82625e8e 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -10,7 +10,7 @@ class ActionController::Base def before_filters filters = _process_action_callbacks.select { |c| c.kind == :before } - filters.map! { |c| c.instance_variable_get(:@raw_filter) } + filters.map! { |c| c.raw_filter } end end @@ -213,6 +213,14 @@ class FilterTest < ActionController::TestCase before_filter :clean_up_tmp, :if => Proc.new { |c| false } end + class ConditionalOptionsSkipFilter < ConditionalFilterController + before_filter :ensure_login + before_filter :clean_up_tmp + + skip_before_filter :ensure_login, if: -> { false } + skip_before_filter :clean_up_tmp, if: -> { true } + end + class PrependingController < TestController prepend_before_filter :wonderful_life # skip_before_filter :fire_flash @@ -593,6 +601,11 @@ class FilterTest < ActionController::TestCase assert_equal %w( ensure_login ), assigns["ran_filter"] end + def test_running_conditional_skip_options + test_process(ConditionalOptionsSkipFilter) + assert_equal %w( ensure_login ), assigns["ran_filter"] + end + def test_running_collection_condition_filters test_process(ConditionalCollectionFilterController) assert_equal %w( ensure_login ), assigns["ran_filter"] diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index c3bdf74d93..f7ec6d71b3 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -117,12 +117,6 @@ class SessionTest < ActiveSupport::TestCase @session.head(path,params,headers) end - def test_options - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:options,path,params,headers) - @session.options(path,params,headers) - end - def test_xml_http_request_get path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( @@ -183,16 +177,6 @@ class SessionTest < ActiveSupport::TestCase @session.xml_http_request(:head,path,params,headers) end - def test_xml_http_request_options - path = "/index"; params = "blah"; headers = {:location => 'blah'} - headers_after_xhr = headers.merge( - "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", - "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" - ) - @session.expects(:process).with(:options,path,params,headers_after_xhr) - @session.xml_http_request(:options,path,params,headers) - end - def test_xml_http_request_override_accept path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"} headers_after_xhr = headers.merge( @@ -250,7 +234,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest @integration_session.stubs(:generic_url_rewriter) @integration_session.stubs(:process) - %w( get post head patch put delete options ).each do |verb| + %w( get post head patch put delete ).each do |verb| assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') } end end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 38b9794b4d..7c27458f46 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -201,11 +201,6 @@ XML assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 } end - def test_options - options :test_params - assert_equal 200, @response.status - end - def test_process_without_flash process :set_flash assert_equal '><', flash['test'] diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index d57b1a5637..0e488d2b88 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -69,6 +69,17 @@ module ActionDispatch end end + test "explicit keys win over implicit keys" do + draw do + resources :foo do + resources :bar, to: SimpleApp.new('foo#show') + end + end + + assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(1, 2) + assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1) + end + private def clear! @set.clear! diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 11614a45dc..67f593c22f 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -530,6 +530,17 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal copy, source end + def test_image_path_with_asset_host_proc_returning_nil + @controller.config.asset_host = Proc.new do |source| + unless source.end_with?("tiff") + "cdn.example.com" + end + end + + assert_equal "/images/file.tiff", image_path("file.tiff") + assert_equal "http://cdn.example.com/images/file.png", image_path("file.png") + end + def test_caching_image_path_with_caching_and_proc_asset_host_using_request @controller.config.asset_host = Proc.new do |source, request| if request.ssl? diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 94d2181e4d..8b09f8b203 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -100,7 +100,7 @@ module ActiveModel def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - terminator: "result == false", + terminator: ->(_,result) { result == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name], only: [:before, :around, :after] diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 8de36b4f8b..cabb9482f2 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -22,7 +22,10 @@ module ActiveModel included do include ActiveSupport::Callbacks - define_callbacks :validation, terminator: "result == false", skip_after_callbacks_if_terminated: true, scope: [:kind, :name] + define_callbacks :validation, + terminator: ->(_,result) { result == false }, + skip_after_callbacks_if_terminated: true, + scope: [:kind, :name] end module ClassMethods diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb index a0cd1402b1..d44a3448df 100644 --- a/activemodel/test/cases/railtie_test.rb +++ b/activemodel/test/cases/railtie_test.rb @@ -9,7 +9,6 @@ class RailtieTest < ActiveModel::TestCase @app ||= Class.new(::Rails::Application) do config.eager_load = false - config.logger = Logger.new(STDOUT) end end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 3241a03b53..3e84297cc2 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -166,7 +166,7 @@ class ValidationsTest < ActiveModel::TestCase def test_invalid_validator Topic.validate :i_dont_exist - assert_raise(NameError) do + assert_raises(NoMethodError) do t = Topic.new t.valid? end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 772fd5999b..858c33c4e5 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Also support extentions in PostgreSQL 9.1. This feature has been supported since 9.1. + + *kennyj* + * Deprecate `ConnectionAdapters::SchemaStatements#distinct`, as it is no longer used by internals. diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc index 2f3d516c43..c3ee34da55 100644 --- a/activerecord/RUNNING_UNIT_TESTS.rdoc +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc @@ -1,31 +1,43 @@ == Setup -If you don't have the environment set make sure to read - - http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#testing-active-record +If you don't have an environment for running tests, read +http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#setting-up-a-development-environment == Running the Tests -You can run a particular test file from the command line, e.g. +To run a specific test: + + $ ruby -Itest test/cases/base_test.rb -n method_name + +To run a set of tests: $ ruby -Itest test/cases/base_test.rb -To run a specific test: +You can also run tests that depend upon a specific database backend. For +example: - $ ruby -Itest test/cases/base_test.rb -n test_something_works + $ bundle exec rake test_sqlite3 -You can run with a database other than the default you set in test/config.yml, using the ARCONN -environment variable: +Simply executing <tt>bundle exec rake test</tt> is equivalent to the following: - $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb + $ bundle exec rake test_mysql + $ bundle exec rake test_mysql2 + $ bundle exec rake test_postgresql + $ bundle exec rake test_sqlite3 -You can run all the tests for a given database via rake: +There should be tests available for each database backend listed in the {Config +File}[rdoc-label:label-Config+File]. (the exact set of available tests is +defined in +Rakefile+) - $ rake test_mysql +== Config File -The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql. +If +test/config.yml+ is present, it's parameters are obeyed. Otherwise, the +parameters in +test/config.example.yml+ are obeyed. -== Custom Config file +You can override the +connections:+ parameter in either file using the +ARCONN+ +(Active Record CONNection) environment variable: + + $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb -By default, the config file is expected to be at the path test/config.yml. You can specify a -custom location with the ARCONFIG environment variable. +You can specify a custom location for the config file using the +ARCONFIG+ +environment variable. diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 0523314128..cd73489cbe 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -125,8 +125,6 @@ namespace :postgresql do %w(arunit arunit2).each do |db| if version < "9.1.0" puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" - else - %x( psql #{config[db]['database']} -c "CREATE EXTENSION hstore;" ) end end end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index e4d17451dc..b81aecb4e5 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -106,12 +106,16 @@ module ActiveRecord ] end - scope_chain_items.each do |item| + constraint = scope_chain_items.inject(constraint) do |chain, item| unless item.is_a?(Relation) item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item) end - constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty? + if item.arel.constraints.empty? + chain + else + chain.and(item.arel.constraints) + end end manager.from(join(table, constraint)) diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index e536f5ebcc..75377bba57 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -1,3 +1,4 @@ +require 'active_model/forbidden_attributes_protection' module ActiveRecord module AttributeAssignment @@ -44,7 +45,7 @@ module ActiveRecord if respond_to?("#{k}=") raise else - raise UnknownAttributeError, "unknown attribute: #{k}" + raise UnknownAttributeError.new(self, k) end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index f23521430d..1826d88500 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -393,6 +393,14 @@ module ActiveRecord TYPES[new] = TYPES[old] end + def self.find_type(field) + if field.type == Mysql::Field::TYPE_TINY && field.length > 1 + TYPES[Mysql::Field::TYPE_LONG] + else + TYPES.fetch(field.type) { Fields::Identity.new } + end + end + register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new register_type Mysql::Field::TYPE_LONG, Fields::Integer.new alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG @@ -425,9 +433,7 @@ module ActiveRecord if field.decimals > 0 types[field.name] = Fields::Decimal.new else - types[field.name] = Fields::TYPES.fetch(field.type) { - Fields::Identity.new - } + types[field.name] = Fields.find_type field end } result_set = ActiveRecord::Result.new(types.keys, result.to_a, types) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index a9ef11aa83..a73f0ac57f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -60,7 +60,7 @@ module ActiveRecord end def json_to_string(object) - if Hash === object + if Hash === object || Array === object ActiveSupport::JSON.encode(object) else object diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 40a3b82839..e9daa5d7ff 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -30,6 +30,7 @@ module ActiveRecord when Array case sql_type when 'point' then super(PostgreSQLColumn.point_to_string(value)) + when 'json' then super(PostgreSQLColumn.json_to_string(value)) else if column.array "'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'" @@ -98,6 +99,7 @@ module ActiveRecord when Array case column.sql_type when 'point' then PostgreSQLColumn.point_to_string(value) + when 'json' then PostgreSQLColumn.json_to_string(value) else return super(value, column) unless column.array PostgreSQLColumn.array_to_string(value, column, self) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 88b09e7999..d5a603cadc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -622,9 +622,9 @@ module ActiveRecord true end - # Returns true if pg > 9.2 + # Returns true if pg > 9.1 def supports_extensions? - postgresql_version >= 90200 + postgresql_version >= 90100 end # Range datatypes weren't introduced until PostgreSQL 9.2 @@ -646,9 +646,9 @@ module ActiveRecord def extension_enabled?(name) if supports_extensions? - res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)", + res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", 'SCHEMA' - res.column_types['exists'].type_cast res.rows.first.first + res.column_types['enabled'].type_cast res.rows.first.first end end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index cd31147414..017b8bace6 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -159,6 +159,15 @@ module ActiveRecord # Raised when unknown attributes are supplied via mass assignment. class UnknownAttributeError < NoMethodError + + attr_reader :record, :attribute + + def initialize(record, attribute) + @record = record + @attribute = attribute.to_s + super("unknown attribute: #{attribute}") + end + end # Raised when an error occurred while doing a mass assignment to an attribute through the diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 8df76c7f5f..40976bc29e 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -116,9 +116,10 @@ module ActiveRecord begin constant = ActiveSupport::Dependencies.constantize(candidate) return constant if candidate == constant.to_s - rescue NameError => e - # We don't want to swallow NoMethodError < NameError errors - raise e unless e.instance_of?(NameError) + # We don't want to swallow NoMethodError < NameError errors + rescue NoMethodError + raise + rescue NameError end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index e36888d4a8..31a0ace864 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -44,7 +44,7 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) + if defined?(APP_RAKEFILE) && engine = Rails::Engine.find(find_engine_path(APP_RAKEFILE)) if engine.paths['db/migrate'].existent ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ae3fa85da9..94ab465cc1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -489,11 +489,13 @@ module ActiveRecord # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} def where_values_hash - equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| + scope = with_default_scope + equalities = scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name } - binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] + binds = Hash[scope.bind_values.find_all(&:first).map { |column, v| [column.name, v] }] + binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]) Hash[equalities.map { |where| name = where.left.name diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index e9142481a3..1b4c473bfc 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -35,8 +35,7 @@ module ActiveRecord def assert_queries(num = 1, options = {}) ignore_none = options.fetch(:ignore_none) { num == :any } SQLCounter.clear_log - yield - ensure + x = yield the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log if num == :any assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed." @@ -44,6 +43,7 @@ module ActiveRecord mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}" assert_equal num, the_log.size, mesg end + x end def assert_no_queries(&block) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index a5955ccba4..77634b40bb 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -10,7 +10,9 @@ module ActiveRecord end included do - define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name] + define_callbacks :commit, :rollback, + terminator: ->(_, result) { result == false }, + scope: [:kind, :name] end # = Active Record Transactions diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index a75883cd3a..4a23287448 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -95,14 +95,27 @@ module ActiveRecord assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq end + def test_tinyint_integer_typecasting + @conn.exec_query('drop table if exists ex_with_non_boolean_tinyint_column') + @conn.exec_query(<<-eosql) + CREATE TABLE `ex_with_non_boolean_tinyint_column` ( + `status` TINYINT(4)) + eosql + insert(@conn, { 'status' => 2 }, 'ex_with_non_boolean_tinyint_column') + + result = @conn.exec_query('SELECT status FROM ex_with_non_boolean_tinyint_column') + + assert_equal 2, result.column_types['status'].type_cast(result.last['status']) + end + private - def insert(ctx, data) + def insert(ctx, data, table='ex') binds = data.map { |name, value| - [ctx.columns('ex').find { |x| x.name == name }, value] + [ctx.columns(table).find { |x| x.name == name }, value] } columns = binds.map(&:first).map(&:name) - sql = "INSERT INTO ex (#{columns.join(", ")}) + sql = "INSERT INTO #{table} (#{columns.join(", ")}) VALUES (#{(['?'] * columns.length).join(', ')})" ctx.exec_insert(sql, 'SQL', binds) diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 6fc08ae4f0..f45c7afcc0 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -83,4 +83,18 @@ class PostgresqlJSONTest < ActiveRecord::TestCase x = JsonDataType.first assert_equal(nil, x.payload) end + + def test_select_array_json_value + @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| + x = JsonDataType.first + assert_equal(['v0', {'k1' => 'v1'}], x.payload) + end + + def test_rewrite_array_json_value + @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| + x = JsonDataType.first + x.payload = ['v1', {'k2' => 'v2'}, 'v3'] + assert x.save! + end + end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index a181c513ae..f10732ddda 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -711,6 +711,15 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } end + def test_bulk_update_raise_unknown_attribute_errro + error = assert_raises(ActiveRecord::UnknownAttributeError) { + @target.new(:hello => "world") + } + assert @target, error.record + assert "hello", error.attribute + assert "unknown attribute: hello", error.message + end + def test_read_attribute_overwrites_private_method_not_considered_implemented # simulate a model with a db column that shares its name an inherited # private method (e.g. Object#system) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index d20ccaa5ca..15d536daf5 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1236,7 +1236,7 @@ class BasicsTest < ActiveRecord::TestCase def test_inspect_instance topic = topics(:first) - assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect end def test_inspect_new_instance diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 187cad9599..c8f56e3c73 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -43,7 +43,7 @@ class CallbackDeveloper < ActiveRecord::Base end class CallbackDeveloperWithFalseValidation < CallbackDeveloper - before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false } + before_validation proc { |model| model.history << [:before_validation, :returning_false]; false } before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index ac093251a5..61f9d4cdae 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -51,6 +51,18 @@ class CounterCacheTest < ActiveRecord::TestCase end end + test 'reset multiple association counters' do + Topic.increment_counter(:replies_count, @topic.id) + assert_difference '@topic.reload.replies_count', -1 do + Topic.reset_counters(@topic.id, :replies, :unique_replies) + end + + Topic.increment_counter(:unique_replies_count, @topic.id) + assert_difference '@topic.reload.unique_replies_count', -1 do + Topic.reset_counters(@topic.id, :replies, :unique_replies) + end + end + test "reset counters with string argument" do Topic.increment_counter('replies_count', @topic.id) diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index a9d46f4fba..b5314bc9be 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -35,18 +35,18 @@ class ReflectionTest < ActiveRecord::TestCase def test_read_attribute_names assert_equal( - %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort, + %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort, @first.attribute_names.sort ) end def test_columns - assert_equal 17, Topic.columns.length + assert_equal 18, Topic.columns.length end def test_columns_are_returned_in_the_order_they_were_declared column_names = Topic.columns.map { |column| column.name } - assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names + assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names end def test_content_columns diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 482c1b3d48..03c7009568 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -12,10 +12,7 @@ module ActiveRecord end def test_construction - relation = nil - assert_nothing_raised do - relation = Relation.new FakeKlass, :b - end + relation = Relation.new FakeKlass, :b assert_equal FakeKlass, relation.klass assert_equal :b, relation.table assert !relation.loaded, 'relation is not loaded' diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index c88262580e..3e82e55d89 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -7,6 +7,7 @@ class Reply < Topic end class UniqueReply < Reply + belongs_to :topic, :foreign_key => 'parent_id', :counter_cache => true validates_uniqueness_of :content, :scope => 'parent_id' end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 8beb58f3fc..188a3f0164 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -675,6 +675,7 @@ ActiveRecord::Schema.define do end t.boolean :approved, :default => true t.integer :replies_count, :default => 0 + t.integer :unique_replies_count, :default => 0 t.integer :parent_id t.string :parent_title t.string :type diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 493c169803..1982811500 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,9 @@ +* Raise an error when multiple `included` blocks are defined for a Concern. + The old behavior would silently discard previously defined blocks, running + only the last one. + + *Mike Dillon* + * Replace `multi_json` with `json`. Since Rails requires Ruby 1.9 and since Ruby 1.9 includes `json` in the standard library, diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 08d503ae30..ffc2c2074e 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -23,6 +23,6 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.6', '>= 0.6.4') s.add_dependency 'json', '~> 1.7' s.add_dependency 'tzinfo', '~> 0.3.37' - s.add_dependency 'minitest', '~> 4.2' + s.add_dependency 'minitest', '~> 5.0' s.add_dependency 'thread_safe','~> 0.1' end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index a151aa03b5..bbfa74f98d 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,9 +1,9 @@ -require 'thread_safe' require 'active_support/concern' require 'active_support/descendants_tracker' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' +require 'thread' module ActiveSupport # Callbacks are code hooks that are run at key points in an object's lifecycle. @@ -76,8 +76,14 @@ module ActiveSupport # save # end def run_callbacks(kind, &block) - runner_name = self.class.__define_callbacks(kind, self) - send(runner_name, &block) + cbs = send("_#{kind}_callbacks") + if cbs.empty? + yield if block_given? + else + runner = cbs.compile + e = Filters::Environment.new(self, false, nil, block) + runner.call(e).value + end end private @@ -88,204 +94,306 @@ module ActiveSupport def halted_callback_hook(filter) end - class Callback #:nodoc:# - @@_callback_sequence = 0 + module Filters + Environment = Struct.new(:target, :halted, :value, :run_block) - class Basic < Callback + class End + def call(env) + block = env.run_block + env.value = !env.halted && (!block || block.call) + env + end end + ENDING = End.new - class Object < Callback - def duplicates?(other) - false + class Before + def self.build(next_callback, user_callback, user_conditions, chain_config, filter) + halted_lambda = chain_config[:terminator] + + if chain_config.key?(:terminator) && user_conditions.any? + halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) + elsif chain_config.key? :terminator + halting(next_callback, user_callback, halted_lambda, filter) + elsif user_conditions.any? + conditional(next_callback, user_callback, user_conditions) + else + simple next_callback, user_callback + end end - end - def self.build(chain, filter, kind, options, _klass) - klass = case filter - when Array, Symbol, String - Callback::Basic - else - Callback::Object - end - klass.new chain, filter, kind, options, _klass - end + private - attr_accessor :chain, :kind, :options, :klass, :raw_filter + def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) + lambda { |env| + target = env.target + value = env.value + halted = env.halted - def initialize(chain, filter, kind, options, klass) - @chain, @kind, @klass = chain, kind, klass - deprecate_per_key_option(options) - normalize_options!(options) + if !halted && user_conditions.all? { |c| c.call(target, value) } + result = user_callback.call target, value + env.halted = halted_lambda.call(target, result) + if env.halted + target.send :halted_callback_hook, filter + end + end + next_callback.call env + } + end - @raw_filter, @options = filter, options - @key = compute_identifier filter - @source = _compile_source(filter) - recompile_options! - end + def self.halting(next_callback, user_callback, halted_lambda, filter) + lambda { |env| + target = env.target + value = env.value + halted = env.halted + + unless halted + result = user_callback.call target, value + env.halted = halted_lambda.call(target, result) + if env.halted + target.send :halted_callback_hook, filter + end + end + next_callback.call env + } + end - def filter - @key - end + def self.conditional(next_callback, user_callback, user_conditions) + lambda { |env| + target = env.target + value = env.value - def deprecate_per_key_option(options) - if options[:per_key] - raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead." + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + next_callback.call env + } end - end - def clone(chain, klass) - obj = super() - obj.chain = chain - obj.klass = klass - obj.options = @options.dup - obj.options[:if] = @options[:if].dup - obj.options[:unless] = @options[:unless].dup - obj + def self.simple(next_callback, user_callback) + lambda { |env| + user_callback.call env.target, env.value + next_callback.call env + } + end end - def normalize_options!(options) - options[:if] = Array(options[:if]) - options[:unless] = Array(options[:unless]) - end + class After + def self.build(next_callback, user_callback, user_conditions, chain_config) + if chain_config[:skip_after_callbacks_if_terminated] + if chain_config.key?(:terminator) && user_conditions.any? + halting_and_conditional(next_callback, user_callback, user_conditions) + elsif chain_config.key?(:terminator) + halting(next_callback, user_callback) + elsif user_conditions.any? + conditional next_callback, user_callback, user_conditions + else + simple next_callback, user_callback + end + else + if user_conditions.any? + conditional next_callback, user_callback, user_conditions + else + simple next_callback, user_callback + end + end + end - def name - chain.name - end + private - def next_id - @@_callback_sequence += 1 - end + def self.halting_and_conditional(next_callback, user_callback, user_conditions) + lambda { |env| + env = next_callback.call env + target = env.target + value = env.value + halted = env.halted - def matches?(_kind, _filter) - @kind == _kind && filter == _filter + if !halted && user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + env + } + end + + def self.halting(next_callback, user_callback) + lambda { |env| + env = next_callback.call env + unless env.halted + user_callback.call env.target, env.value + end + env + } + end + + def self.conditional(next_callback, user_callback, user_conditions) + lambda { |env| + env = next_callback.call env + target = env.target + value = env.value + + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + env + } + end + + def self.simple(next_callback, user_callback) + lambda { |env| + env = next_callback.call env + user_callback.call env.target, env.value + env + } + end end - def duplicates?(other) - return false unless self.class == other.class + class Around + def self.build(next_callback, user_callback, user_conditions, chain_config) + if chain_config.key?(:terminator) && user_conditions.any? + halting_and_conditional(next_callback, user_callback, user_conditions) + elsif chain_config.key? :terminator + halting(next_callback, user_callback) + elsif user_conditions.any? + conditional(next_callback, user_callback, user_conditions) + else + simple(next_callback, user_callback) + end + end + + private + + def self.halting_and_conditional(next_callback, user_callback, user_conditions) + lambda { |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + user_callback.call(target, value) { + env = next_callback.call env + env.value + } + env + else + next_callback.call env + end + } + end + + def self.halting(next_callback, user_callback) + lambda { |env| + target = env.target + value = env.value + + unless env.halted + user_callback.call(target, value) { + env = next_callback.call env + env.value + } + env + else + next_callback.call env + end + } + end + + def self.conditional(next_callback, user_callback, user_conditions) + lambda { |env| + target = env.target + value = env.value + + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call(target, value) { + env = next_callback.call env + env.value + } + env + else + next_callback.call env + end + } + end - matches?(other.kind, other.filter) + def self.simple(next_callback, user_callback) + lambda { |env| + user_callback.call(env.target, env.value) { + env = next_callback.call env + env.value + } + env + } + end end + end - def _update_filter(filter_options, new_options) - filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless) - filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if) + class Callback #:nodoc:# + def self.build(chain, filter, kind, options) + new chain.name, filter, kind, options, chain.config end - def recompile!(_options) - deprecate_per_key_option(_options) - _update_filter(self.options, _options) + attr_accessor :kind, :name + attr_reader :chain_config - recompile_options! + def initialize(name, filter, kind, options, chain_config) + @chain_config = chain_config + @name = name + @kind = kind + @filter = filter + @key = compute_identifier filter + @if = Array(options[:if]) + @unless = Array(options[:unless]) end - # Wraps code with filter - def apply(code) - case @kind - when :before - <<-RUBY_EVAL - if !halted && #{@compiled_options} - # This double assignment is to prevent warnings in 1.9.3 as - # the `result` variable is not always used except if the - # terminator code refers to it. - result = result = #{@source} - halted = (#{chain.config[:terminator]}) - if halted - halted_callback_hook(#{@raw_filter.inspect.inspect}) - end - end - #{code} - RUBY_EVAL - when :after - <<-RUBY_EVAL - #{code} - if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options} - #{@source} - end - RUBY_EVAL - when :around - name = define_conditional_callback - <<-RUBY_EVAL - #{name}(halted) do - #{code} - value - end - RUBY_EVAL - end + def filter; @key; end + def raw_filter; @filter; end + + def merge(chain, new_options) + options = { + :if => @if.dup, + :unless => @unless.dup + } + + options[:if].concat Array(new_options.fetch(:unless, [])) + options[:unless].concat Array(new_options.fetch(:if, [])) + + self.class.build chain, @filter, @kind, options end - private + def matches?(_kind, _filter) + @kind == _kind && filter == _filter + end - def compute_identifier(filter) - case filter - when String, ::Proc - filter.object_id + def duplicates?(other) + case @filter + when Symbol, String + matches?(other.kind, other.filter) else - filter + false end end - # Compile around filters with conditions into proxy methods - # that contain the conditions. - # - # For `set_callback :save, :around, :filter_name, if: :condition`: - # - # def _conditional_callback_save_17 - # if condition - # filter_name do - # yield self - # end - # else - # yield self - # end - # end - def define_conditional_callback - name = "_conditional_callback_#{@kind}_#{next_id}" - @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{name}(halted) - if #{@compiled_options} && !halted - #{@source} do - yield self - end - else - yield self - end - end - RUBY_EVAL - name - end - - # Options support the same options as filters themselves (and support - # symbols, string, procs, and objects), so compile a conditional - # expression based on the options. - def recompile_options! - conditions = ["true"] - - unless options[:if].empty? - conditions << Array(_compile_source(options[:if])) - end + # Wraps code with filter + def apply(next_callback) + user_conditions = conditions_lambdas + user_callback = make_lambda @filter - unless options[:unless].empty? - conditions << Array(_compile_source(options[:unless])).map {|f| "!#{f}"} + case kind + when :before + Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter) + when :after + Filters::After.build(next_callback, user_callback, user_conditions, chain_config) + when :around + Filters::Around.build(next_callback, user_callback, user_conditions, chain_config) end - - @compiled_options = conditions.flatten.join(" && ") end - def _method_name_for_object_filter(kind, filter, append_next_id = true) - class_name = filter.kind_of?(Class) ? filter.to_s : filter.class.to_s - class_name.gsub!(/<|>|#/, '') - class_name.gsub!(/\/|:/, "_") + private - method_name = "_callback_#{kind}_#{class_name}" - method_name << "_#{next_id}" if append_next_id - method_name + def invert_lambda(l) + lambda { |*args, &blk| !l.call(*args, &blk) } end # Filters support: # - # Arrays:: Used in conditions. This is used to specify - # multiple conditions. Used internally to - # merge conditions from skip_* filters. # Symbols:: A method to call. # Strings:: Some content to evaluate. # Procs:: A proc to call with the object. @@ -294,87 +402,105 @@ module ActiveSupport # All of these objects are compiled into methods and handled # the same after this point: # - # Arrays:: Merged together into a single filter. # Symbols:: Already methods. # Strings:: class_eval'ed into methods. # Procs:: define_method'ed into methods. # Objects:: # a method is created that calls the before_foo method # on the object. - def _compile_source(filter) + def make_lambda(filter) case filter - when Array - filter.map {|f| _compile_source(f)} when Symbol - filter + lambda { |target, _, &blk| target.send filter, &blk } when String - "(#{filter})" + l = eval "lambda { |value| #{filter} }" + lambda { |target, value| target.instance_exec(value, &l) } when ::Proc - method_name = "_callback_#{@kind}_#{next_id}" - @klass.send(:define_method, method_name, &filter) - return method_name if filter.arity <= 0 + if filter.arity > 1 + return lambda { |target, _, &block| + raise ArgumentError unless block + target.instance_exec(target, block, &filter) + } + end - method_name << (filter.arity == 1 ? "(self)" : "(self, ::Proc.new)") + if filter.arity <= 0 + lambda { |target, _| target.instance_exec(&filter) } + else + lambda { |target, _| target.instance_exec(target, &filter) } + end else - method_name = _method_name_for_object_filter(kind, filter) - @klass.send(:define_method, "#{method_name}_object") { filter } - - _normalize_legacy_filter(kind, filter) - scopes = Array(chain.config[:scope]) + scopes = Array(chain_config[:scope]) method_to_call = scopes.map{ |s| public_send(s) }.join("_") - @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{method_name}(&blk) - #{method_name}_object.send(:#{method_to_call}, self, &blk) - end - RUBY_EVAL - - method_name + lambda { |target, _, &blk| + filter.public_send method_to_call, target, &blk + } end end - def _normalize_legacy_filter(kind, filter) - if !filter.respond_to?(kind) && filter.respond_to?(:filter) - message = "Filter object with #filter method is deprecated. Define method corresponding " \ - "to filter type (#before, #after or #around)." - ActiveSupport::Deprecation.warn message - filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{kind}(context, &block) filter(context, &block) end - RUBY_EVAL - elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around) - message = "Filter object with #before and #after methods is deprecated. Define #around method instead." - ActiveSupport::Deprecation.warn message - def filter.around(context) - should_continue = before(context) - yield if should_continue - after(context) - end + def compute_identifier(filter) + case filter + when String, ::Proc + filter.object_id + else + filter end end + + def conditions_lambdas + @if.map { |c| make_lambda c } + + @unless.map { |c| invert_lambda make_lambda c } + end end # An Array with a compile method. - class CallbackChain < Array #:nodoc:# + class CallbackChain #:nodoc:# + include Enumerable + attr_reader :name, :config def initialize(name, config) @name = name @config = { - :terminator => "false", :scope => [ :kind ] }.merge!(config) + @chain = [] + @callbacks = nil + @mutex = Mutex.new + end + + def each(&block); @chain.each(&block); end + def index(o); @chain.index(o); end + def empty?; @chain.empty?; end + + def insert(index, o) + @callbacks = nil + @chain.insert(index, o) + end + + def delete(o) + @callbacks = nil + @chain.delete(o) + end + + def clear + @callbacks = nil + @chain.clear + self + end + + def initialize_copy(other) + @callbacks = nil + @chain = other.chain.dup + @mutex = Mutex.new end def compile - method = ["value = nil", "halted = false"] - callbacks = "value = !halted && (!block_given? || yield)" - reverse_each do |callback| - callbacks = callback.apply(callbacks) + @callbacks || @mutex.synchronize do + @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback| + callback.apply chain + end end - method << callbacks - - method << "value" - method.join("\n") end def append(*callbacks) @@ -385,69 +511,45 @@ module ActiveSupport callbacks.each { |c| prepend_one(c) } end + protected + def chain; @chain; end + private def append_one(callback) + @callbacks = nil remove_duplicates(callback) - push(callback) + @chain.push(callback) end def prepend_one(callback) + @callbacks = nil remove_duplicates(callback) - unshift(callback) + @chain.unshift(callback) end def remove_duplicates(callback) - delete_if { |c| callback.duplicates?(c) } + @callbacks = nil + @chain.delete_if { |c| callback.duplicates?(c) } end end module ClassMethods - # This method defines callback chain method for the given kind - # if it was not yet defined. - # This generated method plays caching role. - def __define_callbacks(kind, object) #:nodoc: - name = __callback_runner_name(kind) - unless object.respond_to?(name, true) - str = object.send("_#{kind}_callbacks").compile - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{name}() #{str} end - protected :#{name} - RUBY_EVAL - end - name - end - - def __reset_runner(symbol) - name = __callback_runner_name(symbol) - undef_method(name) if method_defined?(name) - end - - def __callback_runner_name_cache - @__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) } - end - - def __generate_callback_runner_name(kind) - "_run__#{self.name.hash.abs}__#{kind}__callbacks" - end - - def __callback_runner_name(kind) - __callback_runner_name_cache[kind] - end - - # This is used internally to append, prepend and skip callbacks to the - # CallbackChain. - def __update_callbacks(name, filters = [], block = nil) #:nodoc: + def normalize_callback_params(name, filters, block) # :nodoc: type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(block) if block + [type, filters, options.dup] + end + # This is used internally to append, prepend and skip callbacks to the + # CallbackChain. + def __update_callbacks(name) #:nodoc: ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target| - chain = target.send("_#{name}_callbacks") - yield target, chain.dup, type, filters, options - target.__reset_runner(name) + chain = target.get_callbacks name + yield target, chain.dup end end @@ -487,16 +589,15 @@ module ActiveSupport # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the # existing chain rather than appended. def set_callback(name, *filter_list, &block) - mapped = nil - - __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options| - mapped ||= filters.map do |filter| - Callback.build(chain, filter, type, options.dup, self) - end + type, filters, options = normalize_callback_params(name, filter_list, block) + self_chain = get_callbacks name + mapped = filters.map do |filter| + Callback.build(self_chain, filter, type, options) + end + __update_callbacks(name) do |target, chain| options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) - - target.send("_#{name}_callbacks=", chain) + target.set_callbacks name, chain end end @@ -508,36 +609,34 @@ module ActiveSupport # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 } # end def skip_callback(name, *filter_list, &block) - __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options| + type, filters, options = normalize_callback_params(name, filter_list, block) + + __update_callbacks(name) do |target, chain| filters.each do |filter| filter = chain.find {|c| c.matches?(type, filter) } if filter && options.any? - new_filter = filter.clone(chain, self) + new_filter = filter.merge(chain, options) chain.insert(chain.index(filter), new_filter) - new_filter.recompile!(options) end chain.delete(filter) end - target.send("_#{name}_callbacks=", chain) + target.set_callbacks name, chain end end # Remove all set callbacks for the given event. def reset_callbacks(symbol) - callbacks = send("_#{symbol}_callbacks") + callbacks = get_callbacks symbol ActiveSupport::DescendantsTracker.descendants(self).each do |target| - chain = target.send("_#{symbol}_callbacks").dup + chain = target.get_callbacks(symbol).dup callbacks.each { |c| chain.delete(c) } - target.send("_#{symbol}_callbacks=", chain) - target.__reset_runner(symbol) + target.set_callbacks symbol, chain end - self.send("_#{symbol}_callbacks=", callbacks.dup.clear) - - __reset_runner(symbol) + self.set_callbacks symbol, callbacks.dup.clear end # Define sets of events in the object lifecycle that support callbacks. @@ -609,11 +708,28 @@ module ActiveSupport # would call <tt>Audit#save</tt>. def define_callbacks(*callbacks) config = callbacks.last.is_a?(Hash) ? callbacks.pop : {} + if config.key?(:terminator) && String === config[:terminator] + ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda" + value = config[:terminator] + l = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__ + config[:terminator] = lambda { |target, result| target.instance_exec(result, &l) } + end + callbacks.each do |callback| class_attribute "_#{callback}_callbacks" - send("_#{callback}_callbacks=", CallbackChain.new(callback, config)) + set_callbacks callback, CallbackChain.new(callback, config) end end + + protected + + def get_callbacks(name) + send "_#{name}_callbacks" + end + + def set_callbacks(name, callbacks) + send "_#{name}_callbacks=", callbacks + end end end end diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index eeeba60839..b6ae86b583 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -98,6 +98,12 @@ module ActiveSupport # include Bar # works, Bar takes care now of its dependencies # end module Concern + class MultipleIncludedBlocks < StandardError #:nodoc: + def initialize + super "Cannot define multiple 'included' blocks for a Concern" + end + end + def self.extended(base) #:nodoc: base.instance_variable_set("@_dependencies", []) end @@ -117,6 +123,8 @@ module ActiveSupport def included(base = nil, &block) if base.nil? + raise MultipleIncludedBlocks if instance_variable_defined?("@_included_block") + @_included_block = block else super diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 6d49b7b6e1..6fa9967a28 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -71,7 +71,7 @@ class Class def class_attribute(*attrs) options = attrs.extract_options! # double assignment is used to avoid "assigned but unused variable" warning - instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) + instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) instance_predicate = options.fetch(:instance_predicate, true) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 1b20592e4c..bdb8877f55 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -229,7 +229,11 @@ module ActiveSupport # Convert to a regular hash with string keys. def to_hash - Hash.new(default).merge!(self) + _new_hash= {} + each do |key, value| + _new_hash[convert_key(key)] = convert_value(value, true) + end + Hash.new(default).merge!(_new_hash) end protected @@ -237,12 +241,12 @@ module ActiveSupport key.kind_of?(Symbol) ? key.to_s : key end - def convert_value(value) + def convert_value(value, _convert_for_to_hash = false) if value.is_a? Hash - value.nested_under_indifferent_access + _convert_for_to_hash ? value.to_hash : value.nested_under_indifferent_access elsif value.is_a?(Array) value = value.dup if value.frozen? - value.map! { |e| convert_value(e) } + value.map! { |e| convert_value(e, _convert_for_to_hash) } else value end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 7588fdb67c..99fe03e6d0 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -79,6 +79,13 @@ module ActiveSupport def initialize(pattern, delegate) @pattern = pattern @delegate = delegate + @can_publish = delegate.respond_to?(:publish) + end + + def publish(name, *args) + if @can_publish + @delegate.publish name, *args + end end def start(name, id, payload) diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 8b392c36d0..f0962998a0 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -16,10 +16,28 @@ begin rescue LoadError end +module Minitest # :nodoc: + class << self + remove_method :__run + end + + def self.__run reporter, options # :nodoc: + # FIXME: MT5's runnables is not ordered. This is needed because + # we have have tests have cross-class order-dependent bugs. + suites = Runnable.runnables.sort_by { |ts| ts.name.to_s } + + parallel, serial = suites.partition { |s| s.test_order == :parallel } + + ParallelEach.new(parallel).map { |suite| suite.run reporter, options } + + serial.map { |suite| suite.run reporter, options } + end +end + module ActiveSupport - class TestCase < ::MiniTest::Unit::TestCase - Assertion = MiniTest::Assertion - alias_method :method_name, :__name__ + class TestCase < ::Minitest::Test + Assertion = Minitest::Assertion + + alias_method :method_name, :name $tags = {} def self.for_tag(tag) diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb index c446adc16d..5aa5f46310 100644 --- a/activesupport/lib/active_support/testing/autorun.rb +++ b/activesupport/lib/active_support/testing/autorun.rb @@ -1,5 +1,5 @@ gem 'minitest' -require 'minitest/unit' +require 'minitest' -MiniTest::Unit.autorun +Minitest.autorun diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index e16b73a036..9c52ae7768 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -72,16 +72,12 @@ module ActiveSupport end end - def run(runner) - _run_class_setup - - serialized = run_in_isolation do |isolated_runner| - super(isolated_runner) + def run + serialized = run_in_isolation do + super end - retval, proxy = Marshal.load(serialized) - proxy.__replay__(runner) - retval + Marshal.load(serialized) end module Forking @@ -90,9 +86,8 @@ module ActiveSupport pid = fork do read.close - proxy = ProxyTestResult.new - retval = yield proxy - write.puts [Marshal.dump([retval, proxy])].pack("m") + yield + write.puts [Marshal.dump(self.dup)].pack("m") exit! end diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb index 9d43eb179f..f4cee64091 100644 --- a/activesupport/lib/active_support/testing/tagged_logging.rb +++ b/activesupport/lib/active_support/testing/tagged_logging.rb @@ -7,7 +7,7 @@ module ActiveSupport def before_setup if tagged_logger - heading = "#{self.class}: #{__name__}" + heading = "#{self.class}: #{name}" divider = '-' * heading.size tagged_logger.info divider tagged_logger.info heading diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 9659f141cb..f8e2ce22fa 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -525,7 +525,7 @@ module CallbacksTest class CallbackTerminator include ActiveSupport::Callbacks - define_callbacks :save, :terminator => "result == :halt" + define_callbacks :save, :terminator => ->(_,result) { result == :halt } set_callback :save, :before, :first set_callback :save, :before, :second @@ -718,7 +718,7 @@ module CallbacksTest def test_termination_invokes_hook terminator = CallbackTerminator.new terminator.save - assert_equal ":second", terminator.halted + assert_equal :second, terminator.halted end def test_block_never_called_if_terminated @@ -772,22 +772,6 @@ module CallbacksTest end end - class PerKeyOptionDeprecationTest < ActiveSupport::TestCase - - def test_per_key_option_deprecation - assert_raise NotImplementedError do - Phone.class_eval do - set_callback :save, :before, :before_save1, :per_key => {:if => "true"} - end - end - assert_raise NotImplementedError do - Phone.class_eval do - skip_callback :save, :before, :before_save1, :per_key => {:if => "true"} - end - end - end - end - class ExcludingDuplicatesCallbackTest < ActiveSupport::TestCase def test_excludes_duplicates_in_separate_calls model = DuplicatingCallbacks.new @@ -802,6 +786,46 @@ module CallbacksTest end end + class CallbackProcTest < ActiveSupport::TestCase + def build_class(callback) + Class.new { + include ActiveSupport::Callbacks + define_callbacks :foo + set_callback :foo, :before, callback + def run; run_callbacks :foo; end + } + end + + def test_proc_arity_0 + calls = [] + klass = build_class(->() { calls << :foo }) + klass.new.run + assert_equal [:foo], calls + end + + def test_proc_arity_1 + calls = [] + klass = build_class(->(o) { calls << o }) + instance = klass.new + instance.run + assert_equal [instance], calls + end + + def test_proc_arity_2 + assert_raises(ArgumentError) do + klass = build_class(->(x,y) { }) + klass.new.run + end + end + + def test_proc_negative_called_with_empty_list + calls = [] + klass = build_class(->(*args) { calls << args }) + klass.new.run + assert_equal [[]], calls + end + end + class ConditionalTests < ActiveSupport::TestCase def build_class(callback) Class.new { @@ -824,8 +848,9 @@ module CallbacksTest include ActiveSupport::Callbacks define_callbacks :foo, :scope => [:name] set_callback :foo, :before, :foo, :if => callback - def foo; end def run; run_callbacks :foo; end + private + def foo; end } object = klass.new object.run @@ -873,6 +898,46 @@ module CallbacksTest end end + class ResetCallbackTest < ActiveSupport::TestCase + def build_class(memo) + klass = Class.new { + include ActiveSupport::Callbacks + define_callbacks :foo + set_callback :foo, :before, :hello + def run; run_callbacks :foo; end + } + klass.class_eval { + define_method(:hello) { memo << :hi } + } + klass + end + + def test_reset_callbacks + events = [] + klass = build_class events + klass.new.run + assert_equal 1, events.length + + klass.reset_callbacks :foo + klass.new.run + assert_equal 1, events.length + end + + def test_reset_impacts_subclasses + events = [] + klass = build_class events + subclass = Class.new(klass) { set_callback :foo, :before, :world } + subclass.class_eval { define_method(:world) { events << :world } } + + subclass.new.run + assert_equal 2, events.length + + klass.reset_callbacks :foo + subclass.new.run + assert_equal 3, events.length + end + end + class CallbackTypeTest < ActiveSupport::TestCase def build_class(callback, n = 10) Class.new { diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb index 912ce30c29..8e2c298fc6 100644 --- a/activesupport/test/concern_test.rb +++ b/activesupport/test/concern_test.rb @@ -91,4 +91,18 @@ class ConcernTest < ActiveSupport::TestCase @klass.send(:include, Foo) assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2] end + + def test_raise_on_multiple_included_calls + assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do + Module.new do + extend ActiveSupport::Concern + + included do + end + + included do + end + end + end + end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 30d95b75bc..dfcc6cd12a 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -490,6 +490,10 @@ class HashExtTest < ActiveSupport::TestCase roundtrip = mixed_with_default.with_indifferent_access.to_hash assert_equal @strings, roundtrip assert_equal '1234', roundtrip.default + new_to_hash = @nested_mixed.with_indifferent_access.to_hash + assert_not new_to_hash.instance_of?(HashWithIndifferentAccess) + assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess) + assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess) end def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access @@ -502,6 +506,11 @@ class HashExtTest < ActiveSupport::TestCase def test_indifferent_hash_with_array_of_hashes hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access assert_equal "1", hash[:urls][:url].first[:address] + + hash = hash.to_hash + assert_not hash.instance_of?(HashWithIndifferentAccess) + assert_not hash["urls"].instance_of?(HashWithIndifferentAccess) + assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess) end def test_should_preserve_array_subclass_when_value_is_array diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 33627a4e74..f729f0a95b 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -81,6 +81,20 @@ module Notifications end end + class TestSubscriber + attr_reader :starts, :finishes, :publishes + + def initialize + @starts = [] + @finishes = [] + @publishes = [] + end + + def start(*args); @starts << args; end + def finish(*args); @finishes << args; end + def publish(*args); @publishes << args; end + end + class SyncPubSubTest < TestCase def test_events_are_published_to_a_listener @notifier.publish :foo @@ -144,6 +158,14 @@ module Notifications assert_equal [[:foo]], @another end + def test_publish_with_subscriber + subscriber = TestSubscriber.new + @notifier.subscribe nil, subscriber + @notifier.publish :foo + + assert_equal [[:foo]], subscriber.publishes + end + private def event(*args) args diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index a1bdcf2396..08df67827e 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -2,116 +2,6 @@ require 'abstract_unit' module ActiveSupport class TestCaseTest < ActiveSupport::TestCase - class FakeRunner - attr_reader :puked - - def initialize - @puked = [] - end - - def puke(klass, name, e) - @puked << [klass, name, e] - end - - def options - nil - end - - def record(*args) - end - - def info_signal - end - end - - def test_standard_error_raised_within_setup_callback_is_puked - tc = Class.new(TestCase) do - def self.name; nil; end - - setup :bad_callback - def bad_callback; raise 'oh noes' end - def test_true; assert true end - end - - test_name = 'test_true' - fr = FakeRunner.new - - test = tc.new test_name - test.run fr - klass, name, exception = *fr.puked.first - - assert_equal tc, klass - assert_equal test_name, name - assert_equal 'oh noes', exception.message - end - - def test_standard_error_raised_within_teardown_callback_is_puked - tc = Class.new(TestCase) do - def self.name; nil; end - - teardown :bad_callback - def bad_callback; raise 'oh noes' end - def test_true; assert true end - end - - test_name = 'test_true' - fr = FakeRunner.new - - test = tc.new test_name - test.run fr - klass, name, exception = *fr.puked.first - - assert_equal tc, klass - assert_equal test_name, name - assert_equal 'oh noes', exception.message - end - - def test_passthrough_exception_raised_within_test_method_is_not_rescued - tc = Class.new(TestCase) do - def self.name; nil; end - - def test_which_raises_interrupt; raise Interrupt; end - end - - test_name = 'test_which_raises_interrupt' - fr = FakeRunner.new - - test = tc.new test_name - assert_raises(Interrupt) { test.run fr } - end - - def test_passthrough_exception_raised_within_setup_callback_is_not_rescued - tc = Class.new(TestCase) do - def self.name; nil; end - - setup :callback_which_raises_interrupt - def callback_which_raises_interrupt; raise Interrupt; end - def test_true; assert true end - end - - test_name = 'test_true' - fr = FakeRunner.new - - test = tc.new test_name - assert_raises(Interrupt) { test.run fr } - end - - def test_passthrough_exception_raised_within_teardown_callback_is_not_rescued - tc = Class.new(TestCase) do - def self.name; nil; end - - teardown :callback_which_raises_interrupt - def callback_which_raises_interrupt; raise Interrupt; end - def test_true; assert true end - end - - test_name = 'test_true' - fr = FakeRunner.new - - test = tc.new test_name - assert_raises(Interrupt) { test.run fr } - end - def test_pending_deprecation assert_deprecated do pending "should use #skip instead" diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 3e6ac811a4..68f9ec6c00 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -201,6 +201,6 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase end def test_logs_tagged_with_current_test_case - assert_match "#{self.class}: #{__name__}\n", @out.string + assert_match "#{self.class}: #{name}\n", @out.string end end diff --git a/guides/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js index 9e83eb5e7e..5a4fbaa370 100644 --- a/guides/code/getting_started/app/assets/javascripts/application.js +++ b/guides/code/getting_started/app/assets/javascripts/application.js @@ -7,8 +7,7 @@ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. +// stub path allows dependency to be excluded from the asset bundle. // //= require jquery //= require jquery_ujs diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 96229bb4f6..f6bdf129d6 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -94,8 +94,8 @@ module Rails require 'debugger' puts "=> Debugger enabled" rescue LoadError - puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again." - exit + puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." + exit(1) end end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 853d6fa4b9..675ada7ed0 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -116,7 +116,7 @@ module Rails def database_gemfile_entry options[:skip_active_record] ? "" : - <<-GEMFILE.strip_heredoc.chomp + <<-GEMFILE.strip_heredoc # Use #{options[:database]} as the database for Active Record gem '#{gem_for_database}' GEMFILE diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index 7342bffd9d..6899dfe9c9 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -7,8 +7,7 @@ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. +// stub path allows dependency to be excluded from the asset bundle. // <% unless options[:skip_javascript] -%> //= require <%= options[:javascript] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 4f1d56cd2f..f2110c2c70 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -7,8 +7,8 @@ ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end - <%- unless options.skip_active_record? -%> + # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do # self.include_root_in_json = true diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec index f7c12e67dd..5fdf0e1554 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -12,6 +12,7 @@ Gem::Specification.new do |s| s.homepage = "TODO" s.summary = "TODO: Summary of <%= camelized %>." s.description = "TODO: Description of <%= camelized %>." + s.license = "MIT" s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] <% unless options.skip_test_unit? -%> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js index 084d5d1c49..f84de00fd3 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js @@ -7,7 +7,6 @@ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. +// stub path allows dependency to be excluded from the asset bundle. // //= require_tree . diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb index 902361ce77..f7b77bcb3b 100644 --- a/railties/lib/rails/rack/debugger.rb +++ b/railties/lib/rails/rack/debugger.rb @@ -12,8 +12,8 @@ module Rails ::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings) puts "=> Debugger enabled" rescue LoadError - puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again." - exit + puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." + exit(1) end def call(env) diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index fa3ab969ae..6703f8df51 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -214,7 +214,7 @@ module ApplicationTests bundle exec rake db:migrate db:test:clone test` end - assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output) + assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) end @@ -224,7 +224,7 @@ module ApplicationTests bundle exec rake db:migrate db:test:clone test` end - assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output) + assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 1cf53aa4fb..118f22995e 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -32,13 +32,13 @@ module ApplicationTests def test_run_single_file create_test_file :models, 'foo' create_test_file :models, 'bar' - assert_match "1 tests, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb") + assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb") end def test_run_multiple_files create_test_file :models, 'foo' create_test_file :models, 'bar' - assert_match "2 tests, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb") + assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb") end def test_run_file_with_syntax_error @@ -59,7 +59,7 @@ module ApplicationTests run_test_models_command.tap do |output| assert_match "FooTest", output assert_match "BarTest", output - assert_match "2 tests, 2 assertions, 0 failures", output + assert_match "2 runs, 2 assertions, 0 failures", output end end @@ -70,7 +70,7 @@ module ApplicationTests run_test_helpers_command.tap do |output| assert_match "FooHelperTest", output assert_match "BarHelperTest", output - assert_match "2 tests, 2 assertions, 0 failures", output + assert_match "2 runs, 2 assertions, 0 failures", output end end @@ -83,7 +83,7 @@ module ApplicationTests assert_match "FooTest", output assert_match "BarHelperTest", output assert_match "BazUnitTest", output - assert_match "3 tests, 3 assertions, 0 failures", output + assert_match "3 runs, 3 assertions, 0 failures", output end end @@ -94,7 +94,7 @@ module ApplicationTests run_test_controllers_command.tap do |output| assert_match "FooControllerTest", output assert_match "BarControllerTest", output - assert_match "2 tests, 2 assertions, 0 failures", output + assert_match "2 runs, 2 assertions, 0 failures", output end end @@ -105,7 +105,7 @@ module ApplicationTests run_test_mailers_command.tap do |output| assert_match "FooMailerTest", output assert_match "BarMailerTest", output - assert_match "2 tests, 2 assertions, 0 failures", output + assert_match "2 runs, 2 assertions, 0 failures", output end end @@ -118,7 +118,7 @@ module ApplicationTests assert_match "FooMailerTest", output assert_match "BarControllerTest", output assert_match "BazFunctionalTest", output - assert_match "3 tests, 3 assertions, 0 failures", output + assert_match "3 runs, 3 assertions, 0 failures", output end end @@ -127,7 +127,7 @@ module ApplicationTests create_test_file :models, 'foo' run_test_integration_command.tap do |output| assert_match "FooIntegration", output - assert_match "1 tests, 1 assertions, 0 failures", output + assert_match "1 runs, 1 assertions, 0 failures", output end end @@ -136,7 +136,7 @@ module ApplicationTests suites.each { |suite| create_test_file suite, "foo_#{suite}" } run_test_command('') .tap do |output| suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output } - assert_match "7 tests, 7 assertions, 0 failures", output + assert_match "7 runs, 7 assertions, 0 failures", output end end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index ac71fb5884..32c7612a8f 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -168,14 +168,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator FileUtils.cd destination_root quietly { system 'bundle install' } - assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) + assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end def test_ensure_that_tests_works_in_full_mode run_generator [destination_root, "--full", "--skip_active_record"] FileUtils.cd destination_root quietly { system 'bundle install' } - assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) + assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end def test_ensure_that_migration_tasks_work_with_mountable_option |