Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' into loofah

Conflicts:
	actionpack/CHANGELOG.md
  • Loading branch information...
commit c78da4d5c472f7254ed609ef753d7b1719732802 2 parents 4e49134 + 7ee0550
@rafaelfranca rafaelfranca authored
Showing with 3,586 additions and 1,669 deletions.
  1. +1 −1  .travis.yml
  2. +15 −1 Gemfile
  3. +1 −1  Rakefile
  4. +13 −0 actionmailer/CHANGELOG.md
  5. +2 −0  actionmailer/lib/action_mailer.rb
  6. +6 −2 actionmailer/lib/action_mailer/base.rb
  7. +11 −0 actionmailer/lib/action_mailer/delivery_job.rb
  8. +45 −0 actionmailer/lib/action_mailer/message_delivery.rb
  9. +2 −0  actionmailer/lib/action_mailer/preview.rb
  10. +0 −5 actionmailer/test/abstract_unit.rb
  11. +5 −3 actionmailer/test/base_test.rb
  12. +6 −0 actionmailer/test/mailers/delayed_mailer.rb
  13. +82 −0 actionmailer/test/message_delivery_test.rb
  14. +45 −0 actionpack/CHANGELOG.md
  15. +3 −21 actionpack/Rakefile
  16. +0 −3  actionpack/lib/abstract_controller/helpers.rb
  17. +1 −0  actionpack/lib/action_controller.rb
  18. +1 −0  actionpack/lib/action_controller/base.rb
  19. +1 −1  actionpack/lib/action_controller/caching.rb
  20. +31 −6 actionpack/lib/action_controller/metal/conditional_get.rb
  21. +50 −0 actionpack/lib/action_controller/metal/etag_with_template_digest.rb
  22. +17 −227 actionpack/lib/action_controller/metal/mime_responds.rb
  23. +0 −297 actionpack/lib/action_controller/metal/responder.rb
  24. +9 −0 actionpack/lib/action_controller/test_case.rb
  25. +17 −19 actionpack/lib/action_dispatch/middleware/cookies.rb
  26. +1 −1  actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
  27. +74 −42 actionpack/lib/action_dispatch/routing/mapper.rb
  28. +16 −11 actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
  29. +17 −5 actionpack/test/abstract_unit.rb
  30. +23 −0 actionpack/test/controller/action_pack_assertions_test.rb
  31. +1 −1  actionpack/test/controller/live_stream_test.rb
  32. +0 −737 actionpack/test/controller/mime/respond_with_test.rb
  33. +32 −0 actionpack/test/controller/mime/responders_test.rb
  34. +34 −2 actionpack/test/controller/render_test.rb
  35. +89 −0 actionpack/test/dispatch/cookies_test.rb
  36. +1 −1  actionpack/test/dispatch/mapper_test.rb
  37. +1 −1  actionpack/test/dispatch/request_test.rb
  38. +0 −1  actionpack/test/fixtures/respond_with/edit.html.erb
  39. +0 −1  actionpack/test/fixtures/respond_with/new.html.erb
  40. +0 −1  actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb
  41. +0 −1  actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb
  42. +0 −1  actionpack/test/fixtures/respond_with/using_resource.js.erb
  43. +0 −1  actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb
  44. +6 −0 actionview/CHANGELOG.md
  45. +32 −0 actionview/lib/action_view/helpers/tags/placeholderable.rb
  46. +4 −0 actionview/lib/action_view/helpers/tags/text_area.rb
  47. +4 −0 actionview/lib/action_view/helpers/tags/text_field.rb
  48. +1 −0  actionview/lib/action_view/helpers/translation_helper.rb
  49. +3 −1 actionview/lib/action_view/routing_url_for.rb
  50. +0 −5 actionview/test/abstract_unit.rb
  51. +1 −3 actionview/test/activerecord/controller_runtime_test.rb
  52. +168 −0 actionview/test/template/form_helper_test.rb
  53. +1 −0  activejob/CHANGELOG.md
  54. +21 −0 activejob/MIT-LICENSE
  55. +141 −0 activejob/README.md
  56. +84 −0 activejob/Rakefile
  57. +22 −0 activejob/activejob.gemspec
  58. +33 −0 activejob/lib/active_job.rb
  59. +62 −0 activejob/lib/active_job/arguments.rb
  60. +22 −0 activejob/lib/active_job/base.rb
  61. +144 −0 activejob/lib/active_job/callbacks.rb
  62. +71 −0 activejob/lib/active_job/enqueuing.rb
  63. +33 −0 activejob/lib/active_job/execution.rb
  64. +15 −0 activejob/lib/active_job/gem_version.rb
  65. +15 −0 activejob/lib/active_job/identifier.rb
  66. +90 −0 activejob/lib/active_job/logging.rb
  67. +24 −0 activejob/lib/active_job/queue_adapter.rb
  68. +25 −0 activejob/lib/active_job/queue_adapters/backburner_adapter.rb
  69. +23 −0 activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
  70. +15 −0 activejob/lib/active_job/queue_adapters/inline_adapter.rb
  71. +30 −0 activejob/lib/active_job/queue_adapters/qu_adapter.rb
  72. +23 −0 activejob/lib/active_job/queue_adapters/que_adapter.rb
  73. +23 −0 activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
  74. +41 −0 activejob/lib/active_job/queue_adapters/resque_adapter.rb
  75. +35 −0 activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
  76. +34 −0 activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
  77. +25 −0 activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
  78. +18 −0 activejob/lib/active_job/queue_name.rb
  79. +23 −0 activejob/lib/active_job/railtie.rb
  80. +8 −0 activejob/lib/active_job/version.rb
  81. +22 −0 activejob/lib/rails/generators/job/job_generator.rb
  82. +9 −0 activejob/lib/rails/generators/job/templates/job.rb
  83. +3 −0  activejob/test/adapters/backburner.rb
  84. +7 −0 activejob/test/adapters/delayed_job.rb
  85. +1 −0  activejob/test/adapters/inline.rb
  86. +3 −0  activejob/test/adapters/qu.rb
  87. +2 −0  activejob/test/adapters/que.rb
  88. +2 −0  activejob/test/adapters/queue_classic.rb
  89. +2 −0  activejob/test/adapters/resque.rb
  90. +2 −0  activejob/test/adapters/sidekiq.rb
  91. +2 −0  activejob/test/adapters/sneakers.rb
  92. +2 −0  activejob/test/adapters/sucker_punch.rb
  93. +8 −0 activejob/test/cases/adapter_test.rb
  94. +22 −0 activejob/test/cases/callbacks_test.rb
  95. +15 −0 activejob/test/cases/job_serialization_test.rb
  96. +96 −0 activejob/test/cases/logging_test.rb
  97. +77 −0 activejob/test/cases/parameters_test.rb
  98. +23 −0 activejob/test/cases/queue_naming_test.rb
  99. +44 −0 activejob/test/cases/queuing_test.rb
  100. +30 −0 activejob/test/cases/rescue_test.rb
  101. +50 −0 activejob/test/helper.rb
  102. +32 −0 activejob/test/jobs/callback_job.rb
  103. +6 −0 activejob/test/jobs/gid_job.rb
  104. +5 −0 activejob/test/jobs/hello_job.rb
  105. +10 −0 activejob/test/jobs/logging_job.rb
  106. +10 −0 activejob/test/jobs/nested_job.rb
  107. +25 −0 activejob/test/jobs/rescue_job.rb
  108. +20 −0 activejob/test/models/person.rb
  109. +8 −0 activejob/test/support/backburner/inline.rb
  110. +113 −0 activejob/test/support/delayed_job/delayed/backend/test.rb
  111. 0  ...ond_with_additional_params.html.erb → activejob/test/support/delayed_job/delayed/serialization/test.rb
  112. +11 −0 activejob/test/support/queue_classic/inline.rb
  113. +12 −0 activejob/test/support/sneakers/inline.rb
  114. +16 −2 activemodel/lib/active_model/dirty.rb
  115. +0 −5 activemodel/test/cases/helper.rb
  116. +41 −0 activerecord/CHANGELOG.md
  117. +3 −21 activerecord/Rakefile
  118. +2 −2 activerecord/lib/active_record/associations.rb
  119. +1 −1  activerecord/lib/active_record/associations/has_many_association.rb
  120. +1 −1  activerecord/lib/active_record/associations/preloader/through_association.rb
  121. +13 −2 activerecord/lib/active_record/attribute.rb
  122. +10 −7 activerecord/lib/active_record/attribute_methods.rb
  123. +14 −11 activerecord/lib/active_record/attribute_methods/dirty.rb
  124. +21 −3 activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
  125. +11 −10 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
  126. +4 −4 activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
  127. +2 −2 activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
  128. +1 −1  activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
  129. +2 −2 activerecord/lib/active_record/enum.rb
  130. +1 −1  activerecord/lib/active_record/persistence.rb
  131. +15 −3 activerecord/lib/active_record/relation/query_methods.rb
  132. +4 −3 activerecord/lib/active_record/timestamp.rb
  133. +9 −2 activerecord/lib/active_record/type/value.rb
  134. +1 −1  activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
  135. +1 −1  activerecord/test/cases/adapters/mysql/active_schema_test.rb
  136. +1 −1  activerecord/test/cases/adapters/mysql/connection_test.rb
  137. +1 −1  activerecord/test/cases/adapters/mysql2/active_schema_test.rb
  138. +1 −1  activerecord/test/cases/adapters/mysql2/connection_test.rb
  139. +4 −4 activerecord/test/cases/adapters/postgresql/timestamp_test.rb
  140. +57 −0 activerecord/test/cases/ar_schema_test.rb
  141. +4 −4 activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
  142. +19 −1 activerecord/test/cases/attribute_methods_test.rb
  143. +30 −0 activerecord/test/cases/attribute_test.rb
  144. +21 −0 activerecord/test/cases/dirty_test.rb
  145. +1 −1  activerecord/test/cases/finder_test.rb
  146. +0 −5 activerecord/test/cases/helper.rb
  147. +5 −2 activerecord/test/cases/migration/change_schema_test.rb
  148. +2 −2 activerecord/test/cases/migration/change_table_test.rb
  149. +3 −0  activerecord/test/cases/migration/columns_test.rb
  150. +1 −1  activerecord/test/cases/migration/helper.rb
  151. +6 −0 activerecord/test/cases/migration/index_test.rb
  152. +1 −1  activerecord/test/cases/migration_test.rb
  153. +9 −0 activerecord/test/cases/relation/where_test.rb
  154. +4 −0 activerecord/test/cases/schema_dumper_test.rb
  155. +6 −2 activerecord/test/cases/scoping/relation_scoping_test.rb
  156. +1 −1  activerecord/test/cases/test_case.rb
  157. +19 −0 activerecord/test/cases/timestamp_test.rb
  158. +13 −0 activerecord/test/cases/transaction_callbacks_test.rb
  159. +24 −0 activerecord/test/cases/transactions_test.rb
  160. +6 −0 activerecord/test/cases/types_test.rb
  161. +1 −0  activerecord/test/cases/validations/i18n_validation_test.rb
  162. +1 −1  activerecord/test/cases/validations_repair_helper.rb
  163. +5 −5 activerecord/test/schema/schema.rb
  164. +5 −0 activesupport/CHANGELOG.md
  165. +2 −2 activesupport/lib/active_support/inflector/methods.rb
  166. +2 −1  activesupport/lib/active_support/message_encryptor.rb
  167. +2 −2 activesupport/lib/active_support/multibyte/unicode.rb
  168. +5 −1 activesupport/lib/active_support/testing/time_helpers.rb
  169. +0 −5 activesupport/test/abstract_unit.rb
  170. +10 −13 activesupport/test/dependencies_test.rb
  171. +14 −12 activesupport/test/multibyte_chars_test.rb
  172. +3 −3 activesupport/test/multibyte_test_helpers.rb
  173. +10 −3 activesupport/test/test_test.rb
  174. +2 −1  ci/travis.rb
  175. +6 −0 guides/source/4_2_release_notes.md
  176. +253 −0 guides/source/active_job_basics.md
  177. +6 −1 guides/source/caching_with_rails.md
  178. +1 −1  guides/source/contributing_to_ruby_on_rails.md
  179. +15 −0 guides/source/generators.md
  180. +1 −1  guides/source/layouts_and_rendering.md
  181. +4 −4 guides/source/security.md
  182. +1 −1  install.rb
  183. +1 −0  rails.gemspec
  184. +1 −0  railties/lib/rails/all.rb
  185. +7 −0 railties/lib/rails/api/task.rb
  186. +2 −1  railties/lib/rails/application.rb
  187. +1 −1  railties/lib/rails/generators/rails/app/templates/Gemfile
  188. +0 −3  railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
  189. +1 −1  railties/lib/rails/generators/rails/controller/controller_generator.rb
  190. +0 −35 railties/lib/rails/rubyprof_ext.rb
  191. +0 −4 railties/test/abstract_unit.rb
  192. +1 −1  railties/test/generators/model_generator_test.rb
  193. +10 −0 railties/test/generators_test.rb
  194. +1 −1  tasks/release.rb
  195. +41 −0 tools/line_statistics
  196. +112 −50 tools/profile
