aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile5
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb6
-rw-r--r--actionmailer/test/abstract_unit.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb3
-rw-r--r--actionpack/test/abstract_unit.rb5
-rw-r--r--actionview/lib/action_view/base.rb6
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb32
-rw-r--r--actionview/test/abstract_unit.rb5
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb36
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/arguments.rb10
-rw-r--r--activejob/lib/active_job/base.rb3
-rw-r--r--activejob/lib/active_job/queue_adapter.rb32
-rw-r--r--activejob/lib/active_job/queue_adapters.rb1
-rw-r--r--activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb19
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb76
-rw-r--r--activejob/lib/active_job/test_case.rb7
-rw-r--r--activejob/lib/active_job/test_helper.rb196
-rw-r--r--activejob/test/cases/rescue_test.rb5
-rw-r--r--activejob/test/cases/test_case_test.rb14
-rw-r--r--activejob/test/cases/test_helper_test.rb214
-rw-r--r--activejob/test/support/queue_classic/inline.rb12
-rw-r--r--activemodel/test/cases/helper.rb5
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb87
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb8
-rw-r--r--activerecord/lib/active_record/fixtures.rb6
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/consistency_test.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb14
-rw-r--r--activerecord/test/cases/associations/required_test.rb4
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb9
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb36
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb40
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb1
-rw-r--r--activerecord/test/support/schema_dumping_helper.rb9
-rw-r--r--activesupport/CHANGELOG.md11
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb6
-rw-r--r--activesupport/test/abstract_unit.rb5
-rw-r--r--activesupport/test/constantize_test_cases.rb30
-rw-r--r--activesupport/test/dependencies_test.rb2
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb8
-rw-r--r--guides/source/4_2_release_notes.md129
-rw-r--r--guides/source/active_job_basics.md31
-rw-r--r--guides/source/active_record_postgresql.md3
-rw-r--r--guides/source/active_support_core_extensions.md46
-rw-r--r--guides/source/asset_pipeline.md210
-rw-r--r--guides/source/caching_with_rails.md2
-rw-r--r--guides/source/configuring.md4
-rw-r--r--guides/source/form_helpers.md2
-rw-r--r--guides/source/getting_started.md2
-rw-r--r--guides/source/layouts_and_rendering.md4
-rw-r--r--railties/lib/rails/generators/app_base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile4
-rw-r--r--railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb3
-rw-r--r--railties/test/abstract_unit.rb4
-rw-r--r--railties/test/application/default_stack_test.rb41
-rw-r--r--railties/test/application/rake/dbs_test.rb26
65 files changed, 1314 insertions, 198 deletions
diff --git a/Gemfile b/Gemfile
index 7616f2d0be..1d135a1af1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,9 +8,10 @@ gemspec
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'jquery-rails', '~> 3.1.0'
+gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
gem 'coffee-rails', '~> 4.0.0'
-gem 'rails-html-sanitizer'
+gem 'rails-html-sanitizer', github: 'rails/rails-html-sanitizer'
+gem 'rails-deprecated_sanitizer', github: 'rails/rails-deprecated_sanitizer'
gem 'turbolinks', '~> 2.2.3'
# require: false so bcrypt is loaded only when has_secure_password is used.
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index 30425d38f9..ce416b09d5 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -38,9 +38,9 @@ module ActionMailer
# that the message will be sent bypassing checking +perform_deliveries+
# and +raise_delivery_errors+, so use with caution.
#
- # Notifier.welcome(User.first).deliver_later
- # Notifier.welcome(User.first).deliver_later(in: 1.hour)
- # Notifier.welcome(User.first).deliver_later(at: 10.hours.from_now)
+ # Notifier.welcome(User.first).deliver_later!
+ # Notifier.welcome(User.first).deliver_later!(in: 1.hour)
+ # Notifier.welcome(User.first).deliver_later!(at: 10.hours.from_now)
#
# Options:
#
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index c4dbbe25f6..ce426cad1e 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -42,3 +42,8 @@ def jruby_skip(message = '')
end
require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 427a5674bd..f15868d37e 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -197,7 +197,8 @@ module ActionDispatch
case record_or_hash_or_array
when Array
- if record_or_hash_or_array.empty? || record_or_hash_or_array.include?(nil)
+ record_or_hash_or_array = record_or_hash_or_array.compact
+ if record_or_hash_or_array.empty?
raise ArgumentError, "Nil location provided. Can't build URI."
end
if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index e14940d632..799c17119d 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -512,3 +512,8 @@ if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0
# Use N processes (N defaults to 4)
Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT)
end
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 86c55ffb51..d1bade0d3d 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -10,8 +10,10 @@ require 'action_view/lookup_context'
module ActionView #:nodoc:
# = Action View Base
#
- # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERB
- # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used.
+ # Action View templates can be written in several ways.
+ # If the template file has a <tt>.erb</tt> extension, then it uses the erubis[https://rubygems.org/gems/erubis]
+ # template system which can embed Ruby into an HTML document.
+ # If the template file has a <tt>.builder</tt> extension, then Jim Weirich's Builder::XmlMarkup library is used.
#
# == ERB
#
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index dfbc52e3ac..394250f058 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -121,22 +121,6 @@ module ActionView
module ClassMethods #:nodoc:
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
- [:protocol_separator,
- :uri_attributes,
- :bad_tags,
- :allowed_css_properties,
- :allowed_css_keywords,
- :shorthand_css_properties,
- :allowed_protocols].each do |meth|
- meth_name = "sanitized_#{meth}"
- imp = lambda do |name|
- ActiveSupport::Deprecation.warn("#{name} is deprecated and has no effect.")
- end
-
- define_method(meth_name) { imp.(meth_name) }
- define_method("#{meth_name}=") { |value| imp.("#{meth_name}=") }
- end
-
# Vendors the full, link and white list sanitizers.
# This uses html-scanner for the HTML sanitization.
# In the next Rails version this will use Rails::Html::Sanitizer instead.
@@ -189,25 +173,29 @@ module ActionView
@white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
end
+ ##
+ # :method: sanitized_allowed_tags=
+ #
+ # :call-seq: sanitized_allowed_tags=(tags)
+ #
# Replaces the allowed tags for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
# end
#
- def sanitized_allowed_tags=(tags)
- sanitizer_vendor.white_list_sanitizer.allowed_tags = tags
- end
+ ##
+ # :method: sanitized_allowed_attributes=
+ #
+ # :call-seq: sanitized_allowed_attributes=(attributes)
+ #
# Replaces the allowed HTML attributes for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
# end
#
- def sanitized_allowed_attributes=(attributes)
- sanitizer_vendor.white_list_sanitizer.allowed_attributes = attributes
- end
end
end
end
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index d60712255b..923e637f11 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -340,3 +340,8 @@ def jruby_skip(message = '')
end
require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 0d97ddb2f6..4e94304796 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -183,16 +183,33 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- def test_with_nil_in_list
+ def test_with_entirely_nil_list
with_test_routes do
exception = assert_raise ArgumentError do
@series.save
- polymorphic_url([nil, @series])
+ polymorphic_url([nil, nil])
end
assert_equal "Nil location provided. Can't build URI.", exception.message
end
end
+ def test_with_nil_in_list_for_resource_that_could_be_top_level_or_nested
+ with_top_level_and_nested_routes do
+ @blog_post.save
+ assert_equal "http://example.com/posts/#{@blog_post.id}", polymorphic_url([nil, @blog_post])
+ end
+ end
+
+ def test_with_nil_in_list_does_not_generate_invalid_link
+ with_top_level_and_nested_routes do
+ exception = assert_raise NoMethodError do
+ @series.save
+ polymorphic_url([nil, @series])
+ end
+ assert_match(/undefined method `series_url' for/, exception.message)
+ end
+ end
+
def test_with_record
with_test_routes do
@project.save
@@ -626,6 +643,21 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def with_top_level_and_nested_routes(options = {})
+ with_routing do |set|
+ set.draw do
+ resources :blogs do
+ resources :posts
+ resources :series
+ end
+ resources :posts
+ end
+
+ extend @routes.url_helpers
+ yield
+ end
+ end
+
def with_admin_test_routes(options = {})
with_routing do |set|
set.draw do
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 29123170b8..93d9d1b2d8 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -31,4 +31,6 @@ module ActiveJob
autoload :Base
autoload :QueueAdapters
+ autoload :TestCase
+ autoload :TestHelper
end
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index e54f4afbc7..9d4490b0fc 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -5,7 +5,7 @@ module ActiveJob
class DeserializationError < StandardError
attr_reader :original_exception
- def initialize(e)
+ def initialize(e) #:nodoc:
super ("Error while trying to deserialize arguments: #{e.message}")
@original_exception = e
set_backtrace e.backtrace
@@ -30,6 +30,8 @@ module ActiveJob
def deserialize(arguments)
arguments.map { |argument| deserialize_argument(argument) }
+ rescue => e
+ raise DeserializationError.new(e)
end
private
@@ -40,7 +42,7 @@ module ActiveJob
when *TYPE_WHITELIST
argument
when Array
- serialize(argument)
+ argument.map { |arg| serialize_argument(arg) }
when Hash
Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
else
@@ -51,14 +53,12 @@ module ActiveJob
def deserialize_argument(argument)
case argument
when Array
- deserialize(argument)
+ argument.map { |arg| deserialize_argument(arg) }
when Hash
Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access
else
GlobalID::Locator.locate(argument) || argument
end
- rescue => e
- raise DeserializationError.new(e)
end
def serialize_hash_key(key)
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index d5ba253016..1b54786303 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -8,8 +8,7 @@ require 'active_job/logging'
module ActiveJob
class Base
- extend QueueAdapter
-
+ include QueueAdapter
include QueueName
include Enqueuing
include Execution
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 13c23abce4..fb54aec75e 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -3,21 +3,27 @@ require 'active_support/core_ext/string/inflections'
module ActiveJob
module QueueAdapter
- mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter }
+ extend ActiveSupport::Concern
- def queue_adapter=(name_or_adapter)
- @@queue_adapter = \
- case name_or_adapter
- when Symbol, String
- load_adapter(name_or_adapter)
- when Class
- name_or_adapter
- end
- end
+ module ClassMethods
+ mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter }
- private
- def load_adapter(name)
- "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
+ def queue_adapter=(name_or_adapter)
+ @@queue_adapter = \
+ case name_or_adapter
+ when :test
+ ActiveJob::QueueAdapters::TestAdapter.new
+ when Symbol, String
+ load_adapter(name_or_adapter)
+ when Class
+ name_or_adapter
+ end
end
+
+ private
+ def load_adapter(name)
+ "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
+ end
+ end
end
end \ No newline at end of file
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index 007068ff0a..345b01ef00 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -12,5 +12,6 @@ module ActiveJob
autoload :SidekiqAdapter
autoload :SneakersAdapter
autoload :SuckerPunchAdapter
+ autoload :TestAdapter
end
end
diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
index d74f8cf90e..914390a958 100644
--- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
@@ -5,11 +5,26 @@ module ActiveJob
class QueueClassicAdapter
class << self
def enqueue(job, *args)
- QC::Queue.new(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args)
+ build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args)
end
def enqueue_at(job, timestamp, *args)
- raise NotImplementedError
+ queue = build_queue(job.queue_name)
+ unless queue.respond_to?(:enqueue_at)
+ raise NotImplementedError, 'To be able to schedule jobs with Queue Classic ' \
+ 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. '
+ 'You can implement this yourself or you can use the queue_classic-later gem.'
+ end
+ queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.name, *args)
+ end
+
+ # Builds a <tt>QC::Queue</tt> object to schedule jobs on.
+ #
+ # If you have a custom <tt>QC::Queue</tt> subclass you'll need to suclass
+ # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the
+ # <tt>build_queue</tt> method.
+ def build_queue(queue_name)
+ QC::Queue.new(queue_name)
end
end
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
new file mode 100644
index 0000000000..185d6fc7e6
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -0,0 +1,76 @@
+module ActiveJob
+ module QueueAdapters
+ class TestAdapter
+ attr_accessor(:perform_enqueued_jobs) { false }
+ attr_accessor(:perform_enqueued_at_jobs) { false }
+ delegate :name, to: :class
+
+ # Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
+ def enqueued_jobs
+ @enqueued_jobs ||= []
+ end
+
+ # Allows you to overwrite the default enqueued jobs store from an array to some
+ # other object. If you just want to clear the store,
+ # call ActiveJob::QueueAdapters::TestAdapter.enqueued_jobs.clear.
+ #
+ # If you place another object here, please make sure it responds to:
+ #
+ # * << (message)
+ # * clear
+ # * length
+ # * size
+ # * and other common Array methods
+ def enqueued_jobs=(val)
+ @enqueued_jobs = val
+ end
+
+ # Provides a store of all the performed jobs with the TestAdapter so you can check them.
+ def performed_jobs
+ @performed_jobs ||= []
+ end
+
+ # Allows you to overwrite the default performed jobs store from an array to some
+ # other object. If you just want to clear the store,
+ # call ActiveJob::QueueAdapters::TestAdapter.performed_jobs.clear.
+ #
+ # If you place another object here, please make sure it responds to:
+ #
+ # * << (message)
+ # * clear
+ # * length
+ # * size
+ # * and other common Array methods
+ def performed_jobs=(val)
+ @performed_jobs = val
+ end
+
+ def enqueue(job, *args)
+ if perform_enqueued_jobs?
+ performed_jobs << {job: job, args: args, queue: job.queue_name}
+ job.new.execute(*args)
+ else
+ enqueued_jobs << {job: job, args: args, queue: job.queue_name}
+ end
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ if perform_enqueued_at_jobs?
+ performed_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp}
+ job.new.execute(*args)
+ else
+ enqueued_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp}
+ end
+ end
+
+ private
+ def perform_enqueued_jobs?
+ perform_enqueued_jobs
+ end
+
+ def perform_enqueued_at_jobs?
+ perform_enqueued_at_jobs
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/test_case.rb b/activejob/lib/active_job/test_case.rb
new file mode 100644
index 0000000000..d894a7b5cd
--- /dev/null
+++ b/activejob/lib/active_job/test_case.rb
@@ -0,0 +1,7 @@
+require 'active_support/test_case'
+
+module ActiveJob
+ class TestCase < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+ end
+end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
new file mode 100644
index 0000000000..767619097c
--- /dev/null
+++ b/activejob/lib/active_job/test_helper.rb
@@ -0,0 +1,196 @@
+module ActiveJob
+ # Provides helper methods for testing Active Job
+ module TestHelper
+ extend ActiveSupport::Concern
+
+ included do
+ def before_setup
+ @old_queue_adapter = queue_adapter
+ ActiveJob::Base.queue_adapter = :test
+ clear_enqueued_jobs
+ clear_performed_jobs
+ super
+ end
+
+ def after_teardown
+ super
+ ActiveJob::Base.queue_adapter = @old_queue_adapter
+ end
+
+ # Asserts that the number of enqueued jobs matches the given number.
+ #
+ # def test_jobs
+ # assert_enqueued_jobs 0
+ # HelloJob.enqueue('david')
+ # assert_enqueued_jobs 1
+ # HelloJob.enqueue('abdelkader')
+ # assert_enqueued_jobs 2
+ # end
+ #
+ # If a block is passed, that block should cause the specified number of
+ # jobs to be enqueued.
+ #
+ # def test_jobs_again
+ # assert_enqueued_jobs 1 do
+ # HelloJob.enqueue('cristian')
+ # end
+ #
+ # assert_enqueued_jobs 2 do
+ # HelloJob.enqueue('aaron')
+ # HelloJob.enqueue('rafael')
+ # end
+ # end
+ def assert_enqueued_jobs(number)
+ if block_given?
+ original_count = enqueued_jobs.size
+ yield
+ new_count = enqueued_jobs.size
+ assert_equal original_count + number, new_count,
+ "#{number} jobs expected, but #{new_count - original_count} were enqueued"
+ else
+ enqueued_jobs_size = enqueued_jobs.size
+ assert_equal number, enqueued_jobs_size, "#{number} jobs expected, but #{enqueued_jobs_size} were enqueued"
+ end
+ end
+
+ # Assert that no job have been enqueued.
+ #
+ # def test_jobs
+ # assert_no_enqueued_jobs
+ # HelloJob.enqueue('jeremy')
+ # assert_enqueued_jobs 1
+ # end
+ #
+ # If a block is passed, that block should not cause any job to be enqueued.
+ #
+ # def test_jobs_again
+ # assert_no_enqueued_jobs do
+ # # No job should be enqueued from this block
+ # end
+ # end
+ #
+ # Note: This assertion is simply a shortcut for:
+ #
+ # assert_enqueued_jobs 0
+ def assert_no_enqueued_jobs(&block)
+ assert_enqueued_jobs 0, &block
+ end
+
+ # Asserts that the number of performed jobs matches the given number.
+ #
+ # def test_jobs
+ # assert_performed_jobs 0
+ # HelloJob.enqueue('xavier')
+ # assert_performed_jobs 1
+ # HelloJob.enqueue('yves')
+ # assert_performed_jobs 2
+ # end
+ #
+ # If a block is passed, that block should cause the specified number of
+ # jobs to be performed.
+ #
+ # def test_jobs_again
+ # assert_performed_jobs 1 do
+ # HelloJob.enqueue('robin')
+ # end
+ #
+ # assert_performed_jobs 2 do
+ # HelloJob.enqueue('carlos')
+ # HelloJob.enqueue('sean')
+ # end
+ # end
+ def assert_performed_jobs(number)
+ if block_given?
+ original_count = performed_jobs.size
+ yield
+ new_count = performed_jobs.size
+ assert_equal original_count + number, new_count,
+ "#{number} jobs expected, but #{new_count - original_count} were performed"
+ else
+ performed_jobs_size = performed_jobs.size
+ assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
+ end
+ end
+
+ # Asserts that no jobs have been performed.
+ #
+ # def test_jobs
+ # assert_no_performed_jobs
+ # HelloJob.enqueue('matthew')
+ # assert_performed_jobs 1
+ # end
+ #
+ # If a block is passed, that block should not cause any job to be performed.
+ #
+ # def test_jobs_again
+ # assert_no_performed_jobs do
+ # # No job should be performed from this block
+ # end
+ # end
+ #
+ # Note: This assertion is simply a shortcut for:
+ #
+ # assert_performed_jobs 0
+ def assert_no_performed_jobs(&block)
+ assert_performed_jobs 0, &block
+ end
+
+ # Asserts that the job passed in the block has been enqueued with the given arguments.
+ #
+ # def assert_enqueued_job
+ # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
+ # MyJob.enqueue(1,2,3)
+ # end
+ # end
+ def assert_enqueued_with(args = {}, &_block)
+ original_enqueued_jobs = enqueued_jobs.dup
+ clear_enqueued_jobs
+ args.assert_valid_keys(:job, :args, :at, :queue)
+ yield
+ matching_job = enqueued_jobs.any? do |job|
+ args.all? { |key, value| value == job[key] }
+ end
+ assert matching_job
+ ensure
+ queue_adapter.enqueued_jobs = original_enqueued_jobs + enqueued_jobs
+ end
+
+ # Asserts that the job passed in the block has been performed with the given arguments.
+ #
+ # def test_assert_performed_with
+ # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
+ # MyJob.enqueue(1,2,3)
+ # end
+ # end
+ def assert_performed_with(args = {}, &_block)
+ original_performed_jobs = performed_jobs.dup
+ clear_performed_jobs
+ args.assert_valid_keys(:job, :args, :at, :queue)
+ yield
+ matching_job = performed_jobs.any? do |job|
+ args.all? { |key, value| value == job[key] }
+ end
+ assert matching_job, "No performed job found with #{args}"
+ ensure
+ queue_adapter.performed_jobs = original_performed_jobs + performed_jobs
+ end
+
+ def queue_adapter
+ ActiveJob::Base.queue_adapter
+ end
+
+ delegate :enqueued_jobs, :enqueued_jobs=,
+ :performed_jobs, :performed_jobs=,
+ to: :queue_adapter
+
+ private
+ def clear_enqueued_jobs
+ enqueued_jobs.clear
+ end
+
+ def clear_performed_jobs
+ performed_jobs.clear
+ end
+ end
+ end
+end
diff --git a/activejob/test/cases/rescue_test.rb b/activejob/test/cases/rescue_test.rb
index d9ea3c91d7..3af147383e 100644
--- a/activejob/test/cases/rescue_test.rb
+++ b/activejob/test/cases/rescue_test.rb
@@ -28,4 +28,9 @@ class RescueTest < ActiveSupport::TestCase
assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound'
assert_not_includes JobBuffer.values, 'performed beautifully'
end
+
+ test "should not wrap DeserializationError in DeserializationError" do
+ RescueJob.enqueue [Person.new(404)]
+ assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound'
+ end
end
diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb
new file mode 100644
index 0000000000..1d0fdbd22d
--- /dev/null
+++ b/activejob/test/cases/test_case_test.rb
@@ -0,0 +1,14 @@
+require 'helper'
+require 'jobs/hello_job'
+require 'jobs/logging_job'
+require 'jobs/nested_job'
+
+class ActiveJobTestCaseTest < ActiveJob::TestCase
+ def test_include_helper
+ assert_includes self.class.ancestors, ActiveJob::TestHelper
+ end
+
+ def test_set_test_adapter
+ assert_instance_of ActiveJob::QueueAdapters::TestAdapter, self.queue_adapter
+ end
+end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
new file mode 100644
index 0000000000..240aa23ce3
--- /dev/null
+++ b/activejob/test/cases/test_helper_test.rb
@@ -0,0 +1,214 @@
+require 'helper'
+require 'active_support/core_ext/time'
+require 'active_support/core_ext/date'
+require 'jobs/hello_job'
+require 'jobs/logging_job'
+require 'jobs/nested_job'
+
+class EnqueuedJobsTest < ActiveJob::TestCase
+ setup { queue_adapter.perform_enqueued_at_jobs = true }
+
+ def test_assert_enqueued_jobs
+ assert_nothing_raised do
+ assert_enqueued_jobs 1 do
+ HelloJob.enqueue('david')
+ end
+ end
+ end
+
+ def test_repeated_enqueued_jobs_calls
+ assert_nothing_raised do
+ assert_enqueued_jobs 1 do
+ HelloJob.enqueue('abdelkader')
+ end
+ end
+
+ assert_nothing_raised do
+ assert_enqueued_jobs 2 do
+ HelloJob.enqueue('sean')
+ HelloJob.enqueue('yves')
+ end
+ end
+ end
+
+ def test_assert_enqueued_jobs_with_no_block
+ assert_nothing_raised do
+ HelloJob.enqueue('rafael')
+ assert_enqueued_jobs 1
+ end
+
+ assert_nothing_raised do
+ HelloJob.enqueue('aaron')
+ HelloJob.enqueue('matthew')
+ assert_enqueued_jobs 3
+ end
+ end
+
+ def test_assert_no_enqueued_jobs
+ assert_nothing_raised do
+ assert_no_enqueued_jobs do
+ # Scheduled jobs are being performed in this context
+ HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey')
+ end
+ end
+ end
+
+ def test_assert_enqueued_jobs_too_few_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_jobs 2 do
+ HelloJob.enqueue('xavier')
+ end
+ end
+
+ assert_match(/2 .* but 1/, error.message)
+ end
+
+ def test_assert_enqueued_jobs_too_many_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_jobs 1 do
+ HelloJob.enqueue('cristian')
+ HelloJob.enqueue('guillermo')
+ end
+ end
+
+ assert_match(/1 .* but 2/, error.message)
+ end
+
+ def test_assert_no_enqueued_jobs_failure
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_no_enqueued_jobs do
+ HelloJob.enqueue('jeremy')
+ end
+ end
+
+ assert_match(/0 .* but 1/, error.message)
+ end
+
+ def test_assert_enqueued_job
+ assert_enqueued_with(job: LoggingJob, queue: 'default') do
+ NestedJob.enqueue_at(Date.tomorrow.noon)
+ end
+ end
+
+ def test_assert_enqueued_job_failure
+ assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_with(job: LoggingJob, queue: 'default') do
+ NestedJob.enqueue
+ end
+ end
+
+ assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_with(job: NestedJob, queue: 'low') do
+ NestedJob.enqueue
+ end
+ end
+ end
+
+ def test_assert_enqueued_job_args
+ assert_raise ArgumentError do
+ assert_enqueued_with(class: LoggingJob) do
+ NestedJob.enqueue_at(Date.tomorrow.noon)
+ end
+ end
+ end
+end
+
+class PerformedJobsTest < ActiveJob::TestCase
+ setup { queue_adapter.perform_enqueued_jobs = true }
+
+ def test_assert_performed_jobs
+ assert_nothing_raised do
+ assert_performed_jobs 1 do
+ HelloJob.enqueue('david')
+ end
+ end
+ end
+
+ def test_repeated_performed_jobs_calls
+ assert_nothing_raised do
+ assert_performed_jobs 1 do
+ HelloJob.enqueue('abdelkader')
+ end
+ end
+
+ assert_nothing_raised do
+ assert_performed_jobs 2 do
+ HelloJob.enqueue('sean')
+ HelloJob.enqueue('yves')
+ end
+ end
+ end
+
+ def test_assert_performed_jobs_with_no_block
+ assert_nothing_raised do
+ HelloJob.enqueue('rafael')
+ assert_performed_jobs 1
+ end
+
+ assert_nothing_raised do
+ HelloJob.enqueue('aaron')
+ HelloJob.enqueue('matthew')
+ assert_performed_jobs 3
+ end
+ end
+
+ def test_assert_no_performed_jobs
+ assert_nothing_raised do
+ assert_no_performed_jobs do
+ # Scheduled jobs are being enqueued in this context
+ HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey')
+ end
+ end
+ end
+
+ def test_assert_performed_jobs_too_few_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 2 do
+ HelloJob.enqueue('xavier')
+ end
+ end
+
+ assert_match(/2 .* but 1/, error.message)
+ end
+
+ def test_assert_performed_jobs_too_many_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 1 do
+ HelloJob.enqueue('cristian')
+ HelloJob.enqueue('guillermo')
+ end
+ end
+
+ assert_match(/1 .* but 2/, error.message)
+ end
+
+ def test_assert_no_performed_jobs_failure
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_no_performed_jobs do
+ HelloJob.enqueue('jeremy')
+ end
+ end
+
+ assert_match(/0 .* but 1/, error.message)
+ end
+
+ def test_assert_performed_job
+ assert_performed_with(job: NestedJob, queue: 'default') do
+ NestedJob.enqueue
+ end
+ end
+
+ def test_assert_performed_job_failure
+ assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_with(job: LoggingJob, queue: 'default') do
+ NestedJob.enqueue_at(Date.tomorrow.noon)
+ end
+ end
+
+ assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_with(job: NestedJob, queue: 'low') do
+ NestedJob.enqueue_at(Date.tomorrow.noon)
+ end
+ end
+ end
+end
diff --git a/activejob/test/support/queue_classic/inline.rb b/activejob/test/support/queue_classic/inline.rb
index 5e9c295e01..5743d5bbb5 100644
--- a/activejob/test/support/queue_classic/inline.rb
+++ b/activejob/test/support/queue_classic/inline.rb
@@ -7,5 +7,17 @@ module QC
receiver = eval(receiver_str)
receiver.send(message, *args)
end
+
+ def enqueue_in(seconds, method, *args)
+ receiver_str, _, message = method.rpartition('.')
+ receiver = eval(receiver_str)
+ receiver.send(message, *args)
+ end
+
+ def enqueue_at(not_before, method, *args)
+ receiver_str, _, message = method.rpartition('.')
+ receiver = eval(receiver_str)
+ receiver.send(message, *args)
+ end
end
end
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 804e0c24f6..5e80353836 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -13,3 +13,8 @@ I18n.enforce_available_locales = false
require 'active_support/testing/autorun'
require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 865d1b547a..be47ef75e7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,10 @@
+* Schema loading rake tasks (like `db:schema:load` and `db:setup`) maintain
+ the database connection to the current environment.
+
+ Fixes #16757.
+
+ *Joshua Cody*, *Yves Senn*
+
* MySQL: set the connection collation along with the charset.
Sets the connection collation to the database collation configured in
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 4c47af8cb0..26fed22b4c 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -45,20 +45,20 @@ module ActiveRecord
end
def self.get_bind_values(owner, chain)
- bvs = []
- chain.each_with_index do |reflection, i|
- if reflection == chain.last
- bvs << reflection.join_id_for(owner)
- if reflection.type
- bvs << owner.class.base_class.name
- end
- else
- if reflection.type
- bvs << chain[i + 1].klass.base_class.name
- end
+ binds = []
+ last_reflection = chain.last
+
+ binds << last_reflection.join_id_for(owner)
+ if last_reflection.type
+ binds << owner.class.base_class.name
+ end
+
+ chain.each_cons(2).each do |reflection, next_reflection|
+ if reflection.type
+ binds << next_reflection.klass.base_class.name
end
end
- bvs
+ binds
end
private
@@ -96,38 +96,55 @@ module ActiveRecord
bind_value scope, column, value, tracker
end
+ def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
+ join_keys = reflection.join_keys(assoc_klass)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
+
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
+ scope = scope.where(table[key].eq(bind_val))
+
+ if reflection.type
+ value = owner.class.base_class.name
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
+ scope = scope.where(table[reflection.type].eq(bind_val))
+ else
+ scope
+ end
+ end
+
+ def next_chain_scope(scope, table, reflection, chain, tracker, assoc_klass, foreign_table, next_reflection)
+ join_keys = reflection.join_keys(assoc_klass)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
+
+ constraint = table[key].eq(foreign_table[foreign_key])
+
+ if reflection.type
+ value = next_reflection.klass.base_class.name
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
+ scope = scope.where(table[reflection.type].eq(bind_val))
+ end
+
+ scope = scope.joins(join(foreign_table, constraint))
+ end
+
def add_constraints(scope, owner, assoc_klass, refl, tracker)
chain = refl.chain
scope_chain = refl.scope_chain
tables = construct_tables(chain, assoc_klass, refl, tracker)
+ reflection = chain.last
+ table = tables.last
+ scope = last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
+
chain.each_with_index do |reflection, i|
table, foreign_table = tables.shift, tables.first
- join_keys = reflection.join_keys(assoc_klass)
- key = join_keys.key
- foreign_key = join_keys.foreign_key
-
- if reflection == chain.last
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
- scope = scope.where(table[key].eq(bind_val))
-
- if reflection.type
- value = owner.class.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
- scope = scope.where(table[reflection.type].eq(bind_val))
- end
- else
- constraint = table[key].eq(foreign_table[foreign_key])
-
- if reflection.type
- value = chain[i + 1].klass.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
- scope = scope.where(table[reflection.type].eq(bind_val))
- end
-
- scope = scope.joins(join(foreign_table, constraint))
+ unless reflection == chain.last
+ next_reflection = chain[i + 1]
+ scope = next_chain_scope(scope, table, reflection, chain, tracker, assoc_klass, foreign_table, next_reflection)
end
is_first_chain = i == 0
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index 0f9723febb..dc689f399a 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -8,7 +8,7 @@ module ActiveRecord
end
def query_attribute(attr_name)
- value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
+ value = self[attr_name]
case value
when true then true
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index adad6cd542..6bab260f5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/strip'
+
module ActiveRecord
module ConnectionAdapters
class AbstractAdapter
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index cf0e3a260d..fe00f9d750 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -2,7 +2,6 @@ require 'date'
require 'set'
require 'bigdecimal'
require 'bigdecimal/util'
-require 'active_support/core_ext/string/strip'
module ActiveRecord
module ConnectionAdapters #:nodoc:
@@ -326,7 +325,6 @@ module ActiveRecord
end
column.limit = limit
- column.array = options[:array] if column.respond_to?(:array)
column.precision = options[:precision]
column.scale = options[:scale]
column.default = options[:default]
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index d5be8034e7..1a76beb3f3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,4 +1,5 @@
require 'arel/visitors/bind_visitor'
+require 'active_support/core_ext/string/strip'
module ActiveRecord
module ConnectionAdapters
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 83554bbf74..b37630a04c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -131,12 +131,10 @@ module ActiveRecord
column name, type, options
end
- def column(name, type = nil, options = {})
- super
- column = self[name]
+ def new_column_definition(name, type, options) # :nodoc:
+ column = super
column.array = options[:array]
-
- self
+ column
end
private
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index a85ba6b539..4527452f1a 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -515,7 +515,7 @@ module ActiveRecord
::File.join(fixtures_directory, fs_name))
end
- all_loaded_fixtures.update(fixtures_map)
+ update_all_loaded_fixtures fixtures_map
connection.transaction(:requires_new => true) do
fixture_sets.each do |fs|
@@ -562,6 +562,10 @@ module ActiveRecord
@context_class ||= Class.new
end
+ def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
+ all_loaded_fixtures.update(fixtures_map)
+ end
+
attr_reader :table_name, :name, :fixtures, :model_class, :config
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 331c1b7b63..f9b54139d5 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -171,6 +171,7 @@ module ActiveRecord
each_current_configuration(environment) { |configuration|
purge configuration
}
+ ActiveRecord::Base.establish_connection(environment.to_sym)
end
def structure_dump(*arguments)
@@ -217,6 +218,7 @@ module ActiveRecord
each_current_configuration(environment) { |configuration|
load_schema_for configuration, format, file
}
+ ActiveRecord::Base.establish_connection(environment.to_sym)
end
def check_schema_file(filename)
diff --git a/activerecord/test/cases/adapters/mysql/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb
index 083d533bb2..e972d6b330 100644
--- a/activerecord/test/cases/adapters/mysql/consistency_test.rb
+++ b/activerecord/test/cases/adapters/mysql/consistency_test.rb
@@ -12,6 +12,7 @@ class MysqlConsistencyTest < ActiveRecord::TestCase
ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
@connection = ActiveRecord::Base.connection
+ @connection.clear_cache!
@connection.create_table("mysql_consistency") do |t|
t.boolean "a_bool"
t.string "a_string"
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index f3c711a64b..03627135b2 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -9,6 +9,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
+ @connection.clear_cache!
@connection.create_table("mysql_booleans") do |t|
t.boolean "archived"
t.string "published", limit: 1
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 43c9116b5a..1b7e60565d 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -66,12 +66,14 @@ module ActiveRecord
assert_equal :fulltext, index_c.type
end
- def test_drop_temporary_table
- @connection.transaction do
- @connection.create_table(:temp_table, temporary: true)
- # if it doesn't properly say DROP TEMPORARY TABLE, the transaction commit
- # will complain that no transaction is active
- @connection.drop_table(:temp_table, temporary: true)
+ unless mysql_enforcing_gtid_consistency?
+ def test_drop_temporary_table
+ @connection.transaction do
+ @connection.create_table(:temp_table, temporary: true)
+ # if it doesn't properly say DROP TEMPORARY TABLE, the transaction commit
+ # will complain that no transaction is active
+ @connection.drop_table(:temp_table, temporary: true)
+ end
end
end
end
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index a6934a056e..24a332bb4a 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -18,8 +18,8 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute("DROP TABLE IF EXISTS parents")
- @connection.execute("DROP TABLE IF EXISTS children")
+ @connection.execute("DROP TABLE parents") if @connection.table_exists? 'parents'
+ @connection.execute("DROP TABLE children") if @connection.table_exists? 'children'
end
test "belongs_to associations are not required by default" do
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
index 32977b805f..644876752e 100644
--- a/activerecord/test/cases/attribute_decorators_test.rb
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -28,7 +28,7 @@ module ActiveRecord
teardown do
return unless @connection
- @connection.execute 'DROP TABLE IF EXISTS attribute_decorators_model'
+ @connection.execute 'DROP TABLE attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model'
Model.attribute_type_decorations.clear
Model.reset_column_information
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 209ef597db..1a31c2ec4a 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -50,6 +50,10 @@ def mysql_56?
ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
end
+def mysql_enforcing_gtid_consistency?
+ current_adapter?(:MysqlAdapter, :Mysql2Adapter) && 'ON' == ActiveRecord::Base.connection.show_variable('enforce_gtid_consistency')
+end
+
def supports_savepoints?
ActiveRecord::Base.connection.supports_savepoints?
end
@@ -204,3 +208,8 @@ module InTimeZone
end
require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index c985092b4c..ba28d55e4c 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -29,8 +29,8 @@ module ActiveRecord
teardown do
if defined?(@connection)
- @connection.execute "DROP TABLE IF EXISTS astronauts"
- @connection.execute "DROP TABLE IF EXISTS rockets"
+ @connection.execute "DROP TABLE astronauts" if @connection.table_exists? 'astronauts'
+ @connection.execute "DROP TABLE rockets" if @connection.table_exists? 'rockets'
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 633077622c..270e446c69 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -430,30 +430,32 @@ class MigrationTest < ActiveRecord::TestCase
Person.connection.drop_table :binary_testings rescue nil
end
- def test_create_table_with_query
- Person.connection.drop_table :table_from_query_testings rescue nil
- Person.connection.create_table(:person, force: true)
+ unless mysql_enforcing_gtid_consistency?
+ def test_create_table_with_query
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ Person.connection.create_table(:person, force: true)
- Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
+ Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
- columns = Person.connection.columns(:table_from_query_testings)
- assert_equal 1, columns.length
- assert_equal "id", columns.first.name
+ columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal 1, columns.length
+ assert_equal "id", columns.first.name
- Person.connection.drop_table :table_from_query_testings rescue nil
- end
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ end
- def test_create_table_with_query_from_relation
- Person.connection.drop_table :table_from_query_testings rescue nil
- Person.connection.create_table(:person, force: true)
+ def test_create_table_with_query_from_relation
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ Person.connection.create_table(:person, force: true)
- Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
+ Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
- columns = Person.connection.columns(:table_from_query_testings)
- assert_equal 1, columns.length
- assert_equal "id", columns.first.name
+ columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal 1, columns.length
+ assert_equal "id", columns.first.name
- Person.connection.drop_table :table_from_query_testings rescue nil
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ end
end
if current_adapter? :OracleAdapter
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 066e6b6468..d5584ad06c 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require 'support/schema_dumping_helper'
class SchemaDumperTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
self.use_transactional_fixtures = false
setup do
@@ -10,9 +11,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
def standard_dump
@stream = StringIO.new
- ActiveRecord::SchemaDumper.ignore_tables = []
+ old_ignore_tables, ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::SchemaDumper.ignore_tables, []
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, @stream)
@stream.string
+ ensure
+ ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
end
def test_dump_schema_information_outputs_lexically_ordered_versions
@@ -90,20 +93,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dump_includes_not_null_columns
- stream = StringIO.new
-
- ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/]
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- output = stream.string
+ output = dump_all_table_schema([/^[^r]/])
assert_match %r{null: false}, output
end
def test_schema_dump_includes_limit_constraint_for_integer_columns
- stream = StringIO.new
-
- ActiveRecord::SchemaDumper.ignore_tables = [/^(?!integer_limits)/]
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- output = stream.string
+ output = dump_all_table_schema([/^(?!integer_limits)/])
if current_adapter?(:PostgreSQLAdapter)
assert_match %r{c_int_1.*limit: 2}, output
@@ -150,22 +145,14 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dump_with_string_ignored_table
- stream = StringIO.new
-
- ActiveRecord::SchemaDumper.ignore_tables = ['accounts']
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- output = stream.string
+ output = dump_all_table_schema(['accounts'])
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
end
def test_schema_dump_with_regexp_ignored_table
- stream = StringIO.new
-
- ActiveRecord::SchemaDumper.ignore_tables = [/^account/]
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- output = stream.string
+ output = dump_all_table_schema([/^account/])
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
@@ -173,10 +160,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_illegal_ignored_table_value
stream = StringIO.new
- ActiveRecord::SchemaDumper.ignore_tables = [5]
+ old_ignore_tables, ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::SchemaDumper.ignore_tables, [5]
assert_raise(StandardError) do
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
end
+ ensure
+ ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
end
def test_schema_dumps_index_columns_in_right_order
@@ -245,10 +234,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dump_includes_decimal_options
- stream = StringIO.new
- ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/]
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- output = stream.string
+ output = dump_all_table_schema([/^[^n]/])
assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output
end
@@ -441,7 +427,7 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
teardown do
return unless @connection
- @connection.execute 'DROP TABLE IF EXISTS defaults'
+ @connection.execute 'DROP TABLE defaults' if @connection.table_exists? 'defaults'
end
def test_schema_dump_defaults_with_universally_supported_types
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 01d373b691..2fa033ed45 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -309,6 +309,7 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.expects(:purge).
with('database' => 'prod-db')
+ ActiveRecord::Base.expects(:establish_connection).with(:production)
ActiveRecord::Tasks::DatabaseTasks.purge_current('production')
end
diff --git a/activerecord/test/support/schema_dumping_helper.rb b/activerecord/test/support/schema_dumping_helper.rb
index 2ae8d299e5..2d1651454d 100644
--- a/activerecord/test/support/schema_dumping_helper.rb
+++ b/activerecord/test/support/schema_dumping_helper.rb
@@ -8,4 +8,13 @@ module SchemaDumpingHelper
ensure
ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
end
+
+ def dump_all_table_schema(ignore_tables)
+ old_ignore_tables, ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::SchemaDumper.ignore_tables, ignore_tables
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ stream.string
+ ensure
+ ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
+ end
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 3cd2235aec..617baaa1a8 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,9 +1,16 @@
+* `determine_constant_from_test_name` does no longer shadow `NameError`s
+ which happen during constant autoloading.
+
+ Fixes #9933.
+
+ *Guo Xiang Tan*
+
* Added instance_eval version to Object#try, so you can do this:
person.try { name.first }
-
+
instead of:
-
+
person.try { |person| person.name.first }
*DHH*
diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb
index 1b2a75c35d..07d477c0db 100644
--- a/activesupport/lib/active_support/testing/constant_lookup.rb
+++ b/activesupport/lib/active_support/testing/constant_lookup.rb
@@ -36,12 +36,8 @@ module ActiveSupport
while names.size > 0 do
names.last.sub!(/Test$/, "")
begin
- constant = names.join("::").constantize
+ constant = names.join("::").safe_constantize
break(constant) if yield(constant)
- rescue NoMethodError # subclass of NameError
- raise
- rescue NameError
- # Constant wasn't found, move on
ensure
names.pop
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 7ffcae6007..52fbaf8a85 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -38,3 +38,8 @@ def jruby_skip(message = '')
end
require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index 8a9fd4996b..366e4e5ef0 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -1,3 +1,5 @@
+require 'dependencies_test_helpers'
+
module Ace
module Base
class Case
@@ -23,6 +25,8 @@ class Object
end
module ConstantizeTestCases
+ include DependenciesTestHelpers
+
def run_constantize_tests_on
assert_equal Ace::Base::Case, yield("Ace::Base::Case")
assert_equal Ace::Base::Case, yield("::Ace::Base::Case")
@@ -56,6 +60,19 @@ module ConstantizeTestCases
assert_raises(NameError) { yield("Ace::Gas::ConstantizeTestCases") }
assert_raises(NameError) { yield("") }
assert_raises(NameError) { yield("::") }
+ assert_raises(NameError) { yield("Ace::gas") }
+
+ assert_raises(NameError) do
+ with_autoloading_fixtures do
+ yield("RaisesNameError")
+ end
+ end
+
+ assert_raises(NoMethodError) do
+ with_autoloading_fixtures do
+ yield("RaisesNoMethodError")
+ end
+ end
end
def run_safe_constantize_tests_on
@@ -82,5 +99,18 @@ module ConstantizeTestCases
assert_nil yield("Ace::Gas::Base")
assert_nil yield("Ace::Gas::ConstantizeTestCases")
assert_nil yield("#<Class:0x7b8b718b>::Nested_1")
+ assert_nil yield("Ace::gas")
+
+ assert_raises(NameError) do
+ with_autoloading_fixtures do
+ yield("RaisesNameError")
+ end
+ end
+
+ assert_raises(NoMethodError) do
+ with_autoloading_fixtures do
+ yield("RaisesNoMethodError")
+ end
+ end
end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 5fc3de651a..899bb75eae 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -888,6 +888,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_raise(NameError) { assert_equal 123, ::RaisesNameError::FooBarBaz }
end
end
+ ensure
+ remove_constants(:RaisesNameError)
end
def test_autoload_doesnt_shadow_name_error
diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb
index 71a9561189..0f16419c8b 100644
--- a/activesupport/test/testing/constant_lookup_test.rb
+++ b/activesupport/test/testing/constant_lookup_test.rb
@@ -65,4 +65,12 @@ class ConstantLookupTest < ActiveSupport::TestCase
}
}
end
+
+ def test_does_not_swallow_exception_on_no_name_error_within_constant
+ assert_raises(NameError) do
+ with_autoloading_fixtures do
+ self.class.determine_constant_from_test_name('RaisesNameError')
+ end
+ end
+ end
end
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index 4736818712..ae8ef34cdd 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -105,6 +105,135 @@ and
for a full description.
+Incompatibilities
+-----------------
+
+Previously deprecated functionality has been removed. Please refer to the
+individual components for new deprecations in this release.
+
+The following changes may require immediate action upon upgrade.
+
+### `respond_with` / class-level `respond_to`
+
+`respond_with` and the corresponding class-level `respond_to` have been moved to
+the `responders` gem. To use the following, add `gem 'responders', '~> 2.0'` to
+your Gemfile:
+
+```ruby
+# app/controllers/users_controller.rb
+
+class UsersController < ApplicationController
+ respond_to :html, :json
+
+ def show
+ @user = User.find(params[:id])
+ respond_with @user
+ end
+end
+```
+
+Instance-level `respond_to` is unaffected:
+
+```ruby
+# app/controllers/users_controller.rb
+
+class UsersController < ApplicationController
+ def show
+ @user = User.find(params[:id])
+ respond_to do |format|
+ format.html
+ format.json { render json: @user }
+ end
+ end
+end
+```
+
+### Production logging
+
+The default log level in the `production` environment is now `:debug`. This
+makes it consistent with the other environments, and ensures plenty of
+information is available to diagnose problems.
+
+It can be returned to the previous level, `:info`, in the environment
+configuration:
+
+```ruby
+# config/environments/production.rb
+
+# Decrease the log volume.
+config.log_level = :info
+```
+
+### HTML Sanitizer
+
+The HTML sanitizer has been replaced with a new, more robust, implementation
+built upon Loofah and Nokogiri. The new sanitizer is (TODO: betterer).
+
+With a new sanitization algorithm, the sanitized output will change for certain
+pathological inputs.
+
+If you have particular need for the exact output of the old sanitizer, you can
+add `rails-deprecated_sanitizer` to your Gemfile, and it will automatically
+replace the new implementation. Because it is opt-in, the legacy gem will not
+give deprecation warnings.
+
+`rails-deprecated_sanitizer` will be supported for Rails 4.2 only; it will not
+be maintained for Rails 5.0.
+
+See [the blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/)
+for more detail on the changes in the new sanitizer.
+
+### `assert_select`
+
+`assert_select` is now based on Nokogiri, making it (TODO: betterer).
+
+As a result, some previously-valid selectors are now unsupported. If your
+application is using any of these spellings, you will need to update them:
+
+* Values in attribute selectors may need to be quoted if they contain
+ non-alphanumeric characters.
+
+ ```
+ a[href=/] => a[href="/"]
+ a[href$=/] => a[href$="/"]
+ ```
+
+* DOMs built from HTML source containing invalid HTML with improperly
+ nested elements may differ.
+
+ For example:
+
+ ``` ruby
+ # content: <div><i><p></i></div>
+
+ # before:
+ assert_select('div > i') # => true
+ assert_select('div > p') # => false
+ assert_select('i > p') # => true
+
+ # now:
+ assert_select('div > i') # => true
+ assert_select('div > p') # => true
+ assert_select('i > p') # => false
+ ```
+
+* If the data selected contains entities, the value selected for comparison
+ used to be raw (e.g. `AT&amp;T`), and now is evaluated
+ (e.g. `AT&T`).
+
+ ``` ruby
+ # content: <p>AT&amp;T</p>
+
+ # before:
+ assert_select('p', 'AT&amp;T') # => true
+ assert_select('p', 'AT&T') # => false
+
+ # now:
+ assert_select('p', 'AT&T') # => true
+ assert_select('p', 'AT&amp;T') # => false
+ ```
+
+
Railties
--------
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index e2f13d557a..2c6e79a5b0 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -113,18 +113,23 @@ Active Job has adapters for the following queueing backends:
#### Backends Features
-| | Async | Queues | Delayed | Priorities | Timeout | Retries |
-|-----------------------|-------|---------|---------|-------------|---------|---------|
-| **Backburner** | Yes | Yes | Yes | Yes | Job | Global |
-| **Delayed Job** | Yes | Yes | Yes | Job | Global | Global |
-| **Que** | Yes | Yes | Yes | Job | No | Job |
-| **Queue Classic** | Yes | Yes | Gem | No | No | No |
-| **Resque** | Yes | Yes | Gem | Queue | Global | Yes |
-| **Sidekiq** | Yes | Yes | Yes | Queue | No | Job |
-| **Sneakers** | Yes | Yes | No | Queue | Queue | No |
-| **Sucker Punch** | Yes | Yes | Yes | No | No | No |
-| **Active Job Inline** | No | Yes | N/A | N/A | N/A | N/A |
-| **Active Job** | Yes | Yes | Yes | No | No | No |
+| | Async | Queues | Delayed | Priorities | Timeout | Retries |
+|-----------------------|-------|--------|-----------|------------|---------|---------|
+| **Backburner** | Yes | Yes | Yes | Yes | Job | Global |
+| **Delayed Job** | Yes | Yes | Yes | Job | Global | Global |
+| **Que** | Yes | Yes | Yes | Job | No | Job |
+| **Queue Classic** | Yes | Yes | No* | No | No | No |
+| **Resque** | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
+| **Sidekiq** | Yes | Yes | Yes | Queue | No | Job |
+| **Sneakers** | Yes | Yes | No | Queue | Queue | No |
+| **Sucker Punch** | Yes | Yes | No | No | No | No |
+| **Active Job Inline** | No | Yes | N/A | N/A | N/A | N/A |
+| **Active Job** | Yes | Yes | Yes | No | No | No |
+
+NOTE:
+* Queue Classic does not support Job scheduling. However you can implement this
+yourself or you can use the queue_classic-later gem. See the documentation for
+ActiveJob::QueueAdapters::QueueClassicAdapter.
### Change Backends
@@ -197,7 +202,7 @@ class GuestsCleanupJob < ActiveJob::Base
queue_as :default
before_enqueue do |job|
- # do somthing with the job instance
+ # do something with the job instance
end
around_perform do |job, block|
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index a5649e3903..6c94218ef6 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -132,7 +132,8 @@ event = Event.first
event.payload # => {"kind"=>"user_renamed", "change"=>["jack", "john"]}
## Query based on JSON document
-Event.where("payload->'kind' = ?", "user_renamed")
+# The -> operator returns the original JSON type (which might be an object), whereas ->> returns text
+Event.where("payload->>'kind' = ?", "user_renamed")
```
### Range Types
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 5ed392d43d..965de0c761 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1310,6 +1310,38 @@ In above examples "dear" gets cut first, but then `:separator` prevents it.
NOTE: Defined in `active_support/core_ext/string/filters.rb`.
+### `truncate_words`
+
+The method `truncate_words` returns a copy of its receiver truncated after a given number of words:
+
+```ruby
+"Oh dear! Oh dear! I shall be late!".truncate_words(4)
+# => "Oh dear! Oh dear!..."
+```
+
+Ellipsis can be customized with the `:omission` option:
+
+```ruby
+"Oh dear! Oh dear! I shall be late!".truncate_words(4, omission: '&hellip;')
+# => "Oh dear! Oh dear!&hellip;"
+```
+
+Pass a `:separator` to truncate the string at a natural break:
+
+```ruby
+"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: '!')
+# => "Oh dear! Oh dear! I shall be late..."
+```
+
+The option `:separator` can be a regexp:
+
+```ruby
+"Oh dear! Oh dear! I shall be late!".truncate_words(4, separator: /\s/)
+# => "Oh dear! Oh dear!..."
+```
+
+NOTE: Defined in `active_support/core_ext/string/filters.rb`.
+
### `inquiry`
The `inquiry` method converts a string into a `StringInquirer` object making equality checks prettier.
@@ -2862,6 +2894,20 @@ Active Record does not accept unknown options when building associations, for ex
NOTE: Defined in `active_support/core_ext/hash/keys.rb`.
+### Working with Values
+
+#### `transform_values` && `transform_values!`
+
+The method `transform_values` accepts a block and returns a hash that has applied the block operations to each of the values in the receiver.
+
+```ruby
+{ nil => nil, 1 => 1, :x => :a }.transform_values { |value| value.to_s.upcase }
+# => {nil=>"", 1=>"1", :x=>"A"}
+```
+There's also the bang variant `transform_values!` that applies the block operations to values in the very receiver.
+
+NOTE: Defined in `active_support/core_text/hash/transform_values.rb`.
+
### Slicing
Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes:
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index e31cefa5bb..15d743fb33 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -166,7 +166,8 @@ pipeline, the preferred location for these assets is now the `app/assets`
directory. Files in this directory are served by the Sprockets middleware.
Assets can still be placed in the `public` hierarchy. Any assets under `public`
-will be served as static files by the application or web server. You should use
+will be served as static files by the application or web server when
+`config.serve_static_assets` is set to true. You should use
`app/assets` for files that must undergo some pre-processing before they are
served.
@@ -916,24 +917,207 @@ end
### CDNs
-If your assets are being served by a CDN, ensure they don't stick around in your
-cache forever. This can cause problems. If you use
-`config.action_controller.perform_caching = true`, Rack::Cache will use
-`Rails.cache` to store assets. This can cause your cache to fill up quickly.
+CDN stands for [Content Delivery
+Network](http://en.wikipedia.org/wiki/Content_delivery_network), they are
+primarily designed to cache assets all over the world so that when a browser
+requests the asset, a cached copy will be geographically close to that browser.
+If you are serving assets directly from your Rails server in production, the
+best practice is to use a CDN in front of your application.
+
+A common pattern for using a CDN is to set your production application as the
+"origin" server. This means when a browser requests an asset from the CDN and
+there is a cache miss, it will grab the file from your server on the fly and
+then cache it. For example if you are running a Rails application on
+`example.com` and have a CDN configured at `mycdnsubdomain.fictional-cdn.com`,
+then when a request is made to `mycdnsubdomain.fictional-
+cdn.com/assets/smile.png`, the CDN will query your server once at
+`example.com/assets/smile.png` and cache the request. The next request to the
+CDN that comes in to the same URL will hit the cached copy. When the CDN can
+serve an asset directly the request never touches your Rails server. Since the
+assets from a CDN are geographically closer to the browser, the request is
+faster, and since your server doesn't need to spend time serving assets, it can
+focus on serving application code as fast as possible.
+
+#### Set up a CDN to Serve Static Assets
+
+To set up your CDN you have to have your application running in production on
+the internet at a publically available URL, for example `example.com`. Next
+you'll need to sign up for a CDN service from a cloud hosting provider. When you
+do this you need to configure the "origin" of the CDN to point back at your
+website `example.com`, check your provider for documentation on configuring the
+origin server.
+
+The CDN you provisioned should give you a custom subdomain for your application
+such as `mycdnsubdomain.fictional-cdn.com` (note fictional-cdn.com is not a
+valid CDN provider at the time of this writing). Now that you have configured
+your CDN server, you need to tell browsers to use your CDN to grab assets
+instead of your Rails server directly. You can do this by configuring Rails to
+set your CDN as the asset host instead of using a relative path. To set your
+asset host in Rails, you need to set `config.action_controller.asset_host` in
+`config/production.rb`:
-Every cache is different, so evaluate how your CDN handles caching and make sure
-that it plays nicely with the pipeline. You may find quirks related to your
-specific set up, you may not. The defaults NGINX uses, for example, should give
-you no problems when used as an HTTP cache.
+```ruby
+config.action_controller.asset_host = 'mycdnsubdomain.fictional-cdn.com'
+```
+
+NOTE: You only need to provide the "host", this is the subdomain and root
+domain, you do not need to specify a protocol or "scheme" such as `http://` or
+`https://`. When a web page is requested, the protocol in the link to your asset
+that is generated will match how the webpage is accessed by default.
+
+You can also set this value through an [environment
+variable](http://en.wikipedia.org/wiki/Environment_variable) to make running a
+staging copy of your site easier:
+
+```
+config.action_controller.asset_host = ENV['CDN_HOST']
+```
+
+
+
+Note: You would need to set `CDN_HOST` on your server to `mycdnsubdomain
+.fictional-cdn.com` for this to work.
+
+Once you have configured your server and your CDN when you serve a webpage that
+has an asset:
+
+```erb
+<%= asset_path('smile.png') %>
+```
+
+Instead of returning a path such as `/assets/smile.png` (digests are left out
+for readability). The URL generated will have the full path to your CDN.
-If you want to serve only some assets from your CDN, you can use custom
-`:host` option of `asset_url` helper, which overwrites value set in
+```
+http://mycdnsubdomain.fictional-cdn.com/assets/smile.png
+```
+
+If the CDN has a copy of `smile.png` it will serve it to the browser and your
+server doesn't even know it was requested. If the CDN does not have a copy it
+will try to find it a the "origin" `example.com/assets/smile.png` and then store
+it for future use.
+
+If you want to serve only some assets from your CDN, you can use custom `:host`
+option your asset helper, which overwrites value set in
`config.action_controller.asset_host`.
-```ruby
-asset_url 'image.png', :host => 'http://cdn.example.com'
+```erb
+<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %>
```
+#### Customize CDN Caching Behavior
+
+A CDN works by caching content. If the CDN has stale or bad content, then it is
+hurting rather than helping your application. The purpose of this section is to
+describe general caching behavior of most CDNs, your specific provider may
+behave slightly differently.
+
+##### CDN Request Caching
+
+While a CDN is described as being good for caching assets, in reality caches the
+entire request. This includes the body of the asset as well as any headers. The
+most important one being `Cache-Control` which tells the CDN (and web browsers)
+how to cache contents. This means that if someone requests an asset that does
+not exist `/assets/i-dont-exist.png` and your Rails application returns a 404,
+then your CDN will likely cache the 404 page if a valid `Cache-Control` header
+is present.
+
+##### CDN Header Debugging
+
+One way to check the headers are cached properly in your CDN is by using [curl](
+http://explainshell.com/explain?cmd=curl+-I+http%3A%2F%2Fwww.example.com). You
+can request the headers from both your server and your CDN to verify they are
+the same:
+
+```
+$ curl -I http://www.example/assets/application-
+d0e099e021c95eb0de3615fd1d8c4d83.css
+HTTP/1.1 200 OK
+Server: Cowboy
+Date: Sun, 24 Aug 2014 20:27:50 GMT
+Connection: keep-alive
+Last-Modified: Thu, 08 May 2014 01:24:14 GMT
+Content-Type: text/css
+Cache-Control: public, max-age=2592000
+Content-Length: 126560
+Via: 1.1 vegur
+```
+
+Versus the CDN copy.
+
+```
+$ curl -I http://mycdnsubdomain.fictional-cdn.com/application-
+d0e099e021c95eb0de3615fd1d8c4d83.css
+HTTP/1.1 200 OK Server: Cowboy Last-
+Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css
+Cache-Control:
+public, max-age=2592000
+Via: 1.1 vegur
+Content-Length: 126560
+Accept-Ranges:
+bytes
+Date: Sun, 24 Aug 2014 20:28:45 GMT
+Via: 1.1 varnish
+Age: 885814
+Connection: keep-alive
+X-Served-By: cache-dfw1828-DFW
+X-Cache: HIT
+X-Cache-Hits:
+68
+X-Timer: S1408912125.211638212,VS0,VE0
+```
+
+Check your CDN documentation for any additional information they may provide
+such as `X-Cache` or for any additional headers they may add.
+
+##### CDNs and the Cache-Control Header
+
+The [cache control
+header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C
+specification that describes how a request can be cached. When no CDN is used, a
+browser will use this information to cache contents. This is very helpful for
+assets that are not modified so that a browser does not need to re-download a
+website's CSS or javascript on every request. Generally we want our Rails server
+to tell our CDN (and browser) that the asset is "public", that means any cache
+can store the request. Also we commonly want to set `max-age` which is how long
+the cache will store the object before invalidating the cache. The `max-age`
+value is set to seconds with a maximum possible value of `31536000` which is one
+year. You can do this in your rails application by setting
+
+```
+config.static_cache_control = "public, max-age=31536000"
+```
+
+Now when your application serves an asset in production, the CDN will store the
+asset for up to a year. Since most CDNs also cache headers of the request, this
+`Cache-Control` will be passed along to all future browsers seeking this asset,
+the browser then knows that it can store this asset for a very long time before
+needing to re-request it.
+
+##### CDNs and URL based Cache Invalidation
+
+Most CDNs will cache contents of an asset based on the complete URL. This means
+that a request to
+
+```
+http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png
+```
+
+Will be a completely different cache from
+
+```
+http://mycdnsubdomain.fictional-cdn.com/assets/smile.png
+```
+
+If you want to set far future `max-age` in your `Cache-Control` (and you do),
+then make sure when you change your assets that your cache is invalidated. For
+example when changing the smiley face in an image from yellow to blue, you want
+all visitors of your site to get the new blue face. When using a CDN with the
+Rails asset pipeline `config.assets.digest` is set to true by default so that
+each asset will have a different file name when it is changed. This way you
+don't have to ever manually invalidate any items in your cache. By using a
+different unique asset name instead, your users get the latest asset.
+
Customizing the Pipeline
------------------------
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index d0f3a596fe..cbcd053950 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -363,7 +363,7 @@ class ProductsController < ApplicationController
end
```
-If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using respond_to or calling render yourself) then you've got an easy helper in fresh_when:
+If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using `respond_to` or calling render yourself) then you've got an easy helper in `fresh_when`:
```ruby
class ProductsController < ApplicationController
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 38f9609287..dbbd0c1aea 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -112,7 +112,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
-* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`, with auto flushing off in production mode.
+* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`.
* `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below.
@@ -137,7 +137,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.assets.enabled` a flag that controls whether the asset
pipeline is enabled. It is set to true by default.
-* `config.assets.raise_runtime_errors`* Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
+* `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`.
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 2703e357d5..9ced77dfd7 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -623,7 +623,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the
### What Gets Uploaded
-The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example).
+The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a `StringIO` or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example).
```ruby
def upload
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 964bb30856..1769448531 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1627,7 +1627,7 @@ controller. Again, we'll use the same generator we used before:
$ bin/rails generate controller Comments
```
-This creates six files and one empty directory:
+This creates five files and one empty directory:
| File/Directory | Purpose |
| -------------------------------------------- | ---------------------------------------- |
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 6df56c4fee..ac254fc000 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -248,8 +248,8 @@ service requests that are expecting something other than proper HTML.
NOTE: By default, if you use the `:plain` option, the text is rendered without
using the current layout. If you want Rails to put the text into the current
-layout, you need to add the `layout: true` option. Make sure to add a layout
-file with extension `.txt.erb`.
+layout, you need to add the `layout: true` option and use the `.txt.erb`
+extension for the layout file.
#### Rendering HTML
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index b3a67266f7..841d39dad6 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -281,7 +281,7 @@ module Rails
[]
else
gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry]
- gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
+ gems << GemfileEntry.github("#{options[:javascript]}-rails", "rails/#{options[:javascript]}-rails",
"Use #{options[:javascript]} as the JavaScript library")
gems << GemfileEntry.version("turbolinks", nil,
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 72143301a6..7b1a6aa854 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -33,8 +33,8 @@ group :development, :test do
gem 'byebug'
<%- end -%>
- # Access an IRB console on exceptions page and /console in development
- gem 'web-console', '~> 2.0.0.beta2'
+ # Access an IRB console on exception pages or by using <%%= console %> in views
+ gem 'web-console', github: 'rails/web-console', branch: 'master'
<%- if spring_install? %>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
diff --git a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
index 6200218313..feb250e89a 100644
--- a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
+++ b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
@@ -1,7 +1,8 @@
require 'test_helper'
<% module_namespacing do -%>
-class <%= class_name %>JobTest < ActiveSupport::TestCase
+class <%= class_name %>JobTest < ActiveJob::TestCase
+
# test "the truth" do
# assert true
# end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index b6533a5fb2..d8800eaa0f 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -28,6 +28,10 @@ def jruby_skip(message = '')
end
class ActiveSupport::TestCase
+ # FIXME: we have tests that depend on run order, we should fix that and
+ # remove this method call.
+ self.my_tests_are_order_dependent!
+
private
unless defined?(:capture)
diff --git a/railties/test/application/default_stack_test.rb b/railties/test/application/default_stack_test.rb
new file mode 100644
index 0000000000..4778cdd74c
--- /dev/null
+++ b/railties/test/application/default_stack_test.rb
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+require 'isolation/abstract_unit'
+require 'rack/test'
+require 'active_support/json'
+
+module ApplicationTests
+ class DefaultStackTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app(initializers: true)
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "the sanitizer helper" do
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def index
+ render text: self.class.helpers.class.sanitizer_vendor
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get "/foo"
+ assert_equal 'Rails::Html::Sanitizer', last_response.body.strip
+ end
+ end
+end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 15414db00f..c727e0e46d 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -173,6 +173,32 @@ module ApplicationTests
"your test schema automatically, see the release notes for details.\n", output
end
end
+
+ test 'db:setup loads schema and seeds database' do
+ begin
+ @old_env = ENV["RAILS_ENV"]
+ ENV.delete "RAILS_ENV"
+
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: "1") do
+ create_table :users do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ app_file 'db/seeds.rb', <<-RUBY
+ puts ActiveRecord::Base.connection_config[:database]
+ RUBY
+
+ Dir.chdir(app_path) do
+ database_path = `bundle exec rake db:setup`
+ assert_equal "development.sqlite3", File.basename(database_path.strip)
+ end
+ ensure
+ ENV["RAILS_ENV"] = @old_env
+ end
+ end
end
end
end