diff options
44 files changed, 287 insertions, 127 deletions
@@ -52,7 +52,7 @@ group :job do gem "sidekiq", require: false gem "sucker_punch", require: false gem "delayed_job", require: false - gem "queue_classic", github: "rafaelfranca/queue_classic", branch: "update-pg", require: false, platforms: :ruby + gem "queue_classic", github: "QueueClassic/queue_classic", require: false, platforms: :ruby gem "sneakers", require: false gem "que", require: false gem "backburner", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 70a29f501d..1f02b9c4c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,4 +1,11 @@ GIT + remote: https://github.com/QueueClassic/queue_classic.git + revision: 4cdc9b8e804badf7dea7078dd81092972d292c14 + specs: + queue_classic (3.2.0.RC1) + pg (>= 0.17, < 2.0) + +GIT remote: https://github.com/matthewd/websocket-client-simple.git revision: e161305f1a466b9398d86df3b1731b03362da91b branch: close-race @@ -7,14 +14,6 @@ GIT event_emitter websocket -GIT - remote: https://github.com/rafaelfranca/queue_classic.git - revision: dee64b361355d56700ad7aa3b151bf653a617526 - branch: update-pg - specs: - queue_classic (3.2.0.RC1) - pg (>= 0.17, < 2.0) - PATH remote: . specs: diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 4109dd6138..7f3177b64e 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,22 @@ +* Add `Channel::Base#broadcast_to`. + + You can now call `broadcast_to` within a channel action, which equals to + the `self.class.broadcast_to`. + + *Vladimir Dementyev* + +* Make `Channel::Base.broadcasting_for` a public API. + + You can use `.broadcasting_for` to generate a unique stream identifier within + a channel for the specified target (e.g. Active Record model): + + ```ruby + ChatChannel.broadcasting_for(model) # => "chat:<model.to_gid_param>" + ``` + + *Vladimir Dementyev* + + ## Rails 6.0.0.beta1 (January 18, 2019) ## * Merge [`action-cable-testing`](https://github.com/palkan/action-cable-testing) to Rails. diff --git a/actioncable/lib/action_cable/channel/broadcasting.rb b/actioncable/lib/action_cable/channel/broadcasting.rb index 9a96720f4a..9f702e425e 100644 --- a/actioncable/lib/action_cable/channel/broadcasting.rb +++ b/actioncable/lib/action_cable/channel/broadcasting.rb @@ -7,22 +7,32 @@ module ActionCable module Broadcasting extend ActiveSupport::Concern - delegate :broadcasting_for, to: :class + delegate :broadcasting_for, :broadcast_to, to: :class module ClassMethods # Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel. def broadcast_to(model, message) - ActionCable.server.broadcast(broadcasting_for([ channel_name, model ]), message) + ActionCable.server.broadcast(broadcasting_for(model), message) end - def broadcasting_for(model) #:nodoc: + # Returns a unique broadcasting identifier for this <tt>model</tt> in this channel: + # + # CommentsChannel.broadcasting_for("all") # => "comments:all" + # + # You can pass any object as a target (e.g. Active Record model), and it + # would be serialized into a string under the hood. + def broadcasting_for(model) + serialize_broadcasting([ channel_name, model ]) + end + + def serialize_broadcasting(object) #:nodoc: case - when model.is_a?(Array) - model.map { |m| broadcasting_for(m) }.join(":") - when model.respond_to?(:to_gid_param) - model.to_gid_param + when object.is_a?(Array) + object.map { |m| serialize_broadcasting(m) }.join(":") + when object.respond_to?(:to_gid_param) + object.to_gid_param else - model.to_param + object.to_param end end end diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index 81c2c38064..7e1ed3c850 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -99,7 +99,7 @@ module ActionCable # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback. # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages. def stream_for(model, callback = nil, coder: nil, &block) - stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder) + stream_from(broadcasting_for(model), callback || block, coder: coder) end # Unsubscribes all streams associated with this channel from the pubsub queue. diff --git a/actioncable/lib/action_cable/channel/test_case.rb b/actioncable/lib/action_cable/channel/test_case.rb index c4cf0ac0e7..b05d51a61a 100644 --- a/actioncable/lib/action_cable/channel/test_case.rb +++ b/actioncable/lib/action_cable/channel/test_case.rb @@ -143,7 +143,7 @@ module ActionCable # You need to set up your connection manually to provide values for the identifiers. # To do this just use: # - # stub_connection(user: users[:john]) + # stub_connection(user: users(:john)) # # == Testing broadcasting # @@ -157,9 +157,9 @@ module ActionCable # end # # def test_speak - # subscribe room_id: rooms[:chat].id + # subscribe room_id: rooms(:chat).id # - # assert_broadcasts_on(rooms[:chat], text: "Hello, Rails!") do + # assert_broadcasts_on(rooms(:chat), text: "Hello, Rails!") do # perform :speak, message: "Hello, Rails!" # end # end @@ -300,9 +300,7 @@ module ActionCable def broadcasting_for(stream_or_object) return stream_or_object if stream_or_object.is_a?(String) - self.class.channel_class.broadcasting_for( - [self.class.channel_class.channel_name, stream_or_object] - ) + self.class.channel_class.broadcasting_for(stream_or_object) end end diff --git a/actioncable/test/channel/broadcasting_test.rb b/actioncable/test/channel/broadcasting_test.rb index 2cbfabc1d0..fb501a1bc2 100644 --- a/actioncable/test/channel/broadcasting_test.rb +++ b/actioncable/test/channel/broadcasting_test.rb @@ -26,14 +26,23 @@ class ActionCable::Channel::BroadcastingTest < ActionCable::TestCase end test "broadcasting_for with an object" do - assert_equal "Room#1-Campfire", ChatChannel.broadcasting_for(Room.new(1)) + assert_equal( + "action_cable:channel:broadcasting_test:chat:Room#1-Campfire", + ChatChannel.broadcasting_for(Room.new(1)) + ) end test "broadcasting_for with an array" do - assert_equal "Room#1-Campfire:Room#2-Campfire", ChatChannel.broadcasting_for([ Room.new(1), Room.new(2) ]) + assert_equal( + "action_cable:channel:broadcasting_test:chat:Room#1-Campfire:Room#2-Campfire", + ChatChannel.broadcasting_for([ Room.new(1), Room.new(2) ]) + ) end test "broadcasting_for with a string" do - assert_equal "hello", ChatChannel.broadcasting_for("hello") + assert_equal( + "action_cable:channel:broadcasting_test:chat:hello", + ChatChannel.broadcasting_for("hello") + ) end end diff --git a/actioncable/test/channel/test_case_test.rb b/actioncable/test/channel/test_case_test.rb index 9c360d5dc3..a166c41e11 100644 --- a/actioncable/test/channel/test_case_test.rb +++ b/actioncable/test/channel/test_case_test.rb @@ -180,7 +180,7 @@ class BroadcastsTestChannel < ActionCable::Channel::Base def broadcast_to_user(data) user = User.new user_id - self.class.broadcast_to user, text: data["message"] + broadcast_to user, text: data["message"] end end diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index fb2b2bd3b0..1fb3e9db00 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -31,22 +31,34 @@ module ActionDispatch "ActionController::MissingExactTemplate" => "missing_exact_template", ) + cattr_accessor :wrapper_exceptions, default: [ + "ActionView::Template::Error" + ] + attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file def initialize(backtrace_cleaner, exception) @backtrace_cleaner = backtrace_cleaner - @exception = original_exception(exception) + @exception = exception @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner) expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError) end + def unwrapped_exception + if wrapper_exceptions.include?(exception.class.to_s) + exception.cause + else + exception + end + end + def rescue_template @@rescue_templates[@exception.class.name] end def status_code - self.class.status_code_for_exception(@exception.class.name) + self.class.status_code_for_exception(unwrapped_exception.class.name) end def application_trace @@ -122,14 +134,6 @@ module ActionDispatch Array(@exception.backtrace) end - def original_exception(exception) - if @@rescue_responses.has_key?(exception.cause.class.name) - exception.cause - else - exception - end - end - def causes_for(exception) return enum_for(__method__, exception) unless block_given? diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 3c88afd4d3..767143a368 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -45,7 +45,7 @@ module ActionDispatch backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner" wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) status = wrapper.status_code - request.set_header "action_dispatch.exception", wrapper.exception + request.set_header "action_dispatch.exception", wrapper.unwrapped_exception request.set_header "action_dispatch.original_path", request.path_info request.path_info = "/#{status}" response = @exceptions_app.call(request.env) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb index 2fa78dd385..1fbc107e28 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb @@ -2,6 +2,6 @@ <h1>Blocked host: <%= @host %></h1> </header> <div id="container"> - <h2>To allow requests to <%= @host %>, add the following configuration:</h2> - <pre>Rails.application.config.hosts << "<%= @host %>"</pre> + <h2>To allow requests to <%= @host %>, add the following to your environment configuration:</h2> + <pre>config.hosts << "<%= @host %>"</pre> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb index 4e2d1d0b08..a94dd982a7 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb @@ -1,5 +1,5 @@ Blocked host: <%= @host %> -To allow requests to <%= @host %>, add the following configuration: +To allow requests to <%= @host %>, add the following to your environment configuration: - Rails.application.config.hosts << "<%= @host %>" + config.hosts << "<%= @host %>" diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 998498e1b2..7f1c41787a 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -68,10 +68,18 @@ class RedirectController < ActionController::Base redirect_back(fallback_location: "/things/stuff", status: 307) end + def redirect_back_with_status_and_fallback_location_to_another_host + redirect_back(fallback_location: "http://www.rubyonrails.org/", status: 307) + end + def safe_redirect_back_with_status redirect_back(fallback_location: "/things/stuff", status: 307, allow_other_host: false) end + def safe_redirect_back_with_status_and_fallback_location_to_another_host + redirect_back(fallback_location: "http://www.rubyonrails.org/", status: 307, allow_other_host: false) + end + def host_redirect redirect_to action: "other_host", only_path: false, host: "other.test.host" end @@ -280,6 +288,13 @@ class RedirectTest < ActionController::TestCase assert_equal "http://test.host/things/stuff", redirect_to_url end + def test_redirect_back_with_no_referer_redirects_to_another_host + get :redirect_back_with_status_and_fallback_location_to_another_host + + assert_response 307 + assert_equal "http://www.rubyonrails.org/", redirect_to_url + end + def test_safe_redirect_back_from_other_host @request.env["HTTP_REFERER"] = "http://another.host/coming/from" get :safe_redirect_back_with_status @@ -297,6 +312,20 @@ class RedirectTest < ActionController::TestCase assert_equal referer, redirect_to_url end + def test_safe_redirect_back_with_no_referer + get :safe_redirect_back_with_status + + assert_response 307 + assert_equal "http://test.host/things/stuff", redirect_to_url + end + + def test_safe_redirect_back_with_no_referer_redirects_to_another_host + get :safe_redirect_back_with_status_and_fallback_location_to_another_host + + assert_response 307 + assert_equal "http://www.rubyonrails.org/", redirect_to_url + end + def test_redirect_to_record with_routing do |set| set.draw do diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index aadc6be077..6914fb66f9 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -39,52 +39,56 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest def call(env) env["action_dispatch.show_detailed_exceptions"] = @detailed req = ActionDispatch::Request.new(env) + template = ActionView::Template.new(File.read(__FILE__), __FILE__, ActionView::Template::Handlers::Raw.new, {}) + case req.path - when %r{/pass} + when "/pass" [404, { "X-Cascade" => "pass" }, self] - when %r{/not_found} + when "/not_found" raise AbstractController::ActionNotFound - when %r{/runtime_error} + when "/runtime_error" raise RuntimeError - when %r{/method_not_allowed} + when "/method_not_allowed" raise ActionController::MethodNotAllowed - when %r{/intercepted_error} + when "/intercepted_error" raise InterceptedErrorInstance - when %r{/unknown_http_method} + when "/unknown_http_method" raise ActionController::UnknownHttpMethod - when %r{/not_implemented} + when "/not_implemented" raise ActionController::NotImplemented - when %r{/unprocessable_entity} + when "/unprocessable_entity" raise ActionController::InvalidAuthenticityToken - when %r{/not_found_original_exception} + when "/not_found_original_exception" begin raise AbstractController::ActionNotFound.new rescue - raise ActionView::Template::Error.new("template") + raise ActionView::Template::Error.new(template) + end + when "/cause_mapped_to_rescue_responses" + begin + raise ActionController::ParameterMissing, :missing_param_key + rescue + raise NameError.new("uninitialized constant Userr") end - when %r{/missing_template} + when "/missing_template" raise ActionView::MissingTemplate.new(%w(foo), "foo/index", %w(foo), false, "mailer") - when %r{/bad_request} + when "/bad_request" raise ActionController::BadRequest - when %r{/missing_keys} + when "/missing_keys" raise ActionController::UrlGenerationError, "No route matches" - when %r{/parameter_missing} + when "/parameter_missing" raise ActionController::ParameterMissing, :missing_param_key - when %r{/original_syntax_error} + when "/original_syntax_error" eval "broke_syntax =" # `eval` need for raise native SyntaxError at runtime - when %r{/syntax_error_into_view} + when "/syntax_error_into_view" begin eval "broke_syntax =" rescue Exception - template = ActionView::Template.new(File.read(__FILE__), - __FILE__, - ActionView::Template::Handlers::Raw.new, - {}) raise ActionView::Template::Error.new(template) end - when %r{/framework_raises} + when "/framework_raises" method_that_raises - when %r{/nested_exceptions} + when "/nested_exceptions" raise_nested_exceptions else raise "puke!" @@ -313,12 +317,22 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_match(""foo"=>"[FILTERED]"", body) end - test "show registered original exception for wrapped exceptions" do + test "show registered original exception if the last exception is TemplateError" do @app = DevelopmentApp get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 - assert_match(/AbstractController::ActionNotFound/, body) + assert_match %r{AbstractController::ActionNotFound}, body + assert_match %r{Showing <i>.*test/dispatch/debug_exceptions_test.rb</i>}, body + end + + test "show the last exception and cause even when the cause is mapped to resque_responses" do + @app = DevelopmentApp + + get "/cause_mapped_to_rescue_responses", headers: { "action_dispatch.show_exceptions" => true } + assert_response 500 + assert_match %r{ActionController::ParameterMissing}, body + assert_match %r{NameError}, body end test "named urls missing keys raise 500 level error" do @@ -480,6 +494,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_select "#Application-Trace-0" do assert_select "code", /syntax error, unexpected/ end + assert_match %r{Showing <i>.*test/dispatch/debug_exceptions_test.rb</i>}, body end test "debug exceptions app shows user code that caused the error in source view" do diff --git a/actiontext/app/helpers/action_text/tag_helper.rb b/actiontext/app/helpers/action_text/tag_helper.rb index 837b2264b1..8434f2c611 100644 --- a/actiontext/app/helpers/action_text/tag_helper.rb +++ b/actiontext/app/helpers/action_text/tag_helper.rb @@ -4,7 +4,7 @@ module ActionText module TagHelper cattr_accessor(:id, instance_accessor: false) { 0 } - # Returns a `trix-editor` tag that instantiates the Trix JavaScript editor as well as a hidden field + # Returns a +trix-editor+ tag that instantiates the Trix JavaScript editor as well as a hidden field # that Trix will write to on changes, so the content will be sent on form submissions. # # ==== Options @@ -50,7 +50,7 @@ module ActionView::Helpers end module FormHelper - # Returns a `trix-editor` tag that instantiates the Trix JavaScript editor as well as a hidden field + # Returns a +trix-editor+ tag that instantiates the Trix JavaScript editor as well as a hidden field # that Trix will write to on changes, so the content will be sent on form submissions. # # ==== Options diff --git a/actiontext/app/models/action_text/rich_text.rb b/actiontext/app/models/action_text/rich_text.rb index 705dd30983..1f39bc51b9 100644 --- a/actiontext/app/models/action_text/rich_text.rb +++ b/actiontext/app/models/action_text/rich_text.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module ActionText - # The RichText record holds the content produced by the Trix editor in a serialized `body` attribute. + # The RichText record holds the content produced by the Trix editor in a serialized +body+ attribute. # It also holds all the references to the embedded files, which are stored using Active Storage. # This record is then associated with the Active Record model the application desires to have - # rich text content using the `has_rich_text` class method. + # rich text content using the +has_rich_text+ class method. class RichText < ActiveRecord::Base self.table_name = "action_text_rich_texts" diff --git a/actiontext/app/views/active_storage/blobs/_blob.html.erb b/actiontext/app/views/active_storage/blobs/_blob.html.erb index 049f57e804..49ba357dd1 100644 --- a/actiontext/app/views/active_storage/blobs/_blob.html.erb +++ b/actiontext/app/views/active_storage/blobs/_blob.html.erb @@ -1,6 +1,6 @@ <figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>"> <% if blob.representable? %> - <%= image_tag blob.representation(resize_to_fit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> + <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> <% end %> <figcaption class="attachment__caption"> diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index c186cb8422..59d70a1dc4 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -329,14 +329,14 @@ module ActionView # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw") # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw"> # - # Active Storage (images that are uploaded by the users of your app): + # Active Storage blobs (images that are uploaded by the users of your app): # # image_tag(user.avatar) # # => <img src="/rails/active_storage/blobs/.../tiger.jpg" /> - # image_tag(user.avatar.variant(resize_to_fit: [100, 100])) - # # => <img src="/rails/active_storage/variants/.../tiger.jpg" /> - # image_tag(user.avatar.variant(resize_to_fit: [100, 100]), size: '100') - # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" /> + # image_tag(user.avatar.variant(resize_to_limit: [100, 100])) + # # => <img src="/rails/active_storage/representations/.../tiger.jpg" /> + # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100') + # # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" /> def image_tag(source, options = {}) options = options.symbolize_keys check_for_image_tag_errors(options) diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index cb850d75ee..f175c30aa1 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -363,7 +363,7 @@ module ActionView @options = options @block = block - @locals = options[:locals] ? options[:locals].symbolize_keys : {} + @locals = options[:locals] || {} @details = extract_details(options) prepend_formats(options[:formats]) diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 204903c60c..727d3fbc1a 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -485,8 +485,8 @@ class TestController < ActionController::Base render partial: "customer", locals: { customer: Customer.new("david") } end - def partial_with_string_locals - render partial: "customer", locals: { "customer" => Customer.new("david") } + def partial_with_hashlike_locals + render partial: "customer", locals: ActionController::Parameters.new(customer: Customer.new("david")) end def partial_with_form_builder @@ -691,7 +691,7 @@ class RenderTest < ActionController::TestCase get :partial_with_locals, to: "test#partial_with_locals" get :partial_with_nested_object, to: "test#partial_with_nested_object" get :partial_with_nested_object_shorthand, to: "test#partial_with_nested_object_shorthand" - get :partial_with_string_locals, to: "test#partial_with_string_locals" + get :partial_with_hashlike_locals, to: "test#partial_with_hashlike_locals" get :partials_list, to: "test#partials_list" get :render_action_hello_world, to: "test#render_action_hello_world" get :render_action_hello_world_as_string, to: "test#render_action_hello_world_as_string" @@ -1292,8 +1292,8 @@ class RenderTest < ActionController::TestCase assert_equal "Hello: david", @response.body end - def test_partial_with_string_locals - get :partial_with_string_locals + def test_partial_with_hashlike_locals + get :partial_with_hashlike_locals assert_equal "Hello: david", @response.body end diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 9cb8b543b0..51e224d5cd 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "bigdecimal/util" + module ActiveModel module Validations class NumericalityValidator < EachValidator # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 99934a0e31..c8d5f679a8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1006,7 +1006,16 @@ module ActiveRecord # for (not necessarily the current class). def retrieve_connection(spec_name) #:nodoc: pool = retrieve_connection_pool(spec_name) - raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool + + unless pool + # multiple database application + if ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler + raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role." + else + raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." + end + end + pool.connection end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 208c8c9c64..90a130320b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -134,7 +134,7 @@ module ActiveRecord column_name = column_name.to_s checks = [] checks << lambda { |c| c.name == column_name } - checks << lambda { |c| c.type == type } if type + checks << lambda { |c| c.type == type.to_sym rescue nil } if type column_options_keys.each do |attr| checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) end diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 4a941055d1..558cdeccf2 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -158,10 +158,6 @@ module ActiveRecord end def with_handler(handler_key, &blk) # :nodoc: - unless ActiveRecord::Base.connection_handlers.keys.include?(handler_key) - raise ArgumentError, "The #{handler_key} role does not exist. Add it by establishing a connection with `connects_to` or use an existing role (#{ActiveRecord::Base.connection_handlers.keys.join(", ")})." - end - handler = lookup_connection_handler(handler_key) swap_connection_handler(handler, &blk) end diff --git a/activerecord/lib/arel/nodes/and.rb b/activerecord/lib/arel/nodes/and.rb index c530a77bfb..bf516db35f 100644 --- a/activerecord/lib/arel/nodes/and.rb +++ b/activerecord/lib/arel/nodes/and.rb @@ -2,7 +2,7 @@ module Arel # :nodoc: all module Nodes - class And < Arel::Nodes::Node + class And < Arel::Nodes::NodeExpression attr_reader :children def initialize(children) diff --git a/activerecord/lib/arel/nodes/case.rb b/activerecord/lib/arel/nodes/case.rb index b8f83128c8..1c4b727bf6 100644 --- a/activerecord/lib/arel/nodes/case.rb +++ b/activerecord/lib/arel/nodes/case.rb @@ -2,9 +2,7 @@ module Arel # :nodoc: all module Nodes - class Case < Arel::Nodes::Node - include Arel::AliasPredication - + class Case < Arel::Nodes::NodeExpression attr_accessor :case, :conditions, :default def initialize(expression = nil, default = nil) diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 2d71ee2f15..88c2ac5d0a 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -139,8 +139,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase with_real_execute do ActiveRecord::Base.connection.create_table :delete_me ActiveRecord::Base.connection.add_timestamps :delete_me, null: true - assert column_present?("delete_me", "updated_at", "datetime") - assert column_present?("delete_me", "created_at", "datetime") + assert column_exists?("delete_me", "updated_at", "datetime") + assert column_exists?("delete_me", "created_at", "datetime") ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end @@ -152,8 +152,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase t.timestamps null: true end ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true - assert_not column_present?("delete_me", "updated_at", "datetime") - assert_not column_present?("delete_me", "created_at", "datetime") + assert_not column_exists?("delete_me", "updated_at", "datetime") + assert_not column_exists?("delete_me", "created_at", "datetime") ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end @@ -194,9 +194,4 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) end - - def column_present?(table_name, column_name, type) - results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'") - results.first && results.first["Type"] == type - end end diff --git a/activerecord/test/cases/arel/nodes/and_test.rb b/activerecord/test/cases/arel/nodes/and_test.rb index eff54abd91..d123ca9fd0 100644 --- a/activerecord/test/cases/arel/nodes/and_test.rb +++ b/activerecord/test/cases/arel/nodes/and_test.rb @@ -16,6 +16,15 @@ module Arel assert_equal 2, array.uniq.size end end + + describe "functions as node expression" do + it "allows aliasing" do + aliased = And.new(["foo", "bar"]).as("baz") + + assert_kind_of As, aliased + assert_kind_of SqlLiteral, aliased.right + end + end end end end diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb index 865aacc1b5..8988755d24 100644 --- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb @@ -336,13 +336,13 @@ module ActiveRecord end def test_calling_connected_to_on_a_non_existent_handler_raises - error = assert_raises ArgumentError do + error = assert_raises ActiveRecord::ConnectionNotEstablished do ActiveRecord::Base.connected_to(role: :reading) do - yield + Person.first end end - assert_equal "The reading role does not exist. Add it by establishing a connection with `connects_to` or use an existing role (writing).", error.message + assert_equal "No connection pool with 'primary' found for the 'reading' role.", error.message end end end diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb index 9eefc32745..f0a0e7f805 100644 --- a/activerecord/test/cases/unconnected_test.rb +++ b/activerecord/test/cases/unconnected_test.rb @@ -29,6 +29,14 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase end end + def test_error_message_when_connection_not_established + error = assert_raise(ActiveRecord::ConnectionNotEstablished) do + TestRecord.find(1) + end + + assert_equal "No connection pool with 'primary' found.", error.message + end + def test_underlying_adapter_no_longer_active assert_not @underlying.active?, "Removed adapter should no longer be active" end diff --git a/activestorage/README.md b/activestorage/README.md index f658b8d542..2886169ca7 100644 --- a/activestorage/README.md +++ b/activestorage/README.md @@ -101,7 +101,7 @@ Variation of image attachment: ```erb <%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %> -<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %> +<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %> ``` ## Direct uploads diff --git a/activestorage/app/models/active_storage/blob/representable.rb b/activestorage/app/models/active_storage/blob/representable.rb index 03d5511481..32e8fcefdf 100644 --- a/activestorage/app/models/active_storage/blob/representable.rb +++ b/activestorage/app/models/active_storage/blob/representable.rb @@ -10,7 +10,7 @@ module ActiveStorage::Blob::Representable # Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image # files, and it allows any image to be transformed for size, colors, and the like. Example: # - # avatar.variant(resize_to_fit: [100, 100]).processed.service_url + # avatar.variant(resize_to_limit: [100, 100]).processed.service_url # # This will create and process a variant of the avatar blob that's constrained to a height and width of 100px. # Then it'll upload said variant to the service according to a derivative key of the blob and the transformations. @@ -18,7 +18,7 @@ module ActiveStorage::Blob::Representable # Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a # specific variant that can be created by a controller on-demand. Like so: # - # <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %> + # <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %> # # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController # can then produce on-demand. @@ -43,13 +43,13 @@ module ActiveStorage::Blob::Representable # from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer # extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document. # - # blob.preview(resize_to_fit: [100, 100]).processed.service_url + # blob.preview(resize_to_limit: [100, 100]).processed.service_url # # Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand. # Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s # how to use the built-in version: # - # <%= image_tag video.preview(resize_to_fit: [100, 100]) %> + # <%= image_tag video.preview(resize_to_limit: [100, 100]) %> # # This method raises ActiveStorage::UnpreviewableError if no previewer accepts the receiving blob. To determine # whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?. @@ -69,7 +69,7 @@ module ActiveStorage::Blob::Representable # Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob. # - # blob.representation(resize_to_fit: [100, 100]).processed.service_url + # blob.representation(resize_to_limit: [100, 100]).processed.service_url # # Raises ActiveStorage::UnrepresentableError if the receiving blob is neither variable nor previewable. Call # ActiveStorage::Blob#representable? to determine whether a blob is representable. diff --git a/activestorage/app/models/active_storage/preview.rb b/activestorage/app/models/active_storage/preview.rb index dd50494799..bb9d960443 100644 --- a/activestorage/app/models/active_storage/preview.rb +++ b/activestorage/app/models/active_storage/preview.rb @@ -38,7 +38,7 @@ class ActiveStorage::Preview # Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience: # - # blob.preview(resize_to_fit: [100, 100]).processed.service_url + # blob.preview(resize_to_limit: [100, 100]).processed.service_url # # Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview # image is stored with the blob, it is only generated once. diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb index ea57fa5f78..bc0058967a 100644 --- a/activestorage/app/models/active_storage/variant.rb +++ b/activestorage/app/models/active_storage/variant.rb @@ -27,7 +27,7 @@ require "ostruct" # To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided # by Active Storage like so: # -# <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %> +# <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %> # # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController # can then produce on-demand. @@ -36,15 +36,15 @@ require "ostruct" # has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform # the transformations, upload the variant to the service, and return itself again. Example: # -# avatar.variant(resize_to_fit: [100, 100]).processed.service_url +# avatar.variant(resize_to_limit: [100, 100]).processed.service_url # # This will create and process a variant of the avatar blob that's constrained to a height and width of 100. # Then it'll upload said variant to the service according to a derivative key of the blob and the transformations. # # You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the -# ImageProcessing gem (such as +resize_to_fit+): +# ImageProcessing gem (such as +resize_to_limit+): # -# avatar.variant(resize_to_fit: [800, 800], monochrome: true, rotate: "-90") +# avatar.variant(resize_to_limit: [800, 800], monochrome: true, rotate: "-90") # # Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations: # diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb index 3adc2407e5..67568772da 100644 --- a/activestorage/app/models/active_storage/variation.rb +++ b/activestorage/app/models/active_storage/variation.rb @@ -6,7 +6,7 @@ # In case you do need to use this directly, it's instantiated using a hash of transformations where # the key is the command and the value is the arguments. Example: # -# ActiveStorage::Variation.new(resize_to_fit: [100, 100], monochrome: true, trim: true, rotate: "-90") +# ActiveStorage::Variation.new(resize_to_limit: [100, 100], monochrome: true, trim: true, rotate: "-90") # # The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands. class ActiveStorage::Variation diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md index 474a93c83e..e3bb41ae32 100644 --- a/guides/source/active_storage_overview.md +++ b/guides/source/active_storage_overview.md @@ -434,7 +434,7 @@ original blob into the specified format and redirect to its new service location. ```erb -<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %> +<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %> ``` To switch to the Vips processor, you would add the following to diff --git a/guides/source/testing.md b/guides/source/testing.md index f9661c52a0..9667521f3b 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1806,16 +1806,16 @@ require "test_helper" class WebNotificationsChannelTest < ActionCable::Channel::TestCase test "subscribes and stream for user" do - stub_connection current_user: users[:john] + stub_connection current_user: users(:john) subscribe - assert_has_stream_for users[:john] + assert_has_stream_for users(:john) end end ``` -See the API documentation for [`AcionCable::Channel::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Channel/TestCase.html) for more information. +See the API documentation for [`ActionCable::Channel::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Channel/TestCase.html) for more information. ### Custom Assertions And Testing Broadcasts Inside Other Components @@ -1837,6 +1837,33 @@ class ProductTest < ActionCable::TestCase end ``` +If you want to test the broadcasting made with `Channel.broadcast_to`, you shoud use +`Channel.broadcasting_for` to generate an underlying stream name: + +```ruby +# app/jobs/chat_relay_job.rb +class ChatRelayJob < ApplicationJob + def perform_later(room, message) + ChatChannel.broadcast_to room, text: message + end +end + +# test/jobs/chat_relay_job_test.rb +require 'test_helper' + +class ChatRelayJobTest < ActiveJob::TestCase + include ActionCable::TestHelper + + test "broadcast message to room" do + room = rooms(:all) + + assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do + ChatRelayJob.perform_now(room, "Hi!") + end + end +end +``` + Additional Testing Resources ---------------------------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 5b249de47e..e55217c5c4 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Fix deeply nested namespace command printing. + + *Gannon McGibbon* + + ## Rails 6.0.0.beta1 (January 18, 2019) ## * Remove deprecated `after_bundle` helper inside plugins templates. diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb index cbb743346b..50651ad61a 100644 --- a/railties/lib/rails/command/actions.rb +++ b/railties/lib/rails/command/actions.rb @@ -11,10 +11,20 @@ module Rails end def require_application_and_environment! + require_application! + require_environment! + end + + def require_application! require ENGINE_PATH if defined?(ENGINE_PATH) if defined?(APP_PATH) require APP_PATH + end + end + + def require_environment! + if defined?(APP_PATH) Rails.application.require_environment! end end diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb index 766872de8a..a22b198c66 100644 --- a/railties/lib/rails/command/base.rb +++ b/railties/lib/rails/command/base.rb @@ -115,7 +115,7 @@ module Rails # For a Rails::Command::TestCommand placed in <tt>rails/command/test_command.rb</tt> # would return <tt>rails/test</tt>. def default_command_root - path = File.expand_path(File.join("../commands", command_root_namespace), __dir__) + path = File.expand_path(relative_command_path, __dir__) path if File.exist?(path) end @@ -135,12 +135,20 @@ module Rails end def command_root_namespace - (namespace.split(":") - %w( rails )).first + (namespace.split(":") - %w(rails)).join(":") + end + + def relative_command_path + File.join("../commands", *command_root_namespace.split(":")) end def namespaced_commands commands.keys.map do |key| - key == command_root_namespace ? key : "#{command_root_namespace}:#{key}" + if command_root_namespace.match?(/(\A|\:)#{key}\z/) + command_root_namespace + else + "#{command_root_namespace}:#{key}" + end end end end diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb index 852cd401d7..54ccd97506 100644 --- a/railties/lib/rails/commands/credentials/credentials_command.rb +++ b/railties/lib/rails/commands/credentials/credentials_command.rb @@ -20,7 +20,7 @@ module Rails end def edit - require_application_and_environment! + require_application! ensure_editor_available(command: "bin/rails credentials:edit") || (return) @@ -37,7 +37,7 @@ module Rails end def show - require_application_and_environment! + require_application! say credentials.read.presence || missing_credentials_message end diff --git a/railties/test/command/base_test.rb b/railties/test/command/base_test.rb index a49ae8aae7..9132c8b4af 100644 --- a/railties/test/command/base_test.rb +++ b/railties/test/command/base_test.rb @@ -4,10 +4,12 @@ require "abstract_unit" require "rails/command" require "rails/commands/generate/generate_command" require "rails/commands/secrets/secrets_command" +require "rails/commands/db/system/change/change_command" class Rails::Command::BaseTest < ActiveSupport::TestCase test "printing commands" do assert_equal %w(generate), Rails::Command::GenerateCommand.printing_commands assert_equal %w(secrets:setup secrets:edit secrets:show), Rails::Command::SecretsCommand.printing_commands + assert_equal %w(db:system:change), Rails::Command::Db::System::ChangeCommand.printing_commands end end diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb index 7842b0db61..26ffe3070c 100644 --- a/railties/test/commands/credentials_test.rb +++ b/railties/test/commands/credentials_test.rb @@ -63,6 +63,14 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase end end + test "edit command does not raise when an initializer tries to acces non-existent credentials" do + app_file "config/initializers/raise_when_loaded.rb", <<-RUBY + Rails.application.credentials.missing_key! + RUBY + + assert_match(/access_key_id: 123/, run_edit_command(environment: "qa")) + end + test "show credentials" do assert_match(/access_key_id: 123/, run_show_command) end diff --git a/tasks/release.rb b/tasks/release.rb index 6784330fd6..2fdcea9d12 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -218,7 +218,7 @@ namespace :all do <p> <% if @user.avatar.attached? -%> - <%= image_tag @user.avatar.representation(resize_to_fit: [500, 500]) %> + <%= image_tag @user.avatar.representation(resize_to_limit: [500, 500]) %> <% end -%> </p> CODE |