View
2  .travis.yml
@@ -12,7 +12,7 @@ rvm:
env:
- "GEM=railties"
- "GEM=ap"
- - "GEM=am,amo,as,av"
+ - "GEM=am,amo,as,av,aj"
- "GEM=ar:mysql"
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
View
16 Gemfile
@@ -7,7 +7,7 @@ gemspec
# ensure correct loading order
gem 'mocha', '~> 0.14', require: false
-gem 'rack', github: 'rack/rack'
+gem 'rack', github: 'rack/rack', branch: 'master'
gem 'rack-cache', '~> 1.2'
gem 'jquery-rails', '~> 3.1.0'
gem 'turbolinks', github: 'rails/turbolinks', branch: 'master'
@@ -38,6 +38,20 @@ end
# AS
gem 'dalli', '>= 2.2.1'
+# ActiveJob
+gem 'globalid', github: 'rails/globalid'
+gem 'resque', require: false
+gem 'resque-scheduler', require: false
+gem 'sidekiq', require: false
+gem 'sucker_punch', require: false
+gem 'delayed_job', require: false
+gem 'queue_classic', require: false
+gem 'sneakers', '0.1.1.pre', require: false
+gem 'que', require: false
+gem 'backburner', require: false
+gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
+gem 'qu-redis', require: false
+
# Add your own local bundler stuff
local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
instance_eval File.read local_gemfile if File.exist? local_gemfile
View
2  Rakefile
@@ -11,7 +11,7 @@ task :build => "all:build"
desc "Release all gems to rubygems and create a tag"
task :release => "all:release"
-PROJECTS = %w(activesupport activemodel actionpack actionview actionmailer activerecord railties)
+PROJECTS = %w(activesupport activemodel actionpack actionview actionmailer activerecord railties activejob)
desc 'Run all tests by default'
task :default => %w(test test:isolated)
View
13 actionmailer/CHANGELOG.md
@@ -1,3 +1,16 @@
+* Added #deliver_later in addition to #deliver, which will enqueue a job to render and
+ deliver the mail instead of delivering it right at that moment. The job is enqueued
+ using the new Active Job framework in Rails, and will use whatever queue is configured for Rails.
+
+ *DHH/Abdelkader Boudih/Cristian Bica*
+
+
+* Make ActionMailer::Previews methods class methods. Previously they were
+ instance methods and ActionMailer tries to render a message when they
+ are called.
+
+ *Cristian Bica*
+
* Deprecate `*_path` helpers in email views. When used they generate
non-working links and are not the intention of most developers. Instead
we recommend to use `*_url` helper.
View
2  actionmailer/lib/action_mailer.rb
@@ -45,4 +45,6 @@ module ActionMailer
autoload :Previews, 'action_mailer/preview'
autoload :TestCase
autoload :TestHelper
+ autoload :MessageDelivery
+ autoload :DeliveryJob
end
View
8 actionmailer/lib/action_mailer/base.rb
@@ -548,8 +548,8 @@ def set_payload_for_mail(payload, mail) #:nodoc:
end
def method_missing(method_name, *args) # :nodoc:
- if respond_to?(method_name)
- new(method_name, *args).message
+ if action_methods.include?(method_name.to_s)
+ MessageDelivery.new(self, method_name, *args)
else
super
end
@@ -586,6 +586,10 @@ def process(method_name, *args) #:nodoc:
class NullMail #:nodoc:
def body; '' end
+ def respond_to?(string, include_all=false)
+ true
+ end
+
def method_missing(*args)
nil
end
View
11 actionmailer/lib/action_mailer/delivery_job.rb
@@ -0,0 +1,11 @@
+require 'active_job'
+
+module ActionMailer
+ class DeliveryJob < ActiveJob::Base
+ queue_as :mailers
+
+ def perform(mailer, mail_method, delivery_method, *args)
+ mailer.constantize.public_send(mail_method, *args).send(delivery_method)
+ end
+ end
+end
View
45 actionmailer/lib/action_mailer/message_delivery.rb
@@ -0,0 +1,45 @@
+require 'delegate'
+
+module ActionMailer
+ class MessageDelivery < Delegator
+ def initialize(mailer, mail_method, *args)
+ @mailer = mailer
+ @mail_method = mail_method
+ @args = args
+ end
+
+ def __getobj__
+ @obj ||= @mailer.send(:new, @mail_method, *@args).message
+ end
+
+ def __setobj__(obj)
+ @obj = obj
+ end
+
+ def message #:nodoc:
+ __getobj__
+ end
+
+ def deliver_later!(options={})
+ enqueue_delivery :deliver!, options
+ end
+
+ def deliver_later(options={})
+ enqueue_delivery :deliver, options
+ end
+
+ private
+ def enqueue_delivery(delivery_method, options={})
+ args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
+ enqueue_method = :enqueue
+ if options[:at]
+ enqueue_method = :enqueue_at
+ args.unshift options[:at]
+ elsif options[:in]
+ enqueue_method = :enqueue_in
+ args.unshift options[:in]
+ end
+ ActionMailer::DeliveryJob.send enqueue_method, *args
+ end
+ end
+end
View
2  actionmailer/lib/action_mailer/preview.rb
@@ -22,7 +22,9 @@ module Previews #:nodoc:
# :nodoc:
mattr_accessor :preview_interceptors, instance_writer: false
self.preview_interceptors = []
+ end
+ module ClassMethods
# Register one or more Interceptors which will be called before mail is previewed.
def register_preview_interceptors(*interceptors)
interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
View
5 actionmailer/test/abstract_unit.rb
@@ -51,8 +51,3 @@ 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!
View
8 actionmailer/test/base_test.rb
@@ -11,6 +11,8 @@
class BaseTest < ActiveSupport::TestCase
setup do
+ @original_delivery_method = ActionMailer::Base.delivery_method
+ ActionMailer::Base.delivery_method = :test
@original_asset_host = ActionMailer::Base.asset_host
@original_assets_dir = ActionMailer::Base.assets_dir
end
@@ -19,6 +21,7 @@ class BaseTest < ActiveSupport::TestCase
ActionMailer::Base.asset_host = @original_asset_host
ActionMailer::Base.assets_dir = @original_assets_dir
BaseMailer.deliveries.clear
+ ActionMailer::Base.delivery_method = @original_delivery_method
end
test "method call to mail does not raise error" do
@@ -240,7 +243,7 @@ def welcome
end
end
- e = assert_raises(RuntimeError) { LateAttachmentMailer.welcome }
+ e = assert_raises(RuntimeError) { LateAttachmentMailer.welcome.message }
assert_match(/Can't add attachments after `mail` was called./, e.message)
end
@@ -252,7 +255,7 @@ def welcome
end
end
- e = assert_raises(RuntimeError) { LateInlineAttachmentMailer.welcome }
+ e = assert_raises(RuntimeError) { LateInlineAttachmentMailer.welcome.message }
assert_match(/Can't add attachments after `mail` was called./, e.message)
end
@@ -468,7 +471,6 @@ def welcome
end
test "calling deliver on the action should increment the deliveries collection if using the test mailer" do
- BaseMailer.delivery_method = :test
BaseMailer.welcome.deliver
assert_equal(1, BaseMailer.deliveries.length)
end
View
6 actionmailer/test/mailers/delayed_mailer.rb
@@ -0,0 +1,6 @@
+class DelayedMailer < ActionMailer::Base
+
+ def test_message(*)
+ mail(from: 'test-sender@test.com', to: 'test-receiver@test.com', subject: 'Test Subject', body: 'Test Body')
+ end
+end
View
82 actionmailer/test/message_delivery_test.rb
@@ -0,0 +1,82 @@
+# encoding: utf-8
+gem 'activejob'
+require 'active_job'
+require 'abstract_unit'
+require 'minitest/mock'
+require_relative 'mailers/delayed_mailer'
+
+class MessageDeliveryTest < ActiveSupport::TestCase
+
+ setup do
+ @previous_logger = ActiveJob::Base.logger
+ @previous_delivery_method = ActionMailer::Base.delivery_method
+ ActionMailer::Base.delivery_method = :test
+ ActiveJob::Base.logger = Logger.new(nil)
+ @mail = DelayedMailer.test_message(1, 2, 3)
+ ActionMailer::Base.deliveries.clear
+ end
+
+ teardown do
+ ActiveJob::Base.logger = @previous_logger
+ ActionMailer::Base.delivery_method = @previous_delivery_method
+ end
+
+ test 'should have a message' do
+ assert @mail.message
+ end
+
+ test 'its message should be a Mail::Message' do
+ assert_equal Mail::Message , @mail.message.class
+ end
+
+ test 'should respond to .deliver' do
+ assert_respond_to @mail, :deliver
+ end
+
+ test 'should respond to .deliver!' do
+ assert_respond_to @mail, :deliver!
+ end
+
+ test 'should respond to .deliver_later' do
+ assert_respond_to @mail, :deliver_later
+ end
+
+ test 'should respond to .deliver_later!' do
+ assert_respond_to @mail, :deliver_later!
+ end
+
+ test 'should enqueue and run correctly in activejob' do
+ @mail.deliver_later!
+ assert_equal 1 , ActionMailer::Base.deliveries.size
+ end
+
+ test 'should enqueue the email with :deliver delivery method' do
+ ret = ActionMailer::DeliveryJob.stub :enqueue, ->(*args){ args } do
+ @mail.deliver_later
+ end
+ assert_equal ['DelayedMailer', 'test_message', 'deliver', 1, 2, 3], ret
+ end
+
+ test 'should enqueue the email with :deliver! delivery method' do
+ ret = ActionMailer::DeliveryJob.stub :enqueue, ->(*args){ args } do
+ @mail.deliver_later!
+ end
+ assert_equal ['DelayedMailer', 'test_message', 'deliver!', 1, 2, 3], ret
+ end
+
+ test 'should enqueue a delivery with a delay' do
+ ret = ActionMailer::DeliveryJob.stub :enqueue_in, ->(*args){ args } do
+ @mail.deliver_later in: 600
+ end
+ assert_equal [600, 'DelayedMailer', 'test_message', 'deliver', 1, 2, 3], ret
+ end
+
+ test 'should enqueue a delivery at a specific time' do
+ later_time = Time.now.to_i + 3600
+ ret = ActionMailer::DeliveryJob.stub :enqueue_at, ->(*args){ args } do
+ @mail.deliver_later at: later_time
+ end
+ assert_equal [later_time, 'DelayedMailer', 'test_message', 'deliver', 1, 2, 3], ret
+ end
+
+end
View
45 actionpack/CHANGELOG.md
@@ -2,6 +2,51 @@
*Kasper Timm Hansen*
+* Use the Active Support JSON encoder for cookie jars using the `:json` or
+ `:hybrid` serializer. This allows you to serialize custom Ruby objects into
+ cookies by defining the `#as_json` hook on such objects.
+
+ Fixes #16520.
+
+ *Godfrey Chan*
+
+* Add `config.action_dispatch.cookies_digest` option for setting custom
+ digest. The default remains the same - 'SHA1'.
+
+ *Łukasz Strzałkowski*
+
+* Move `respond_with` (and the class-level `respond_to`) to
+ the `responders` gem.
+
+ *José Valim*
+
+* When your templates change, browser caches bust automatically.
+
+ New default: the template digest is automatically included in your ETags.
+ When you call `fresh_when @post`, the digest for `posts/show.html.erb`
+ is mixed in so future changes to the HTML will blow HTTP caches for you.
+ This makes it easy to HTTP-cache many more of your actions.
+
+ If you render a different template, you can now pass the `:template`
+ option to include its digest instead:
+
+ fresh_when @post, template: 'widgets/show'
+
+ Pass `template: false` to skip the lookup. To turn this off entirely, set:
+
+ config.action_controller.etag_with_template_digest = false
+
+ *Jeremy Kemper*
+
+* Remove deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError`
+ in favor of `AbstractController::Helpers::MissingHelperError`.
+
+ *Yves Senn*
+
+* Fix `assert_template` not being able to assert that no files were rendered.
+
+ *Guo Xiang Tan*
+
* Extract source code for the entire exception stack trace for
better debugging and diagnosis.
View
24 actionpack/Rakefile
@@ -40,27 +40,9 @@ task :release => :package do
end
task :lines do
- lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
-
- FileList["lib/**/*.rb"].each do |file_name|
- next if file_name =~ /vendor/
- File.open(file_name, 'r') do |f|
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
- end
- end
- puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
-
- total_lines += lines
- total_codelines += codelines
-
- lines, codelines = 0, 0
- end
-
- puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
+ load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics'
+ files = FileList["lib/**/*.rb"]
+ CodeTools::LineStatistics.new(files).print_loc
end
rule '.rb' => '.y' do |t|
View
3  actionpack/lib/abstract_controller/helpers.rb
@@ -27,9 +27,6 @@ def initialize(error, path)
end
module ClassMethods
- MissingHelperError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('AbstractController::Helpers::ClassMethods::MissingHelperError',
- 'AbstractController::Helpers::MissingHelperError')
-
# When a class is inherited, wrap its helper module in a new module.
# This ensures that the parent class's module can be changed
# independently of the child class's.
View
1  actionpack/lib/action_controller.rb
@@ -17,6 +17,7 @@ module ActionController
autoload :ConditionalGet
autoload :Cookies
autoload :DataStreaming
+ autoload :EtagWithTemplateDigest
autoload :Flash
autoload :ForceSSL
autoload :Head
View
1  actionpack/lib/action_controller/base.rb
@@ -213,6 +213,7 @@ def self.without_modules(*modules)
Rendering,
Renderers::All,
ConditionalGet,
+ EtagWithTemplateDigest,
RackDelegation,
Caching,
MimeResponds,
View
2  actionpack/lib/action_controller/caching.rb
@@ -16,7 +16,7 @@ module ActionController
# All the caching stores from ActiveSupport::Cache are available to be used as backends
# for Action Controller caching.
#
- # Configuration examples (MemoryStore is the default):
+ # Configuration examples (FileStore is the default):
#
# config.action_controller.cache_store = :memory_store
# config.action_controller.cache_store = :file_store, '/path/to/cache/directory'
View
37 actionpack/lib/action_controller/metal/conditional_get.rb
@@ -41,6 +41,11 @@ def etag(&etagger)
# * <tt>:last_modified</tt>.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cachable by other devices (proxy caches).
+ # * <tt>:template</tt> By default, the template digest for the current
+ # controller/action is included in ETags. If the action renders a
+ # different template, you can include its digest instead. If the action
+ # doesn't render a template at all, you can pass <tt>template: false</tt>
+ # to skip any attempt to check for a template digest.
#
# === Example:
#
@@ -66,18 +71,24 @@ def etag(&etagger)
# @article = Article.find(params[:id])
# fresh_when(@article, public: true)
# end
+ #
+ # When rendering a different template than the default controller/action
+ # style, you can indicate which digest to include in the ETag:
+ #
+ # before_action { fresh_when @article, template: 'widgets/show' }
+ #
def fresh_when(record_or_options, additional_options = {})
if record_or_options.is_a? Hash
options = record_or_options
- options.assert_valid_keys(:etag, :last_modified, :public)
+ options.assert_valid_keys(:etag, :last_modified, :public, :template)
else
record = record_or_options
options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
end
- response.etag = combine_etags(options[:etag]) if options[:etag]
- response.last_modified = options[:last_modified] if options[:last_modified]
- response.cache_control[:public] = true if options[:public]
+ response.etag = combine_etags(options) if options[:etag] || options[:template]
+ response.last_modified = options[:last_modified] if options[:last_modified]
+ response.cache_control[:public] = true if options[:public]
head :not_modified if request.fresh?(response)
end
@@ -93,6 +104,11 @@ def fresh_when(record_or_options, additional_options = {})
# * <tt>:last_modified</tt>.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cachable by other devices (proxy caches).
+ # * <tt>:template</tt> By default, the template digest for the current
+ # controller/action is included in ETags. If the action renders a
+ # different template, you can include its digest instead. If the action
+ # doesn't render a template at all, you can pass <tt>template: false</tt>
+ # to skip any attempt to check for a template digest.
#
# === Example:
#
@@ -133,6 +149,14 @@ def fresh_when(record_or_options, additional_options = {})
# end
# end
# end
+ #
+ # When rendering a different template than the default controller/action
+ # style, you can indicate which digest to include in the ETag:
+ #
+ # def show
+ # super if stale? @article, template: 'widgets/show'
+ # end
+ #
def stale?(record_or_options, additional_options = {})
fresh_when(record_or_options, additional_options)
!request.fresh?(response)
@@ -168,8 +192,9 @@ def expires_now
end
private
- def combine_etags(etag)
- [ etag, *etaggers.map { |etagger| instance_exec(&etagger) }.compact ]
+ def combine_etags(options)
+ etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
+ etags.unshift options[:etag]
end
end
end
View
50 actionpack/lib/action_controller/metal/etag_with_template_digest.rb
@@ -0,0 +1,50 @@
+module ActionController
+ # When our views change, they should bubble up into HTTP cache freshness
+ # and bust browser caches. So the template digest for the current action
+ # is automatically included in the ETag.
+ #
+ # Enabled by default for apps that use Action View. Disable by setting
+ #
+ # config.action_controller.etag_with_template_digest = false
+ #
+ # Override the template to digest by passing `:template` to `fresh_when`
+ # and `stale?` calls. For example:
+ #
+ # # We're going to render widgets/show, not posts/show
+ # fresh_when @post, template: 'widgets/show'
+ #
+ # # We're not going to render a template, so omit it from the ETag.
+ # fresh_when @post, template: false
+ #
+ module EtagWithTemplateDigest
+ extend ActiveSupport::Concern
+
+ include ActionController::ConditionalGet
+
+ included do
+ class_attribute :etag_with_template_digest
+ self.etag_with_template_digest = true
+
+ ActiveSupport.on_load :action_view, yield: true do |action_view_base|
+ etag do |options|
+ determine_template_etag(options) if etag_with_template_digest
+ end
+ end
+ end
+
+ private
+ def determine_template_etag(options)
+ if template = pick_template_for_etag(options)
+ lookup_and_digest_template(template)
+ end
+ end
+
+ def pick_template_for_etag(options)
+ options.fetch(:template) { "#{controller_name}/#{action_name}" }
+ end
+
+ def lookup_and_digest_template(template)
+ ActionView::Digestor.digest name: template, finder: lookup_context
+ end
+ end
+end
View
244 actionpack/lib/action_controller/metal/mime_responds.rb
@@ -5,56 +5,22 @@ module ActionController #:nodoc:
module MimeResponds
extend ActiveSupport::Concern
- included do
- class_attribute :responder, :mimes_for_respond_to
- self.responder = ActionController::Responder
- clear_respond_to
- end
-
module ClassMethods
- # Defines mime types that are rendered by default when invoking
- # <tt>respond_with</tt>.
- #
- # respond_to :html, :xml, :json
- #
- # Specifies that all actions in the controller respond to requests
- # for <tt>:html</tt>, <tt>:xml</tt> and <tt>:json</tt>.
- #
- # To specify on per-action basis, use <tt>:only</tt> and
- # <tt>:except</tt> with an array of actions or a single action:
- #
- # respond_to :html
- # respond_to :xml, :json, except: [ :edit ]
- #
- # This specifies that all actions respond to <tt>:html</tt>
- # and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
- # <tt>:json</tt>.
- #
- # respond_to :json, only: :create
- #
- # This specifies that the <tt>:create</tt> action and no other responds
- # to <tt>:json</tt>.
- def respond_to(*mimes)
- options = mimes.extract_options!
-
- only_actions = Array(options.delete(:only)).map(&:to_s)
- except_actions = Array(options.delete(:except)).map(&:to_s)
-
- new = mimes_for_respond_to.dup
- mimes.each do |mime|
- mime = mime.to_sym
- new[mime] = {}
- new[mime][:only] = only_actions unless only_actions.empty?
- new[mime][:except] = except_actions unless except_actions.empty?
- end
- self.mimes_for_respond_to = new.freeze
+ def respond_to(*)
+ raise NoMethodError, "The controller-level `respond_to' feature has " \
+ "been extracted to the `responders` gem. Add it to your Gemfile to " \
+ "continue using this feature:\n" \
+ " gem 'responders', '~> 2.0'\n" \
+ "Consult the Rails upgrade guide for details."
end
+ end
- # Clear all mime types in <tt>respond_to</tt>.
- #
- def clear_respond_to
- self.mimes_for_respond_to = Hash.new.freeze
- end
+ def respond_with(*)
+ raise NoMethodError, "The `respond_with' feature has been extracted " \
+ "to the `responders` gem. Add it to your Gemfile to continue using " \
+ "this feature:\n" \
+ " gem 'responders', '~> 2.0'\n" \
+ "Consult the Rails upgrade guide for details."
end
# Without web-service support, an action which collects the data for displaying a list of people
@@ -217,7 +183,7 @@ def clear_respond_to
# format.html.phone { redirect_to progress_path }
# format.html.none { render "trash" }
# end
- #
+ #
# Variants also support common `any`/`all` block that formats have.
#
# It works for both inline:
@@ -253,189 +219,13 @@ def clear_respond_to
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- if collector = retrieve_collector_from_mimes(mimes, &block)
- response = collector.response
- response ? response.call : render({})
- end
- end
-
- # For a given controller action, respond_with generates an appropriate
- # response based on the mime-type requested by the client.
- #
- # If the method is called with just a resource, as in this example -
- #
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
- #
- # def index
- # @people = Person.all
- # respond_with @people
- # end
- # end
- #
- # then the mime-type of the response is typically selected based on the
- # request's Accept header and the set of available formats declared
- # by previous calls to the controller's class method +respond_to+. Alternatively
- # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
- # the controller.
- #
- # If an acceptable format is not identified, the application returns a
- # '406 - not acceptable' status. Otherwise, the default response is to render
- # a template named after the current action and the selected format,
- # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
- # depends on the selected format:
- #
- # * for an html response - if the request method is +get+, an exception
- # is raised but for other requests such as +post+ the response
- # depends on whether the resource has any validation errors (i.e.
- # assuming that an attempt has been made to save the resource,
- # e.g. by a +create+ action) -
- # 1. If there are no errors, i.e. the resource
- # was saved successfully, the response +redirect+'s to the resource
- # i.e. its +show+ action.
- # 2. If there are validation errors, the response
- # renders a default action, which is <tt>:new</tt> for a
- # +post+ request or <tt>:edit</tt> for +patch+ or +put+.
- # Thus an example like this -
- #
- # respond_to :html, :xml
- #
- # def create
- # @user = User.new(params[:user])
- # flash[:notice] = 'User was successfully created.' if @user.save
- # respond_with(@user)
- # end
- #
- # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
- #
- # def create
- # @user = User.new(params[:user])
- # respond_to do |format|
- # if @user.save
- # flash[:notice] = 'User was successfully created.'
- # format.html { redirect_to(@user) }
- # format.xml { render xml: @user }
- # else
- # format.html { render action: "new" }
- # format.xml { render xml: @user }
- # end
- # end
- # end
- #
- # * for a JavaScript request - if the template isn't found, an exception is
- # raised.
- # * for other requests - i.e. data formats such as xml, json, csv etc, if
- # the resource passed to +respond_with+ responds to <code>to_<format></code>,
- # the method attempts to render the resource in the requested format
- # directly, e.g. for an xml request, the response is equivalent to calling
- # <code>render xml: resource</code>.
- #
- # === Nested resources
- #
- # As outlined above, the +resources+ argument passed to +respond_with+
- # can play two roles. It can be used to generate the redirect url
- # for successful html requests (e.g. for +create+ actions when
- # no template exists), while for formats other than html and JavaScript
- # it is the object that gets rendered, by being converted directly to the
- # required format (again assuming no template exists).
- #
- # For redirecting successful html requests, +respond_with+ also supports
- # the use of nested resources, which are supplied in the same way as
- # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.comments.build(params[:task])
- # flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task)
- # end
- #
- # This would cause +respond_with+ to redirect to <code>project_task_url</code>
- # instead of <code>task_url</code>. For request formats other than html or
- # JavaScript, if multiple resources are passed in this way, it is the last
- # one specified that is rendered.
- #
- # === Customizing response behavior
- #
- # Like +respond_to+, +respond_with+ may also be called with a block that
- # can be used to overwrite any of the default responses, e.g. -
- #
- # def create
- # @user = User.new(params[:user])
- # flash[:notice] = "User was successfully created." if @user.save
- #
- # respond_with(@user) do |format|
- # format.html { render }
- # end
- # end
- #
- # The argument passed to the block is an ActionController::MimeResponds::Collector
- # object which stores the responses for the formats defined within the
- # block. Note that formats with responses defined explicitly in this way
- # do not have to first be declared using the class method +respond_to+.
- #
- # Also, a hash passed to +respond_with+ immediately after the specified
- # resource(s) is interpreted as a set of options relevant to all
- # formats. Any option accepted by +render+ can be used, e.g.
- # respond_with @people, status: 200
- # However, note that these options are ignored after an unsuccessful attempt
- # to save a resource, e.g. when automatically rendering <tt>:new</tt>
- # after a post request.
- #
- # Two additional options are relevant specifically to +respond_with+ -
- # 1. <tt>:location</tt> - overwrites the default redirect location used after
- # a successful html +post+ request.
- # 2. <tt>:action</tt> - overwrites the default render action used after an
- # unsuccessful html +post+ request.
- def respond_with(*resources, &block)
- if self.class.mimes_for_respond_to.empty?
- raise "In order to use respond_with, first you need to declare the " \
- "formats your controller responds to in the class level."
- end
-
- if collector = retrieve_collector_from_mimes(&block)
- options = resources.size == 1 ? {} : resources.extract_options!
- options = options.clone
- options[:default_response] = collector.response
- (options.delete(:responder) || self.class.responder).call(self, resources, options)
- end
- end
-
- protected
-
- # Collect mimes declared in the class method respond_to valid for the
- # current action.
- def collect_mimes_from_class_level #:nodoc:
- action = action_name.to_s
-
- self.class.mimes_for_respond_to.keys.select do |mime|
- config = self.class.mimes_for_respond_to[mime]
-
- if config[:except]
- !config[:except].include?(action)
- elsif config[:only]
- config[:only].include?(action)
- else
- true
- end
- end
- end
-
- # Returns a Collector object containing the appropriate mime-type response
- # for the current request, based on the available responses defined by a block.
- # In typical usage this is the block passed to +respond_with+ or +respond_to+.
- #
- # Sends :not_acceptable to the client and returns nil if no suitable format
- # is available.
- def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
- mimes ||= collect_mimes_from_class_level
collector = Collector.new(mimes, request.variant)
block.call(collector) if block_given?
- format = collector.negotiate_format(request)
- if format
+ if format = collector.negotiate_format(request)
_process_format(format)
- collector
+ response = collector.response
+ response ? response.call : render({})
else
raise ActionController::UnknownFormat
end
View
297 actionpack/lib/action_controller/metal/responder.rb
@@ -1,297 +0,0 @@
-require 'active_support/json'
-
-module ActionController #:nodoc:
- # Responsible for exposing a resource to different mime requests,
- # usually depending on the HTTP verb. The responder is triggered when
- # <code>respond_with</code> is called. The simplest case to study is a GET request:
- #
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
- #
- # def index
- # @people = Person.all
- # respond_with(@people)
- # end
- # end
- #
- # When a request comes in, for example for an XML response, three steps happen:
- #
- # 1) the responder searches for a template at people/index.xml;
- #
- # 2) if the template is not available, it will invoke <code>#to_xml</code> on the given resource;
- #
- # 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it.
- #
- # === Built-in HTTP verb semantics
- #
- # The default \Rails responder holds semantics for each HTTP verb. Depending on the
- # content type, verb and the resource status, it will behave differently.
- #
- # Using \Rails default responder, a POST request for creating an object could
- # be written as:
- #
- # def create
- # @user = User.new(params[:user])
- # flash[:notice] = 'User was successfully created.' if @user.save
- # respond_with(@user)
- # end
- #
- # Which is exactly the same as:
- #
- # def create
- # @user = User.new(params[:user])
- #
- # respond_to do |format|
- # if @user.save
- # flash[:notice] = 'User was successfully created.'
- # format.html { redirect_to(@user) }
- # format.xml { render xml: @user, status: :created, location: @user }
- # else
- # format.html { render action: "new" }
- # format.xml { render xml: @user.errors, status: :unprocessable_entity }
- # end
- # end
- # end
- #
- # The same happens for PATCH/PUT and DELETE requests.
- #
- # === Nested resources
- #
- # You can supply nested resources as you do in <code>form_for</code> and <code>polymorphic_url</code>.
- # Consider the project has many tasks example. The create action for
- # TasksController would be like:
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.tasks.build(params[:task])
- # flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task)
- # end
- #
- # Giving several resources ensures that the responder will redirect to
- # <code>project_task_url</code> instead of <code>task_url</code>.
- #
- # Namespaced and singleton resources require a symbol to be given, as in
- # polymorphic urls. If a project has one manager which has many tasks, it
- # should be invoked as:
- #
- # respond_with(@project, :manager, @task)
- #
- # Note that if you give an array, it will be treated as a collection,
- # so the following is not equivalent:
- #
- # respond_with [@project, :manager, @task]
- #
- # === Custom options
- #
- # <code>respond_with</code> also allows you to pass options that are forwarded
- # to the underlying render call. Those options are only applied for success
- # scenarios. For instance, you can do the following in the create method above:
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.tasks.build(params[:task])
- # flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task, status: 201)
- # end
- #
- # This will return status 201 if the task was saved successfully. If not,
- # it will simply ignore the given options and return status 422 and the
- # resource errors. You can also override the location to redirect to:
- #
- # respond_with(@project, location: root_path)
- #
- # To customize the failure scenario, you can pass a block to
- # <code>respond_with</code>:
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.tasks.build(params[:task])
- # respond_with(@project, @task, status: 201) do |format|
- # if @task.save
- # flash[:notice] = 'Task was successfully created.'
- # else
- # format.html { render "some_special_template" }
- # end
- # end
- # end
- #
- # Using <code>respond_with</code> with a block follows the same syntax as <code>respond_to</code>.
- class Responder
- attr_reader :controller, :request, :format, :resource, :resources, :options
-
- DEFAULT_ACTIONS_FOR_VERBS = {
- :post => :new,
- :patch => :edit,
- :put => :edit
- }
-
- def initialize(controller, resources, options={})
- @controller = controller
- @request = @controller.request
- @format = @controller.formats.first
- @resource = resources.last
- @resources = resources
- @options = options
- @action = options.delete(:action)
- @default_response = options.delete(:default_response)
- end
-
- delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
-
- # Undefine :to_json and :to_yaml since it's defined on Object
- undef_method(:to_json) if method_defined?(:to_json)
- undef_method(:to_yaml) if method_defined?(:to_yaml)
-
- # Initializes a new responder and invokes the proper format. If the format is
- # not defined, call to_format.
- #
- def self.call(*args)
- new(*args).respond
- end
-
- # Main entry point for responder responsible to dispatch to the proper format.
- #
- def respond
- method = "to_#{format}"
- respond_to?(method) ? send(method) : to_format
- end
-
- # HTML format does not render the resource, it always attempt to render a
- # template.
- #
- def to_html
- default_render
- rescue ActionView::MissingTemplate => e
- navigation_behavior(e)
- end
-
- # to_js simply tries to render a template. If no template is found, raises the error.
- def to_js
- default_render
- end
-
- # All other formats follow the procedure below. First we try to render a
- # template, if the template is not available, we verify if the resource
- # responds to :to_format and display it.
- #
- def to_format
- if get? || !has_errors? || response_overridden?
- default_render
- else
- display_errors
- end
- rescue ActionView::MissingTemplate => e
- api_behavior(e)
- end
-
- protected
-
- # This is the common behavior for formats associated with browsing, like :html, :iphone and so forth.
- def navigation_behavior(error)
- if get?
- raise error
- elsif has_errors? && default_action
- render :action => default_action
- else
- redirect_to navigation_location
- end
- end
-
- # This is the common behavior for formats associated with APIs, such as :xml and :json.
- def api_behavior(error)
- raise error unless resourceful?
- raise MissingRenderer.new(format) unless has_renderer?
-
- if get?
- display resource
- elsif post?
- display resource, :status => :created, :location => api_location
- else
- head :no_content
- end
- end
-
- # Checks whether the resource responds to the current format or not.
- #
- def resourceful?
- resource.respond_to?("to_#{format}")
- end
-
- # Returns the resource location by retrieving it from the options or
- # returning the resources array.
- #
- def resource_location
- options[:location] || resources
- end
- alias :navigation_location :resource_location
- alias :api_location :resource_location
-
- # If a response block was given, use it, otherwise call render on
- # controller.
- #
- def default_render
- if @default_response
- @default_response.call(options)
- else
- controller.default_render(options)
- end
- end
-
- # Display is just a shortcut to render a resource with the current format.
- #
- # display @user, status: :ok
- #
- # For XML requests it's equivalent to:
- #
- # render xml: @user, status: :ok
- #
- # Options sent by the user are also used:
- #
- # respond_with(@user, status: :created)
- # display(@user, status: :ok)
- #
- # Results in:
- #
- # render xml: @user, status: :created
- #
- def display(resource, given_options={})
- controller.render given_options.merge!(options).merge!(format => resource)
- end
-
- def display_errors
- controller.render format => resource_errors, :status => :unprocessable_entity
- end
-
- # Check whether the resource has errors.
- #
- def has_errors?
- resource.respond_to?(:errors) && !resource.errors.empty?
- end
-
- # Check whether the necessary Renderer is available
- def has_renderer?
- Renderers::RENDERERS.include?(format)
- end
-
- # By default, render the <code>:edit</code> action for HTML requests with errors, unless
- # the verb was POST.
- #
- def default_action
- @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
- end
-
- def resource_errors
- respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
- end
-
- def json_resource_errors
- {:errors => resource.errors}
- end
-
- def response_overridden?
- @default_response.present?
- end
- end
-end
View
9 actionpack/lib/action_controller/test_case.rb
@@ -93,6 +93,13 @@ def reset_template_assertion
# # assert that no partials were rendered
# assert_template partial: false
#
+ # # assert that a file was rendered
+ # assert_template file: "README.rdoc"
+ #
+ # # assert that no file was rendered
+ # assert_template file: nil
+ # assert_template file: false
+ #
# In a view test case, you can also assert that specific locals are passed
# to partials:
#
@@ -142,6 +149,8 @@ def assert_template(options = {}, message = nil)
if options[:file]
assert_includes @_files.keys, options[:file]
+ elsif options.key?(:file)
+ assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
end
if expected_partial = options[:partial]
View
36 actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -3,6 +3,7 @@
require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
require 'active_support/message_verifier'
+require 'active_support/json'
module ActionDispatch
class Request < Rack::Request
@@ -90,6 +91,7 @@ class Cookies
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
+ COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -173,10 +175,14 @@ def signed_or_encrypted
end
end
+ # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
+ # to the Message{Encryptor,Verifier} allows us to handle the
+ # (de)serialization step within the cookie jar, which gives us the
+ # opportunity to detect and migrate legacy cookies.
module VerifyAndUpgradeLegacySignedMessage
def initialize(*args)
super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: NullSerializer)
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def verify_and_upgrade_legacy_signed_message(name, signed_message)
@@ -212,7 +218,8 @@ def self.options_for_env(env) #:nodoc:
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
- serializer: env[COOKIES_SERIALIZER]
+ serializer: env[COOKIES_SERIALIZER],
+ digest: env[COOKIES_DIGEST]
}
end
@@ -385,24 +392,11 @@ def []=(name, options)
class JsonSerializer
def self.load(value)
- JSON.parse(value, quirks_mode: true)
+ ActiveSupport::JSON.decode(value)
end
def self.dump(value)
- JSON.generate(value, quirks_mode: true)
- end
- end
-
- # Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
- # allows us to handle the (de)serialization step within the cookie jar,
- # which gives us the opportunity to detect and migrate legacy cookies.
- class NullSerializer
- def self.load(value)
- value
- end
-
- def self.dump(value)
- value
+ ActiveSupport::JSON.encode(value)
end
end
@@ -441,6 +435,10 @@ def serializer
serializer
end
end
+
+ def digest
+ @options[:digest] || 'SHA1'
+ end
end
class SignedCookieJar #:nodoc:
@@ -451,7 +449,7 @@ def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
- @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def [](name)
@@ -508,7 +506,7 @@ def initialize(parent_jar, key_generator, options = {})
@options = options
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def [](name)
View
2  actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
@@ -3,7 +3,7 @@ Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unse
<% @traces.each do |name, trace| %>
<% if trace.any? %>
<%= name %>
-<%= trace.map(&:trace).join("\n") %>
+<%= trace.map { |t| t[:trace] }.join("\n") %>
<% end %>
<% end %>
View
116 actionpack/lib/action_dispatch/routing/mapper.rb
@@ -63,7 +63,7 @@ class Mapping #:nodoc:
attr_reader :requirements, :conditions, :defaults
attr_reader :to, :default_controller, :default_action, :as, :anchor
- def self.build(scope, set, path, options)
+ def self.build(scope, set, path, as, options)
options = scope[:options].merge(options) if scope[:options]
options.delete :only
@@ -74,10 +74,10 @@ def self.build(scope, set, path, options)
defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
- new scope, set, path, defaults, options
+ new scope, set, path, defaults, as, options
end
- def initialize(scope, set, path, defaults, options)
+ def initialize(scope, set, path, defaults, as, options)
@requirements, @conditions = {}, {}
@defaults = defaults
@set = set
@@ -85,7 +85,7 @@ def initialize(scope, set, path, defaults, options)
@to = options.delete :to
@default_controller = options.delete(:controller) || scope[:controller]
@default_action = options.delete(:action) || scope[:action]
- @as = options.delete :as
+ @as = as
@anchor = options.delete :anchor
formatted = options.delete :format
@@ -1046,8 +1046,6 @@ module Resources
VALID_ON_OPTIONS = [:new, :collection, :member]
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS = %w(index create new show update destroy)
- RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
- RESOURCE_SCOPES = [:resource, :resources]
class Resource #:nodoc:
attr_reader :controller, :path, :options, :param
@@ -1521,7 +1519,7 @@ def decomposed_match(path, options) # :nodoc:
if on = options.delete(:on)
send(on) { decomposed_match(path, options) }
else
- case @scope[:scope_level]
+ case @scope.scope_level
when :resources
nested { decomposed_match(path, options) }
when :resource
@@ -1544,13 +1542,13 @@ def add_route(action, options) # :nodoc:
action = nil
end
- if !options.fetch(:as, true) # if it's set to nil or false
- options.delete(:as)
- else
- options[:as] = name_for_action(options[:as], action)
- end
+ as = if !options.fetch(:as, true) # if it's set to nil or false
+ options.delete(:as)
+ else
+ name_for_action(options.delete(:as), action)
+ end
- mapping = Mapping.build(@scope, @set, URI.parser.escape(path), options)
+ mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
@@ -1564,7 +1562,7 @@ def root(path, options={})
raise ArgumentError, "must be called with a path and/or options"
end
- if @scope[:scope_level] == :resources
+ if @scope.resources?
with_scope_level(:root) do
scope(parent_resource.path) do
super(options)
@@ -1631,15 +1629,15 @@ def scope_action_options #:nodoc:
end
def resource_scope? #:nodoc:
- RESOURCE_SCOPES.include? @scope[:scope_level]
+ @scope.resource_scope?
end
def resource_method_scope? #:nodoc:
- RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
+ @scope.resource_method_scope?
end
def nested_scope? #:nodoc:
- @scope[:scope_level] == :nested
+ @scope.nested?
end
def with_exclusive_scope
@@ -1655,7 +1653,7 @@ def with_exclusive_scope
end
def with_scope_level(kind)
- @scope = @scope.new(:scope_level => kind)
+ @scope = @scope.new_level(kind)
yield
ensure
@scope = @scope.parent
@@ -1699,8 +1697,8 @@ def param_constraint #:nodoc:
@scope[:constraints][parent_resource.param]
end
- def canonical_action?(action, flag) #:nodoc:
- flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
+ def canonical_action?(action) #:nodoc:
+ resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
def shallow_scope(path, options = {}) #:nodoc:
@@ -1714,7 +1712,7 @@ def shallow_scope(path, options = {}) #:nodoc:
end
def path_for_action(action, path) #:nodoc:
- if canonical_action?(action, path.blank?)
+ if path.blank? && canonical_action?(action)
@scope[:path].to_s
else
"#{@scope[:path]}/#{action_path(action, path)}"
@@ -1729,15 +1727,17 @@ def action_path(name, path = nil) #:nodoc:
def prefix_name_for_action(as, action) #:nodoc:
if as
prefix = as
- elsif !canonical_action?(action, @scope[:scope_level])
+ elsif !canonical_action?(action)
prefix = action
end
- prefix.to_s.tr('-', '_') if prefix
+
+ if prefix && prefix != '/' && !prefix.empty?
+ Mapper.normalize_name prefix.to_s.tr('-', '_')
+ end
end
def name_for_action(as, action) #:nodoc:
prefix = prefix_name_for_action(as, action)
- prefix = Mapper.normalize_name(prefix) if prefix
name_prefix = @scope[:as]
if parent_resource
@@ -1747,22 +1747,9 @@ def name_for_action(as, action) #:nodoc:
member_name = parent_resource.member_name
end
- name = case @scope[:scope_level]
- when :nested
- [name_prefix, prefix]
- when :collection
- [prefix, name_prefix, collection_name]
- when :new
- [prefix, :new, name_prefix, member_name]
- when :member
- [prefix, name_prefix, member_name]
- when :root
- [name_prefix, collection_name, prefix]
- else
- [name_prefix, member_name, prefix]
- end
+ name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
- if candidate = name.select(&:present?).join("_").presence
+ if candidate = name.compact.join("_").presence
# If a name was not explicitly given, we check if it is valid
# and return nil in case it isn't. Otherwise, we pass the invalid name
# forward so the underlying router engine treats it and raises an exception.
@@ -1897,11 +1884,48 @@ class Scope # :nodoc:
:controller, :action, :path_names, :constraints,
:shallow, :blocks, :defaults, :options]
- attr_reader :parent
+ RESOURCE_SCOPES = [:resource, :resources]
+ RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
+
+ attr_reader :parent, :scope_level
- def initialize(hash, parent = {})
+ def initialize(hash, parent = {}, scope_level = nil)
@hash = hash
@parent = parent
+ @scope_level = scope_level
+ end
+
+ def nested?
+ scope_level == :nested
+ end
+
+ def resources?
+ scope_level == :resources
+ end
+
+ def resource_method_scope?
+ RESOURCE_METHOD_SCOPES.include? scope_level
+ end
+
+ def action_name(name_prefix, prefix, collection_name, member_name)
+ case scope_level
+ when :nested
+ [name_prefix, prefix]
+ when :collection
+ [prefix, name_prefix, collection_name]
+ when :new
+ [prefix, :new, name_prefix, member_name]
+ when :member
+ [prefix, name_prefix, member_name]
+ when :root
+ [name_prefix, collection_name, prefix]
+ else
+ [name_prefix, member_name, prefix]
+ end
+ end
+
+ def resource_scope?
+ RESOURCE_SCOPES.include? scope_level
end
def options
@@ -1909,7 +1933,15 @@ def options
end
def new(hash)
- self.class.new hash, self
+ self.class.new hash, self, scope_level
+ end
+
+ def new_level(level)
+ self.class.new(self, self, level)
+ end
+
+ def fetch(key, &block)
+ @hash.fetch(key, &block)
end
def [](key)
View
27 actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -142,22 +142,27 @@ def polymorphic_path(record_or_hash_or_array, options = {})
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__ + 1
- def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
- end # end
- #
- def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
- end # end
+ def #{action}_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url_for_action("#{action}", record_or_hash, options)
+ end
+
+ def #{action}_polymorphic_path(record_or_hash, options = {})
+ polymorphic_path_for_action("#{action}", record_or_hash, options)
+ end
EOT
end
private
+ def polymorphic_url_for_action(action, record_or_hash, options)
+ polymorphic_url(record_or_hash, options.merge(:action => action))
+ end
+
+ def polymorphic_path_for_action(action, record_or_hash, options)
+ options = options.merge(:action => action, :routing_type => :path)
+ polymorphic_path(record_or_hash, options)
+ end
+
class HelperMethodBuilder # :nodoc:
CACHE = { 'path' => {}, 'url' => {} }
View
22 actionpack/test/abstract_unit.rb
@@ -484,6 +484,9 @@ def shutdown
method = job[1]
reporter = job[2]
result = Minitest.run_one_method klass, method
+ if result.error?
+ translate_exceptions result
+ end
queue.record reporter, result
end
}
@@ -491,14 +494,23 @@ def shutdown
@size.times { @queue << nil }
pool.each { |pid| Process.waitpid pid }
end
+
+ private
+ def translate_exceptions(result)
+ result.failures.map! { |e|
+ begin
+ Marshal.dump e
+ e
+ rescue TypeError
+ ex = Exception.new e.message
+ ex.set_backtrace e.backtrace
+ Minitest::UnexpectedError.new ex
+ end
+ }
+ end
end
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!
View
23 actionpack/test/controller/action_pack_assertions_test.rb
@@ -482,6 +482,11 @@ def test_with_file_failure
assert_raise(ActiveSupport::TestCase::Assertion) do
assert_template :file => 'test/hello_world'
end
+
+ get :render_file_absolute_path
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template file: nil
+ end
end
def test_with_nil_passes_when_no_template_rendered
@@ -606,6 +611,24 @@ def test_assert_template_reset_between_requests
get :nothing
assert_template nil
+
+ get :partial
+ assert_template partial: 'test/_partial'
+
+ get :nothing
+ assert_template partial: nil
+
+ get :render_with_layout
+ assert_template layout: 'layouts/standard'
+
+ get :nothing
+ assert_template layout: nil
+
+ get :render_file_relative_path
+ assert_template file: 'README.rdoc'
+
+ get :nothing
+ assert_template file: nil
end
end
View
2  actionpack/test/controller/live_stream_test.rb
@@ -162,7 +162,7 @@ def thread_locals
end
def with_stale
- render :text => 'stale' if stale?(:etag => "123")
+ render text: 'stale' if stale?(etag: "123", template: false)
end
def exception_in_view
View
737 actionpack/test/controller/mime/respond_with_test.rb
@@ -1,737 +0,0 @@
-require 'abstract_unit'
-require 'controller/fake_models'
-
-class RespondWithController < ActionController::Base
- class CustomerWithJson < Customer
- def to_json; super; end
- end
-
- respond_to :html, :json, :touch
- respond_to :xml, :except => :using_resource_with_block
- respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ]
-
- def using_resource
- respond_with(resource)
- end
-
- def using_hash_resource
- respond_with({:result => resource})
- end
-
- def using_resource_with_block
- respond_with(resource) do |format|
- format.csv { render :text => "CSV" }
- end
- end
-
- def using_resource_with_overwrite_block
- respond_with(resource) do |format|
- format.html { render :text => "HTML" }
- end
- end
-
- def using_resource_with_collection
- respond_with([resource, Customer.new("jamis", 9)])
- end
-
- def using_resource_with_parent
- respond_with(Quiz::Store.new("developer?", 11), Customer.new("david", 13))
- end
-
- def using_resource_with_status_and_location
- respond_with(resource, :location => "http://test.host/", :status => :created)
- end
-
- def using_resource_with_json
- respond_with(CustomerWithJson.new("david", request.delete? ? nil : 13))
- end
-
- def using_invalid_resource_with_template
- respond_with(resource)
- end
-
- def using_options_with_template
- @customer = resource
- respond_with(@customer, :status => 123, :location => "http://test.host/")
- end
-
- def using_resource_with_responder
- responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
- respond_with(resource, :responder => responder)
- end
-
- def using_resource_with_action
- respond_with(resource, :action => :foo) do |format|
- format.html { raise ActionView::MissingTemplate.new([], "bar", ["foo"], {}, false) }
- end
- end
-
- def using_responder_with_respond
- responder = Class.new(ActionController::Responder) do
- def respond; @controller.render :text => "respond #{format}"; end
- end
- respond_with(resource, :responder => responder)
- end
-
- def respond_with_additional_params
- @params = RespondWithController.params
- respond_with({:result => resource}, @params)
- end
-
-protected
- def self.params
- {
- :foo => 'bar'
- }
- end
-
- def resource
- Customer.new("david", request.delete? ? nil : 13)
- end
-end
-
-class InheritedRespondWithController < RespondWithController
- clear_respond_to
- respond_to :xml, :json
-
- def index
- respond_with(resource) do |format|
- format.json { render :text => "JSON" }
- end
- end
-end
-
-class RenderJsonRespondWithController < RespondWithController
- clear_respond_to
- respond_to :json
-
- def index
- respond_with(resource) do |format|
- format.json { render :json => RenderJsonTestException.new('boom') }
- end
- end
-
- def create
- resource = ValidatedCustomer.new(params[:name], 1)
- respond_with(resource) do |format|
- format.json do
- if resource.errors.empty?
- render :json => { :valid => true }
- else
- render :json => { :valid => false }
- end
- end
- end
- end
-end
-
-class CsvRespondWithController < ActionController::Base
- respond_to :csv
-
- class RespondWithCsv
- def to_csv
- "c,s,v"
- end
- end
-
- def index
- respond_with(RespondWithCsv.new)
- end
-end
-
-class EmptyRespondWithController < ActionController::Base
- def index
- respond_with(Customer.new("david", 13))
- end
-end
-
-class RespondWithControllerTest < ActionController::TestCase
- def setup
- super
- @request.host = "www.example.com"
- Mime::Type.register_alias('text/html', :iphone)
- Mime::Type.register_alias('text/html', :touch)
- Mime::Type.register('text/x-mobile', :mobile)
- end
-
- def teardown
- super
- Mime::Type.unregister(:iphone)
- Mime::Type.unregister(:touch)
- Mime::Type.unregister(:mobile)
- end
-
- def test_respond_with_shouldnt_modify_original_hash
- get :respond_with_additional_params
- assert_equal RespondWithController.params, assigns(:params)
- end
-
- def test_using_resource
- @request.accept = "application/xml"
- get :using_resource
- assert_equal "application/xml", @response.content_type
- assert_equal "<name>david</name>", @response.body
-
- @request.accept = "application/json"
- assert_raise ActionView::MissingTemplate do
- get :using_resource
- end
- end