diff options
74 files changed, 1185 insertions, 561 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index d12aa0772f..5d0e815f86 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,66 +28,66 @@ GIT GIT remote: git://github.com/turbolinks/turbolinks-rails.git - revision: 1604dcd7bad911f1471d65e4f47cd19d844354f1 + revision: 65884729016dbb4d032f12bb01b7e7c1ddeb68ac specs: - turbolinks (5.0.0.beta1) + turbolinks (5.0.0.beta2) turbolinks-source PATH remote: . specs: - actioncable (5.0.0.beta2) - actionpack (= 5.0.0.beta2) + actioncable (5.0.0.beta3) + actionpack (= 5.0.0.beta3) nio4r (~> 1.2) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.beta2) - actionpack (= 5.0.0.beta2) - actionview (= 5.0.0.beta2) - activejob (= 5.0.0.beta2) + actionmailer (5.0.0.beta3) + actionpack (= 5.0.0.beta3) + actionview (= 5.0.0.beta3) + activejob (= 5.0.0.beta3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (5.0.0.beta2) - actionview (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) + actionpack (5.0.0.beta3) + actionview (= 5.0.0.beta3) + activesupport (= 5.0.0.beta3) rack (~> 2.x) rack-test (~> 0.6.3) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.beta2) - activesupport (= 5.0.0.beta2) + actionview (5.0.0.beta3) + activesupport (= 5.0.0.beta3) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.beta2) - activesupport (= 5.0.0.beta2) + activejob (5.0.0.beta3) + activesupport (= 5.0.0.beta3) globalid (>= 0.3.6) - activemodel (5.0.0.beta2) - activesupport (= 5.0.0.beta2) - activerecord (5.0.0.beta2) - activemodel (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) + activemodel (5.0.0.beta3) + activesupport (= 5.0.0.beta3) + activerecord (5.0.0.beta3) + activemodel (= 5.0.0.beta3) + activesupport (= 5.0.0.beta3) arel (~> 7.0) - activesupport (5.0.0.beta2) + activesupport (5.0.0.beta3) concurrent-ruby (~> 1.0) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - rails (5.0.0.beta2) - actioncable (= 5.0.0.beta2) - actionmailer (= 5.0.0.beta2) - actionpack (= 5.0.0.beta2) - actionview (= 5.0.0.beta2) - activejob (= 5.0.0.beta2) - activemodel (= 5.0.0.beta2) - activerecord (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) + rails (5.0.0.beta3) + actioncable (= 5.0.0.beta3) + actionmailer (= 5.0.0.beta3) + actionpack (= 5.0.0.beta3) + actionview (= 5.0.0.beta3) + activejob (= 5.0.0.beta3) + activemodel (= 5.0.0.beta3) + activerecord (= 5.0.0.beta3) + activesupport (= 5.0.0.beta3) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.beta2) + railties (= 5.0.0.beta3) sprockets-rails (>= 2.0.0) - railties (5.0.0.beta2) - actionpack (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) + railties (5.0.0.beta3) + actionpack (= 5.0.0.beta3) + activesupport (= 5.0.0.beta3) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) @@ -158,7 +158,7 @@ GEM mime-types (>= 1.16, < 3) metaclass (0.0.4) method_source (0.8.2) - mime-types (2.99) + mime-types (2.99.1) mini_portile2 (2.0.0) minitest (5.3.3) mocha (0.14.0) @@ -239,7 +239,7 @@ GEM sprockets (3.5.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.0.1) + sprockets-rails (3.0.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) @@ -252,7 +252,7 @@ GEM thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) - turbolinks-source (5.0.0.beta1.1) + turbolinks-source (5.0.0.beta2) tzinfo (1.2.2) thread_safe (~> 0.1) tzinfo-data (1.2015.7) diff --git a/RAILS_VERSION b/RAILS_VERSION index 60b8d0bf66..d727b28ee9 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -5.0.0.beta2 +5.0.0.beta3 diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index bfc229d795..e2e6819f98 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,7 +1,10 @@ -* Added ActionCable::SubscriptionAdapter::EventedRedis.em_redis_connector/redis_connector and - ActionCable::SubscriptionAdapter::Redis.redis_connector factory methods for redis connections, - so you can overwrite with your own initializers. This is used when you want to use different-than-standard Redis adapters, - like for Makara distributed Redis. +## Rails 5.0.0.beta3 (February 24, 2016) ## + +* Added `em_redis_connector` and `redis_connector` to + `ActionCable::SubscriptionAdapter::EventedRedis` and added `redis_connector` + to `ActionCable::SubscriptionAdapter::Redis`, so you can overwrite with your + own initializers. This is used when you want to use different-than-standard + Redis adapters, like for Makara distributed Redis. *DHH* diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb index a71603e61a..67adeefaff 100644 --- a/actioncable/lib/action_cable/gem_version.rb +++ b/actioncable/lib/action_cable/gem_version.rb @@ -8,7 +8,7 @@ module ActionCable MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 1531f2b471..604e332dad 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,11 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + +* Add support to fragment cache in Action Mailer. + + Now you can use fragment caching in your mailers views. + + *Stan Lo* + * Reset `ActionMailer::Base.deliveries` after every test in `ActionDispatch::IntegrationTest`. diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 4259eb0bee..559cd06d91 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -430,6 +430,7 @@ module ActionMailer include AbstractController::Translation include AbstractController::AssetPaths include AbstractController::Callbacks + include AbstractController::Caching include ActionView::Layouts @@ -947,6 +948,18 @@ module ActionMailer container.add_part(part) end + # This and #instrument_name is for caching instrument + def instrument_payload(key) + { + mailer: mailer_name, + key: key + } + end + + def instrument_name + "action_mailer" + end + ActiveSupport.run_load_hooks(:action_mailer, self) end end diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb index a1ee5fb238..cbe5fc3e64 100644 --- a/actionmailer/lib/action_mailer/gem_version.rb +++ b/actionmailer/lib/action_mailer/gem_version.rb @@ -8,7 +8,7 @@ module ActionMailer MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index ae89492b0f..215d0199af 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -25,6 +25,7 @@ module ActionMailer options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first options.show_previews = Rails.env.development? if options.show_previews.nil? + options.cache_store ||= Rails.cache if options.show_previews options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb new file mode 100644 index 0000000000..b4344eb167 --- /dev/null +++ b/actionmailer/test/caching_test.rb @@ -0,0 +1,229 @@ +require 'fileutils' +require 'abstract_unit' +require 'mailers/base_mailer' +require 'mailers/caching_mailer' + +CACHE_DIR = 'test_cache' +# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed +FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) + +class FragmentCachingMailer < ActionMailer::Base + abstract! + + def some_action; end +end + +class BaseCachingTest < ActiveSupport::TestCase + def setup + super + @store = ActiveSupport::Cache::MemoryStore.new + @mailer = FragmentCachingMailer.new + @mailer.perform_caching = true + @mailer.cache_store = @store + end + + def test_fragment_cache_key + assert_equal 'views/what a key', @mailer.fragment_cache_key('what a key') + end +end + +class FragmentCachingTest < BaseCachingTest + def test_read_fragment_with_caching_enabled + @store.write('views/name', 'value') + assert_equal 'value', @mailer.read_fragment('name') + end + + def test_read_fragment_with_caching_disabled + @mailer.perform_caching = false + @store.write('views/name', 'value') + assert_nil @mailer.read_fragment('name') + end + + def test_fragment_exist_with_caching_enabled + @store.write('views/name', 'value') + assert @mailer.fragment_exist?('name') + assert !@mailer.fragment_exist?('other_name') + end + + def test_fragment_exist_with_caching_disabled + @mailer.perform_caching = false + @store.write('views/name', 'value') + assert !@mailer.fragment_exist?('name') + assert !@mailer.fragment_exist?('other_name') + end + + def test_write_fragment_with_caching_enabled + assert_nil @store.read('views/name') + assert_equal 'value', @mailer.write_fragment('name', 'value') + assert_equal 'value', @store.read('views/name') + end + + def test_write_fragment_with_caching_disabled + assert_nil @store.read('views/name') + @mailer.perform_caching = false + assert_equal 'value', @mailer.write_fragment('name', 'value') + assert_nil @store.read('views/name') + end + + def test_expire_fragment_with_simple_key + @store.write('views/name', 'value') + @mailer.expire_fragment 'name' + assert_nil @store.read('views/name') + end + + def test_expire_fragment_with_regexp + @store.write('views/name', 'value') + @store.write('views/another_name', 'another_value') + @store.write('views/primalgrasp', 'will not expire ;-)') + + @mailer.expire_fragment(/name/) + + assert_nil @store.read('views/name') + assert_nil @store.read('views/another_name') + assert_equal 'will not expire ;-)', @store.read('views/primalgrasp') + end + + def test_fragment_for + @store.write('views/expensive', 'fragment content') + fragment_computed = false + + view_context = @mailer.view_context + + buffer = 'generated till now -> '.html_safe + buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true } + + assert !fragment_computed + assert_equal 'generated till now -> fragment content', buffer + end + + def test_html_safety + assert_nil @store.read('views/name') + content = 'value'.html_safe + assert_equal content, @mailer.write_fragment('name', content) + + cached = @store.read('views/name') + assert_equal content, cached + assert_equal String, cached.class + + html_safe = @mailer.read_fragment('name') + assert_equal content, html_safe + assert html_safe.html_safe? + end +end + +class FunctionalFragmentCachingTest < BaseCachingTest + def setup + super + @store = ActiveSupport::Cache::MemoryStore.new + @mailer = CachingMailer.new + @mailer.perform_caching = true + @mailer.cache_store = @store + end + + def test_fragment_caching + email = @mailer.fragment_cache + expected_body = "\"Welcome\"" + + assert_match expected_body, email.body.encoded + assert_match "\"Welcome\"", + @store.read("views/caching/#{template_digest("caching_mailer/fragment_cache")}") + end + + def test_fragment_caching_in_partials + email = @mailer.fragment_cache_in_partials + assert_match(/Old fragment caching in a partial/, email.body.encoded) + + assert_match("Old fragment caching in a partial", + @store.read("views/caching/#{template_digest("caching_mailer/_partial")}")) + end + + def test_skip_fragment_cache_digesting + email = @mailer.skip_fragment_cache_digesting + expected_body = "No Digest" + + assert_match expected_body, email.body.encoded + assert_match expected_body, @store.read("views/no_digest") + end + + private + + def template_digest(name) + ActionView::Digestor.digest(name: name, finder: @mailer.lookup_context) + end +end + +class CacheHelperOutputBufferTest < BaseCachingTest + + class MockController + def read_fragment(name, options) + return false + end + + def write_fragment(name, fragment, options) + fragment + end + end + + def setup + super + end + + def test_output_buffer + output_buffer = ActionView::OutputBuffer.new + controller = MockController.new + cache_helper = Class.new do + def self.controller; end; + def self.output_buffer; end; + def self.output_buffer=; end; + end + cache_helper.extend(ActionView::Helpers::CacheHelper) + + cache_helper.stub :controller, controller do + cache_helper.stub :output_buffer, output_buffer do + assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do + assert_nothing_raised do + cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } + end + end + end + end + end + + def test_safe_buffer + output_buffer = ActiveSupport::SafeBuffer.new + controller = MockController.new + cache_helper = Class.new do + def self.controller; end; + def self.output_buffer; end; + def self.output_buffer=; end; + end + cache_helper.extend(ActionView::Helpers::CacheHelper) + + cache_helper.stub :controller, controller do + cache_helper.stub :output_buffer, output_buffer do + assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do + assert_nothing_raised do + cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } + end + end + end + end + end +end + +class ViewCacheDependencyTest < BaseCachingTest + class NoDependenciesMailer < ActionMailer::Base + end + class HasDependenciesMailer < ActionMailer::Base + view_cache_dependency { "trombone" } + view_cache_dependency { "flute" } + end + + def test_view_cache_dependencies_are_empty_by_default + assert NoDependenciesMailer.new.view_cache_dependencies.empty? + end + + def test_view_cache_dependencies_are_listed_in_declaration_order + assert_equal %w(trombone flute), HasDependenciesMailer.new.view_cache_dependencies + end +end diff --git a/actionmailer/test/fixtures/caching_mailer/_partial.html.erb b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb new file mode 100644 index 0000000000..8e965f52b4 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb @@ -0,0 +1,3 @@ +<% cache :caching do %> + Old fragment caching in a partial +<% end %> diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb new file mode 100644 index 0000000000..90189627da --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb @@ -0,0 +1,3 @@ +<% cache :caching do %> +"Welcome" +<% end %> diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb new file mode 100644 index 0000000000..2957d083e8 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb @@ -0,0 +1 @@ +<%= render "partial" %> diff --git a/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb new file mode 100644 index 0000000000..0d52429a81 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb @@ -0,0 +1,3 @@ +<%= cache :no_digest, skip_digest: true do %> + No Digest +<% end %> diff --git a/actionmailer/test/mailers/caching_mailer.rb b/actionmailer/test/mailers/caching_mailer.rb new file mode 100644 index 0000000000..345d267a36 --- /dev/null +++ b/actionmailer/test/mailers/caching_mailer.rb @@ -0,0 +1,15 @@ +class CachingMailer < ActionMailer::Base + self.mailer_name = "caching_mailer" + + def fragment_cache + mail(subject: "welcome", template_name: "fragment_cache") + end + + def fragment_cache_in_partials + mail(subject: "welcome", template_name: "fragment_cache_in_partials") + end + + def skip_fragment_cache_digesting + mail(subject: "welcome", template_name: "skip_fragment_cache_digesting") + end +end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d473ab427d..da96aef98b 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,14 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + +* Update session to have indifferent access across multiple requests. + + session[:deep][:hash] = "Magic" + + session[:deep][:hash] == "Magic" + session[:deep]["hash"] == "Magic" + + *Tom Prats* + * Add application/gzip as a default mime type. *Mehmet Emin İNAÇ* @@ -37,13 +48,13 @@ end end ``` - + Passing `as: :json` to integration test request helpers will set the format, content type and encode the parameters as JSON. - + Then on the response side, `parsed_body` will parse the body according to the content type the response has. - + Currently JSON is the only supported MIME type. Add your own with `ActionDispatch::IntegrationTest.register_encoder`. diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 56c4033387..1e57cbaac4 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -6,6 +6,7 @@ module AbstractController extend ActiveSupport::Autoload autoload :Base + autoload :Caching autoload :Callbacks autoload :Collector autoload :DoubleRenderError, "abstract_controller/rendering" @@ -15,4 +16,9 @@ module AbstractController autoload :Translation autoload :AssetPaths autoload :UrlFor + + def self.eager_load! + super + AbstractController::Caching.eager_load! + end end diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb new file mode 100644 index 0000000000..0dea50889a --- /dev/null +++ b/actionpack/lib/abstract_controller/caching.rb @@ -0,0 +1,62 @@ +module AbstractController + module Caching + extend ActiveSupport::Concern + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Fragments + end + + module ConfigMethods + def cache_store + config.cache_store + end + + def cache_store=(store) + config.cache_store = ActiveSupport::Cache.lookup_store(store) + end + + private + def cache_configured? + perform_caching && cache_store + end + end + + include ConfigMethods + include AbstractController::Caching::Fragments + + included do + extend ConfigMethods + + config_accessor :default_static_extension + self.default_static_extension ||= '.html' + + config_accessor :perform_caching + self.perform_caching = true if perform_caching.nil? + + class_attribute :_view_cache_dependencies + self._view_cache_dependencies = [] + helper_method :view_cache_dependencies if respond_to?(:helper_method) + end + + module ClassMethods + def view_cache_dependency(&dependency) + self._view_cache_dependencies += [dependency] + end + end + + def view_cache_dependencies + self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact + end + + protected + # Convenience accessor. + def cache(key, options = {}, &block) + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) + else + yield + end + end + end +end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb index b9ad51a9cf..3257a731ed 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -1,4 +1,4 @@ -module ActionController +module AbstractController module Caching # Fragment caching is used for caching various blocks within # views without caching the entire action as a whole. This is @@ -135,13 +135,8 @@ module ActionController end def instrument_fragment_cache(name, key) # :nodoc: - payload = { - controller: controller_name, - action: action_name, - key: key - } - - ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield } + payload = instrument_payload(key) + ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield } end end end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 40f33a9de0..62f5905205 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -9,12 +9,15 @@ module ActionController autoload :API autoload :Base - autoload :Caching autoload :Metal autoload :Middleware autoload :Renderer autoload :FormBuilder + eager_autoload do + autoload :Caching + end + autoload_under "metal" do autoload :ConditionalGet autoload :Cookies @@ -47,11 +50,6 @@ module ActionController autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' - - def self.eager_load! - super - ActionController::Caching.eager_load! - end end # Common Active Support usage in Action Controller diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 0b8fa2ea09..a9a8508abc 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -1,6 +1,3 @@ -require 'fileutils' -require 'uri' - module ActionController # \Caching is a cheap way of speeding up slow applications by keeping the result of # calculations, renderings, and database calls around for subsequent requests. @@ -23,65 +20,25 @@ module ActionController # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211') # config.action_controller.cache_store = MyOwnStore.new('parameter') module Caching - extend ActiveSupport::Concern extend ActiveSupport::Autoload - - eager_autoload do - autoload :Fragments - end - - module ConfigMethods - def cache_store - config.cache_store - end - - def cache_store=(store) - config.cache_store = ActiveSupport::Cache.lookup_store(store) - end - - private - def cache_configured? - perform_caching && cache_store - end - end - - include AbstractController::Callbacks - - include ConfigMethods - include Fragments + extend ActiveSupport::Concern included do - extend ConfigMethods - - config_accessor :default_static_extension - self.default_static_extension ||= '.html' - - config_accessor :perform_caching - self.perform_caching = true if perform_caching.nil? - - class_attribute :_view_cache_dependencies - self._view_cache_dependencies = [] - helper_method :view_cache_dependencies if respond_to?(:helper_method) + include AbstractController::Caching end - module ClassMethods - def view_cache_dependency(&dependency) - self._view_cache_dependencies += [dependency] - end - end + private - def view_cache_dependencies - self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact - end + def instrument_payload(key) + { + controller: controller_name, + action: action_name, + key: key + } + end - protected - # Convenience accessor. - def cache(key, options = {}, &block) - if cache_configured? - cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) - else - yield - end + def instrument_name + "action_controller" end end end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 25ec3cf5b6..a01110d474 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -579,7 +579,7 @@ module ActionController end def inspect - "<#{self.class} #{@parameters}>" + "<#{self.class} #{@parameters} permitted: #{@permitted}>" end def method_missing(method_sym, *args, &block) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 700317614f..6548ce326b 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -176,7 +176,7 @@ module ActionController def initialize(session = {}) super(nil, nil) @id = SecureRandom.hex(16) - @data = stringify_keys(session) + @data = session.with_indifferent_access @loaded = true end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 5427425ef7..316a9f08b7 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -403,6 +403,10 @@ module ActionDispatch def commit_flash end + def ssl? + super || scheme == 'wss'.freeze + end + private def check_method(name) HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}") diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 42890225fa..38d0da3e67 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -9,7 +9,7 @@ module ActionDispatch # Singleton object used to determine if an optional param wasn't specified Unspecified = Object.new - + # Creates a session hash, merging the properties of the previous session if any def self.create(store, req, default_options) session_was = find req @@ -61,7 +61,7 @@ module ActionDispatch def initialize(by, req) @by = by @req = req - @delegate = {} + @delegate = {}.with_indifferent_access @loaded = false @exists = nil # we haven't checked yet end @@ -88,13 +88,13 @@ module ActionDispatch # nil if the given key is not found in the session. def [](key) load_for_read! - @delegate[key.to_s] + @delegate[key] end # Returns true if the session has the given key or false. def has_key?(key) load_for_read! - @delegate.key?(key.to_s) + @delegate.key?(key) end alias :key? :has_key? alias :include? :has_key? @@ -112,7 +112,7 @@ module ActionDispatch # Writes given value to given key of the session. def []=(key, value) load_for_write! - @delegate[key.to_s] = value + @delegate[key] = value end # Clears the session. @@ -139,13 +139,13 @@ module ActionDispatch # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} def update(hash) load_for_write! - @delegate.update stringify_keys(hash) + @delegate.update hash end # Deletes given key from the session. def delete(key) load_for_write! - @delegate.delete key.to_s + @delegate.delete key end # Returns value of the given key from the session, or raises +KeyError+ @@ -165,9 +165,9 @@ module ActionDispatch def fetch(key, default=Unspecified, &block) load_for_read! if default == Unspecified - @delegate.fetch(key.to_s, &block) + @delegate.fetch(key, &block) else - @delegate.fetch(key.to_s, default, &block) + @delegate.fetch(key, default, &block) end end @@ -211,15 +211,9 @@ module ActionDispatch def load! id, session = @by.load_session @req options[:id] = id - @delegate.replace(stringify_keys(session)) + @delegate.replace(session) @loaded = true end - - def stringify_keys(other) - other.each_with_object({}) { |(key, value), hash| - hash[key.to_s] = value - } - end end end end diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index 778c5482d3..157f401f54 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -8,7 +8,7 @@ module ActionPack MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 22f1cc7c22..03a9c9ae78 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -322,3 +322,12 @@ class RedirectToSSLTest < ActionController::TestCase assert_equal 'ihaz', response.body end end + +class ForceSSLControllerLevelTest < ActionController::TestCase + def test_no_redirect_websocket_ssl_request + request.env['rack.url_scheme'] = 'wss' + request.env['Upgrade'] = 'websocket' + get :cheeseburger + assert_response 200 + end +end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index 4ef5bed30d..cea265f9ab 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -4,6 +4,8 @@ require 'active_support/core_ext/hash/transform_values' class ParametersAccessorsTest < ActiveSupport::TestCase setup do + ActionController::Parameters.permit_all_parameters = false + @params = ActionController::Parameters.new( person: { age: '32', @@ -176,12 +178,20 @@ class ParametersAccessorsTest < ActiveSupport::TestCase assert(@params != false) end - test "inspect shows both class name and parameters" do + test "inspect shows both class name, parameters and permitted flag" do assert_equal( '<ActionController::Parameters {"person"=>{"age"=>"32", '\ - '"name"=>{"first"=>"David", "last"=>"Heinemeier Hansson"}, ' \ - '"addresses"=>[{"city"=>"Chicago", "state"=>"Illinois"}]}}>', + '"name"=>{"first"=>"David", "last"=>"Heinemeier Hansson"}, ' \ + '"addresses"=>[{"city"=>"Chicago", "state"=>"Illinois"}]}} permitted: false>', @params.inspect ) end + + test "inspect prints updated permitted flag in the output" do + assert_match(/permitted: false/, @params.inspect) + + @params.permit! + + assert_match(/permitted: true/, @params.inspect) + end end diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index 7dcbcc5c21..3433d82791 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -105,6 +105,16 @@ module ActionDispatch end end + def test_indifferent_access + s = Session.create(store, req, {}) + + s[:one] = { test: "deep" } + s[:two] = { "test" => "deep" } + + assert_equal 'deep', s[:one]["test"] + assert_equal 'deep', s[:two][:test] + end + private def store Class.new { diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb index d38d1bbce6..c9ce5cad42 100644 --- a/actionpack/test/dispatch/session/abstract_store_test.rb +++ b/actionpack/test/dispatch/session/abstract_store_test.rb @@ -46,6 +46,22 @@ module ActionDispatch assert_equal session.to_hash, session1.to_hash end + def test_previous_session_has_indifferent_access + env = {} + as = MemoryStore.new app + as.call(env) + + assert @env + session = Request::Session.find ActionDispatch::Request.new @env + session[:foo] = { bar: "baz" } + + as.call(@env) + session = Request::Session.find ActionDispatch::Request.new @env + + assert_equal session[:foo][:bar], "baz" + assert_equal session[:foo]["bar"], "baz" + end + private def app(&block) @env = nil diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index dbb996973d..b911392cf1 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -12,6 +12,11 @@ class CacheStoreTest < ActionDispatch::IntegrationTest head :ok end + def set_deep_session_value + session[:foo] = { bar: "baz" } + head :ok + end + def set_serialized_session_value session[:foo] = SessionAutoloadTest::Foo.new head :ok @@ -21,6 +26,14 @@ class CacheStoreTest < ActionDispatch::IntegrationTest render plain: "foo: #{session[:foo].inspect}" end + def get_deep_session_value_with_symbol + render plain: "foo: { bar: #{session[:foo][:bar].inspect} }" + end + + def get_deep_session_value_with_string + render plain: "foo: { \"bar\" => #{session[:foo]["bar"].inspect} }" + end + def get_session_id render plain: "#{request.session.id}" end @@ -160,6 +173,22 @@ class CacheStoreTest < ActionDispatch::IntegrationTest end end + def test_previous_session_has_indifferent_access + with_test_route_set do + get '/set_deep_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/get_deep_session_value_with_symbol' + assert_response :success + assert_equal 'foo: { bar: "baz" }', response.body + + get '/get_deep_session_value_with_string' + assert_response :success + assert_equal 'foo: { "bar" => "baz" }', response.body + end + end + private def with_test_route_set with_routing do |set| diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index f07e215e3a..71402b021a 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -24,10 +24,23 @@ class CookieStoreTest < ActionDispatch::IntegrationTest render plain: Rack::Utils.escape(Verifier.generate(session.to_hash)) end + def set_deep_session_value + session[:foo] = { bar: "baz" } + render plain: Rack::Utils.escape(Verifier.generate(session.to_hash)) + end + def get_session_value render plain: "foo: #{session[:foo].inspect}" end + def get_deep_session_value_with_symbol + render plain: "foo: { bar: #{session[:foo][:bar].inspect} }" + end + + def get_deep_session_value_with_string + render plain: "foo: { \"bar\" => #{session[:foo]["bar"].inspect} }" + end + def get_session_id render plain: "id: #{request.session.id}" end @@ -81,6 +94,15 @@ class CookieStoreTest < ActionDispatch::IntegrationTest end end + def test_session_indifferent_access + with_test_route_set do + cookies[SessionKey] = SignedBar + get '/get_session_value' + assert_response :success + assert_equal 'foo: "bar"', response.body + end + end + def test_getting_session_id with_test_route_set do cookies[SessionKey] = SignedBar @@ -332,6 +354,18 @@ class CookieStoreTest < ActionDispatch::IntegrationTest end end + def test_previous_session_has_indifferent_access + with_test_route_set do + get '/set_deep_session_value' + + get '/get_deep_session_value_with_symbol' + assert_equal 'foo: { bar: "baz" }', response.body + + get '/get_deep_session_value_with_string' + assert_equal 'foo: { "bar" => "baz" }', response.body + end + end + private # Overwrite get to send SessionSecret in env hash diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index 3fed9bad4f..2e6b42856f 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -13,6 +13,11 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest head :ok end + def set_deep_session_value + session[:foo] = { bar: "baz" } + head :ok + end + def set_serialized_session_value session[:foo] = SessionAutoloadTest::Foo.new head :ok @@ -22,6 +27,14 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest render plain: "foo: #{session[:foo].inspect}" end + def get_deep_session_value_with_symbol + render plain: "foo: { bar: #{session[:foo][:bar].inspect} }" + end + + def get_deep_session_value_with_string + render plain: "foo: { \"bar\" => #{session[:foo]["bar"].inspect} }" + end + def get_session_id render plain: "#{request.session.id}" end @@ -179,6 +192,24 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest rescue Dalli::RingError => ex skip ex.message, ex.backtrace end + + def test_previous_session_has_indifferent_access + with_test_route_set do + get '/set_deep_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/get_deep_session_value_with_symbol' + assert_response :success + assert_equal 'foo: { bar: "baz" }', response.body + + get '/get_deep_session_value_with_string' + assert_response :success + assert_equal 'foo: { "bar" => "baz" }', response.body + end + rescue Dalli::RingError => ex + skip ex.message, ex.backtrace + end rescue LoadError, RuntimeError, Dalli::DalliError $stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again." end diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 3e61d123e3..332c2ae3c8 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -60,4 +60,11 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase session = ActionController::TestSession.new(one: '1') assert_equal(2, session.fetch('2') { |key| key.to_i }) end + + def test_fetch_returns_indifferent_access + session = ActionController::TestSession.new(three: { two: '1' }) + three = session.fetch(:three) + assert_equal('1', three[:two]) + assert_equal('1', three["two"]) + end end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index bd7ce14e04..d084f2b1e0 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,30 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + +* Collection rendering can cache and fetch multiple partials at once. + + Collections rendered as: + + ```ruby + <%= render partial: 'notifications/notification', collection: @notifications, as: :notification, cached: true %> + ``` + + will read several partials from cache at once. The templates in the collection + that haven't been cached already will automatically be written to cache. Works + great alongside individual template fragment caching. For instance if the + template the collection renders is cached like: + + ```ruby + # notifications/_notification.html.erb + <% cache notification do %> + <%# ... %> + <% end %> + ``` + + Then any collection renders shares that cache when attempting to read multiple + ones at once. + + *Kasper Timm Hansen* + * Add support for nested hashes/arrays to `:params` option of `button_to` helper. *James Coleman* @@ -197,31 +224,6 @@ *Ulisses Almeida* -* Collection rendering can cache and fetch multiple partials at once. - - Collections rendered as: - - ```ruby - <%= render partial: 'notifications/notification', collection: @notifications, as: :notification, cached: true %> - ``` - - will read several partials from cache at once. The templates in the collection - that haven't been cached already will automatically be written to cache. Works - great alongside individual template fragment caching. For instance if the - template the collection renders is cached like: - - ```ruby - # notifications/_notification.html.erb - <% cache notification do %> - <%# ... %> - <% end %> - ``` - - Then any collection renders shares that cache when attempting to read multiple - ones at once. - - *Kasper Timm Hansen* - * Fixed a dependency tracker bug that caused template dependencies not count layouts as dependencies for partials. diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index bb5c96cb39..efb565bf59 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -8,7 +8,7 @@ module ActionView MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 229ef03879..913164fbc5 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,5 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + * Change the default adapter from inline to async. It's a better default as tests will then not mistakenly come to rely on behavior happening synchronously. This is especially important with things like jobs kicked off in Active Record lifecycle callbacks. diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb index bc88221027..be4fabf545 100644 --- a/activejob/lib/active_job/gem_version.rb +++ b/activejob/lib/active_job/gem_version.rb @@ -8,7 +8,7 @@ module ActiveJob MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index ed0c05c1e5..e06b98736d 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -4,325 +4,321 @@ require 'active_support/core_ext/hash/keys' module ActiveJob # Provides helper methods for testing Active Job module TestHelper - extend ActiveSupport::Concern + delegate :enqueued_jobs, :enqueued_jobs=, + :performed_jobs, :performed_jobs=, + to: :queue_adapter - included do - def before_setup # :nodoc: - test_adapter = ActiveJob::QueueAdapters::TestAdapter.new + def before_setup # :nodoc: + test_adapter = ActiveJob::QueueAdapters::TestAdapter.new - @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass| - # only override explicitly set adapters, a quirk of `class_attribute` - klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter) - end.map do |klass| - [klass, klass.queue_adapter].tap do - klass.queue_adapter = test_adapter - end + @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass| + # only override explicitly set adapters, a quirk of `class_attribute` + klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter) + end.map do |klass| + [klass, klass.queue_adapter].tap do + klass.queue_adapter = test_adapter end - - clear_enqueued_jobs - clear_performed_jobs - super end - def after_teardown # :nodoc: - super - @old_queue_adapters.each do |(klass, adapter)| - klass.queue_adapter = adapter - end - end + clear_enqueued_jobs + clear_performed_jobs + super + end - # Asserts that the number of enqueued jobs matches the given number. - # - # def test_jobs - # assert_enqueued_jobs 0 - # HelloJob.perform_later('david') - # assert_enqueued_jobs 1 - # HelloJob.perform_later('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.perform_later('cristian') - # end - # - # assert_enqueued_jobs 2 do - # HelloJob.perform_later('aaron') - # HelloJob.perform_later('rafael') - # end - # end - # - # The number of times a specific job is enqueued can be asserted. - # - # def test_logging_job - # assert_enqueued_jobs 1, only: LoggingJob do - # LoggingJob.perform_later - # HelloJob.perform_later('jeremy') - # end - # end - def assert_enqueued_jobs(number, only: nil) - if block_given? - original_count = enqueued_jobs_size(only: only) - yield - new_count = enqueued_jobs_size(only: only) - assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" - else - actual_count = enqueued_jobs_size(only: only) - assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" - end + def after_teardown # :nodoc: + super + @old_queue_adapters.each do |(klass, adapter)| + klass.queue_adapter = adapter end + end - # Asserts that no jobs have been enqueued. - # - # def test_jobs - # assert_no_enqueued_jobs - # HelloJob.perform_later('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 - # - # It can be asserted that no jobs of a specific kind are enqueued: - # - # def test_no_logging - # assert_no_enqueued_jobs only: LoggingJob do - # HelloJob.perform_later('jeremy') - # end - # end - # - # Note: This assertion is simply a shortcut for: - # - # assert_enqueued_jobs 0, &block - def assert_no_enqueued_jobs(only: nil, &block) - assert_enqueued_jobs 0, only: only, &block + # Asserts that the number of enqueued jobs matches the given number. + # + # def test_jobs + # assert_enqueued_jobs 0 + # HelloJob.perform_later('david') + # assert_enqueued_jobs 1 + # HelloJob.perform_later('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.perform_later('cristian') + # end + # + # assert_enqueued_jobs 2 do + # HelloJob.perform_later('aaron') + # HelloJob.perform_later('rafael') + # end + # end + # + # The number of times a specific job is enqueued can be asserted. + # + # def test_logging_job + # assert_enqueued_jobs 1, only: LoggingJob do + # LoggingJob.perform_later + # HelloJob.perform_later('jeremy') + # end + # end + def assert_enqueued_jobs(number, only: nil) + if block_given? + original_count = enqueued_jobs_size(only: only) + yield + new_count = enqueued_jobs_size(only: only) + assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" + else + actual_count = enqueued_jobs_size(only: only) + assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" end + end - # Asserts that the number of performed jobs matches the given number. - # If no block is passed, <tt>perform_enqueued_jobs</tt> - # must be called around the job call. - # - # def test_jobs - # assert_performed_jobs 0 - # - # perform_enqueued_jobs do - # HelloJob.perform_later('xavier') - # end - # assert_performed_jobs 1 - # - # perform_enqueued_jobs do - # HelloJob.perform_later('yves') - # assert_performed_jobs 2 - # end - # 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.perform_later('robin') - # end - # - # assert_performed_jobs 2 do - # HelloJob.perform_later('carlos') - # HelloJob.perform_later('sean') - # end - # end - # - # The block form supports filtering. If the :only option is specified, - # then only the listed job(s) will be performed. - # - # def test_hello_job - # assert_performed_jobs 1, only: HelloJob do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later - # end - # end - # - # An array may also be specified, to support testing multiple jobs. - # - # def test_hello_and_logging_jobs - # assert_nothing_raised do - # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later('stewie') - # RescueJob.perform_later('david') - # end - # end - # end - def assert_performed_jobs(number, only: nil) - if block_given? - original_count = performed_jobs.size - perform_enqueued_jobs(only: only) { yield } - new_count = performed_jobs.size - assert_equal number, new_count - original_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 enqueued. + # + # def test_jobs + # assert_no_enqueued_jobs + # HelloJob.perform_later('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 + # + # It can be asserted that no jobs of a specific kind are enqueued: + # + # def test_no_logging + # assert_no_enqueued_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_enqueued_jobs 0, &block + def assert_no_enqueued_jobs(only: nil, &block) + assert_enqueued_jobs 0, only: only, &block + end - # Asserts that no jobs have been performed. - # - # def test_jobs - # assert_no_performed_jobs - # - # perform_enqueued_jobs do - # HelloJob.perform_later('matthew') - # assert_performed_jobs 1 - # end - # 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 - # - # The block form supports filtering. If the :only option is specified, - # then only the listed job(s) will be performed. - # - # def test_hello_job - # assert_performed_jobs 1, only: HelloJob do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later - # end - # end - # - # An array may also be specified, to support testing multiple jobs. - # - # def test_hello_and_logging_jobs - # assert_nothing_raised do - # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later('stewie') - # RescueJob.perform_later('david') - # end - # end - # end - # - # Note: This assertion is simply a shortcut for: - # - # assert_performed_jobs 0, &block - def assert_no_performed_jobs(only: nil, &block) - assert_performed_jobs 0, only: only, &block + # Asserts that the number of performed jobs matches the given number. + # If no block is passed, <tt>perform_enqueued_jobs</tt> + # must be called around the job call. + # + # def test_jobs + # assert_performed_jobs 0 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('xavier') + # end + # assert_performed_jobs 1 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('yves') + # assert_performed_jobs 2 + # end + # 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.perform_later('robin') + # end + # + # assert_performed_jobs 2 do + # HelloJob.perform_later('carlos') + # HelloJob.perform_later('sean') + # end + # end + # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, only: HelloJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # An array may also be specified, to support testing multiple jobs. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + def assert_performed_jobs(number, only: nil) + if block_given? + original_count = performed_jobs.size + perform_enqueued_jobs(only: only) { yield } + new_count = performed_jobs.size + assert_equal number, new_count - original_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 the job passed in the block has been enqueued with the given arguments. - # - # def test_assert_enqueued_with - # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do - # MyJob.perform_later(1,2,3) - # end - # - # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do - # MyJob.set(wait_until: Date.tomorrow.noon).perform_later - # end - # end - def assert_enqueued_with(args = {}) - original_enqueued_jobs_count = enqueued_jobs.count - args.assert_valid_keys(:job, :args, :at, :queue) - serialized_args = serialize_args_for_assertion(args) - yield - in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count) - matching_job = in_block_jobs.find do |job| - serialized_args.all? { |key, value| value == job[key] } - end - assert matching_job, "No enqueued job found with #{args}" - instantiate_job(matching_job) - end + # Asserts that no jobs have been performed. + # + # def test_jobs + # assert_no_performed_jobs + # + # perform_enqueued_jobs do + # HelloJob.perform_later('matthew') + # assert_performed_jobs 1 + # end + # 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 + # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, only: HelloJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # An array may also be specified, to support testing multiple jobs. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_performed_jobs 0, &block + def assert_no_performed_jobs(only: nil, &block) + assert_performed_jobs 0, only: only, &block + 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.perform_later(1,2,3) - # end - # - # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do - # MyJob.set(wait_until: Date.tomorrow.noon).perform_later - # end - # end - def assert_performed_with(args = {}) - original_performed_jobs_count = performed_jobs.count - args.assert_valid_keys(:job, :args, :at, :queue) - serialized_args = serialize_args_for_assertion(args) - perform_enqueued_jobs { yield } - in_block_jobs = performed_jobs.drop(original_performed_jobs_count) - matching_job = in_block_jobs.find do |job| - serialized_args.all? { |key, value| value == job[key] } - end - assert matching_job, "No performed job found with #{args}" - instantiate_job(matching_job) + # Asserts that the job passed in the block has been enqueued with the given arguments. + # + # def test_assert_enqueued_with + # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do + # MyJob.perform_later(1,2,3) + # end + # + # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_enqueued_with(args = {}) + original_enqueued_jobs_count = enqueued_jobs.count + args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) + yield + in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count) + matching_job = in_block_jobs.find do |job| + serialized_args.all? { |key, value| value == job[key] } end + assert matching_job, "No enqueued job found with #{args}" + instantiate_job(matching_job) + end - def perform_enqueued_jobs(only: nil) - old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs - old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs - old_filter = queue_adapter.filter - - begin - queue_adapter.perform_enqueued_jobs = true - queue_adapter.perform_enqueued_at_jobs = true - queue_adapter.filter = only - yield - ensure - queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs - queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs - queue_adapter.filter = old_filter - 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.perform_later(1,2,3) + # end + # + # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_performed_with(args = {}) + original_performed_jobs_count = performed_jobs.count + args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) + perform_enqueued_jobs { yield } + in_block_jobs = performed_jobs.drop(original_performed_jobs_count) + matching_job = in_block_jobs.find do |job| + serialized_args.all? { |key, value| value == job[key] } end + assert matching_job, "No performed job found with #{args}" + instantiate_job(matching_job) + end + + def perform_enqueued_jobs(only: nil) + old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs + old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs + old_filter = queue_adapter.filter - def queue_adapter - ActiveJob::Base.queue_adapter + begin + queue_adapter.perform_enqueued_jobs = true + queue_adapter.perform_enqueued_at_jobs = true + queue_adapter.filter = only + yield + ensure + queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs + queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs + queue_adapter.filter = old_filter end + end - delegate :enqueued_jobs, :enqueued_jobs=, - :performed_jobs, :performed_jobs=, - to: :queue_adapter + def queue_adapter + ActiveJob::Base.queue_adapter + end - private - def clear_enqueued_jobs # :nodoc: - enqueued_jobs.clear - end + private + def clear_enqueued_jobs # :nodoc: + enqueued_jobs.clear + end - def clear_performed_jobs # :nodoc: - performed_jobs.clear - end + def clear_performed_jobs # :nodoc: + performed_jobs.clear + end - def enqueued_jobs_size(only: nil) # :nodoc: - if only - enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) } - else - enqueued_jobs.count - end + def enqueued_jobs_size(only: nil) # :nodoc: + if only + enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) } + else + enqueued_jobs.count end + end - def serialize_args_for_assertion(args) # :nodoc: - args.dup.tap do |serialized_args| - serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args] - serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at] - end + def serialize_args_for_assertion(args) # :nodoc: + args.dup.tap do |serialized_args| + serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args] + serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at] end + end - def instantiate_job(payload) # :nodoc: - job = payload[:job].new(*payload[:args]) - job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at) - job.queue_name = payload[:queue] - job - end - end + def instantiate_job(payload) # :nodoc: + job = payload[:job].new(*payload[:args]) + job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at) + job.queue_name = payload[:queue] + job + end end end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 318e507ff1..fb7ab5cb40 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta2 (February 01, 2016) ## * No changes. diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb index 94514a0657..62862aa4e9 100644 --- a/activemodel/lib/active_model/gem_version.rb +++ b/activemodel/lib/active_model/gem_version.rb @@ -8,7 +8,7 @@ module ActiveModel MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c18403865f..c1a8803457 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,5 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + * Ensure that mutations of the array returned from `ActiveRecord::Relation#to_a` do not affect the original relation, by returning a duplicate array each time. @@ -1452,18 +1454,6 @@ *Chris Sinjakli* -* Validation errors would be raised for parent records when an association - was saved when the parent had `validate: false`. It should not be the - responsibility of the model to validate an associated object unless the - object was created or modified by the parent. - - This fixes the issue by skipping validations if the parent record is - persisted, not changed, and not marked for destruction. - - Fixes #17621. - - *Eileen M. Uchitelle*, *Aaron Patterson* - * Fix n+1 query problem when eager loading nil associations (fixes #18312) *Sammy Larbi* diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index aa1f5c4fb4..73be4cb271 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -8,7 +8,7 @@ module ActiveRecord MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 0d5a8e6f25..ae78ceee01 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -195,15 +195,23 @@ module ActiveRecord # Nested attributes for an associated collection can also be passed in # the form of a hash of hashes instead of an array of hashes: # - # Member.create(name: 'joe', - # posts_attributes: { first: { title: 'Foo' }, - # second: { title: 'Bar' } }) + # Member.create( + # name: 'joe', + # posts_attributes: { + # first: { title: 'Foo' }, + # second: { title: 'Bar' } + # } + # ) # # has the same effect as # - # Member.create(name: 'joe', - # posts_attributes: [ { title: 'Foo' }, - # { title: 'Bar' } ]) + # Member.create( + # name: 'joe', + # posts_attributes: [ + # { title: 'Foo' }, + # { title: 'Bar' } + # ] + # ) # # The keys of the hash which is the value for +:posts_attributes+ are # ignored in this case. diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 4566aeb45b..6a6250eec3 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -516,13 +516,12 @@ class MigrationTest < ActiveRecord::TestCase data_column = columns.detect { |c| c.name == "data" } assert_nil data_column.default - + ensure Person.connection.drop_table :binary_testings, if_exists: true end 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" @@ -530,12 +529,11 @@ class MigrationTest < ActiveRecord::TestCase columns = Person.connection.columns(:table_from_query_testings) assert_equal 1, columns.length assert_equal "id", columns.first.name - + ensure 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) Person.connection.create_table :table_from_query_testings, as: Person.select(:id) @@ -543,7 +541,7 @@ class MigrationTest < ActiveRecord::TestCase columns = Person.connection.columns(:table_from_query_testings) assert_equal 1, columns.length assert_equal "id", columns.first.name - + ensure Person.connection.drop_table :table_from_query_testings rescue nil end end @@ -586,9 +584,7 @@ class MigrationTest < ActiveRecord::TestCase end if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) - def test_out_of_range_limit_should_raise - Person.connection.drop_table :test_integer_limits, if_exists: true - + def test_out_of_range_integer_limit_should_raise e = assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do Person.connection.create_table :test_integer_limits, :force => true do |t| t.column :bigone, :integer, :limit => 10 @@ -596,16 +592,22 @@ class MigrationTest < ActiveRecord::TestCase end assert_match(/No integer type has byte size 10/, e.message) + ensure + Person.connection.drop_table :test_integer_limits, if_exists: true + end + end - unless current_adapter?(:PostgreSQLAdapter) - assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do - Person.connection.create_table :test_text_limits, :force => true do |t| - t.column :bigtext, :text, :limit => 0xfffffffff - end + if current_adapter?(:Mysql2Adapter) + def test_out_of_range_text_limit_should_raise + e = assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do + Person.connection.create_table :test_text_limits, force: true do |t| + t.text :bigtext, limit: 0xfffffffff end end + + assert_match(/No text type has byte length #{0xfffffffff}/, e.message) ensure - Person.connection.drop_table :test_integer_limits, if_exists: true + Person.connection.drop_table :test_text_limits, if_exists: true end end @@ -727,7 +729,7 @@ class ReservedWordsMigrationTest < ActiveRecord::TestCase connection.add_index :values, :value connection.remove_index :values, :column => :value end - + ensure connection.drop_table :values rescue nil end end @@ -743,7 +745,7 @@ class ExplicitlyNamedIndexMigrationTest < ActiveRecord::TestCase connection.add_index :values, :value, name: 'a_different_name' connection.remove_index :values, column: :value, name: 'a_different_name' end - + ensure connection.drop_table :values rescue nil end end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 2a5d203813..1a169d36be 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,5 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + * Deprecate arguments on `assert_nothing_raised`. `assert_nothing_raised` does not assert the arguments that have been passed diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index f89fc0fe14..35f084dd7a 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -1,3 +1,5 @@ +require 'rbconfig' + module ActiveSupport class Deprecation module Reporting @@ -81,17 +83,17 @@ module ActiveSupport def extract_callstack(callstack) return _extract_callstack(callstack) if callstack.first.is_a? String - rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" offending_line = callstack.find { |frame| - frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root) + frame.absolute_path && !ignored_callstack(frame.absolute_path) } || callstack.first + [offending_line.path, offending_line.lineno, offending_line.label] end def _extract_callstack(callstack) warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE - rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" - offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first + offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first + if offending_line if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) md.captures @@ -100,6 +102,12 @@ module ActiveSupport end end end + + RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG['rubylibdir']) + end end end end diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index fc08273b6d..4166ffc2fb 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -8,7 +8,7 @@ module ActiveSupport MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb index 7626b28108..de48e717b6 100644 --- a/activesupport/lib/active_support/logger.rb +++ b/activesupport/lib/active_support/logger.rb @@ -1,8 +1,10 @@ require 'active_support/logger_silence' +require 'active_support/logger_thread_safe_level' require 'logger' module ActiveSupport class Logger < ::Logger + include ActiveSupport::LoggerThreadSafeLevel include LoggerSilence # Returns true if the logger destination matches one of the sources @@ -48,6 +50,11 @@ module ActiveSupport logger.level = level super(level) end + + define_method(:local_level=) do |level| + logger.local_level = level if logger.respond_to?(:local_level=) + super(level) if respond_to?(:local_level=) + end end end diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb index 125d81d973..3eb8098c77 100644 --- a/activesupport/lib/active_support/logger_silence.rb +++ b/activesupport/lib/active_support/logger_silence.rb @@ -7,39 +7,22 @@ module LoggerSilence included do cattr_accessor :silencer - attr_reader :local_levels self.silencer = true end - def after_initialize - @local_levels = Concurrent::Map.new(:initial_capacity => 2) - end - - def local_log_id - Thread.current.__id__ - end - - def level - local_levels[local_log_id] || super - end - # Silences the logger for the duration of the block. def silence(temporary_level = Logger::ERROR) if silencer begin - old_local_level = local_levels[local_log_id] - local_levels[local_log_id] = temporary_level + old_local_level = local_level + self.local_level = temporary_level yield self ensure - if old_local_level - local_levels[local_log_id] = old_local_level - else - local_levels.delete(local_log_id) - end + self.local_level = old_local_level end else yield self end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb new file mode 100644 index 0000000000..5fedb5e689 --- /dev/null +++ b/activesupport/lib/active_support/logger_thread_safe_level.rb @@ -0,0 +1,31 @@ +require 'active_support/concern' + +module ActiveSupport + module LoggerThreadSafeLevel # :nodoc: + extend ActiveSupport::Concern + + def after_initialize + @local_levels = Concurrent::Map.new(initial_capacity: 2) + end + + def local_log_id + Thread.current.__id__ + end + + def local_level + @local_levels[local_log_id] + end + + def local_level=(level) + if level + @local_levels[local_log_id] = level + else + @local_levels.delete(local_log_id) + end + end + + def level + local_level || super + end + end +end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 2d2e25c970..1fc12d0bc1 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -76,8 +76,9 @@ module ActiveSupport # end def assert_nothing_raised(*args) if args.present? - ActiveSupport::Deprecation.warn("Passing arguments to assert_nothing_raised - is deprecated and will be removed in Rails 5.1.") + ActiveSupport::Deprecation.warn( + "Passing arguments to assert_nothing_raised " \ + "is deprecated and will be removed in Rails 5.1.") end yield end diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb index 317e09b7f2..5a91420f1e 100644 --- a/activesupport/test/logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -141,6 +141,50 @@ class LoggerTest < ActiveSupport::TestCase assert @output.string.include?("THIS IS HERE") end + def test_logger_silencing_works_for_broadcast + another_output = StringIO.new + another_logger = Logger.new(another_output) + + @logger.extend Logger.broadcast(another_logger) + + @logger.debug "CORRECT DEBUG" + @logger.silence do + @logger.debug "FAILURE" + @logger.error "CORRECT ERROR" + end + + assert @output.string.include?("CORRECT DEBUG") + assert @output.string.include?("CORRECT ERROR") + assert_not @output.string.include?("FAILURE") + + assert another_output.string.include?("CORRECT DEBUG") + assert another_output.string.include?("CORRECT ERROR") + assert_not another_output.string.include?("FAILURE") + end + + def test_broadcast_silencing_does_not_break_plain_ruby_logger + another_output = StringIO.new + another_logger = ::Logger.new(another_output) + + @logger.extend Logger.broadcast(another_logger) + + @logger.debug "CORRECT DEBUG" + @logger.silence do + @logger.debug "FAILURE" + @logger.error "CORRECT ERROR" + end + + assert @output.string.include?("CORRECT DEBUG") + assert @output.string.include?("CORRECT ERROR") + assert_not @output.string.include?("FAILURE") + + assert another_output.string.include?("CORRECT DEBUG") + assert another_output.string.include?("CORRECT ERROR") + assert another_output.string.include?("FAILURE") + # We can't silence plain ruby Logger cause with thread safety + # but at least we don't break it + end + def test_logger_level_per_object_thread_safety logger1 = Logger.new(StringIO.new) logger2 = Logger.new(StringIO.new) diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index d58016053b..d35d0f1976 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta2 (February 01, 2016) ## * No changes. diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index f5abbd0cd4..91bca16356 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -316,6 +316,9 @@ Please refer to the [Changelog][action-mailer] for detailed changes. * Template lookup now respects default locale and I18n fallbacks. ([commit](https://github.com/rails/rails/commit/ecb1981b)) +* Template can use fragment cache like Action View template. + ([Pull Request](https://github.com/rails/rails/pull/22825)) + * Added `_mailer` suffix to mailers created via generator, following the same naming convention used in controllers and jobs. ([Pull Request](https://github.com/rails/rails/pull/18074)) @@ -327,6 +330,9 @@ Please refer to the [Changelog][action-mailer] for detailed changes. the mailer queue name. ([Pull Request](https://github.com/rails/rails/pull/18587)) +* Added `config.action_mailer.perform_caching` configuration to determine whether your templates should perform caching or not. + ([Pull Request](https://github.com/rails/rails/pull/22825)) + Active Record ------------- diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 91ea4efb55..558c16f5b0 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -407,6 +407,22 @@ use the rendered text for the text part. The render command is the same one used inside of Action Controller, so you can use all the same options, such as `:text`, `:inline` etc. +#### Caching mailer view + +You can do cache in mailer views like in application views using `cache` method. + +``` +<% cache do %> + <%= @company.name %> +<% end %> +``` + +And in order to use this feature, you need to configure your application with this: + +``` + config.action_mailer.perform_caching = true +``` + ### Action Mailer Layouts Just like controller views, you can also have mailer layouts. The layout name diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 5e6eae1071..29e0943741 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -599,7 +599,7 @@ This would add something like "Process data files (0.34523)" to the log, which y #### cache -A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `ActionController::Caching::Fragments` for more information. +A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `AbstractController::Caching::Fragments` for more information. ```erb <% cache do %> diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 5dd54bf8ad..b6c612794c 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -335,8 +335,8 @@ include the 'data-turbolinks-track' option which causes turbolinks to check if an asset has been updated and if so loads it into the page: ```erb -<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> -<%= javascript_include_tag "application", "data-turbolinks-track" => true %> +<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => "reload" %> +<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %> ``` In regular views you can access images in the `public/assets/images` directory diff --git a/guides/source/configuring.md b/guides/source/configuring.md index a5fb396f15..23866b1800 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -531,6 +531,9 @@ There are a number of settings available on `config.action_mailer`: * `config.action_mailer.deliver_later_queue_name` specifies the queue name for mailers. By default this is `mailers`. +* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is false in all environments. + + ### Configuring Active Support There are a few configuration options available in Active Support: diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 2a289dd33a..422bc647ef 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -317,7 +317,7 @@ The Article model is directly available to users of the application, so - follow resources :articles ``` -TIP: Declaring a resource has a number of side-affects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources. +TIP: Declaring a resource has a number of side effects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources. When dealing with RESTful resources, calls to `form_for` can get significantly easier if you rely on **record identification**. In short, you can just pass the model instance and have Rails figure out model name and the rest: diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 5a46baff2d..3bb5d3c8a6 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -22,7 +22,7 @@ $ rails new blog -m ~/template.rb $ rails new blog -m http://example.com/template.rb ``` -You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL. +You can use the task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL. ```bash $ bin/rails rails:template LOCATION=~/template.rb @@ -38,7 +38,7 @@ The Rails templates API is easy to understand. Here's an example of a typical Ra # template.rb generate(:scaffold, "person name:string") route "root to: 'people#index'" -rake("db:migrate") +rails_command("db:migrate") after_bundle do git :init @@ -175,18 +175,24 @@ Executes an arbitrary command. Just like the backticks. Let's say you want to re run "rm README.rdoc" ``` -### rake(command, options = {}) +### rails_command(command, options = {}) -Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database: +Runs the supplied task in the Rails application. Let's say you want to migrate the database: ```ruby -rake "db:migrate" +rails_command "db:migrate" ``` -You can also run rake tasks with a different Rails environment: +You can also run tasks with a different Rails environment: ```ruby -rake "db:migrate", env: 'production' +rails_command "db:migrate", env: 'production' +``` + +You can also run tasks as a super-user: + +```ruby +rails_command "log:clear", sudo: true ``` ### route(routing_code) @@ -226,7 +232,7 @@ CODE These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to Freeze Rails only if the user wants to: ```ruby -rake("rails:freeze:gems") if yes?("Freeze rails gems?") +rails_command("rails:freeze:gems") if yes?("Freeze rails gems?") # no?(question) acts just the opposite. ``` diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 26ff5da7a3..c58aee96db 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -350,8 +350,8 @@ $("<%= escape_javascript(render @user) %>").appendTo("#users"); Turbolinks ---------- -Rails 4 ships with the [Turbolinks gem](https://github.com/turbolinks/turbolinks). -This gem uses Ajax to speed up page rendering in most applications. +Rails ships with the [Turbolinks library](https://github.com/turbolinks/turbolinks), +which uses Ajax to speed up page rendering in most applications. ### How Turbolinks Works @@ -364,14 +364,14 @@ will then use PushState to change the URL to the correct one, preserving refresh semantics and giving you pretty URLs. The only thing you have to do to enable Turbolinks is have it in your Gemfile, -and put `//= require turbolinks` in your CoffeeScript manifest, which is usually +and put `//= require turbolinks` in your JavaScript manifest, which is usually `app/assets/javascripts/application.js`. -If you want to disable Turbolinks for certain links, add a `data-no-turbolink` +If you want to disable Turbolinks for certain links, add a `data-turbolinks="false"` attribute to the tag: ```html -<a href="..." data-no-turbolink>No turbolinks here</a>. +<a href="..." data-turbolinks="false">No turbolinks here</a>. ``` ### Page Change Events @@ -389,7 +389,7 @@ event that this relies on will not be fired. If you have code that looks like this, you must change your code to do this instead: ```coffeescript -$(document).on "page:change", -> +$(document).on "turbolinks:load", -> alert "page has loaded!" ``` diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index a3be5356a1..40140c49f6 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,15 @@ +## Rails 5.0.0.beta3 (February 24, 2016) ## + +* Alias `rake` with `rails_command` in the Rails Application Templates API + following Rails 5 convention of preferring "rails" to "rake" to run tasks. + + *claudiob* + +* Generate applications with an option to log to STDOUT in production + using the environment variable `RAILS_LOG_TO_STDOUT`. + + *Richard Schneeman* + * Change fail fast of `bin/rails test` interrupts run on error. *Yuji Yaginuma* diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 93e0151602..081222425c 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 9ca731347a..5fa487b78e 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -216,8 +216,9 @@ module Rails log :rake, command env = options[:env] || ENV["RAILS_ENV"] || 'development' sudo = options[:sudo] && RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ ? 'sudo ' : '' - in_root { run("#{sudo}#{extify(:rake)} #{command} RAILS_ENV=#{env}", verbose: false) } + in_root { run("#{sudo}#{extify(:rails)} #{command} RAILS_ENV=#{env}", verbose: false) } end + alias :rails_command :rake # Just run the capify command in root # diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 9adfcc6ee7..ed4e73fb90 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -311,12 +311,7 @@ module Rails end def coffee_gemfile_entry - comment = 'Use CoffeeScript for .coffee assets and views' - if options.dev? || options.edge? - GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', nil, comment - else - GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment - end + GemfileEntry.version 'coffee-rails', '~> 4.1.0', 'Use CoffeeScript for .coffee assets and views' end def javascript_gemfile_entry @@ -328,8 +323,8 @@ module Rails "Use #{options[:javascript]} as the JavaScript library") unless options[:skip_turbolinks] - gems << GemfileEntry.version("turbolinks", nil, - "Turbolinks makes following links in your web application faster. Read more: https://github.com/turbolinks/turbolinks") + gems << GemfileEntry.version("turbolinks", "~> 5.x", + "Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks") end gems diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt index 68b5c051b2..ec781721cb 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -11,8 +11,8 @@ <%%= stylesheet_link_tag 'application', media: 'all' %> <%- else -%> <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%> - <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => 'reload' %> + <%%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %> <%- else -%> <%%= stylesheet_link_tag 'application', media: 'all' %> <%%= javascript_include_tag 'application' %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index d4e2b1c756..e6a2de0928 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -15,18 +15,22 @@ Rails.application.configure do # Enable/disable caching. By default caching is disabled. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true + config.cache_store = :memory_store config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=172800' } else config.action_controller.perform_caching = false + config.cache_store = :null_store end <%- unless options.skip_action_mailer? -%> # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false <%- end -%> # Print deprecation notices to the Rails logger. diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index e14e2c7286..236e42fcd7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -71,6 +71,7 @@ Rails.application.configure do # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}" <%- unless options.skip_action_mailer? -%> + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index e8c8b00669..42fee3b036 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -28,6 +28,7 @@ Rails.application.configure do # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false <%- unless options.skip_action_mailer? -%> + config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb index 1bf274bc66..c7f311f811 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb @@ -42,3 +42,6 @@ environment ENV.fetch("RAILS_ENV") { "development" } # on_worker_boot do # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) # end + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 1c7d1e1f5f..d03dd1afcc 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -999,7 +999,7 @@ module ApplicationTests app 'development' post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json" - assert_equal '<ActionController::Parameters {"title"=>"foo"}>', last_response.body + assert_equal '<ActionController::Parameters {"title"=>"foo"} permitted: false>', last_response.body end test "config.action_controller.permit_all_parameters = true" do diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 3300850604..6158748194 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -202,7 +202,7 @@ class ActionsTest < Rails::Generators::TestCase end def test_rake_should_run_rake_command_with_default_env - assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false]) do + assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false]) do with_rails_env nil do action :rake, 'log:clear' end @@ -210,13 +210,13 @@ class ActionsTest < Rails::Generators::TestCase end def test_rake_with_env_option_should_run_rake_command_in_env - assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do action :rake, 'log:clear', env: 'production' end end def test_rake_with_rails_env_variable_should_run_rake_command_in_env - assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do with_rails_env "production" do action :rake, 'log:clear' end @@ -224,7 +224,7 @@ class ActionsTest < Rails::Generators::TestCase end def test_env_option_should_win_over_rails_env_variable_when_running_rake - assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do with_rails_env "staging" do action :rake, 'log:clear', env: 'production' end @@ -232,13 +232,51 @@ class ActionsTest < Rails::Generators::TestCase end def test_rake_with_sudo_option_should_run_rake_command_with_sudo - assert_called_with(generator, :run, ["sudo rake log:clear RAILS_ENV=development", verbose: false]) do + assert_called_with(generator, :run, ["sudo rails log:clear RAILS_ENV=development", verbose: false]) do with_rails_env nil do action :rake, 'log:clear', sudo: true end end end + def test_rails_command_should_run_rails_command_with_default_env + assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false]) do + with_rails_env nil do + action :rails_command, 'log:clear' + end + end + end + + def test_rails_command_with_env_option_should_run_rails_command_in_env + assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do + action :rails_command, 'log:clear', env: 'production' + end + end + + def test_rails_command_with_rails_env_variable_should_run_rails_command_in_env + assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do + with_rails_env "production" do + action :rails_command, 'log:clear' + end + end + end + + def test_env_option_should_win_over_rails_env_variable_when_running_rails + assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do + with_rails_env "staging" do + action :rails_command, 'log:clear', env: 'production' + end + end + end + + def test_rails_command_with_sudo_option_should_run_rails_command_with_sudo + assert_called_with(generator, :run, ["sudo rails log:clear RAILS_ENV=development", verbose: false]) do + with_rails_env nil do + action :rails_command, 'log:clear', sudo: true + end + end + end + def test_capify_should_run_the_capify_command assert_called_with(generator, :run, ['capify .', verbose: false]) do action :capify! diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 921a5b36b5..c2c3638f07 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -64,8 +64,8 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_assets run_generator - assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track' => true/) - assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track' => true/) + assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track' => 'reload'/) + assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track' => 'reload'/) assert_file("app/assets/stylesheets/application.css") assert_file("app/assets/javascripts/application.js") end diff --git a/version.rb b/version.rb index 93e0151602..081222425c 100644 --- a/version.rb +++ b/version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end |